diff --git a/README.md b/README.md index 6396a1a..a00bb47 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,11 @@ constable ./... constable skips files that carry the standard generated-code comment, `// Code generated ... DO NOT EDIT.`. A diagnostic in such a file is not actionable because the next run of the generator overwrites the file. +Set `-include-generated` to report in them anyway, + +```sh +constable -include-generated ./... +``` ## Example diff --git a/cmd/constable/main_test.go b/cmd/constable/main_test.go index fd60f1d..8eaa6a2 100644 --- a/cmd/constable/main_test.go +++ b/cmd/constable/main_test.go @@ -400,6 +400,44 @@ func TestCLIEmbeddedAnalyzersSkipGeneratedFiles(t *testing.T) { assert.Empty(t, output) } +func TestCLIIncludeGenerated(t *testing.T) { + binary := buildBinary(t) + moduleDir := writeModule(t, module{ + GoMod: []string{ + "module example.com/generated", + "", + "go 1.26", + }, + Files: map[string][]string{"gen.go": { + "// Code generated by hand. DO NOT EDIT.", + "", + "package generated", + "", + "import \"os\"", + "", + "func F() {", + "\tos.Remove(\"x\")", + "}", + "", + "//constable:nonmutating", + "func G(p *int) {", + "\t*p = 1", + "}", + }}, + }) + + output, err := runConstable(t, binary, moduleDir) + require.NoError(t, err, output) + assert.Empty(t, output) + + // One flag turns the skip off for the analyzers that constable owns and for + // the analyzers that it embeds. + output, err = runConstable(t, binary, moduleDir, "-include-generated") + require.Error(t, err, output) + assert.Contains(t, output, "unchecked error") + assert.Contains(t, output, "//constable:nonmutating function mutates pointer parameter p") +} + func buildBinary(t *testing.T) string { t.Helper() diff --git a/internal/driver/driver.go b/internal/driver/driver.go index 8030248..7c7bd3f 100644 --- a/internal/driver/driver.go +++ b/internal/driver/driver.go @@ -25,6 +25,8 @@ import ( "golang.org/x/tools/go/analysis/checker" "golang.org/x/tools/go/analysis/unitchecker" "golang.org/x/tools/go/packages" + + "github.com/nnutter/constable/internal/generated" ) // Options controls driver output. @@ -152,9 +154,12 @@ func Main(version string, hooks Hooks, analyzers ...*analysis.Analyzer) { flag.BoolVar(&opts.Fix, "fix", false, "apply all suggested fixes") flag.BoolVar(&opts.Diff, "diff", false, "with -fix, don't update the files, but print a unified diff") flag.BoolVar(&opts.IncludeTests, "test", true, "indicates whether test files should be analyzed, too") + includeGenerated := flag.Bool("include-generated", false, "report in generated files, which constable skips by default") flag.Parse() + generated.Include(*includeGenerated) + if *printFlags { writeFlagsJSON() return diff --git a/internal/generated/generated.go b/internal/generated/generated.go index a41d941..4ad6397 100644 --- a/internal/generated/generated.go +++ b/internal/generated/generated.go @@ -4,12 +4,24 @@ package generated import "go/ast" +// include reports whether analyzers report in generated files. +var include bool + +// Include makes analyzers report in generated files when enabled is true. +// +// The driver sets it from -include-generated, so every analyzer follows one +// answer without carrying a flag of its own. +func Include(enabled bool) { + include = enabled +} + // Skipped reports whether analyzers omit the diagnostics of file. // // A diagnostic in a generated file is not actionable because the next run of // the generating tool overwrites the file. The mark is the Go convention, // "// Code generated DO NOT EDIT.", so an author controls it with the -// comment alone. +// comment alone. Include overrides the rule for a run that wants every +// diagnostic. func Skipped(file *ast.File) bool { - return ast.IsGenerated(file) + return !include && ast.IsGenerated(file) } diff --git a/internal/generated/skip_test.go b/internal/generated/skip_test.go index f08eb77..d3f8047 100644 --- a/internal/generated/skip_test.go +++ b/internal/generated/skip_test.go @@ -32,3 +32,10 @@ var reportFuncs = &analysis.Analyzer{ func TestSkip(t *testing.T) { analysistest.Run(t, analysistest.TestData(), generated.Skip(reportFuncs), "g") } + +func TestSkipWithInclude(t *testing.T) { + generated.Include(true) + t.Cleanup(func() { generated.Include(false) }) + + analysistest.Run(t, analysistest.TestData(), generated.Skip(reportFuncs), "h") +} diff --git a/internal/generated/testdata/src/h/h.go b/internal/generated/testdata/src/h/h.go new file mode 100644 index 0000000..1de71b3 --- /dev/null +++ b/internal/generated/testdata/src/h/h.go @@ -0,0 +1,3 @@ +package h + +func Gen() {} // want `func Gen` diff --git a/internal/generated/testdata/src/h/zz_generated.go b/internal/generated/testdata/src/h/zz_generated.go new file mode 100644 index 0000000..f5b79f0 --- /dev/null +++ b/internal/generated/testdata/src/h/zz_generated.go @@ -0,0 +1,5 @@ +// Code generated by hand. DO NOT EDIT. + +package h + +func Generated() {} // want `func Generated`