Skip to content

Commit bf5db81

Browse files
committed
gopls/internal/lsp/cache: improve ad-hoc warning for nested modules
Our warning for the specific (and somewhat arbitrary) case of opening a nested module was stale. Update it for workspaces. This is a very weak distillation of CL 448695, targeted at improving this one error message. Change-Id: Ic78e2f8ff8004a78ac2a0650b40767de9dfe153c Reviewed-on: https://go-review.googlesource.com/c/tools/+/454563 Run-TryBot: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> Reviewed-by: Alan Donovan <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent aa9f4b2 commit bf5db81

File tree

6 files changed

+95
-43
lines changed

6 files changed

+95
-43
lines changed

gopls/internal/lsp/cache/load.go

+43-29
Original file line numberDiff line numberDiff line change
@@ -275,26 +275,34 @@ func (m *moduleErrorMap) Error() string {
275275
return buf.String()
276276
}
277277

278-
// workspaceLayoutErrors returns a diagnostic for every open file, as well as
279-
// an error message if there are no open files.
280-
func (s *snapshot) workspaceLayoutError(ctx context.Context) *source.CriticalError {
278+
// workspaceLayoutErrors returns an error decribing a misconfiguration of the
279+
// workspace, along with related diagnostic.
280+
//
281+
// The unusual argument ordering of results is intentional: if the resulting
282+
// error is nil, so must be the resulting diagnostics.
283+
//
284+
// If ctx is cancelled, it may return ctx.Err(), nil.
285+
//
286+
// TODO(rfindley): separate workspace diagnostics from critical workspace
287+
// errors.
288+
func (s *snapshot) workspaceLayoutError(ctx context.Context) (error, []*source.Diagnostic) {
281289
// TODO(rfindley): do we really not want to show a critical error if the user
282290
// has no go.mod files?
283291
if len(s.workspace.getKnownModFiles()) == 0 {
284-
return nil
292+
return nil, nil
285293
}
286294

287295
// TODO(rfindley): both of the checks below should be delegated to the workspace.
288296
if s.view.effectiveGO111MODULE() == off {
289-
return nil
297+
return nil, nil
290298
}
291299
if s.workspace.moduleSource != legacyWorkspace {
292-
return nil
300+
return nil, nil
293301
}
294302

295303
// If the user has one module per view, there is nothing to warn about.
296304
if s.ValidBuildConfiguration() && len(s.workspace.getKnownModFiles()) == 1 {
297-
return nil
305+
return nil, nil
298306
}
299307

300308
// Apply diagnostics about the workspace configuration to relevant open
@@ -320,51 +328,57 @@ go workspaces (go.work files).
320328
See the documentation for more information on setting up your workspace:
321329
https://github.com/golang/tools/blob/master/gopls/doc/workspace.md.`
322330
}
323-
return &source.CriticalError{
324-
MainError: fmt.Errorf(msg),
325-
Diagnostics: s.applyCriticalErrorToFiles(ctx, msg, openFiles),
326-
}
331+
return fmt.Errorf(msg), s.applyCriticalErrorToFiles(ctx, msg, openFiles)
327332
}
328333

