-
Notifications
You must be signed in to change notification settings - Fork 1k
Respect canonical import comments #1017
Changes from 4 commits
d6140bd
0e41a07
19c7f60
242e387
90d2a5e
1853dc3
47e42c7
611e64b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Copyright 2017 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 canonical // import "vanity1" | ||
|
||
var ( | ||
A = "A" | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Copyright 2017 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 canonical // import "vanity2" | ||
|
||
var ( | ||
B = "B" | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Copyright 2017 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 pkg // import "canonical" | ||
|
||
var ( | ||
A = "A" | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
// Copyright 2017 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 sub // import "canonical/subpackage" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,9 @@ | |
package pkgtree | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"go/ast" | ||
"go/build" | ||
"go/parser" | ||
gscan "go/scanner" | ||
|
@@ -123,9 +125,9 @@ func ListPackages(fileRoot, importRoot string) (PackageTree, error) { | |
ip := filepath.ToSlash(filepath.Join(importRoot, strings.TrimPrefix(wp, fileRoot))) | ||
|
||
// Find all the imports, across all os/arch combos | ||
//p, err := fullPackageInDir(wp) | ||
p := &build.Package{ | ||
Dir: wp, | ||
Dir: wp, | ||
ImportPath: ip, | ||
} | ||
err = fillPackage(p) | ||
|
||
|
@@ -152,18 +154,23 @@ func ListPackages(fileRoot, importRoot string) (PackageTree, error) { | |
} | ||
} | ||
|
||
if pkg.CommentPath != "" && !strings.HasPrefix(pkg.CommentPath, importRoot) { | ||
return &NonCanonicalImportRoot{ | ||
ImportRoot: importRoot, | ||
Canonical: pkg.CommentPath, | ||
} | ||
} | ||
|
||
// This area has some...fuzzy rules, but check all the imports for | ||
// local/relative/dot-ness, and record an error for the package if we | ||
// see any. | ||
var lim []string | ||
for _, imp := range append(pkg.Imports, pkg.TestImports...) { | ||
switch { | ||
// Do allow the single-dot, at least for now | ||
case imp == "..": | ||
lim = append(lim, imp) | ||
case strings.HasPrefix(imp, "./"): | ||
lim = append(lim, imp) | ||
case strings.HasPrefix(imp, "../"): | ||
if build.IsLocalImport(imp) { | ||
// Do allow the single-dot, at least for now | ||
if imp == "." { | ||
continue | ||
} | ||
lim = append(lim, imp) | ||
} | ||
} | ||
|
@@ -210,6 +217,7 @@ func fillPackage(p *build.Package) error { | |
|
||
var testImports []string | ||
var imports []string | ||
var importComments []string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok so i was originally going to suggest that we just keep a single so, this is fine. just thought you might be interested that i checked that through 😄 |
||
for _, file := range gofiles { | ||
// Skip underscore-led or dot-led files, in keeping with the rest of the toolchain. | ||
bPrefix := filepath.Base(file)[0] | ||
|
@@ -234,6 +242,10 @@ func fillPackage(p *build.Package) error { | |
|
||
var ignored bool | ||
for _, c := range pf.Comments { | ||
ic := findImportComment(pf.Name, c) | ||
if ic != "" { | ||
importComments = append(importComments, ic) | ||
} | ||
if c.Pos() > pf.Package { // +build comment must come before package | ||
continue | ||
} | ||
|
@@ -282,14 +294,98 @@ func fillPackage(p *build.Package) error { | |
} | ||
} | ||
} | ||
|
||
importComments = uniq(importComments) | ||
if len(importComments) > 1 { | ||
return &ConflictingImportComments{ | ||
ImportPath: p.ImportPath, | ||
ImportComments: importComments, | ||
} | ||
} | ||
if len(importComments) > 0 { | ||
p.ImportComment = importComments[0] | ||
} | ||
imports = uniq(imports) | ||
testImports = uniq(testImports) | ||
p.Imports = imports | ||
p.TestImports = testImports | ||
return nil | ||
} | ||
|
||
var ( | ||
slashSlash = []byte("//") | ||
slashStar = []byte("/*") | ||
starSlash = []byte("*/") | ||
newline = []byte("\n") | ||
importKwd = []byte("import ") | ||
) | ||
|
||
func findImportComment(pkgName *ast.Ident, c *ast.CommentGroup) string { | ||
afterPkg := pkgName.NamePos + token.Pos(len(pkgName.Name)) + 1 | ||
commentSlash := c.List[0].Slash | ||
if afterPkg != commentSlash { | ||
return "" | ||
} | ||
text := []byte(c.List[0].Text) | ||
switch { | ||
case bytes.HasPrefix(text, slashSlash): | ||
eol := bytes.IndexByte(text, '\n') | ||
if eol < 0 { | ||
eol = len(text) | ||
} | ||
text = text[2:eol] | ||
case bytes.HasPrefix(text, slashStar): | ||
text = text[2:] | ||
end := bytes.Index(text, starSlash) | ||
if end < 0 { | ||
// malformed comment | ||
return "" | ||
} | ||
text = text[:end] | ||
if bytes.IndexByte(text, '\n') > 0 { | ||
// multiline comment, can't be an import comment | ||
return "" | ||
} | ||
} | ||
text = bytes.TrimSpace(text) | ||
if !bytes.HasPrefix(text, importKwd) { | ||
return "" | ||
} | ||
quotedPath := bytes.TrimSpace(text[len(importKwd):]) | ||
return string(bytes.Trim(quotedPath, `"`)) | ||
} | ||
|
||
// ConflictingImportComments indicates that the package declares more than one | ||
// different canonical paths. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: s/paths/path/ |
||
type ConflictingImportComments struct { | ||
ImportPath string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because the provenance of these errors can be a little confusing, let's include comments explaining the intent of the information recorded in each of the struct fields here, analogous to |
||
ImportComments []string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For more clarity, let's name this |
||
} | ||
|
||
func (e *ConflictingImportComments) Error() string { | ||
return fmt.Sprintf("import path %s had conflicting import comments: %s", | ||
e.ImportPath, quotedPaths(e.ImportComments)) | ||
} | ||
|
||
// NonCanonicalImportRoot reports the situation when the dependee imports a | ||
// package via something else that the package's declared canonical path. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: s/else that/other than/ |
||
type NonCanonicalImportRoot struct { | ||
ImportRoot string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As with the above, let's include brief field-by-field comments on these to explain where they come from. |
||
Canonical string | ||
} | ||
|
||
func (e *NonCanonicalImportRoot) Error() string { | ||
return fmt.Sprintf("importing via path %q, but package insists on a canonical path %q", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think this phrasing may end up being a bit confusing, as it would come out:
(case variation here used only to provide a clear example, this probably isn't how it'll be seen most of the time) i don't have a specific wording suggestion, but we should tweak this a little bit to accommodate the fact that we're contrasting a root path with what could potentially be a subpackage path. |
||
e.ImportRoot, e.Canonical) | ||
} | ||
|
||
func quotedPaths(ps []string) string { | ||
var quoted []string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. microoptimization nit: |
||
for _, p := range ps { | ||
quoted = append(quoted, fmt.Sprintf("%q", p)) | ||
} | ||
return strings.Join(quoted, ", ") | ||
} | ||
|
||
// LocalImportsError indicates that a package contains at least one relative | ||
// import that will prevent it from compiling. | ||
// | ||
|
@@ -308,7 +404,7 @@ func (e *LocalImportsError) Error() string { | |
case 1: | ||
return fmt.Sprintf("import path %s had a local import: %q", e.ImportPath, e.LocalImports[0]) | ||
default: | ||
return fmt.Sprintf("import path %s had local imports: %q", e.ImportPath, strings.Join(e.LocalImports, "\", \"")) | ||
return fmt.Sprintf("import path %s had local imports: %s", e.ImportPath, quotedPaths(e.LocalImports)) | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this needs softening a bit, as right now, the only cases where we return an actual error from
ListPackages()
itself (which is what this would result in) are more physical than logical - e.g., we can't actually read the filesystem. the rest of the API is built around that kind of assumption, too.so, instead of returning the error directly, let's make this an error that we put in the
PackageOrErr.Err
slot on each package where we encounter the problem.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I presume an equivalent change should be made to prevent
ConflictingImportComments
from leaking as well?