@@ -63,16 +63,52 @@ var (
63
63
func (l * Linter ) Lint (packages [][]string , ruleSet []Rule , config Config ) (<- chan Failure , error ) {
64
64
failures := make (chan Failure )
65
65
66
+ perModVersions := make (map [string ]* goversion.Version )
67
+ perPkgVersions := make ([]* goversion.Version , len (packages ))
68
+ for n , files := range packages {
69
+ if len (files ) == 0 {
70
+ continue
71
+ }
72
+ if config .GoVersion != nil {
73
+ perPkgVersions [n ] = config .GoVersion
74
+ continue
75
+ }
76
+
77
+ dir , err := filepath .Abs (filepath .Dir (files [0 ]))
78
+ if err != nil {
79
+ return nil , err
80
+ }
81
+
82
+ alreadyKnownMod := false
83
+ for d , v := range perModVersions {
84
+ if strings .HasPrefix (dir , d ) {
85
+ perPkgVersions [n ] = v
86
+ alreadyKnownMod = true
87
+ break
88
+ }
89
+ }
90
+ if alreadyKnownMod {
91
+ continue
92
+ }
93
+
94
+ d , v , err := detectGoMod (dir )
95
+ if err != nil {
96
+ return nil , err
97
+ }
98
+ perModVersions [d ] = v
99
+ perPkgVersions [n ] = v
100
+ }
101
+
66
102
var wg sync.WaitGroup
67
- for _ , pkg := range packages {
103
+ for n := range packages {
68
104
wg .Add (1 )
69
- go func (pkg []string ) {
70
- if err := l .lintPackage (pkg , ruleSet , config , failures ); err != nil {
105
+ go func (pkg []string , gover * goversion. Version ) {
106
+ if err := l .lintPackage (pkg , gover , ruleSet , config , failures ); err != nil {
71
107
fmt .Fprintln (os .Stderr , err )
72
108
os .Exit (1 )
73
109
}
74
110
defer wg .Done ()
75
- }(pkg )
111
+ }(packages [ n ], perPkgVersions [ n ] )
76
112
}
77
113
78
114
go func () {
@@ -83,20 +119,15 @@ func (l *Linter) Lint(packages [][]string, ruleSet []Rule, config Config) (<-cha
83
119
return failures , nil
84
120
}
85
121
86
- func (l * Linter ) lintPackage (filenames []string , ruleSet []Rule , config Config , failures chan Failure ) error {
122
+ func (l * Linter ) lintPackage (filenames []string , gover * goversion. Version , ruleSet []Rule , config Config , failures chan Failure ) error {
87
123
if len (filenames ) == 0 {
88
124
return nil
89
125
}
90
126
91
- goVersion , err := detectGoVersion (filepath .Dir (filenames [0 ]))
92
- if err != nil {
93
- return err
94
- }
95
-
96
127
pkg := & Package {
97
128
fset : token .NewFileSet (),
98
129
files : map [string ]* File {},
99
- goVersion : goVersion ,
130
+ goVersion : gover ,
100
131
}
101
132
for _ , filename := range filenames {
102
133
content , err := l .readFile (filename )
@@ -124,37 +155,38 @@ func (l *Linter) lintPackage(filenames []string, ruleSet []Rule, config Config,
124
155
return nil
125
156
}
126
157
127
- func detectGoVersion (dir string ) (ver * goversion.Version , err error ) {
158
+ func detectGoMod (dir string ) (rootDir string , ver * goversion.Version , err error ) {
128
159
// https://github.com/golang/go/issues/44753#issuecomment-790089020
129
160
cmd := exec .Command ("go" , "list" , "-m" , "-json" )
130
161
cmd .Dir = dir
131
162
132
- raw , err := cmd .Output ()
163
+ out , err := cmd .Output ()
133
164
if err != nil {
134
- return nil , fmt .Errorf ("command go list: %w" , err )
135
- }
136
-
137
- var v struct {
138
- GoMod string `json:"GoMod"`
139
- GoVersion string `json:"GoVersion"`
165
+ return "" , nil , fmt .Errorf ("command go list: %w" , err )
140
166
}
141
- if err = json .Unmarshal (raw , & v ); err != nil {
142
- return nil , fmt .Errorf ("can't parse the output of go list: %w" , err )
143
- }
144
-
145
- if v .GoMod == "" {
146
- // this package is outside a module, so assume
147
- // an old-style source directory
148
167
149
- if v := os .Getenv ("GOVERSION" ); v != "" {
150
- return goversion .NewVersion (strings .TrimPrefix (v , "go" ))
168
+ // NOTE: A package may be part of a go workspace. In this case `go list -m`
169
+ // lists all modules in the workspace, so we need to go through them all.
170
+ d := json .NewDecoder (bytes .NewBuffer (out ))
171
+ for d .More () {
172
+ var v struct {
173
+ GoMod string `json:"GoMod"`
174
+ GoVersion string `json:"GoVersion"`
175
+ Dir string `json:"Dir"`
176
+ }
177
+ if err = d .Decode (& v ); err != nil {
178
+ return "" , nil , err
179
+ }
180
+ if v .GoMod == "" {
181
+ return "" , nil , fmt .Errorf ("not part of a module: %q" , dir )
182
+ }
183
+ if v .Dir != "" && strings .HasPrefix (dir , v .Dir ) {
184
+ rootDir = v .Dir
185
+ ver , err = goversion .NewVersion (strings .TrimPrefix (v .GoVersion , "go" ))
186
+ return rootDir , ver , err
151
187
}
152
-
153
- // assume the last version that does not have generics
154
- return goversion .Must (goversion .NewVersion ("1.17" )), nil
155
188
}
156
-
157
- return goversion .NewVersion (strings .TrimPrefix (v .GoVersion , "go" ))
189
+ return "" , nil , fmt .Errorf ("not part of a module: %q" , dir )
158
190
}
159
191
160
192
// isGenerated reports whether the source file is generated code
0 commit comments