329334
// If the user has one active go.mod file, they may still be editing files
330335
// in nested modules. Check the module of each open file and add warnings
331336
// that the nested module must be opened as a workspace folder.
332337
if len(s.workspace.ActiveModFiles()) == 1 {
333338
// Get the active root go.mod file to compare against.
334-
var rootModURI span.URI
339+
var rootMod string
335340
for uri := range s.workspace.ActiveModFiles() {
336-
rootModURI = uri
341+
rootMod = uri.Filename()
337342
}
338-
nestedModules := map[string][]source.VersionedFileHandle{}
343+
rootDir := filepath.Dir(rootMod)
344+
nestedModules := make(map[string][]source.VersionedFileHandle)
339345
for _, fh := range openFiles {
340-
modURI := moduleForURI(s.workspace.knownModFiles, fh.URI())
341-
if modURI != rootModURI {
342-
modDir := filepath.Dir(modURI.Filename())
346+
mod, err := findRootPattern(ctx, filepath.Dir(fh.URI().Filename()), "go.mod", s)
347+
if err != nil {
348+
if ctx.Err() != nil {
349+
return ctx.Err(), nil
350+
}
351+
continue
352+
}
353+
if mod == "" {
354+
continue
355+
}
356+
if mod != rootMod && source.InDir(rootDir, mod) {
357+
modDir := filepath.Dir(mod)
343358
nestedModules[modDir] = append(nestedModules[modDir], fh)
344359
}
345360
}
361+
var multiModuleMsg string
362+
if s.view.goversion >= 18 {
363+
multiModuleMsg = `To work on multiple modules at once, please use a go.work file.
364+
See https://github.com/golang/tools/blob/master/gopls/doc/workspace.md for more information on using workspaces.`
365+
} else {
366+
multiModuleMsg = `To work on multiple modules at once, please upgrade to Go 1.18 and use a go.work file.
367+
See https://github.com/golang/tools/blob/master/gopls/doc/workspace.md for more information on using workspaces.`
368+
}
346369
// Add a diagnostic to each file in a nested module to mark it as
347370
// "orphaned". Don't show a general diagnostic in the progress bar,
348371
// because the user may still want to edit a file in a nested module.
349372
var srcDiags []*source.Diagnostic
350373
for modDir, uris := range nestedModules {
351-
msg := fmt.Sprintf(`This file is in %s, which is a nested module in the %s module.
352-
gopls currently requires one module per workspace folder.
353-
Please open %s as a separate workspace folder.
354-
You can learn more here: https://github.com/golang/tools/blob/master/gopls/doc/workspace.md.
355-
`, modDir, filepath.Dir(rootModURI.Filename()), modDir)
374+
msg := fmt.Sprintf("This file is in %s, which is a nested module in the %s module.\n%s", modDir, rootMod, multiModuleMsg)
356375
srcDiags = append(srcDiags, s.applyCriticalErrorToFiles(ctx, msg, uris)...)
357376
}
358377
if len(srcDiags) != 0 {
359-
return &source.CriticalError{
360-
MainError: fmt.Errorf(`You are working in a nested module.
361-
Please open it as a separate workspace folder. Learn more:
362-
https://github.com/golang/tools/blob/master/gopls/doc/workspace.md.`),
363-
Diagnostics: srcDiags,
364-
}
378+
return fmt.Errorf("You have opened a nested module.\n%s", multiModuleMsg), srcDiags
365379
}
366380
}
367-
return nil
381+
return nil, nil
368382
}
369383

370384
func (s *snapshot) applyCriticalErrorToFiles(ctx context.Context, msg string, files []source.VersionedFileHandle) []*source.Diagnostic {

gopls/internal/lsp/cache/mod_tidy.go

+3
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ func (s *snapshot) ModTidy(ctx context.Context, pm *source.ParsedModule) (*sourc
6363
Diagnostics: criticalErr.Diagnostics,
6464
}, nil
6565
}
66+
if ctx.Err() != nil { // must check ctx after GetCriticalError
67+
return nil, ctx.Err()
68+
}
6669

6770
if err := s.awaitLoaded(ctx); err != nil {
6871
return nil, err

gopls/internal/lsp/cache/snapshot.go

+22-2
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,8 @@ func (s *snapshot) CachedImportPaths(ctx context.Context) (map[PackagePath]sourc
12121212
return results, nil
12131213
}
12141214

1215+
// TODO(rfindley): clarify that this is only active modules. Or update to just
1216+
// use findRootPattern.
12151217
func (s *snapshot) GoModForFile(uri span.URI) span.URI {
12161218
return moduleForURI(s.workspace.activeModFiles, uri)
12171219
}
@@ -1396,13 +1398,31 @@ func (s *snapshot) GetCriticalError(ctx context.Context) *source.CriticalError {
13961398
//
13971399
// TODO(rfindley): re-evaluate this heuristic.
13981400
if containsCommandLineArguments(wsPkgs) {
1399-
return s.workspaceLayoutError(ctx)
1401+
err, diags := s.workspaceLayoutError(ctx)
1402+
if err != nil {
1403+
if ctx.Err() != nil {
1404+
return nil // see the API documentation for source.Snapshot
1405+
}
1406+
return &source.CriticalError{
1407+
MainError: err,
1408+
Diagnostics: diags,
1409+
}
1410+
}
14001411
}
14011412
return nil
14021413
}
14031414

14041415
if errMsg := loadErr.MainError.Error(); strings.Contains(errMsg, "cannot find main module") || strings.Contains(errMsg, "go.mod file not found") {
1405-
return s.workspaceLayoutError(ctx)
1416+
err, diags := s.workspaceLayoutError(ctx)
1417+
if err != nil {
1418+
if ctx.Err() != nil {
1419+
return nil // see the API documentation for source.Snapshot
1420+
}
1421+
return &source.CriticalError{
1422+
MainError: err,
1423+
Diagnostics: diags,
1424+
}
1425+
}
14061426
}
14071427
return loadErr
14081428
}

