Skip to content

Commit 46e932f

Browse files
xieyuschengopherbot
authored andcommitted
internal/analysisinternal: add std import at the start of the import group
This CL ensures that standard library imports are added at the start of the import group rather than at the end. In projects with third-party dependencies (such as gopls), imports are usually organized in the following order: standard library imports, imports from the module itself, and third-party imports. Previously, when analysisinternal.AddImport was called to add a new standard library package (e.g., "slices") to a file that already imports both standard library and third-party modules, the newly added "slices" import would inappropriately be placed among the third-party imports. This violates widely accepted conventions, and gofmt does not automatically fix such cases. This CL solves the problem when running 'modernize -fix ./...' under gopls, we need to manually move the new added std package to put std imports together. Change-Id: I714c92c205542358dcdd1a6ed6db171be9c5fb56 Reviewed-on: https://go-review.googlesource.com/c/tools/+/664436 LUCI-TryBot-Result: Go LUCI <[email protected]> Commit-Queue: Alan Donovan <[email protected]> Auto-Submit: Alan Donovan <[email protected]> Reviewed-by: Alan Donovan <[email protected]> Reviewed-by: Junyang Shao <[email protected]>
1 parent d346382 commit 46e932f

File tree

3 files changed

+93
-14
lines changed

3 files changed

+93
-14
lines changed

gopls/internal/analysis/modernize/modernize.go

+1-13
Original file line numberDiff line numberDiff line change
@@ -170,22 +170,10 @@ func enclosingFile(c cursor.Cursor) *ast.File {
170170
// specified standard packages or their dependencies.
171171
func within(pass *analysis.Pass, pkgs ...string) bool {
172172
path := pass.Pkg.Path()
173-
return standard(path) &&
173+
return analysisinternal.IsStdPackage(path) &&
174174
moreiters.Contains(stdlib.Dependencies(pkgs...), path)
175175
}
176176

177-
// standard reports whether the specified package path belongs to a
178-
// package in the standard library (including internal dependencies).
179-
func standard(path string) bool {
180-
// A standard package has no dot in its first segment.
181-
// (It may yet have a dot, e.g. "vendor/golang.org/x/foo".)
182-
slash := strings.IndexByte(path, '/')
183-
if slash < 0 {
184-
slash = len(path)
185-
}
186-
return !strings.Contains(path[:slash], ".") && path != "testdata"
187-
}
188-
189177
var (
190178
builtinAny = types.Universe.Lookup("any")
191179
builtinAppend = types.Universe.Lookup("append")

internal/analysisinternal/addimport_test.go

+71
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,59 @@ func _(io.Reader) {
238238
want: `package a
239239
240240
import (
241+
"fmt"
241242
"io"
243+
)
244+
245+
func _(io.Reader) {
246+
fmt
247+
}`,
248+
},
249+
{
250+
descr: descr("add import to group which imports std and a 3rd module"),
251+
src: `package a
252+
253+
import (
254+
"io"
255+
256+
"vendor/golang.org/x/net/dns/dnsmessage"
257+
)
258+
259+
func _(io.Reader) {
260+
«fmt fmt»
261+
}`,
262+
want: `package a
263+
264+
import (
242265
"fmt"
266+
"io"
267+
268+
"vendor/golang.org/x/net/dns/dnsmessage"
243269
)
244270
271+
func _(io.Reader) {
272+
fmt
273+
}`,
274+
},
275+
{
276+
descr: descr("add import to group which imports std and a 3rd module without parens"),
277+
src: `package a
278+
279+
import "io"
280+
281+
import "vendor/golang.org/x/net/dns/dnsmessage"
282+
283+
func _(io.Reader) {
284+
«fmt fmt»
285+
}`,
286+
want: `package a
287+
288+
import "fmt"
289+
290+
import "io"
291+
292+
import "vendor/golang.org/x/net/dns/dnsmessage"
293+
245294
func _(io.Reader) {
246295
fmt
247296
}`,
@@ -315,3 +364,25 @@ func _(io.Reader) {
315364
})
316365
}
317366
}
367+
368+
func TestIsStdPackage(t *testing.T) {
369+
testCases := []struct {
370+
pkgpath string
371+
isStd bool
372+
}{
373+
{pkgpath: "os", isStd: true},
374+
{pkgpath: "net/http", isStd: true},
375+
{pkgpath: "vendor/golang.org/x/net/dns/dnsmessage", isStd: true},
376+
{pkgpath: "golang.org/x/net/dns/dnsmessage", isStd: false},
377+
{pkgpath: "testdata", isStd: false},
378+
}
379+
380+
for _, tc := range testCases {
381+
t.Run(tc.pkgpath, func(t *testing.T) {
382+
got := analysisinternal.IsStdPackage(tc.pkgpath)
383+
if got != tc.isStd {
384+
t.Fatalf("got %t want %t", got, tc.isStd)
385+
}
386+
})
387+
}
388+
}

internal/analysisinternal/analysis.go

+21-1
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,15 @@ func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member
280280
// If the first decl is an import group, add this new import at the end.
281281
if gd, ok := before.(*ast.GenDecl); ok && gd.Tok == token.IMPORT && gd.Rparen.IsValid() {
282282
pos = gd.Rparen
283-
newText = "\t" + newText + "\n"
283+
// if it's a std lib, we should append it at the beginning of import group.
284+
// otherwise we may see the std package is put at the last behind a 3rd module which doesn't follow our convention.
285+
// besides, gofmt doesn't help in this case.
286+
if IsStdPackage(pkgpath) && len(gd.Specs) != 0 {
287+
pos = gd.Specs[0].Pos()
288+
newText += "\n\t"
289+
} else {
290+
newText = "\t" + newText + "\n"
291+
}
284292
} else {
285293
pos = before.Pos()
286294
newText = "import " + newText + "\n\n"
@@ -637,3 +645,15 @@ func Comments(file *ast.File, start, end token.Pos) iter.Seq[*ast.Comment] {
637645
}
638646
}
639647
}
648+
649+
// IsStdPackage reports whether the specified package path belongs to a
650+
// package in the standard library (including internal dependencies).
651+
func IsStdPackage(path string) bool {
652+
// A standard package has no dot in its first segment.
653+
// (It may yet have a dot, e.g. "vendor/golang.org/x/foo".)
654+
slash := strings.IndexByte(path, '/')
655+
if slash < 0 {
656+
slash = len(path)
657+
}
658+
return !strings.Contains(path[:slash], ".") && path != "testdata"
659+
}

0 commit comments

Comments
 (0)