// Copyright 2019 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package cache import ( "context" "regexp" "strconv" "strings" "golang.org/x/mod/modfile" "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/lsp/telemetry" "golang.org/x/tools/internal/memoize" "golang.org/x/tools/internal/telemetry/log" "golang.org/x/tools/internal/telemetry/trace" errors "golang.org/x/xerrors" ) type parseModHandle struct { handle *memoize.Handle file source.FileHandle } type parseModData struct { memoize.NoCopy modfile *modfile.File err error } func (c *cache) ParseModHandle(fh source.FileHandle) source.ParseModHandle { h := c.store.Bind(fh.Identity(), func(ctx context.Context) interface{} { data := &parseModData{} data.modfile, data.err = parseMod(ctx, fh) return data }) return &parseModHandle{ handle: h, file: fh, } } func parseMod(ctx context.Context, fh source.FileHandle) (*modfile.File, error) { ctx, done := trace.StartSpan(ctx, "cache.parseMod", telemetry.File.Of(fh.Identity().URI.Filename())) defer done() buf, _, err := fh.Read(ctx) if err != nil { return nil, err } f, err := modfile.Parse(fh.Identity().URI.Filename(), buf, nil) if err != nil { // TODO(golang/go#36486): This can be removed when modfile.Parse returns structured errors. re := regexp.MustCompile(`.*:([\d]+): (.+)`) matches := re.FindStringSubmatch(strings.TrimSpace(err.Error())) if len(matches) < 3 { log.Error(ctx, "could not parse golang/x/mod error message", err) return nil, err } line, e := strconv.Atoi(matches[1]) if e != nil { return nil, err } contents := strings.Split(string(buf), "\n")[line-1] return nil, &source.Error{ Message: matches[2], Range: protocol.Range{ Start: protocol.Position{Line: float64(line - 1), Character: float64(0)}, End: protocol.Position{Line: float64(line - 1), Character: float64(len(contents))}, }, } } return f, nil } func (pgh *parseModHandle) String() string { return pgh.File().Identity().URI.Filename() } func (pgh *parseModHandle) File() source.FileHandle { return pgh.file } func (pgh *parseModHandle) Parse(ctx context.Context) (*modfile.File, error) { v := pgh.handle.Get(ctx) if v == nil { return nil, errors.Errorf("no parsed file for %s", pgh.File().Identity().URI) } data := v.(*parseModData) return data.modfile, data.err }