gopls/internal/lsp/cache/view.go

+22-12
Original file line numberDiff line numberDiff line change
@@ -859,33 +859,38 @@ func (s *Session) getWorkspaceInformation(ctx context.Context, folder span.URI,
859859
// Otherwise, it returns folder.
860860
// TODO (rFindley): move this to workspace.go
861861
// TODO (rFindley): simplify this once workspace modules are enabled by default.
862-
func findWorkspaceRoot(ctx context.Context, folder span.URI, fs source.FileSource, excludePath func(string) bool, experimental bool) (span.URI, error) {
862+
func findWorkspaceRoot(ctx context.Context, folderURI span.URI, fs source.FileSource, excludePath func(string) bool, experimental bool) (span.URI, error) {
863863
patterns := []string{"go.work", "go.mod"}
864864
if experimental {
865865
patterns = []string{"go.work", "gopls.mod", "go.mod"}
866866
}
867+
folder := folderURI.Filename()
867868
for _, basename := range patterns {
868-
dir, err := findRootPattern(ctx, folder, basename, fs)
869+
match, err := findRootPattern(ctx, folder, basename, fs)
869870
if err != nil {
870-
return "", fmt.Errorf("finding %s: %w", basename, err)
871+
if ctxErr := ctx.Err(); ctxErr != nil {
872+
return "", ctxErr
873+
}
874+
return "", err
871875
}
872-
if dir != "" {
876+
if match != "" {
877+
dir := span.URIFromPath(filepath.Dir(match))
873878
return dir, nil
874879
}
875880
}
876881

877882
// The experimental workspace can handle nested modules at this point...
878883
if experimental {
879-
return folder, nil
884+
return folderURI, nil
880885
}
881886

882887
// ...else we should check if there's exactly one nested module.
883-
all, err := findModules(folder, excludePath, 2)
888+
all, err := findModules(folderURI, excludePath, 2)
884889
if err == errExhausted {
885890
// Fall-back behavior: if we don't find any modules after searching 10000
886891
// files, assume there are none.
887892
event.Log(ctx, fmt.Sprintf("stopped searching for modules after %d files", fileLimit))
888-
return folder, nil
893+
return folderURI, nil
889894
}
890895
if err != nil {
891896
return "", err
@@ -896,19 +901,24 @@ func findWorkspaceRoot(ctx context.Context, folder span.URI, fs source.FileSourc
896901
return dirURI(uri), nil
897902
}
898903
}
899-
return folder, nil
904+
return folderURI, nil
900905
}
901906

902-
func findRootPattern(ctx context.Context, folder span.URI, basename string, fs source.FileSource) (span.URI, error) {
903-
dir := folder.Filename()
907+
// findRootPattern looks for files with the given basename in dir or any parent
908+
// directory of dir, using the provided FileSource. It returns the first match,
909+
// starting from dir and search parents.
910+
//
911+
// The resulting string is either the file path of a matching file with the
912+
// given basename, or "" if none was found.
913+
func findRootPattern(ctx context.Context, dir, basename string, fs source.FileSource) (string, error) {
904914
for dir != "" {
905915
target := filepath.Join(dir, basename)
906916
exists, err := fileExists(ctx, span.URIFromPath(target), fs)
907917
if err != nil {
908-
return "", err
918+
return "", err // not readable or context cancelled
909919
}
910920
if exists {
911-
return span.URIFromPath(dir), nil
921+
return target, nil
912922
}
913923
// Trailing separators must be trimmed, otherwise filepath.Split is a noop.
914924
next, _ := filepath.Split(strings.TrimRight(dir, string(filepath.Separator)))

gopls/internal/lsp/diagnostics.go

+3
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@ func (s *Server) diagnose(ctx context.Context, snapshot source.Snapshot, forceAn
295295
return
296296
}
297297
criticalErr := snapshot.GetCriticalError(ctx)
298+
if ctx.Err() != nil { // must check ctx after GetCriticalError
299+
return
300+
}
298301

299302
// Show the error as a progress error report so that it appears in the
300303
// status bar. If a client doesn't support progress reports, the error

gopls/internal/lsp/source/view.go

+2
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ type Snapshot interface {
214214
MetadataForFile(ctx context.Context, uri span.URI) ([]*Metadata, error)
215215

216216
// GetCriticalError returns any critical errors in the workspace.
217+
//
218+
// A nil result may mean success, or context cancellation.
217219
GetCriticalError(ctx context.Context) *CriticalError
218220

219221
// BuildGoplsMod generates a go.mod file for all modules in the workspace.

0 commit comments

Comments
 (0)