go/analysis/passes/modernize: add importcomment analyzer The new analyzer removes canonical import path comments, such as package foo // import "example.com/foo" The go command enforced these comments in GOPATH mode via "go get", but ignores them once a package belongs to a module, so they are obsolete in module-based projects. The fix deletes the comment. The analyzer only runs when pass.Module is set, matching the agreed behavior of removing the comment when a go.mod is present without inspecting the import path. Fixes golang/go#80049 Change-Id: I0ad4eb113496a2fba03eb1052e615af3f804a347 GitHub-Last-Rev: dbfa397f47d50fb487e38cacbe6c78da684b26ea GitHub-Pull-Request: golang/tools#655 Reviewed-on: https://go-review.googlesource.com/c/tools/+/796860 Auto-Submit: Alan Donovan <adonovan@google.com> Reviewed-by: Alan Donovan <adonovan@google.com> Reviewed-by: Madeline Kalil <mkalil@google.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/go/analysis/passes/modernize/doc.go b/go/analysis/passes/modernize/doc.go index 260dd50..c5545c0 100644 --- a/go/analysis/passes/modernize/doc.go +++ b/go/analysis/passes/modernize/doc.go
@@ -182,6 +182,19 @@ This fix only applies to `range` loops. +# Analyzer importcomment + +importcomment: remove obsolete comments specifying canonical import path + +The importcomment analyzer removes comments specifying the canonical +import path, such as + + package foo // import "example.com/foo" + +The go command enforced these comments in GOPATH mode via "go get", but +ignores them in module mode, so they are obsolete once the package +belongs to a module. The fix removes the comment. + # Analyzer mapsloop mapsloop: replace explicit loops over maps with calls to maps package
diff --git a/go/analysis/passes/modernize/export_test.go b/go/analysis/passes/modernize/export_test.go index 0823331..0401cf0 100644 --- a/go/analysis/passes/modernize/export_test.go +++ b/go/analysis/passes/modernize/export_test.go
@@ -7,6 +7,7 @@ package modernize var ( + ImportCommentAnalyzer = importCommentAnalyzer SlicesBackwardAnalyzer = slicesBackwardAnalyzer UnsafeFuncsAnalyzer = unsafeFuncsAnalyzer )
diff --git a/go/analysis/passes/modernize/importcomment.go b/go/analysis/passes/modernize/importcomment.go new file mode 100644 index 0000000..1538783 --- /dev/null +++ b/go/analysis/passes/modernize/importcomment.go
@@ -0,0 +1,69 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package modernize + +import ( + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/internal/analysis/analyzerutil" +) + +var importCommentAnalyzer = &analysis.Analyzer{ + Name: "importcomment", + Doc: analyzerutil.MustExtractDoc(doc, "importcomment"), + URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#importcomment", + Run: importcomment, +} + +func importcomment(pass *analysis.Pass) (any, error) { + // Import path comments are ignored in module mode. + if pass.Module == nil { + return nil, nil + } + + for _, file := range pass.Files { + // An import comment follows the package name on the same line. + pkgEnd := file.Name.End() + pkgLine := pass.Fset.Position(pkgEnd).Line + for _, c := range file.Comments { + if len(c.List) != 1 { + continue + } + if c.Pos() < pkgEnd { + continue + } + commentLine := pass.Fset.Position(c.Pos()).Line + if commentLine > pkgLine { + break // comments are sorted; the rest are on later lines + } + // Have: package p // comment + if !isImportComment(c.Text()) { + continue + } + pass.Report(analysis.Diagnostic{ + Pos: c.Pos(), + End: c.End(), + Message: "canonical import path comment is ignored in module mode", + SuggestedFixes: []analysis.SuggestedFix{{ + Message: "Remove obsolete import path comment", + TextEdits: []analysis.TextEdit{{ + Pos: pkgEnd, // deletes the preceding space too + End: c.End(), + }}, + }}, + }) + } + } + + return nil, nil +} + +// isImportComment reports whether text, a comment's content with its +// markers removed, is a canonical import path comment, import "path". +func isImportComment(text string) bool { + text = strings.TrimSpace(text) + return strings.HasPrefix(text, `import "`) && strings.HasSuffix(text, `"`) +}
diff --git a/go/analysis/passes/modernize/modernize.go b/go/analysis/passes/modernize/modernize.go index d7dfe55..23dd8ab 100644 --- a/go/analysis/passes/modernize/modernize.go +++ b/go/analysis/passes/modernize/modernize.go
@@ -38,6 +38,7 @@ EmbedLitAnalyzer, ErrorsAsTypeAnalyzer, ForVarAnalyzer, + importCommentAnalyzer, // awaiting public symbol MapsLoopAnalyzer, MinMaxAnalyzer, NewExprAnalyzer,
diff --git a/go/analysis/passes/modernize/modernize_test.go b/go/analysis/passes/modernize/modernize_test.go index bc8ecfb..bb2f7ac 100644 --- a/go/analysis/passes/modernize/modernize_test.go +++ b/go/analysis/passes/modernize/modernize_test.go
@@ -5,6 +5,7 @@ package modernize_test import ( + "path/filepath" "testing" . "golang.org/x/tools/go/analysis/analysistest" @@ -86,6 +87,13 @@ RunWithSuggestedFixes(t, TestData(), modernize.PlusBuildAnalyzer, "plusbuild") } +func TestImportComment(t *testing.T) { + // Loaded in module mode (a go.mod at the testdata root) so that + // pass.Module is set, which the analyzer requires. + dir := filepath.Join(TestData(), "importcommentmod") + RunWithSuggestedFixes(t, dir, modernize.ImportCommentAnalyzer, "./...") +} + func TestReflectTypeFor(t *testing.T) { testenv.NeedsGo1Point(t, 25) // requires go1.25 types.Var.Kind RunWithSuggestedFixes(t, TestData(), modernize.ReflectTypeForAnalyzer, "reflecttypefor")
diff --git a/go/analysis/passes/modernize/testdata/importcommentmod/block.go b/go/analysis/passes/modernize/testdata/importcommentmod/block.go new file mode 100644 index 0000000..654039d --- /dev/null +++ b/go/analysis/passes/modernize/testdata/importcommentmod/block.go
@@ -0,0 +1,5 @@ +// want +2 `canonical import path comment is ignored in module mode` + +package importcommentmod /* import "example.com/importcommentmod" */ + +func block() {}
diff --git a/go/analysis/passes/modernize/testdata/importcommentmod/block.go.golden b/go/analysis/passes/modernize/testdata/importcommentmod/block.go.golden new file mode 100644 index 0000000..dea04ac --- /dev/null +++ b/go/analysis/passes/modernize/testdata/importcommentmod/block.go.golden
@@ -0,0 +1,5 @@ +// want +2 `canonical import path comment is ignored in module mode` + +package importcommentmod + +func block() {}
diff --git a/go/analysis/passes/modernize/testdata/importcommentmod/go.mod b/go/analysis/passes/modernize/testdata/importcommentmod/go.mod new file mode 100644 index 0000000..32f9f97 --- /dev/null +++ b/go/analysis/passes/modernize/testdata/importcommentmod/go.mod
@@ -0,0 +1,3 @@ +module example.com/importcommentmod + +go 1.24
diff --git a/go/analysis/passes/modernize/testdata/importcommentmod/importcomment.go b/go/analysis/passes/modernize/testdata/importcommentmod/importcomment.go new file mode 100644 index 0000000..b92c8f8 --- /dev/null +++ b/go/analysis/passes/modernize/testdata/importcommentmod/importcomment.go
@@ -0,0 +1,7 @@ +// want +2 `canonical import path comment is ignored in module mode` + +package importcommentmod // import "example.com/importcommentmod" + +import "fmt" + +var _ = fmt.Sprint
diff --git a/go/analysis/passes/modernize/testdata/importcommentmod/importcomment.go.golden b/go/analysis/passes/modernize/testdata/importcommentmod/importcomment.go.golden new file mode 100644 index 0000000..029a680 --- /dev/null +++ b/go/analysis/passes/modernize/testdata/importcommentmod/importcomment.go.golden
@@ -0,0 +1,7 @@ +// want +2 `canonical import path comment is ignored in module mode` + +package importcommentmod + +import "fmt" + +var _ = fmt.Sprint
diff --git a/go/analysis/passes/modernize/testdata/importcommentmod/other.go b/go/analysis/passes/modernize/testdata/importcommentmod/other.go new file mode 100644 index 0000000..26c5010 --- /dev/null +++ b/go/analysis/passes/modernize/testdata/importcommentmod/other.go
@@ -0,0 +1,3 @@ +package importcommentmod // regular comment, not an import path + +func other() {}
diff --git a/gopls/doc/analyzers.md b/gopls/doc/analyzers.md index 10f829c..70d2b91 100644 --- a/gopls/doc/analyzers.md +++ b/gopls/doc/analyzers.md
@@ -3320,6 +3320,20 @@ Package documentation: [ifaceassert](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ifaceassert) +<a id='importcomment'></a> +## `importcomment`: remove obsolete comments specifying canonical import path + +The importcomment analyzer removes comments specifying the canonical import path, such as + + package foo // import "example.com/foo" + +The go command enforced these comments in GOPATH mode via "go get", but ignores them in module mode, so they are obsolete once the package belongs to a module. The fix removes the comment. + + +Default: on. + +Package documentation: [importcomment](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#importcomment) + <a id='infertypeargs'></a> ## `infertypeargs`: check for unnecessary type arguments in call expressions
diff --git a/gopls/internal/doc/api.json b/gopls/internal/doc/api.json index b9cf154..24efa51 100644 --- a/gopls/internal/doc/api.json +++ b/gopls/internal/doc/api.json
@@ -1527,6 +1527,12 @@ "Status": "" }, { + "Name": "\"importcomment\"", + "Doc": "remove obsolete comments specifying canonical import path\n\nThe importcomment analyzer removes comments specifying the canonical\nimport path, such as\n\n\tpackage foo // import \"example.com/foo\"\n\nThe go command enforced these comments in GOPATH mode via \"go get\", but\nignores them in module mode, so they are obsolete once the package\nbelongs to a module. The fix removes the comment.", + "Default": "true", + "Status": "" + }, + { "Name": "\"infertypeargs\"", "Doc": "check for unnecessary type arguments in call expressions\n\nExplicit type arguments may be omitted from call expressions if they can be\ninferred from function arguments, or from other type arguments:\n\n\tfunc f[T any](T) {}\n\t\n\tfunc _() {\n\t\tf[string](\"foo\") // string could be inferred\n\t}\n", "Default": "true", @@ -3542,6 +3548,12 @@ "Default": true }, { + "Name": "importcomment", + "Doc": "remove obsolete comments specifying canonical import path\n\nThe importcomment analyzer removes comments specifying the canonical\nimport path, such as\n\n\tpackage foo // import \"example.com/foo\"\n\nThe go command enforced these comments in GOPATH mode via \"go get\", but\nignores them in module mode, so they are obsolete once the package\nbelongs to a module. The fix removes the comment.", + "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#importcomment", + "Default": true + }, + { "Name": "infertypeargs", "Doc": "check for unnecessary type arguments in call expressions\n\nExplicit type arguments may be omitted from call expressions if they can be\ninferred from function arguments, or from other type arguments:\n\n\tfunc f[T any](T) {}\n\t\n\tfunc _() {\n\t\tf[string](\"foo\") // string could be inferred\n\t}\n", "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/infertypeargs",