Skip to content

Commit

Permalink
Implement import comment extraction
Browse files Browse the repository at this point in the history
Look for import comments following the package declaration. The
implementation of findImportComment() very closely follows go/build's
findImportComment().

Tests and validation against importRoot to come in a later commit.
  • Loading branch information
rtfb committed Aug 14, 2017
1 parent 0e41a07 commit 116ee62
Showing 1 changed file with 74 additions and 3 deletions.
77 changes: 74 additions & 3 deletions internal/gps/pkgtree/pkgtree.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
package pkgtree

import (
"bytes"
"fmt"
"go/ast"
"go/build"
"go/parser"
gscan "go/scanner"
Expand Down Expand Up @@ -125,7 +127,8 @@ func ListPackages(fileRoot, importRoot string) (PackageTree, error) {
// 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)

Expand All @@ -140,7 +143,7 @@ func ListPackages(fileRoot, importRoot string) (PackageTree, error) {
}
} else {
switch err.(type) {
case gscan.ErrorList, *gscan.Error, *build.NoGoError:
case gscan.ErrorList, *gscan.Error, *build.NoGoError, *ConflictingImportComments:
// This happens if we encounter malformed or nonexistent Go
// source code
ptree.Packages[ip] = PackageOrErr{
Expand Down Expand Up @@ -208,6 +211,7 @@ func fillPackage(p *build.Package) error {

var testImports []string
var imports []string
var importComments []string
for _, file := range gofiles {
// Skip underscore-led or dot-led files, in keeping with the rest of the toolchain.
bPrefix := filepath.Base(file)[0]
Expand All @@ -232,6 +236,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
}
Expand Down Expand Up @@ -280,14 +288,77 @@ 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 conflict
type ConflictingImportComments struct {
ImportPath string
ImportComments []string
}

func (e *ConflictingImportComments) Error() string {
return fmt.Sprintf("import path %s had conflicting import comments: %q",
e.ImportPath, strings.Join(e.ImportComments, "\", \""))
}

// LocalImportsError indicates that a package contains at least one relative
// import that will prevent it from compiling.
//
Expand Down

0 comments on commit 116ee62

Please sign in to comment.