Directives¶
A directive is a line comment //declscope:name, with a lowercase name, no spaces, and any argument after a space. Any other comment starting with declscope: is malformed. A trailing // reason is ignored.
| Directive | Level | Effect |
|---|---|---|
//declscope:shared, //declscope:private |
Declaration or file | States the scope. On a type it also reaches the type's fields, but not its methods |
//declscope:ignore |
Declaration or file | Silences every rule the analyzer reports. overexported must be named |
//declscope:ignore <rules> |
Declaration or file | Silences the named rules, comma-separated. Naming filter silences nothing |
//declscope:core |
File | Joins the file to the core namespace |
//declscope:namespace <name> |
File | Joins the file to an explicitly named namespace |
Caution
//declscope:shared was spelled //declscope:package before v0.19.0, and defaults.unexported: shared was package. v0.20.0 and later read neither. To upgrade from v0.18.x or earlier, go through v0.19.0 first and run -fix there: see #186.
Placement¶
A declaration-level directive goes in the doc comment, directly above the declaration. A file-level directive goes above the package clause.
A directive on a block reaches every spec in it. A directive on one spec overrides the block's.
A directive on a type reaches its fields and its interface method names. It does not reach its methods, which take their own file's level.
Where a file-level directive may sit
//declscope:namespace must come before the package clause. Three placements are accepted there.
| Placement | Example |
|---|---|
A blank line before package, as this README writes |
|
Directly above package |
|
At the bottom of the package doc comment, after a blank // line: Go's own convention for a directive in a doc comment |
Go excludes a //tool:name comment from a doc comment, so none of them reaches the rendered documentation.
Ignore levels¶
- Every level is consulted. An ignore counts as used whenever it covers a rule that would have fired, so overlapping ignores never make one another look unused.
- Ignores are consulted before the baseline. A suppression the baseline would also have absorbed still counts as used.
Unused directives¶
The unused rule reports a directive that changes nothing. A directive that declscope cannot read belongs to the directive rule instead.
Off, loose and strict¶
rules.unused |
//declscope:ignore is reported when |
//declscope:shared or //declscope:private is reported when |
-fix |
|---|---|---|---|
off |
Never | Never | None |
loose |
It silenced no report | Deleting it would change no declaration's scope under any config | None |
strict (default) |
It silenced no report | Deleting it would change no declaration's scope under the current config | Deletes a redundant scope directive, unless withheld |
- One comment gets one report, however many declarations it reaches. A block's directive is reported once.
- Without the directive, a declaration falls to the next row of scope resolution. Only
defaults.unexportedthere depends on the config. - Under
strict, a directive thatloosekeeps also stays when deleting it would make a nearer directive redundant. offstill leaves thedirectiverule on.- An ignore of
boundaryis not reported where a file the build leaves out names a declaration it reaches, or for a field, its type: a literal written by position names only the type. That file may cross into it under another build tag, where the ignore silences the crossing. The run that reads the file still reports an ignore with nothing to silence.
Warning
Under strict, changing defaults.unexported changes the reports. With unexported: shared, every //declscope:shared on an unexported declaration that nothing else widens is reported, and one -fix run deletes them. loose never reports a directive only because it names the current default.
Example: one file under each mode
package app
type user struct {
//declscope:private
name string
}
//declscope:shared
func Helper() {}
//declscope:ignore boundary
func greet(u user) string { return u.name }
| Line | off |
loose |
strict |
|---|---|---|---|
| 4 | Nothing | Nothing | unused //declscope:private on user.name: it already has private scope |
| 8 | Nothing | unused //declscope:shared on Helper: nothing it reaches takes a scope |
unused //declscope:shared on Helper: it already has shared scope |
| 11 | Nothing | unused //declscope:ignore boundary on greet |
unused //declscope:ignore boundary on greet |
Line 4 is where the modes differ. Deleting line 4 leaves user.name private only because defaults.unexported is private, which loose does not rely on.
Report messages¶
Every unused report names the directive and says why it is unused. loose and strict word the reason differently.
The strict fix¶
-fix deletes a directive that strict reports, unless deleting it would change another report. Then the report stays, with no fix.
Example: one directive deleted, one withheld
$ declscope ./... # with rules.unused: strict
user.go:4:6: func normalize is declared private by //declscope:private, but is used from namespace "order"
order.go:3:9: used here, in namespace "order"
user.go:3:1: unused //declscope:private on normalize: it already has private scope
user.go:6:1: unused //declscope:private on trim: it already has private scope
-fix changes user.go. The boundary report names normalize's directive, so that one stays.
Silencing an unused report¶
| Ignore | Silences the unused report of |
|---|---|
//declscope:ignore unused on a declaration |
Every other directive on that declaration |
Bare //declscope:ignore on a declaration |
The scope directive on that declaration. Not the other ignores there, so a group of ignores cannot excuse one another |
//declscope:ignore unused, or a bare one, above the package clause |
Every other directive in the file |
| Any ignore on a type or a block | Nothing inside it. For other rules it reaches inside, but here one ignore would hide every stale directive under it |
No ignore silences its own unused report.
Example
package app
//declscope:shared
//declscope:ignore unused
func A() {}
//declscope:shared
//declscope:ignore directive
func B() {}
//declscope:ignore unused
type T struct {
//declscope:shared
F int
}
//declscope:ignore qualify
//declscope:ignore unused
func C() {}
//declscope:ignore unused
func D() {}
$ declscope ./...
user.go:7:1: unused //declscope:shared on B: it already has shared scope
user.go:13:2: unused //declscope:shared on T.F: it already has shared scope
user.go:8:1: unused //declscope:ignore directive on B
user.go:11:1: unused //declscope:ignore unused on T
user.go:21:1: unused //declscope:ignore unused on D
| Declaration | Result | Why |
|---|---|---|
A |
Quiet | ignore unused silences the scope directive's report |
B |
Both reported | ignore directive names another rule, so it silenced nothing |
T |
Both reported | The type's ignore does not reach the field |
C |
Quiet | ignore unused silences the report on ignore qualify |
D |
Reported | Its only candidate is its own report |
Malformed directives¶
Only //declscope:name is a directive. Any other comment starting with declscope:, and an unknown, conflicting or misplaced directive, is reported by the directive rule and has no effect.
Every malformed-directive report
The rule is always on, and has no configuration key. //declscope:ignore directive silences it, on the declaration or above the package clause.
<name> stands for the directive's own name, such as shared or core.
| Directive | Report |
|---|---|
//declscope:foo on a declaration |
unknown directive declscope:foo |
//declscope:foo above the package clause |
declscope:foo is not a file-level directive |
// declscope:shared, /*declscope:shared*/, or any other comment starting with declscope: that is not //declscope:name |
malformed declscope directive: write it as //declscope:name |
//declscope:shared x, or an argument to another directive that takes none |
//declscope:<name> takes no argument |
//declscope:namespace with no name |
declscope:namespace requires a name |
//declscope:namespace Billing |
namespace "Billing" is not a valid lowerCamelCase identifier |
//declscope:private and //declscope:shared together |
conflicting scope directives: ... |
//declscope:core and //declscope:namespace together |
conflicting namespace directives: a core file's namespace is the core |
//declscope:ignore foo |
unknown rule "foo" in declscope:ignore (want one of boundary, qualify, surplus, unused, directive, filter, overexported) |
//declscope:ignore , or //declscope:ignore boundary, |
empty rule name in declscope:ignore |
| The same directive twice on one declaration or file. Two ignores naming the same rules, in any order, count as the same directive | duplicate declscope:<name> directive, at the second |
//declscope:namespace or //declscope:core after the package clause |
declscope:<name> must appear before the package clause |
A valid shared, private or ignore directive attached to no declaration, such as one inside a function body |
misplaced declscope:<name>: no declaration here for it to bind to; ... |