Skip to content
This repository was archived by the owner on Sep 9, 2020. It is now read-only.

Commit 667d662

Browse files
authoredOct 7, 2017
Merge pull request #1017 from rtfb/respect-import-comments-issue-902
[WIP] Respect canonical import comments
2 parents a4c6879 + 611e64b commit 667d662

File tree

7 files changed

+201
-13
lines changed

7 files changed

+201
-13
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package canonical // import "vanity1"
6+
7+
var (
8+
A = "A"
9+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package canonical // import "vanity2"
6+
7+
var (
8+
B = "B"
9+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package pkg // import "canonical"
6+
7+
var (
8+
A = "A"
9+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package sub // import "canonical/subpackage"

‎internal/gps/identifier.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ import (
4141
type ProjectRoot string
4242

4343
// A ProjectIdentifier provides the name and source location of a dependency. It
44-
// is related to, but differs in two keys ways from, an plain import path.
44+
// is related to, but differs in two key ways from, a plain import path.
4545
//
4646
// First, ProjectIdentifiers do not identify a single package. Rather, they
47-
// encompasses the whole tree of packages, including tree's root - the
47+
// encompass the whole tree of packages, including tree's root - the
4848
// ProjectRoot. In gps' current design, this ProjectRoot almost always
4949
// corresponds to the root of a repository.
5050
//

‎internal/gps/pkgtree/pkgtree.go

+109-11
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
package pkgtree
66

77
import (
8+
"bytes"
89
"fmt"
10+
"go/ast"
911
"go/build"
1012
"go/parser"
1113
gscan "go/scanner"
@@ -130,9 +132,9 @@ func ListPackages(fileRoot, importRoot string) (PackageTree, error) {
130132
ip := filepath.ToSlash(filepath.Join(importRoot, strings.TrimPrefix(wp, fileRoot)))
131133

132134
// Find all the imports, across all os/arch combos
133-
//p, err := fullPackageInDir(wp)
134135
p := &build.Package{
135-
Dir: wp,
136+
Dir: wp,
137+
ImportPath: ip,
136138
}
137139
err = fillPackage(p)
138140

@@ -159,18 +161,26 @@ func ListPackages(fileRoot, importRoot string) (PackageTree, error) {
159161
}
160162
}
161163

164+
if pkg.CommentPath != "" && !strings.HasPrefix(pkg.CommentPath, importRoot) {
165+
ptree.Packages[ip] = PackageOrErr{
166+
Err: &NonCanonicalImportRoot{
167+
ImportRoot: importRoot,
168+
Canonical: pkg.CommentPath,
169+
},
170+
}
171+
return nil
172+
}
173+
162174
// This area has some...fuzzy rules, but check all the imports for
163175
// local/relative/dot-ness, and record an error for the package if we
164176
// see any.
165177
var lim []string
166178
for _, imp := range append(pkg.Imports, pkg.TestImports...) {
167-
switch {
168-
// Do allow the single-dot, at least for now
169-
case imp == "..":
170-
lim = append(lim, imp)
171-
case strings.HasPrefix(imp, "./"):
172-
lim = append(lim, imp)
173-
case strings.HasPrefix(imp, "../"):
179+
if build.IsLocalImport(imp) {
180+
// Do allow the single-dot, at least for now
181+
if imp == "." {
182+
continue
183+
}
174184
lim = append(lim, imp)
175185
}
176186
}
@@ -217,6 +227,7 @@ func fillPackage(p *build.Package) error {
217227

218228
var testImports []string
219229
var imports []string
230+
var importComments []string
220231
for _, file := range gofiles {
221232
// Skip underscore-led or dot-led files, in keeping with the rest of the toolchain.
222233
bPrefix := filepath.Base(file)[0]
@@ -241,6 +252,10 @@ func fillPackage(p *build.Package) error {
241252

242253
var ignored bool
243254
for _, c := range pf.Comments {
255+
ic := findImportComment(pf.Name, c)
256+
if ic != "" {
257+
importComments = append(importComments, ic)
258+
}
244259
if c.Pos() > pf.Package { // +build comment must come before package
245260
continue
246261
}
@@ -289,14 +304,97 @@ func fillPackage(p *build.Package) error {
289304
}
290305
}
291306
}
292-
307+
importComments = uniq(importComments)
308+
if len(importComments) > 1 {
309+
return &ConflictingImportComments{
310+
ImportPath: p.ImportPath,
311+
ConflictingImportComments: importComments,
312+
}
313+
}
314+
if len(importComments) > 0 {
315+
p.ImportComment = importComments[0]
316+
}
293317
imports = uniq(imports)
294318
testImports = uniq(testImports)
295319
p.Imports = imports
296320
p.TestImports = testImports
297321
return nil
298322
}
299323

324+
var (
325+
slashSlash = []byte("//")
326+
slashStar = []byte("/*")
327+
starSlash = []byte("*/")
328+
importKwd = []byte("import ")
329+
)
330+
331+
func findImportComment(pkgName *ast.Ident, c *ast.CommentGroup) string {
332+
afterPkg := pkgName.NamePos + token.Pos(len(pkgName.Name)) + 1
333+
commentSlash := c.List[0].Slash
334+
if afterPkg != commentSlash {
335+
return ""
336+
}
337+
text := []byte(c.List[0].Text)
338+
switch {
339+
case bytes.HasPrefix(text, slashSlash):
340+
eol := bytes.IndexByte(text, '\n')
341+
if eol < 0 {
342+
eol = len(text)
343+
}
344+
text = text[2:eol]
345+
case bytes.HasPrefix(text, slashStar):
346+
text = text[2:]
347+
end := bytes.Index(text, starSlash)
348+
if end < 0 {
349+
// malformed comment
350+
return ""
351+
}
352+
text = text[:end]
353+
if bytes.IndexByte(text, '\n') > 0 {
354+
// multiline comment, can't be an import comment
355+
return ""
356+
}
357+
}
358+
text = bytes.TrimSpace(text)
359+
if !bytes.HasPrefix(text, importKwd) {
360+
return ""
361+
}
362+
quotedPath := bytes.TrimSpace(text[len(importKwd):])
363+
return string(bytes.Trim(quotedPath, `"`))
364+
}
365+
366+
// ConflictingImportComments indicates that the package declares more than one
367+
// different canonical path.
368+
type ConflictingImportComments struct {
369+
ImportPath string // An import path refering to this package
370+
ConflictingImportComments []string // All distinct "canonical" paths encountered in the package files
371+
}
372+
373+
func (e *ConflictingImportComments) Error() string {
374+
return fmt.Sprintf("import path %s had conflicting import comments: %s",
375+
e.ImportPath, quotedPaths(e.ConflictingImportComments))
376+
}
377+
378+
// NonCanonicalImportRoot reports the situation when the dependee imports a
379+
// package via something other than the package's declared canonical path.
380+
type NonCanonicalImportRoot struct {
381+
ImportRoot string // A root path that is being used to import a package
382+
Canonical string // A canonical path declared by the package being imported
383+
}
384+
385+
func (e *NonCanonicalImportRoot) Error() string {
386+
return fmt.Sprintf("import root %q is not a prefix for the package's declared canonical path %q",
387+
e.ImportRoot, e.Canonical)
388+
}
389+
390+
func quotedPaths(ps []string) string {
391+
quoted := make([]string, 0, len(ps))
392+
for _, p := range ps {
393+
quoted = append(quoted, fmt.Sprintf("%q", p))
394+
}
395+
return strings.Join(quoted, ", ")
396+
}
397+
300398
// LocalImportsError indicates that a package contains at least one relative
301399
// import that will prevent it from compiling.
302400
//
@@ -315,7 +413,7 @@ func (e *LocalImportsError) Error() string {
315413
case 1:
316414
return fmt.Sprintf("import path %s had a local import: %q", e.ImportPath, e.LocalImports[0])
317415
default:
318-
return fmt.Sprintf("import path %s had local imports: %q", e.ImportPath, strings.Join(e.LocalImports, "\", \""))
416+
return fmt.Sprintf("import path %s had local imports: %s", e.ImportPath, quotedPaths(e.LocalImports))
319417
}
320418
}
321419

‎internal/gps/pkgtree/pkgtree_test.go

+58
Original file line numberDiff line numberDiff line change
@@ -1289,6 +1289,64 @@ func TestListPackages(t *testing.T) {
12891289
},
12901290
},
12911291
},
1292+
"canonical": {
1293+
fileRoot: j("canonical"),
1294+
importRoot: "canonical",
1295+
out: PackageTree{
1296+
ImportRoot: "canonical",
1297+
Packages: map[string]PackageOrErr{
1298+
"canonical": {
1299+
P: Package{
1300+
ImportPath: "canonical",
1301+
CommentPath: "canonical",
1302+
Name: "pkg",
1303+
Imports: []string{},
1304+
},
1305+
},
1306+
"canonical/sub": {
1307+
P: Package{
1308+
ImportPath: "canonical/sub",
1309+
CommentPath: "canonical/subpackage",
1310+
Name: "sub",
1311+
Imports: []string{},
1312+
},
1313+
},
1314+
},
1315+
},
1316+
},
1317+
"conflicting canonical comments": {
1318+
fileRoot: j("canon_confl"),
1319+
importRoot: "canon_confl",
1320+
out: PackageTree{},
1321+
err: &ConflictingImportComments{
1322+
ImportPath: "canon_confl",
1323+
ConflictingImportComments: []string{
1324+
"vanity1",
1325+
"vanity2",
1326+
},
1327+
},
1328+
},
1329+
"non-canonical": {
1330+
fileRoot: j("canonical"),
1331+
importRoot: "noncanonical",
1332+
out: PackageTree{
1333+
ImportRoot: "noncanonical",
1334+
Packages: map[string]PackageOrErr{
1335+
"noncanonical": PackageOrErr{
1336+
Err: &NonCanonicalImportRoot{
1337+
ImportRoot: "noncanonical",
1338+
Canonical: "canonical",
1339+
},
1340+
},
1341+
"noncanonical/sub": PackageOrErr{
1342+
Err: &NonCanonicalImportRoot{
1343+
ImportRoot: "noncanonical",
1344+
Canonical: "canonical/subpackage",
1345+
},
1346+
},
1347+
},
1348+
},
1349+
},
12921350
}
12931351

12941352
for name, fix := range table {

0 commit comments

Comments
 (0)
This repository has been archived.