internal

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 17, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package internal implements the declscope analysis.

The analysis answers two different questions about two kinds of declaration.

Package-level identifiers compete in one flat namespace, so the problem there is namespace pollution. An unexported identifier is private to its file's namespace unless a directive widens it, and the naming rule asks it to carry that namespace somewhere in its name, so that the owning unit is legible at every use site. The namespace marks ownership. It never grants reach.

Methods and struct fields are already namespaced by the type that owns them and cannot collide with anything, so the problem there is not pollution but encapsulation: Go makes every unexported member visible to the whole package, with no way to say otherwise. Members are exempt from the naming rule, and their bound is the namespace of the type, not of the file.

surplus.go implements the surplus rule: a //declscope:package directive is reported when declscope can see no use of what it widens from another namespace. spec/surplus.fsl is the model; NeverReportsReachable is the property everything here serves.

The rule is unlike the others in that it concludes from an absence. boundary needs a use to find and qualify reads a name, but this rule reports because it saw nothing — so every way a declaration can be reached without its name being spelled is a way for the rule to be wrong, and being wrong is expensive: the advice is to delete a directive, and deleting one that was holding something up breaks a package the analysis never saw. Every check below therefore silences the rule on a doubt, and there is no fix.

Reach the reference index cannot see, and what answers for it:

  • another namespace spells the name -> c.refs (usedOutside)
  • a struct conversion writes fields pairwise, spelling none of them
  • a method set satisfies an interface contract in the package (satisfies)
  • an exported type carries an unexported method out of the package, where an importer completes the satisfaction (carrierExposed)
  • //go:linkname or //export names the declaration as text (linkname)
  • generated files, cgo and assembly hold reference sites the analysis never reads (opaqueSource) — the rule switches off for the package

seesAllFiles is separate: with -test=false, in the non-test variant of a package with in-package tests, or with build-excluded files, some file that may hold the use was never read, and the rule switches off rather than guess.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Collect

func Collect(pass *analysis.Pass, opts Options) []baseline.Key

Collect returns every violation in the package, ignoring any configured baseline. It is the entry point used to regenerate a baseline.

func Run

func Run(pass *analysis.Pass, opts Options) (any, error)

Run performs the analysis for one package.

Types

type FilterPattern added in v0.6.0

type FilterPattern struct {
	Pattern string
	Base    string
}

FilterPattern is a pattern together with the directory it was written in. The two travel as a pair because a chain of config files states patterns at several depths, and each anchors to its own: "gen/**" in the root and "gen/**" in a nested file name different directories.

Base is empty for a pattern from options no config file produced, which leaves it floating, since there is no directory to call "here".

type Mode

type Mode int

Mode says when a name must carry its namespace: always, never, or only once a package has a second namespace. Only rules.qualify reads one.

const (
	// ModeNever disables the rule.
	ModeNever Mode = iota

	// ModeAlways applies the rule to every package. For the naming rule this
	// means a package gaining its second namespace is not a mass rename.
	ModeAlways

	// ModeOnDemand applies the rule only to a package with more than one
	// namespace. In a package with one there is no boundary for a prefix to
	// mark: every other rule is structurally inert there, since every
	// reference is already inside the single namespace, and a prefix repeated
	// on every declaration would distinguish nothing.
	ModeOnDemand
)

func (Mode) Applies

func (m Mode) Applies(namespaces int) bool

Applies reports whether the rule applies to a package with the given number of namespaces.

func (Mode) String

func (m Mode) String() string

String returns the spelling the settings use.

type ModeSet

type ModeSet []Mode

ModeSet is the values one setting accepts, in the order an error message names them. Each setting declares its own, so that a rejected value is answered with what that setting accepts rather than with everything Mode can hold.

func (ModeSet) Parse

func (s ModeSet) Parse(value string) (Mode, bool)

Parse reads a setting's value: always, never or ondemand, and of those only the members of the set.

func (ModeSet) String

func (s ModeSet) String() string

String lists the accepted spellings the way an error message names them: "always, never or ondemand".

type Options

