5
5
package pkgtree
6
6
7
7
import (
8
+ "bytes"
8
9
"fmt"
10
+ "go/ast"
9
11
"go/build"
10
12
"go/parser"
11
13
gscan "go/scanner"
@@ -130,9 +132,9 @@ func ListPackages(fileRoot, importRoot string) (PackageTree, error) {
130
132
ip := filepath .ToSlash (filepath .Join (importRoot , strings .TrimPrefix (wp , fileRoot )))
131
133
132
134
// Find all the imports, across all os/arch combos
133
- //p, err := fullPackageInDir(wp)
134
135
p := & build.Package {
135
- Dir : wp ,
136
+ Dir : wp ,
137
+ ImportPath : ip ,
136
138
}
137
139
err = fillPackage (p )
138
140
@@ -159,18 +161,26 @@ func ListPackages(fileRoot, importRoot string) (PackageTree, error) {
159
161
}
160
162
}
161
163
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
+
162
174
// This area has some...fuzzy rules, but check all the imports for
163
175
// local/relative/dot-ness, and record an error for the package if we
164
176
// see any.
165
177
var lim []string
166
178
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
+ }
174
184
lim = append (lim , imp )
175
185
}
176
186
}
@@ -217,6 +227,7 @@ func fillPackage(p *build.Package) error {
217
227
218
228
var testImports []string
219
229
var imports []string
230
+ var importComments []string
220
231
for _ , file := range gofiles {
221
232
// Skip underscore-led or dot-led files, in keeping with the rest of the toolchain.
222
233
bPrefix := filepath .Base (file )[0 ]
@@ -241,6 +252,10 @@ func fillPackage(p *build.Package) error {
241
252
242
253
var ignored bool
243
254
for _ , c := range pf .Comments {
255
+ ic := findImportComment (pf .Name , c )
256
+ if ic != "" {
257
+ importComments = append (importComments , ic )
258
+ }
244
259
if c .Pos () > pf .Package { // +build comment must come before package
245
260
continue
246
261
}
@@ -289,14 +304,97 @@ func fillPackage(p *build.Package) error {
289
304
}
290
305
}
291
306
}
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
+ }
293
317
imports = uniq (imports )
294
318
testImports = uniq (testImports )
295
319
p .Imports = imports
296
320
p .TestImports = testImports
297
321
return nil
298
322
}
299
323
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
+
300
398
// LocalImportsError indicates that a package contains at least one relative
301
399
// import that will prevent it from compiling.
302
400
//
@@ -315,7 +413,7 @@ func (e *LocalImportsError) Error() string {
315
413
case 1 :
316
414
return fmt .Sprintf ("import path %s had a local import: %q" , e .ImportPath , e .LocalImports [0 ])
317
415
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 ))
319
417
}
320
418
}
321
419
0 commit comments