internal/lsp: remove Mod handles

Continuing the massacre, remove ParseModHandle, and Mod*Handle, from the
source API.

Notably, having the snapshot available means we can simplify the go
command invocation paths a lot.

Change-Id: Ief4ef41e42f93d653f719a230004861e5e1ef70b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/244769
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
Heschi Kreinick 2020-07-24 13:39:58 -04:00
parent b47602a87a
commit 6467de6f59
11 changed files with 210 additions and 344 deletions

View File

@ -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.

View File

@ -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) {

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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
}

View File

@ -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
}

View File

@ -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) {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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.