diff --git a/internal/lsp/cache/mod.go b/internal/lsp/cache/mod.go index 099457f4ad..f31fa4fe64 100644 --- a/internal/lsp/cache/mod.go +++ b/internal/lsp/cache/mod.go @@ -29,44 +29,31 @@ const ( type parseModHandle struct { handle *memoize.Handle - - mod, sum source.FileHandle } type parseModData struct { memoize.NoCopy - parsed *modfile.File - m *protocol.ColumnMapper - - // parseErrors refers to syntax errors found in the go.mod file. - parseErrors []source.Error + parsed *source.ParsedModule // err is any error encountered while parsing the file. err error } -func (mh *parseModHandle) Mod() source.FileHandle { - return mh.mod -} - -func (mh *parseModHandle) Sum() source.FileHandle { - return mh.sum -} - -func (mh *parseModHandle) Parse(ctx context.Context, s source.Snapshot) (*modfile.File, *protocol.ColumnMapper, []source.Error, error) { +func (mh *parseModHandle) parse(ctx context.Context, s source.Snapshot) (*source.ParsedModule, error) { v, err := mh.handle.Get(ctx, s.(*snapshot)) if err != nil { - return nil, nil, nil, err + return nil, err } data := v.(*parseModData) - return data.parsed, data.m, data.parseErrors, data.err + return data.parsed, data.err } -func (s *snapshot) ParseModHandle(ctx context.Context, modFH source.FileHandle) (source.ParseModHandle, error) { +func (s *snapshot) ParseMod(ctx context.Context, modFH source.FileHandle) (*source.ParsedModule, error) { if handle := s.getModHandle(modFH.URI()); handle != nil { - return handle, nil + return handle.parse(ctx, s) } + h := s.view.session.cache.store.Bind(modFH.Identity().String(), func(ctx context.Context, _ memoize.Arg) interface{} { _, done := event.Start(ctx, "cache.ParseModHandle", tag.URI.Of(modFH.URI())) defer done() @@ -80,55 +67,52 @@ func (s *snapshot) ParseModHandle(ctx context.Context, modFH source.FileHandle) Converter: span.NewContentConverter(modFH.URI().Filename(), contents), Content: contents, } - parsed, err := modfile.Parse(modFH.URI().Filename(), contents, nil) - if err != nil { - parseErr, _ := extractModParseErrors(modFH.URI(), m, err, contents) - var parseErrors []source.Error - if parseErr != nil { - parseErrors = append(parseErrors, *parseErr) - } - return &parseModData{ - parseErrors: parseErrors, - err: err, + data := &parseModData{ + parsed: &source.ParsedModule{ + Mapper: m, + }, + } + data.parsed.File, data.err = modfile.Parse(modFH.URI().Filename(), contents, nil) + if data.err != nil { + // Attempt to convert the error to a non-fatal parse error. + if parseErr, extractErr := extractModParseErrors(modFH.URI(), m, data.err, contents); extractErr == nil { + data.err = nil + data.parsed.ParseErrors = []source.Error{*parseErr} } } - return &parseModData{ - parsed: parsed, - m: m, - } + return data }) + + pmh := &parseModHandle{handle: h} + s.mu.Lock() + s.parseModHandles[modFH.URI()] = pmh + s.mu.Unlock() + + return pmh.parse(ctx, s) +} + +func (s *snapshot) sumFH(ctx context.Context, modFH source.FileHandle) (source.FileHandle, error) { // Get the go.sum file, either from the snapshot or directly from the // cache. Avoid (*snapshot).GetFile here, as we don't want to add // nonexistent file handles to the snapshot if the file does not exist. sumURI := span.URIFromPath(sumFilename(modFH.URI())) sumFH := s.FindFile(sumURI) if sumFH == nil { - fh, err := s.view.session.cache.getFile(ctx, sumURI) - if err != nil && !os.IsNotExist(err) { + var err error + sumFH, err = s.view.session.cache.getFile(ctx, sumURI) + if err != nil { return nil, err } - if fh.err != nil && !os.IsNotExist(fh.err) { - return nil, fh.err - } - // If the file doesn't exist, we can just keep the go.sum nil. - if err != nil || fh.err != nil { - sumFH = nil - } else { - sumFH = fh - } } - s.mu.Lock() - defer s.mu.Unlock() - s.parseModHandles[modFH.URI()] = &parseModHandle{ - handle: h, - mod: modFH, - sum: sumFH, + _, err := sumFH.Read() + if err != nil { + return nil, err } - return s.parseModHandles[modFH.URI()], nil + return sumFH, nil } func sumFilename(modURI span.URI) string { - return modURI.Filename()[:len(modURI.Filename())-len("mod")] + "sum" + return strings.TrimSuffix(modURI.Filename(), ".mod") + ".sum" } // extractModParseErrors processes the raw errors returned by modfile.Parse, @@ -197,7 +181,7 @@ type modWhyData struct { err error } -func (mwh *modWhyHandle) Why(ctx context.Context, s source.Snapshot) (map[string]string, error) { +func (mwh *modWhyHandle) why(ctx context.Context, s source.Snapshot) (map[string]string, error) { v, err := mwh.handle.Get(ctx, s.(*snapshot)) if err != nil { return nil, err @@ -206,7 +190,7 @@ func (mwh *modWhyHandle) Why(ctx context.Context, s source.Snapshot) (map[string return data.why, data.err } -func (s *snapshot) ModWhyHandle(ctx context.Context) (source.ModWhyHandle, error) { +func (s *snapshot) ModWhy(ctx context.Context) (map[string]string, error) { if err := s.awaitLoaded(ctx); err != nil { return nil, err } @@ -214,7 +198,6 @@ func (s *snapshot) ModWhyHandle(ctx context.Context) (source.ModWhyHandle, error if err != nil { return nil, err } - cfg := s.config(ctx) key := modKey{ sessionID: s.view.session.id, cfg: hashConfig(s.config(ctx)), @@ -228,46 +211,42 @@ func (s *snapshot) ModWhyHandle(ctx context.Context) (source.ModWhyHandle, error snapshot := arg.(*snapshot) - pmh, err := snapshot.ParseModHandle(ctx, fh) - if err != nil { - return &modWhyData{err: err} - } - - parsed, _, _, err := pmh.Parse(ctx, snapshot) + pm, err := snapshot.ParseMod(ctx, fh) if err != nil { return &modWhyData{err: err} } // No requires to explain. - if len(parsed.Require) == 0 { + if len(pm.File.Require) == 0 { return &modWhyData{} } // Run `go mod why` on all the dependencies. args := []string{"why", "-m"} - for _, req := range parsed.Require { + for _, req := range pm.File.Require { args = append(args, req.Mod.Path) } - _, stdout, err := runGoCommand(ctx, cfg, pmh, snapshot.view.tmpMod, "mod", args) + stdout, err := snapshot.RunGoCommand(ctx, "mod", args) if err != nil { return &modWhyData{err: err} } whyList := strings.Split(stdout.String(), "\n\n") - if len(whyList) != len(parsed.Require) { + if len(whyList) != len(pm.File.Require) { return &modWhyData{ - err: fmt.Errorf("mismatched number of results: got %v, want %v", len(whyList), len(parsed.Require)), + err: fmt.Errorf("mismatched number of results: got %v, want %v", len(whyList), len(pm.File.Require)), } } - why := make(map[string]string, len(parsed.Require)) - for i, req := range parsed.Require { + why := make(map[string]string, len(pm.File.Require)) + for i, req := range pm.File.Require { why[req.Mod.Path] = whyList[i] } return &modWhyData{why: why} }) + + mwh := &modWhyHandle{handle: h} s.mu.Lock() - defer s.mu.Unlock() - s.modWhyHandle = &modWhyHandle{ - handle: h, - } - return s.modWhyHandle, nil + s.modWhyHandle = mwh + s.mu.Unlock() + + return s.modWhyHandle.why(ctx, s) } type modUpgradeHandle struct { @@ -290,7 +269,7 @@ func (muh *modUpgradeHandle) Upgrades(ctx context.Context, s source.Snapshot) (m return data.upgrades, data.err } -func (s *snapshot) ModUpgradeHandle(ctx context.Context) (source.ModUpgradeHandle, error) { +func (s *snapshot) ModUpgrade(ctx context.Context) (map[string]string, error) { if err := s.awaitLoaded(ctx); err != nil { return nil, err } @@ -312,28 +291,24 @@ func (s *snapshot) ModUpgradeHandle(ctx context.Context) (source.ModUpgradeHandl snapshot := arg.(*snapshot) - pmh, err := s.ParseModHandle(ctx, fh) + pm, err := s.ParseMod(ctx, fh) if err != nil { return &modUpgradeData{err: err} } - parsed, _, _, err := pmh.Parse(ctx, snapshot) - if err != nil { - return &modUpgradeData{err: err} - } // No requires to upgrade. - if len(parsed.Require) == 0 { + if len(pm.File.Require) == 0 { return &modUpgradeData{} } // Run "go list -mod readonly -u -m all" to be able to see which deps can be // upgraded without modifying mod file. args := []string{"-u", "-m", "all"} - if !snapshot.view.tmpMod || containsVendor(pmh.Mod().URI()) { + if !snapshot.view.tmpMod || containsVendor(fh.URI()) { // Use -mod=readonly if the module contains a vendor directory // (see golang/go#38711). args = append([]string{"-mod", "readonly"}, args...) } - _, stdout, err := runGoCommand(ctx, cfg, pmh, snapshot.view.tmpMod, "list", args) + stdout, err := snapshot.RunGoCommand(ctx, "list", args) if err != nil { return &modUpgradeData{err: err} } @@ -364,12 +339,12 @@ func (s *snapshot) ModUpgradeHandle(ctx context.Context) (source.ModUpgradeHandl upgrades: upgrades, } }) + muh := &modUpgradeHandle{handle: h} s.mu.Lock() - defer s.mu.Unlock() - s.modUpgradeHandle = &modUpgradeHandle{ - handle: h, - } - return s.modUpgradeHandle, nil + s.modUpgradeHandle = muh + s.mu.Unlock() + + return s.modUpgradeHandle.Upgrades(ctx, s) } // containsVendor reports whether the module has a vendor folder. diff --git a/internal/lsp/cache/mod_tidy.go b/internal/lsp/cache/mod_tidy.go index 3a96fc00dc..2ecfc77e29 100644 --- a/internal/lsp/cache/mod_tidy.go +++ b/internal/lsp/cache/mod_tidy.go @@ -19,7 +19,6 @@ import ( "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/memoize" - "golang.org/x/tools/internal/packagesinternal" "golang.org/x/tools/internal/span" ) @@ -34,57 +33,32 @@ type modTidyKey struct { type modTidyHandle struct { handle *memoize.Handle - - pmh source.ParseModHandle } type modTidyData struct { memoize.NoCopy - // tidiedContent is the content of the tidied file. - tidiedContent []byte - - // diagnostics are any errors and associated suggested fixes for - // the go.mod file. - diagnostics []source.Error - - err error + tidied *source.TidiedModule + err error } -func (mth *modTidyHandle) ParseModHandle() source.ParseModHandle { - return mth.pmh -} - -func (mth *modTidyHandle) Tidy(ctx context.Context, s source.Snapshot) ([]source.Error, error) { +func (mth *modTidyHandle) tidy(ctx context.Context, s source.Snapshot) (*source.TidiedModule, error) { v, err := mth.handle.Get(ctx, s.(*snapshot)) if err != nil { return nil, err } data := v.(*modTidyData) - return data.diagnostics, data.err + return data.tidied, data.err } -func (mth *modTidyHandle) TidiedContent(ctx context.Context, s source.Snapshot) ([]byte, error) { - v, err := mth.handle.Get(ctx, s.(*snapshot)) - if err != nil { - return nil, err - } - data := v.(*modTidyData) - return data.tidiedContent, data.err -} - -func (s *snapshot) ModTidyHandle(ctx context.Context) (source.ModTidyHandle, error) { +func (s *snapshot) ModTidy(ctx context.Context) (*source.TidiedModule, error) { if !s.view.tmpMod { return nil, source.ErrTmpModfileUnsupported } if handle := s.getModTidyHandle(); handle != nil { - return handle, nil + return handle.tidy(ctx, s) } - fh, err := s.GetFile(ctx, s.view.modURI) - if err != nil { - return nil, err - } - pmh, err := s.ParseModHandle(ctx, fh) + modFH, err := s.GetFile(ctx, s.view.modURI) if err != nil { return nil, err } @@ -111,7 +85,7 @@ func (s *snapshot) ModTidyHandle(ctx context.Context) (source.ModTidyHandle, err view: s.view.root.Filename(), imports: importHash, unsavedOverlays: overlayHash, - gomod: pmh.Mod().Identity().String(), + gomod: modFH.Identity().String(), cfg: hashConfig(cfg), } h := s.view.session.cache.store.Bind(key, func(ctx context.Context, arg memoize.Arg) interface{} { @@ -119,21 +93,26 @@ func (s *snapshot) ModTidyHandle(ctx context.Context) (source.ModTidyHandle, err defer done() snapshot := arg.(*snapshot) - original, m, parseErrors, err := pmh.Parse(ctx, snapshot) - if err != nil || len(parseErrors) > 0 { + pm, err := snapshot.ParseMod(ctx, modFH) + if err != nil { + return &modTidyData{err: err} + } + if len(pm.ParseErrors) > 0 { return &modTidyData{ - diagnostics: parseErrors, - err: err, + tidied: &source.TidiedModule{ + Parsed: pm, + }, + err: fmt.Errorf("could not parse module to tidy: %v", pm.ParseErrors), } } - tmpURI, inv, cleanup, err := goCommandInvocation(ctx, cfg, pmh, "mod", []string{"tidy"}) + tmpURI, runner, inv, cleanup, err := snapshot.goCommandInvocation(ctx, true, "mod", []string{"tidy"}) if err != nil { return &modTidyData{err: err} } // Keep the temporary go.mod file around long enough to parse it. defer cleanup() - if _, err := packagesinternal.GetGoCmdRunner(cfg).Run(ctx, *inv); err != nil { + if _, err := runner.Run(ctx, *inv); err != nil { return &modTidyData{err: err} } // Go directly to disk to get the temporary mod file, since it is @@ -150,9 +129,9 @@ func (s *snapshot) ModTidyHandle(ctx context.Context) (source.ModTidyHandle, err } // Get the dependencies that are different between the original and // ideal go.mod files. - unusedDeps := make(map[string]*modfile.Require, len(original.Require)) + unusedDeps := make(map[string]*modfile.Require, len(pm.File.Require)) missingDeps := make(map[string]*modfile.Require, len(ideal.Require)) - for _, req := range original.Require { + for _, req := range pm.File.Require { unusedDeps[req.Mod.Path] = req } for _, req := range ideal.Require { @@ -166,7 +145,7 @@ func (s *snapshot) ModTidyHandle(ctx context.Context) (source.ModTidyHandle, err // First, compute any errors specific to the go.mod file. These include // unused dependencies and modules with incorrect // indirect comments. /// Both the diagnostic and the fix will appear on the go.mod file. - modRequireErrs, err := modRequireErrors(m, missingDeps, unusedDeps, options) + modRequireErrs, err := modRequireErrors(pm.Mapper, missingDeps, unusedDeps, options) if err != nil { return &modTidyData{err: err} } @@ -179,22 +158,25 @@ func (s *snapshot) ModTidyHandle(ctx context.Context) (source.ModTidyHandle, err // go.mod file. The fixes will be for the go.mod file, but the // diagnostics should appear on the import statements in the Go or // go.mod files. - missingModuleErrs, err := missingModuleErrors(ctx, snapshot, m, workspacePkgs, ideal.Require, missingDeps, original, options) + missingModuleErrs, err := missingModuleErrors(ctx, snapshot, pm.Mapper, workspacePkgs, ideal.Require, missingDeps, pm.File, options) if err != nil { return &modTidyData{err: err} } return &modTidyData{ - tidiedContent: tempContents, - diagnostics: append(modRequireErrs, missingModuleErrs...), + tidied: &source.TidiedModule{ + Parsed: pm, + TidiedContent: tempContents, + Errors: append(modRequireErrs, missingModuleErrs...), + }, } }) + + mth := &modTidyHandle{handle: h} s.mu.Lock() - defer s.mu.Unlock() - s.modTidyHandle = &modTidyHandle{ - handle: h, - pmh: pmh, - } - return s.modTidyHandle, nil + s.modTidyHandle = mth + s.mu.Unlock() + + return mth.tidy(ctx, s) } func hashImports(ctx context.Context, wsPackages []source.Package) (string, error) { diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go index d9fb9625b5..6ea8e4f45d 100644 --- a/internal/lsp/cache/snapshot.go +++ b/internal/lsp/cache/snapshot.go @@ -142,83 +142,55 @@ func (s *snapshot) config(ctx context.Context) *packages.Config { } func (s *snapshot) RunGoCommandDirect(ctx context.Context, verb string, args []string) error { - cfg := s.config(ctx) - _, _, err := runGoCommand(ctx, cfg, nil, s.view.tmpMod, verb, args) - return err -} - -func (s *snapshot) RunGoCommand(ctx context.Context, verb string, args []string) (*bytes.Buffer, error) { - cfg := s.config(ctx) - var pmh source.ParseModHandle - if s.view.tmpMod { - modFH, err := s.GetFile(ctx, s.view.modURI) - if err != nil { - return nil, err - } - pmh, err = s.ParseModHandle(ctx, modFH) - if err != nil { - return nil, err - } - } - _, stdout, err := runGoCommand(ctx, cfg, pmh, s.view.tmpMod, verb, args) - return stdout, err -} - -func (s *snapshot) RunGoCommandPiped(ctx context.Context, verb string, args []string, stdout, stderr io.Writer) error { - cfg := s.config(ctx) - var pmh source.ParseModHandle - if s.view.tmpMod { - modFH, err := s.GetFile(ctx, s.view.modURI) - if err != nil { - return err - } - pmh, err = s.ParseModHandle(ctx, modFH) - if err != nil { - return err - } - } - _, inv, cleanup, err := goCommandInvocation(ctx, cfg, pmh, verb, args) + _, runner, inv, cleanup, err := s.goCommandInvocation(ctx, false, verb, args) if err != nil { return err } defer cleanup() - runner := packagesinternal.GetGoCmdRunner(cfg) - return runner.RunPiped(ctx, *inv, stdout, stderr) + _, err = runner.Run(ctx, *inv) + return err } -// runGoCommand runs the given go command with the given config. -// The given go.mod file is used to construct the temporary go.mod file, which -// is then passed to the go command via the BuildFlags. -// It assumes that modURI is only provided when the -modfile flag is enabled. -func runGoCommand(ctx context.Context, cfg *packages.Config, pmh source.ParseModHandle, tmpMod bool, verb string, args []string) (span.URI, *bytes.Buffer, error) { - // Don't pass in the ParseModHandle if we are not using the -modfile flag. - var tmpPMH source.ParseModHandle - if tmpMod { - tmpPMH = pmh - } - tmpURI, inv, cleanup, err := goCommandInvocation(ctx, cfg, tmpPMH, verb, args) +func (s *snapshot) RunGoCommand(ctx context.Context, verb string, args []string) (*bytes.Buffer, error) { + _, runner, inv, cleanup, err := s.goCommandInvocation(ctx, true, verb, args) if err != nil { - return "", nil, err + return nil, err } defer cleanup() - runner := packagesinternal.GetGoCmdRunner(cfg) - stdout, err := runner.Run(ctx, *inv) - return tmpURI, stdout, err + return runner.Run(ctx, *inv) +} + +func (s *snapshot) RunGoCommandPiped(ctx context.Context, verb string, args []string, stdout, stderr io.Writer) error { + _, runner, inv, cleanup, err := s.goCommandInvocation(ctx, true, verb, args) + if err != nil { + return err + } + defer cleanup() + return runner.RunPiped(ctx, *inv, stdout, stderr) } // Assumes that modURI is only provided when the -modfile flag is enabled. -func goCommandInvocation(ctx context.Context, cfg *packages.Config, pmh source.ParseModHandle, verb string, args []string) (tmpURI span.URI, inv *gocommand.Invocation, cleanup func(), err error) { +func (s *snapshot) goCommandInvocation(ctx context.Context, allowTempModfile bool, verb string, args []string) (tmpURI span.URI, runner *gocommand.Runner, inv *gocommand.Invocation, cleanup func(), err error) { cleanup = func() {} // fallback - if pmh != nil { - tmpURI, cleanup, err = tempModFile(pmh.Mod(), pmh.Sum()) + cfg := s.config(ctx) + if allowTempModfile && s.view.tmpMod { + modFH, err := s.GetFile(ctx, s.view.modURI) if err != nil { - return "", nil, nil, err + return "", nil, nil, cleanup, err + } + // Use the go.sum if it happens to be available. + sumFH, _ := s.sumFH(ctx, modFH) + + tmpURI, cleanup, err = tempModFile(modFH, sumFH) + if err != nil { + return "", nil, nil, cleanup, err } cfg.BuildFlags = append(cfg.BuildFlags, fmt.Sprintf("-modfile=%s", tmpURI.Filename())) } - return tmpURI, &gocommand.Invocation{ + runner = packagesinternal.GetGoCmdRunner(cfg) + return tmpURI, runner, &gocommand.Invocation{ Verb: verb, Args: args, Env: cfg.Env, diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go index cbe70ecb2c..72674d9066 100644 --- a/internal/lsp/cache/view.go +++ b/internal/lsp/cache/view.go @@ -562,15 +562,11 @@ func (v *View) WorkspaceDirectories(ctx context.Context) ([]string, error) { if err != nil { return nil, err } - pmh, err := v.Snapshot().ParseModHandle(ctx, fh) + pm, err := v.Snapshot().ParseMod(ctx, fh) if err != nil { return nil, err } - parsed, _, _, err := pmh.Parse(ctx, v.Snapshot()) - if err != nil { - return nil, err - } - for _, replace := range parsed.Replace { + for _, replace := range pm.File.Replace { dirs = append(dirs, replace.New.Path) } return dirs, nil diff --git a/internal/lsp/code_action.go b/internal/lsp/code_action.go index bd19f55d7b..850a466a69 100644 --- a/internal/lsp/code_action.go +++ b/internal/lsp/code_action.go @@ -454,20 +454,19 @@ func documentChanges(fh source.FileHandle, edits []protocol.TextEdit) []protocol } func moduleQuickFixes(ctx context.Context, snapshot source.Snapshot, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) { - mth, err := snapshot.ModTidyHandle(ctx) + modFH, err := snapshot.GetFile(ctx, snapshot.View().ModFile()) + if err != nil { + return nil, err + } + tidied, err := snapshot.ModTidy(ctx) if err == source.ErrTmpModfileUnsupported { return nil, nil } if err != nil { return nil, err } - errors, err := mth.Tidy(ctx, snapshot) - if err != nil { - return nil, err - } - pmh := mth.ParseModHandle() var quickFixes []protocol.CodeAction - for _, e := range errors { + for _, e := range tidied.Errors { var diag *protocol.Diagnostic for _, d := range diagnostics { if sameDiagnostic(d, e) { @@ -486,14 +485,14 @@ func moduleQuickFixes(ctx context.Context, snapshot source.Snapshot, diagnostics Edit: protocol.WorkspaceEdit{}, } for uri, edits := range fix.Edits { - if uri != pmh.Mod().URI() { + if uri != modFH.URI() { continue } action.Edit.DocumentChanges = append(action.Edit.DocumentChanges, protocol.TextDocumentEdit{ TextDocument: protocol.VersionedTextDocumentIdentifier{ - Version: pmh.Mod().Version(), + Version: modFH.Version(), TextDocumentIdentifier: protocol.TextDocumentIdentifier{ - URI: protocol.URIFromSpanURI(pmh.Mod().URI()), + URI: protocol.URIFromSpanURI(modFH.URI()), }, }, Edits: edits, @@ -510,25 +509,21 @@ func sameDiagnostic(d protocol.Diagnostic, e source.Error) bool { } func goModTidy(ctx context.Context, snapshot source.Snapshot) (*protocol.CodeAction, error) { - mth, err := snapshot.ModTidyHandle(ctx) + tidied, err := snapshot.ModTidy(ctx) if err != nil { return nil, err } - uri := mth.ParseModHandle().Mod().URI() - _, m, _, err := mth.ParseModHandle().Parse(ctx, snapshot) + modFH, err := snapshot.GetFile(ctx, snapshot.View().ModFile()) if err != nil { return nil, err } - left, err := mth.ParseModHandle().Mod().Read() + left, err := modFH.Read() if err != nil { return nil, err } - right, err := mth.TidiedContent(ctx, snapshot) - if err != nil { - return nil, err - } - edits := snapshot.View().Options().ComputeEdits(uri, string(left), string(right)) - protocolEdits, err := source.ToProtocolEdits(m, edits) + right := tidied.TidiedContent + edits := snapshot.View().Options().ComputeEdits(modFH.URI(), string(left), string(right)) + protocolEdits, err := source.ToProtocolEdits(tidied.Parsed.Mapper, edits) if err != nil { return nil, err } @@ -538,9 +533,9 @@ func goModTidy(ctx context.Context, snapshot source.Snapshot) (*protocol.CodeAct Edit: protocol.WorkspaceEdit{ DocumentChanges: []protocol.TextDocumentEdit{{ TextDocument: protocol.VersionedTextDocumentIdentifier{ - Version: mth.ParseModHandle().Mod().Version(), + Version: modFH.Version(), TextDocumentIdentifier: protocol.TextDocumentIdentifier{ - URI: protocol.URIFromSpanURI(uri), + URI: protocol.URIFromSpanURI(modFH.URI()), }, }, Edits: protocolEdits, diff --git a/internal/lsp/link.go b/internal/lsp/link.go index a56e673b82..35caccc631 100644 --- a/internal/lsp/link.go +++ b/internal/lsp/link.go @@ -46,16 +46,12 @@ func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLink func modLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.DocumentLink, error) { view := snapshot.View() - pmh, err := snapshot.ParseModHandle(ctx, fh) - if err != nil { - return nil, err - } - file, m, _, err := pmh.Parse(ctx, snapshot) + pm, err := snapshot.ParseMod(ctx, fh) if err != nil { return nil, err } var links []protocol.DocumentLink - for _, req := range file.Require { + for _, req := range pm.File.Require { if req.Syntax == nil { continue } @@ -65,7 +61,7 @@ func modLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandl } dep := []byte(req.Mod.Path) s, e := req.Syntax.Start.Byte, req.Syntax.End.Byte - i := bytes.Index(m.Content[s:e], dep) + i := bytes.Index(pm.Mapper.Content[s:e], dep) if i == -1 { continue } @@ -73,25 +69,25 @@ func modLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandl // dependency within the require statement. start, end := token.Pos(s+i), token.Pos(s+i+len(dep)) target := fmt.Sprintf("https://%s/mod/%s", view.Options().LinkTarget, req.Mod.String()) - l, err := toProtocolLink(view, m, target, start, end, source.Mod) + l, err := toProtocolLink(view, pm.Mapper, target, start, end, source.Mod) if err != nil { return nil, err } links = append(links, l) } // TODO(ridersofrohan): handle links for replace and exclude directives. - if syntax := file.Syntax; syntax == nil { + if syntax := pm.File.Syntax; syntax == nil { return links, nil } // Get all the links that are contained in the comments of the file. - for _, expr := range file.Syntax.Stmt { + for _, expr := range pm.File.Syntax.Stmt { comments := expr.Comment() if comments == nil { continue } for _, section := range [][]modfile.Comment{comments.Before, comments.Suffix, comments.After} { for _, comment := range section { - l, err := findLinksInString(ctx, view, comment.Token, token.Pos(comment.Start.Byte), m, source.Mod) + l, err := findLinksInString(ctx, view, comment.Token, token.Pos(comment.Start.Byte), pm.Mapper, source.Mod) if err != nil { return nil, err } diff --git a/internal/lsp/mod/code_lens.go b/internal/lsp/mod/code_lens.go index 5f0a0b4305..13afd61529 100644 --- a/internal/lsp/mod/code_lens.go +++ b/internal/lsp/mod/code_lens.go @@ -28,19 +28,11 @@ func CodeLens(ctx context.Context, snapshot source.Snapshot, uri span.URI) ([]pr if err != nil { return nil, err } - pmh, err := snapshot.ParseModHandle(ctx, fh) + pm, err := snapshot.ParseMod(ctx, fh) if err != nil { return nil, err } - file, m, _, err := pmh.Parse(ctx, snapshot) - if err != nil { - return nil, err - } - muh, err := snapshot.ModUpgradeHandle(ctx) - if err != nil { - return nil, err - } - upgrades, err := muh.Upgrades(ctx, snapshot) + upgrades, err := snapshot.ModUpgrade(ctx) if err != nil { return nil, err } @@ -48,14 +40,14 @@ func CodeLens(ctx context.Context, snapshot source.Snapshot, uri span.URI) ([]pr codelens []protocol.CodeLens allUpgrades []string ) - for _, req := range file.Require { + for _, req := range pm.File.Require { dep := req.Mod.Path latest, ok := upgrades[dep] if !ok { continue } // Get the range of the require directive. - rng, err := positionsToRange(uri, m, req.Syntax.Start, req.Syntax.End) + rng, err := positionsToRange(uri, pm.Mapper, req.Syntax.Start, req.Syntax.End) if err != nil { return nil, err } @@ -74,9 +66,9 @@ func CodeLens(ctx context.Context, snapshot source.Snapshot, uri span.URI) ([]pr allUpgrades = append(allUpgrades, dep) } // If there is at least 1 upgrade, add an "Upgrade all dependencies" to the module statement. - if module := file.Module; len(allUpgrades) > 0 && module != nil && module.Syntax != nil { + if module := pm.File.Module; len(allUpgrades) > 0 && module != nil && module.Syntax != nil { // Get the range of the module directive. - rng, err := positionsToRange(uri, m, module.Syntax.Start, module.Syntax.End) + rng, err := positionsToRange(uri, pm.Mapper, module.Syntax.Start, module.Syntax.End) if err != nil { return nil, err } diff --git a/internal/lsp/mod/diagnostics.go b/internal/lsp/mod/diagnostics.go index 878294dfa9..43fd95edda 100644 --- a/internal/lsp/mod/diagnostics.go +++ b/internal/lsp/mod/diagnostics.go @@ -34,7 +34,7 @@ func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.File if err != nil { return nil, err } - mth, err := snapshot.ModTidyHandle(ctx) + tidied, err := snapshot.ModTidy(ctx) if err == source.ErrTmpModfileUnsupported { return nil, nil } @@ -44,11 +44,7 @@ func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.File if err != nil { return nil, err } - diagnostics, err := mth.Tidy(ctx, snapshot) - if err != nil { - return nil, err - } - for _, e := range diagnostics { + for _, e := range tidied.Errors { diag := &source.Diagnostic{ Message: e.Message, Range: e.Range, @@ -98,16 +94,12 @@ func ExtractGoCommandError(ctx context.Context, snapshot source.Snapshot, fh sou break } } - pmh, err := snapshot.ParseModHandle(ctx, fh) - if err != nil { - return nil, err - } - parsed, m, _, err := pmh.Parse(ctx, snapshot) + pm, err := snapshot.ParseMod(ctx, fh) if err != nil { return nil, err } toDiagnostic := func(line *modfile.Line) (*source.Diagnostic, error) { - rng, err := rangeFromPositions(fh.URI(), m, line.Start, line.End) + rng, err := rangeFromPositions(fh.URI(), pm.Mapper, line.Start, line.End) if err != nil { return nil, err } @@ -119,19 +111,19 @@ func ExtractGoCommandError(ctx context.Context, snapshot source.Snapshot, fh sou } // Check if there are any require, exclude, or replace statements that // match this module version. - for _, req := range parsed.Require { + for _, req := range pm.File.Require { if req.Mod != v { continue } return toDiagnostic(req.Syntax) } - for _, ex := range parsed.Exclude { + for _, ex := range pm.File.Exclude { if ex.Mod != v { continue } return toDiagnostic(ex.Syntax) } - for _, rep := range parsed.Replace { + for _, rep := range pm.File.Replace { if rep.New != v && rep.Old != v { continue } @@ -139,7 +131,7 @@ func ExtractGoCommandError(ctx context.Context, snapshot source.Snapshot, fh sou } // No match for the module path was found in the go.mod file. // Show the error on the module declaration. - return toDiagnostic(parsed.Module.Syntax) + return toDiagnostic(pm.File.Module.Syntax) } func rangeFromPositions(uri span.URI, m *protocol.ColumnMapper, s, e modfile.Position) (protocol.Range, error) { diff --git a/internal/lsp/mod/format.go b/internal/lsp/mod/format.go index 5d73f04bd8..da97ca267d 100644 --- a/internal/lsp/mod/format.go +++ b/internal/lsp/mod/format.go @@ -12,19 +12,15 @@ func Format(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ctx, done := event.Start(ctx, "mod.Format") defer done() - pmh, err := snapshot.ParseModHandle(ctx, fh) + pm, err := snapshot.ParseMod(ctx, fh) if err != nil { return nil, err } - file, m, _, err := pmh.Parse(ctx, snapshot) - if err != nil { - return nil, err - } - formatted, err := file.Format() + formatted, err := pm.File.Format() if err != nil { return nil, err } // Calculate the edits to be made due to the change. - diff := snapshot.View().Options().ComputeEdits(fh.URI(), string(m.Content), string(formatted)) - return source.ToProtocolEdits(m, diff) + diff := snapshot.View().Options().ComputeEdits(fh.URI(), string(pm.Mapper.Content), string(formatted)) + return source.ToProtocolEdits(pm.Mapper, diff) } diff --git a/internal/lsp/mod/hover.go b/internal/lsp/mod/hover.go index b66471496f..f0fa26bdb2 100644 --- a/internal/lsp/mod/hover.go +++ b/internal/lsp/mod/hover.go @@ -26,19 +26,15 @@ func Hover(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, defer done() // Get the position of the cursor. - pmh, err := snapshot.ParseModHandle(ctx, fh) + pm, err := snapshot.ParseMod(ctx, fh) if err != nil { return nil, fmt.Errorf("getting modfile handle: %w", err) } - file, m, _, err := pmh.Parse(ctx, snapshot) - if err != nil { - return nil, err - } - spn, err := m.PointSpan(position) + spn, err := pm.Mapper.PointSpan(position) if err != nil { return nil, fmt.Errorf("computing cursor position: %w", err) } - hoverRng, err := spn.Range(m.Converter) + hoverRng, err := spn.Range(pm.Mapper.Converter) if err != nil { return nil, fmt.Errorf("computing hover range: %w", err) } @@ -46,10 +42,10 @@ func Hover(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, // Confirm that the cursor is at the position of a require statement. var req *modfile.Require var startPos, endPos int - for _, r := range file.Require { + for _, r := range pm.File.Require { dep := []byte(r.Mod.Path) s, e := r.Syntax.Start.Byte, r.Syntax.End.Byte - i := bytes.Index(m.Content[s:e], dep) + i := bytes.Index(pm.Mapper.Content[s:e], dep) if i == -1 { continue } @@ -68,37 +64,30 @@ func Hover(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, } // Get the `go mod why` results for the given file. - mwh, err := snapshot.ModWhyHandle(ctx) + why, err := snapshot.ModWhy(ctx) if err != nil { return nil, err } - why, err := mwh.Why(ctx, snapshot) - if err != nil { - return nil, fmt.Errorf("running go mod why: %w", err) - } - if why == nil { - return nil, nil - } explanation, ok := why[req.Mod.Path] if !ok { return nil, nil } // Get the range to highlight for the hover. - line, col, err := m.Converter.ToPosition(startPos) + line, col, err := pm.Mapper.Converter.ToPosition(startPos) if err != nil { return nil, err } start := span.NewPoint(line, col, startPos) - line, col, err = m.Converter.ToPosition(endPos) + line, col, err = pm.Mapper.Converter.ToPosition(endPos) if err != nil { return nil, err } end := span.NewPoint(line, col, endPos) spn = span.New(fh.URI(), start, end) - rng, err := m.Range(spn) + rng, err := pm.Mapper.Range(spn) if err != nil { return nil, err } diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go index db76226474..1aed9f5b7b 100644 --- a/internal/lsp/source/view.go +++ b/internal/lsp/source/view.go @@ -74,23 +74,17 @@ type Snapshot interface { // -modfile flag. RunGoCommandDirect(ctx context.Context, verb string, args []string) error - // ParseModHandle is used to parse go.mod files. - ParseModHandle(ctx context.Context, fh FileHandle) (ParseModHandle, error) + // ParseMod is used to parse go.mod files. + ParseMod(ctx context.Context, fh FileHandle) (*ParsedModule, error) - // ModWhyHandle is used get the results of `go mod why` for a given module. - // It only works for go.mod files that can be parsed, hence it takes a - // ParseModHandle. - ModWhyHandle(ctx context.Context) (ModWhyHandle, error) + // ModWhy returns the results of `go mod why` for the snapshot's module. + ModWhy(ctx context.Context) (map[string]string, error) - // ModWhyHandle is used get the possible upgrades for the dependencies of - // a given module. It only works for go.mod files that can be parsed, hence - // it takes a ParseModHandle. - ModUpgradeHandle(ctx context.Context) (ModUpgradeHandle, error) + // ModUpgrade returns the possible updates for the snapshot's module. + ModUpgrade(ctx context.Context) (map[string]string, error) - // ModWhyHandle is used get the results of `go mod tidy` for a given - // module. It only works for go.mod files that can be parsed, hence it - // takes a ParseModHandle. - ModTidyHandle(ctx context.Context) (ModTidyHandle, error) + // ModTidy returns the results of `go mod tidy` for the snapshot's module. + ModTidy(ctx context.Context) (*TidiedModule, error) // PackagesForFile returns the packages that this file belongs to. PackagesForFile(ctx context.Context, uri span.URI) ([]Package, error) @@ -183,6 +177,7 @@ type BuiltinPackage interface { ParsedFile() *ParsedGoFile } +// A ParsedGoFile contains the results of parsing a Go file. type ParsedGoFile struct { memoize.NoCopy @@ -197,6 +192,27 @@ type ParsedGoFile struct { ParseErr error } +// A ParsedModule contains the results of parsing a go.mod file. +type ParsedModule struct { + memoize.NoCopy + + File *modfile.File + Mapper *protocol.ColumnMapper + ParseErrors []Error +} + +// A TidedModule contains the results of running `go mod tidy` on a module. +type TidiedModule struct { + memoize.NoCopy + + // The parsed module, which is guaranteed to have parsed successfully. + Parsed *ParsedModule + // Diagnostics representing changes made by `go mod tidy`. + Errors []Error + // The bytes of the go.mod file after it was tidied. + TidiedContent []byte +} + // Session represents a single connection from a client. // This is the level at which things like open files are maintained on behalf // of the client. @@ -319,41 +335,6 @@ type Cache interface { GetFile(ctx context.Context, uri span.URI) (FileHandle, error) } -type ParseModHandle interface { - // Mod returns the file handle for the go.mod file. - Mod() FileHandle - - // Sum returns the file handle for the analogous go.sum file. It may be nil. - Sum() FileHandle - - // Parse returns the parsed go.mod file, a column mapper, and a list of - // parse for the go.mod file. - Parse(ctx context.Context, snapshot Snapshot) (*modfile.File, *protocol.ColumnMapper, []Error, error) -} - -type ModUpgradeHandle interface { - // Upgrades returns the latest versions for each of the module's - // dependencies. - Upgrades(ctx context.Context, snapshot Snapshot) (map[string]string, error) -} - -type ModWhyHandle interface { - // Why returns the results of `go mod why` for every dependency of the - // module. - Why(ctx context.Context, snapshot Snapshot) (map[string]string, error) -} - -type ModTidyHandle interface { - // Mod is the ParseModHandle associated with the go.mod file being tidied. - ParseModHandle() ParseModHandle - - // Tidy returns the results of `go mod tidy` for the module. - Tidy(ctx context.Context, snapshot Snapshot) ([]Error, error) - - // TidiedContent is the content of the tidied go.mod file. - TidiedContent(ctx context.Context, snapshot Snapshot) ([]byte, error) -} - var ErrTmpModfileUnsupported = errors.New("-modfile is unsupported for this Go version") // ParseMode controls the content of the AST produced when parsing a source file.