@@ -4,17 +4,14 @@ import (
44 "fmt"
55 "go/ast"
66 "go/token"
7- "path/filepath"
8- "regexp"
9- "strconv"
107 "strings"
118
129 "github.com/dgageot/rubocop-go/cop"
1310)
1411
15- // ConfigVersionImport enforces that config version packages (pkg/config/vN)
16- // only import their immediate predecessor (pkg/config/v{N-1}) and the shared
17- // types package (pkg/config/types). This preserves the strict migration chain:
12+ // ConfigVersionImport enforces that config version packages (pkg/config/vN
13+ // and pkg/config/latest) only import their immediate predecessor and the
14+ // shared types package, preserving the strict migration chain:
1815// v0 → v1 → v2 → … → latest.
1916type ConfigVersionImport struct {}
2017
@@ -24,100 +21,69 @@ func (*ConfigVersionImport) Description() string {
2421}
2522func (* ConfigVersionImport ) Severity () cop.Severity { return cop .Error }
2623
27- // configVersionRe matches "pkg/config/vN" at the end of an import path.
28- var configVersionRe = regexp .MustCompile (`pkg/config/v(\d+)$` )
29-
30- // Check inspects import declarations in config version packages.
3124func (c * ConfigVersionImport ) Check (fset * token.FileSet , file * ast.File ) []cop.Offense {
3225 if len (file .Imports ) == 0 {
3326 return nil
3427 }
3528
36- // Determine which config version package this file belongs to.
37- filename := fset .Position (file .Package ).Filename
38- dirVersion , isVersioned := extractDirVersion (filename )
39- dirIsLatest := isLatestDir (filename )
29+ dir := configDir (fset .Position (file .Package ).Filename )
30+ if dir == "" {
31+ return nil
32+ }
33+ // Black-box test files (package <dir>_test) are external to the package
34+ // and may import what they please.
35+ if strings .HasSuffix (file .Name .Name , "_test" ) {
36+ return nil
37+ }
4038
41- if ! isVersioned && ! dirIsLatest {
39+ dirVersion , isVersioned := versionFromDir (dir )
40+ isLatest := dir == "latest"
41+ if ! isVersioned && ! isLatest {
4242 return nil
4343 }
4444
4545 var offenses []cop.Offense
46-
4746 for _ , imp := range file .Imports {
48- importPath := strings .Trim (imp .Path .Value , `"` )
49-
50- if ! strings .Contains (importPath , "pkg/config/" ) {
51- continue
52- }
47+ path := importPath (imp )
5348
54- if strings .HasSuffix (importPath , "pkg/config/types" ) {
49+ if ! strings .Contains ( path , "pkg/config/" ) || strings . HasSuffix (path , "pkg/config/types" ) {
5550 continue
5651 }
57-
58- if isVersioned {
59- offenses = append (offenses , c .checkVersionedImport (fset , imp , importPath , dirVersion )... )
60- } else if dirIsLatest {
61- offenses = append (offenses , c .checkLatestImport (fset , imp , importPath )... )
52+ if msg := importViolation (path , dirVersion , isLatest ); msg != "" {
53+ offenses = append (offenses , offense (c , fset , imp .Path , msg ))
6254 }
6355 }
64-
6556 return offenses
6657}
6758
68- func (c * ConfigVersionImport ) checkVersionedImport (fset * token.FileSet , imp * ast.ImportSpec , importPath string , dirVersion int ) []cop.Offense {
69- if strings .HasSuffix (importPath , "pkg/config/latest" ) {
70- return []cop.Offense {cop .NewOffense (c , fset , imp .Path .Pos (), imp .Path .End (),
71- fmt .Sprintf ("config v%d must not import pkg/config/latest" , dirVersion ))}
59+ // importViolation returns a non-empty error message if the given import path
60+ // is forbidden inside a config-version package, or "" if the import is fine.
61+ // dirVersion is the importing package's N (only meaningful when !isLatest).
62+ func importViolation (path string , dirVersion int , isLatest bool ) string {
63+ if isLatest {
64+ // pkg/config/latest may only import other config-version packages.
65+ // (The "must be the immediate predecessor" rule lives in the
66+ // LatestImportsPredecessor cop.)
67+ if _ , ok := versionFromImport (path ); ok {
68+ return ""
69+ }
70+ return "pkg/config/latest must only import config version or types packages, not " + path
7271 }
7372
74- m := configVersionRe .FindStringSubmatch (importPath )
75- if m == nil {
76- return nil
73+ // Versioned package (vN).
74+ if strings .HasSuffix (path , "pkg/config/latest" ) {
75+ return fmt .Sprintf ("config v%d must not import pkg/config/latest" , dirVersion )
76+ }
77+ imported , ok := versionFromImport (path )
78+ if ! ok {
79+ return ""
7780 }
78-
79- importedVersion , _ := strconv .Atoi (m [1 ])
8081 expected := dirVersion - 1
81-
8282 if expected < 0 {
83- return []cop.Offense {cop .NewOffense (c , fset , imp .Path .Pos (), imp .Path .End (),
84- "config v0 must not import other config version packages" )}
83+ return "config v0 must not import other config version packages"
8584 }
86-
87- if importedVersion != expected {
88- return []cop.Offense {cop .NewOffense (c , fset , imp .Path .Pos (), imp .Path .End (),
89- fmt .Sprintf ("config v%d must import v%d (its predecessor), not v%d" , dirVersion , expected , importedVersion ))}
90- }
91-
92- return nil
93- }
94-
95- func (c * ConfigVersionImport ) checkLatestImport (fset * token.FileSet , imp * ast.ImportSpec , importPath string ) []cop.Offense {
96- if configVersionRe .MatchString (importPath ) {
97- return nil
85+ if imported != expected {
86+ return fmt .Sprintf ("config v%d must import v%d (its predecessor), not v%d" , dirVersion , expected , imported )
9887 }
99-
100- return []cop.Offense {cop .NewOffense (c , fset , imp .Path .Pos (), imp .Path .End (),
101- "pkg/config/latest should only import config version or types packages, not " + importPath )}
102- }
103-
104- func extractDirVersion (filename string ) (int , bool ) {
105- normalized := filepath .ToSlash (filename )
106-
107- re := regexp .MustCompile (`/pkg/config/v(\d+)/` )
108- m := re .FindStringSubmatch (normalized )
109- if m == nil {
110- return 0 , false
111- }
112-
113- v , err := strconv .Atoi (m [1 ])
114- if err != nil {
115- return 0 , false
116- }
117- return v , true
118- }
119-
120- func isLatestDir (filename string ) bool {
121- normalized := filepath .ToSlash (filename )
122- return strings .Contains (normalized , "/pkg/config/latest/" )
88+ return ""
12389}
0 commit comments