type Options struct {
	// Unexported is the scope of a declaration that states none of its own and
	// inherits none. There is no Exported counterpart: what is reachable from
	// outside the package is not declscope's subject, and a key that claimed
	// otherwise would promise an enforcement the analysis cannot perform.
	Unexported scope.Scope

	// Qualify says when a package-level declaration must carry its namespace
	// somewhere in its name.
	Qualify Mode

	// Vocabulary lists, per namespace, extra words that carry the namespace as
	// its own spelling would: irregular inflections and domain synonyms that
	// no generated form reaches (mouse: wheel, index: indices). A word is
	// matched exactly the way the namespace is — starting at a word boundary,
	// with the right edge free — so it is a spelling, never a scope.
	Vocabulary map[string][]string

	// AllowBoundary turns the boundary rule off. The rule reports a private
	// declaration used from outside its namespace, and is the one this tool
	// exists for, so switching it off leaves only the naming rule.
	//
	// It is here for the repository that wants the ownership mark in a name
	// without the scope behind it. Reach stays unchecked, //declscope:package
	// stops meaning anything, and surplus keeps auditing directives that no
	// longer do a job -- set allowSurplus alongside it.
	//
	// This is not the way to adopt declscope gradually. A baseline records
	// what a codebase already has and still reports what is new, which a
	// switch cannot do.
	AllowBoundary bool

	// AllowSurplus turns the surplus rule off. The rule reports a
	// //declscope:package directive when no use from another namespace is
	// visible to declscope, and is on by default: a directive nobody needed is
	// a thing the author would want told.
	//
	// The polarity is stated rather than inverted in the reader's head. A key
	// named surplus would have read as "surplus: yes please", which is the
	// opposite of what setting it to true would do.
	//
	// The rule never has a fix. It concludes from an absence, so every case it
	// cannot see is one where the directive stays and the advice would be to
	// delete it.
	AllowSurplus bool

	// NameExported widens the naming rule to exported declarations. Inside
	// the package an exported name is read as bare as any other, so the package
	// qualifier that explains an external use is absent exactly where the
	// namespace mark is wanted. The violation is reported. The rename is never
	// offered, since the uses outside the package cannot be seen.
	NameExported bool

	// Only narrows the analysis, one group per config file that stated it. A
	// file is read when it matches at least one pattern in EVERY group, so the
	// groups intersect and a nested config can narrow further but never widen.
	// No groups is not "match nothing" but "no restriction", which is why a
	// repository with no config is read whole.
	//
	// The groups are separate rather than one flat list because each config
	// file anchors its own patterns: "gen/**" in the root and "gen/**" in a
	// nested file name different directories, and flattening them would lose
	// which is which.
	Only [][]FilterPattern

	// Omit takes files back out, and the chain unions rather than intersecting:
	// a file matching any pattern from any level is not read. An omit written
	// at the root therefore holds everywhere below it. Omit is the stronger of
	// the two, and applies whether or not Only let the file through.
	Omit []FilterPattern

	// BaselinePath is the baseline file that applies, resolved relative to
	// the config file that named it or found by the default-named lookup.
	// Empty means no baseline.
	BaselinePath string

	// Baseline suppresses violations that were already present when declscope
	// was adopted. It is nil when none is configured, and also while a
	// baseline is being regenerated: config.Resolve loads it, config.
	// ResolveForBaseline deliberately does not, so that a baseline which fails
	// to parse cannot block its own regeneration.
	Baseline *baseline.Set
	// contains filtered or unexported fields
}

func DefaultOptions

func DefaultOptions() Options

DefaultOptions mirrors the rules stated in the README: every declaration in the subject is private to its namespace until something widens it. The naming rule is off by default — measured over the repositories this tool was built against, packages with zero boundary violations still drew dozens of naming ones, and whether a name reads well with its namespace in it depends on the part of speech of the file name, which the tool cannot see. A codebase that wants the convention states rules.naming.qualify itself.

func (*Options) Compile

func (o *Options) Compile() error

Compile prepares the filter patterns. It must be called before use.

It does not load the baseline. Loading is the resolver's decision, since the same options serve both analysis, where the baseline suppresses, and regeneration, where the existing file must be ignored — otherwise one that fails to parse could never be regenerated.

func (Options) NearestOnly added in v0.6.0

func (o Options) NearestOnly() (FilterPattern, bool)

NearestOnly reports the only group of the config file closest to the package, and whether there is one. It is what tells a config whose only matched nothing from one whose only was cancelled by a group above it.

func (Options) NearestOnlyAdmits added in v0.6.0

func (o Options) NearestOnlyAdmits(path string) bool

NearestOnlyAdmits reports whether the nearest only group would read the file on its own, with the groups above it and every omit set aside.

func (Options) Skips added in v0.6.0

func (o Options) Skips(path string) bool

Skips reports whether a file is outside the scope of the analysis.

Within one level the two tests are applied in the order the config reads, and the order is not a choice: both lists ask about one path, so narrowing before subtracting and subtracting before narrowing name the same set.

Across levels they compose differently. Every only group must admit the file, and any omit pattern from any level rejects it, so a config file can only ever shrink what is read.

Directories

Path Synopsis
Package baseline records violations that already exist in a codebase so that adopting declscope does not require fixing them all at once.
Package baseline records violations that already exist in a codebase so that adopting declscope does not require fixing them all at once.
Package config loads declscope's YAML configuration.
Package config loads declscope's YAML configuration.
Package directive parses declscope comment directives.
Package directive parses declscope comment directives.
Package namespace resolves the pseudo-namespace that a Go source file belongs to.
Package namespace resolves the pseudo-namespace that a Go source file belongs to.
Package rule names declscope's checks.
Package rule names declscope's checks.
Package scope defines the two pseudo visibility levels that declscope layers on top of Go's exported/unexported distinction.
Package scope defines the two pseudo visibility levels that declscope layers on top of Go's exported/unexported distinction.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL