@@ -10,7 +10,6 @@ import (
10
10
"path/filepath"
11
11
"regexp"
12
12
"strings"
13
- "unicode"
14
13
15
14
"golang.org/x/mod/modfile"
16
15
"golang.org/x/mod/module"
@@ -217,8 +216,6 @@ func (s *snapshot) ModWhy(ctx context.Context, fh source.FileHandle) (map[string
217
216
return mwh .why (ctx , s )
218
217
}
219
218
220
- var moduleAtVersionRe = regexp .MustCompile (`^(?P<module>.*)@(?P<version>.*)$` )
221
-
222
219
// extractGoCommandError tries to parse errors that come from the go command
223
220
// and shape them into go.mod diagnostics.
224
221
func (s * snapshot ) extractGoCommandErrors (ctx context.Context , snapshot source.Snapshot , fh source.FileHandle , goCmdError string ) []* source.Error {
@@ -236,106 +233,108 @@ func (s *snapshot) extractGoCommandErrors(ctx context.Context, snapshot source.S
236
233
return srcErrs
237
234
}
238
235
236
+ var moduleVersionInErrorRe = regexp .MustCompile (`[:\s]([+-._~0-9A-Za-z]+)@([+-._~0-9A-Za-z]+)[:\s]` )
237
+
239
238
// matchErrorToModule attempts to match module version in error messages.
240
239
// Some examples:
241
240
//
242
241
// [email protected] : reading example.com/@v/v1.2.2.mod: no such file or directory
243
242
// go: github.com/cockroachdb/apd/[email protected] : reading github.com/cockroachdb/apd/go.mod at revision v2.0.72: unknown revision v2.0.72
244
243
// go: [email protected] requires\n\[email protected] : parsing go.mod:\n\tmodule declares its path as: bob.org\n\tbut was required as: random.org
245
244
//
246
- // We split on colons and whitespace, and attempt to match on something
247
- // that matches module@version. If we're able to find a match, we try to
248
- // find anything that matches it in the go.mod file .
245
+ // We search for module@version, starting from the end to find the most
246
+ // relevant module, e.g. [email protected] above. Then we associate the error
247
+ // with a directive that references any of the modules mentioned .
249
248
func (s * snapshot ) matchErrorToModule (ctx context.Context , fh source.FileHandle , goCmdError string ) * source.Error {
250
- var v module.Version
251
- fields := strings .FieldsFunc (goCmdError , func (r rune ) bool {
252
- return unicode .IsSpace (r ) || r == ':'
253
- })
254
- for _ , field := range fields {
255
- match := moduleAtVersionRe .FindStringSubmatch (field )
256
- if match == nil {
257
- continue
258
- }
259
- path , version := match [1 ], match [2 ]
249
+ pm , err := s .ParseMod (ctx , fh )
250
+ if err != nil {
251
+ return nil
252
+ }
253
+
254
+ var innermost * module.Version
255
+ var reference * modfile.Line
256
+ matches := moduleVersionInErrorRe .FindAllStringSubmatch (goCmdError , - 1 )
257
+
258
+ outer:
259
+ for i := len (matches ) - 1 ; i >= 0 ; i -- {
260
+ ver := module.Version {Path : matches [i ][1 ], Version : matches [i ][2 ]}
260
261
// Any module versions that come from the workspace module should not
261
262
// be shown to the user.
262
- if source .IsWorkspaceModuleVersion (version ) {
263
+ if source .IsWorkspaceModuleVersion (ver . Version ) {
263
264
continue
264
265
}
265
- if err := module .Check (path , version ); err != nil {
266
+ if err := module .Check (ver . Path , ver . Version ); err != nil {
266
267
continue
267
268
}
268
- v .Path , v .Version = path , version
269
- break
269
+ if innermost == nil {
270
+ innermost = & ver
271
+ }
272
+
273
+ for _ , req := range pm .File .Require {
274
+ if req .Mod == ver {
275
+ reference = req .Syntax
276
+ break outer
277
+ }
278
+ }
279
+ for _ , ex := range pm .File .Exclude {
280
+ if ex .Mod == ver {
281
+ reference = ex .Syntax
282
+ break outer
283
+ }
284
+ }
285
+ for _ , rep := range pm .File .Replace {
286
+ if rep .New == ver || rep .Old == ver {
287
+ reference = rep .Syntax
288
+ break outer
289
+ }
290
+ }
270
291
}
271
- pm , err := s .ParseMod (ctx , fh )
292
+
293
+ if reference == nil {
294
+ // No match for the module path was found in the go.mod file.
295
+ // Show the error on the module declaration, if one exists.
296
+ if pm .File .Module == nil {
297
+ return nil
298
+ }
299
+ reference = pm .File .Module .Syntax
300
+ }
301
+
302
+ rng , err := rangeFromPositions (pm .Mapper , reference .Start , reference .End )
272
303
if err != nil {
273
304
return nil
274
305
}
275
- toSourceError := func (line * modfile.Line ) * source.Error {
276
- rng , err := rangeFromPositions (pm .Mapper , line .Start , line .End )
306
+ disabledByGOPROXY := strings .Contains (goCmdError , "disabled by GOPROXY=off" )
307
+ shouldAddDep := strings .Contains (goCmdError , "to add it" )
308
+ if innermost != nil && (disabledByGOPROXY || shouldAddDep ) {
309
+ args , err := source .MarshalArgs (fh .URI (), false , []string {fmt .Sprintf ("%v@%v" , innermost .Path , innermost .Version )})
277
310
if err != nil {
278
311
return nil
279
312
}
280
- disabledByGOPROXY := strings .Contains (goCmdError , "disabled by GOPROXY=off" )
281
- shouldAddDep := strings .Contains (goCmdError , "to add it" )
282
- if v .Path != "" && (disabledByGOPROXY || shouldAddDep ) {
283
- args , err := source .MarshalArgs (fh .URI (), false , []string {fmt .Sprintf ("%v@%v" , v .Path , v .Version )})
284
- if err != nil {
285
- return nil
286
- }
287
- msg := goCmdError
288
- if disabledByGOPROXY {
289
- msg = fmt .Sprintf ("%v@%v has not been downloaded" , v .Path , v .Version )
290
- }
291
- return & source.Error {
292
- Message : msg ,
293
- Kind : source .ListError ,
294
- Range : rng ,
295
- URI : fh .URI (),
296
- SuggestedFixes : []source.SuggestedFix {{
297
- Title : fmt .Sprintf ("Download %v@%v" , v .Path , v .Version ),
298
- Command : & protocol.Command {
299
- Title : source .CommandAddDependency .Title ,
300
- Command : source .CommandAddDependency .ID (),
301
- Arguments : args ,
302
- },
303
- }},
304
- }
313
+ msg := goCmdError
314
+ if disabledByGOPROXY {
315
+ msg = fmt .Sprintf ("%v@%v has not been downloaded" , innermost .Path , innermost .Version )
305
316
}
306
317
return & source.Error {
307
- Message : goCmdError ,
318
+ Message : msg ,
319
+ Kind : source .ListError ,
308
320
Range : rng ,
309
321
URI : fh .URI (),
310
- Kind : source .ListError ,
311
- }
312
- }
313
- // Check if there are any require, exclude, or replace statements that
314
- // match this module version.
315
- for _ , req := range pm .File .Require {
316
- if req .Mod != v {
317
- continue
318
- }
319
- return toSourceError (req .Syntax )
320
- }
321
- for _ , ex := range pm .File .Exclude {
322
- if ex .Mod != v {
323
- continue
322
+ SuggestedFixes : []source.SuggestedFix {{
323
+ Title : fmt .Sprintf ("Download %v@%v" , innermost .Path , innermost .Version ),
324
+ Command : & protocol.Command {
325
+ Title : source .CommandAddDependency .Title ,
326
+ Command : source .CommandAddDependency .ID (),
327
+ Arguments : args ,
328
+ },
329
+ }},
324
330
}
325
- return toSourceError (ex .Syntax )
326
331
}
327
- for _ , rep := range pm .File .Replace {
328
- if rep .New != v && rep .Old != v {
329
- continue
330
- }
331
- return toSourceError (rep .Syntax )
332
- }
333
- // No match for the module path was found in the go.mod file.
334
- // Show the error on the module declaration, if one exists.
335
- if pm .File .Module == nil {
336
- return nil
332
+ return & source.Error {
333
+ Message : goCmdError ,
334
+ Range : rng ,
335
+ URI : fh .URI (),
336
+ Kind : source .ListError ,
337
337
}
338
- return toSourceError (pm .File .Module .Syntax )
339
338
}
340
339
341
340
// errorPositionRe matches errors messages of the form <filename>:<line>:<col>,
0 commit comments