From 9c1447805a00c452604792c7abdf24fe0aec34bb Mon Sep 17 00:00:00 2001 From: Thomas Bruyelle Date: Wed, 11 Dec 2024 18:44:41 +0100 Subject: [PATCH] fix: transpile step - remove the unecessary tools.Build step - add GO111MODULE=off to remove the error related to the absence of go.mod - filter error correctly according to file notification - see NOTEs for more contextualized infos --- internal/lsp/build.go | 75 ++++++++++++++++++++++-------------- internal/lsp/diagnostics.go | 30 +++++++++------ internal/lsp/server.go | 14 ++++++- internal/tools/build.go | 12 ------ internal/tools/transpile.go | 13 ++++++- testdata/document_open.txtar | 8 ++-- 6 files changed, 93 insertions(+), 59 deletions(-) delete mode 100644 internal/tools/build.go diff --git a/internal/lsp/build.go b/internal/lsp/build.go index 8062401..35f1c45 100644 --- a/internal/lsp/build.go +++ b/internal/lsp/build.go @@ -20,47 +20,49 @@ type ErrorInfo struct { Tool string } -func (s *server) TranspileAndBuild(file *GnoFile) ([]ErrorInfo, error) { - pkgDir := filepath.Dir(file.URI.Filename()) - pkgName := filepath.Base(pkgDir) - tmpDir := filepath.Join(s.env.GNOHOME, "gnopls", "tmp", pkgName) +func (s *server) Transpile() ([]ErrorInfo, error) { + moduleName := filepath.Base(s.workspaceFolder) + tmpDir := filepath.Join(s.env.GNOHOME, "gnopls", "tmp", moduleName) - err := copyDir(pkgDir, tmpDir) + err := copyDir(s.workspaceFolder, tmpDir) if err != nil { return nil, err } - preOut, _ := tools.Transpile(tmpDir) - slog.Info(string(preOut)) + preOut, errTranspile := tools.Transpile(tmpDir) if len(preOut) > 0 { - return parseErrors(file, string(preOut), "transpile") + // parse errors even if errTranspile!=nil bc that's always the case if + // there's errors to parse. + slog.Info("transpile error", "out", string(preOut), "err", errTranspile) + errors, errParse := s.parseErrors(string(preOut), "transpile") + if errParse != nil { + return nil, errParse + } + if len(errors) == 0 && errTranspile != nil { + // no parsed errors but errTranspile!=nil, this is an unexpected error. + // (for example the gno binary was not found) + return nil, errTranspile + } + return errors, nil } - - buildOut, _ := tools.Build(tmpDir) - slog.Info(string(buildOut)) - return parseErrors(file, string(buildOut), "build") + return nil, nil } // This is used to extract information from the `gno build` command // (see `parseError` below). // // TODO: Maybe there's a way to get this in a structured format? +// -> will be available in go1.24 with `go build -json` var errorRe = regexp.MustCompile(`(?m)^([^#]+?):(\d+):(\d+):(.+)$`) -// parseErrors parses the output of the `gno build` command for errors. -// -// They look something like this: +// parseErrors parses the output of the `gno transpile -gobuild` command for +// errors. // +// The format is: // ``` -// command-line-arguments -// # command-line-arguments -// :20:9: undefined: strin -// -// : build pkg: std go compiler: exit status 1 -// -// 1 go build errors +// ::: // ``` -func parseErrors(file *GnoFile, output, cmd string) ([]ErrorInfo, error) { +func (s *server) parseErrors(output, cmd string) ([]ErrorInfo, error) { errors := []ErrorInfo{} matches := errorRe.FindAllStringSubmatch(output, -1) @@ -69,24 +71,39 @@ func parseErrors(file *GnoFile, output, cmd string) ([]ErrorInfo, error) { } for _, match := range matches { + path := match[1] line, err := strconv.Atoi(match[2]) if err != nil { - return nil, err + return nil, fmt.Errorf("parseErrors '%s': %w", match, err) } column, err := strconv.Atoi(match[3]) if err != nil { - return nil, err + return nil, fmt.Errorf("parseErrors '%s': %w", match, err) } - slog.Info("parsing", "line", line, "column", column, "msg", match[4]) - - errorInfo := findError(file, match[1], line, column, match[4], cmd) - errors = append(errors, errorInfo) + msg := strings.TrimSpace(match[4]) + slog.Info("parseErrors", "path", path, "line", line, "column", column, "msg", msg) + errors = append(errors, ErrorInfo{ + FileName: filepath.Join(s.workspaceFolder, path), + Line: line, + Column: column, + Span: []int{column, column}, + Msg: msg, + Tool: cmd, + }) } return errors, nil } +// NOTE(tb): This function tries to guess the column start and end of the +// error, by splitting the line into tokens (space separated). While this +// might work most of the time, in some cases it might not, so I think +// it is preferable not to try to guess and just return: +// column_start = column_end +// just like the output of gno transpile which only returns the start of the +// column. This is why this function is no longer invoked in parseError. +// // findError finds the error in the document, shifting the line and column // numbers to account for the header information in the generated Go file. func findError(file *GnoFile, fname string, line, col int, msg string, tool string) ErrorInfo { diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go index e8ac9b3..3793f81 100644 --- a/internal/lsp/diagnostics.go +++ b/internal/lsp/diagnostics.go @@ -2,32 +2,40 @@ package lsp import ( "context" - "path/filepath" - "strings" "go.lsp.dev/jsonrpc2" "go.lsp.dev/protocol" ) func (s *server) getTranspileDiagnostics(file *GnoFile) ([]protocol.Diagnostic, error) { - errors, err := s.TranspileAndBuild(file) + errors, err := s.Transpile() if err != nil { return nil, err } - if pkg, ok := s.cache.pkgs.Get(filepath.Dir(string(file.URI.Filename()))); ok { - filename := filepath.Base(file.URI.Filename()) - for _, er := range pkg.TypeCheckResult.Errors() { - // Skip errors from other files in the same package - if !strings.HasSuffix(er.FileName, filename) { - continue + /* + NOTE(tb): not sure we really need this, bc Transpile already returns + all the errors we need. The ones in the cache are just duplicates or + potentially obsolete. + + if pkg, ok := s.cache.pkgs.Get(filepath.Dir(string(file.URI.Filename()))); ok { + filename := filepath.Base(file.URI.Filename()) + for _, er := range pkg.TypeCheckResult.Errors() { + // Skip errors from other files in the same package + if !strings.HasSuffix(er.FileName, filename) { + continue + } + errors = append(errors, er) } - errors = append(errors, er) } - } + */ diagnostics := make([]protocol.Diagnostic, 0) // Init required for JSONRPC to send an empty array for _, er := range errors { + if file.URI.Filename() != er.FileName { + // Ignore error thay does not target file + continue + } diagnostics = append(diagnostics, protocol.Diagnostic{ Range: *posToRange(er.Line, er.Span), Severity: protocol.DiagnosticSeverityError, diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 4788821..11fd7ca 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -24,8 +24,9 @@ type server struct { completionStore *CompletionStore cache *Cache - formatOpt tools.FormattingOption - initialized atomic.Bool + formatOpt tools.FormattingOption + initialized atomic.Bool + workspaceFolder string } func BuildServerHandler(conn jsonrpc2.Conn, e *env.Env) jsonrpc2.Handler { @@ -94,6 +95,15 @@ func (s *server) Initialize(ctx context.Context, reply jsonrpc2.Replier, req jso if err := json.Unmarshal(req.Params(), ¶ms); err != nil { return sendParseError(ctx, reply, err) } + if len(params.WorkspaceFolders) > 0 { + s.workspaceFolder = params.WorkspaceFolders[0].Name + } + if s.workspaceFolder == "" { + // Not all LSP client have migrated to the latest WorkspaceFolders, some + // like vim-lsp are still using the deprecated one RootUri. + s.workspaceFolder = params.RootURI.Filename() //nolint:staticcheck + } + slog.Info("Initialize", "params", params, "workspaceFolder", s.workspaceFolder) return reply(ctx, protocol.InitializeResult{ ServerInfo: &protocol.ServerInfo{ diff --git a/internal/tools/build.go b/internal/tools/build.go deleted file mode 100644 index d48ff6f..0000000 --- a/internal/tools/build.go +++ /dev/null @@ -1,12 +0,0 @@ -package tools - -import ( - "os/exec" - "path/filepath" -) - -// Build a Gno package: gno transpile -gobuild . -// TODO: Remove this in the favour of directly using tools/transpile.go -func Build(rootDir string) ([]byte, error) { - return exec.Command("gno", "transpile", "-skip-imports", "-gobuild", filepath.Join(rootDir)).CombinedOutput() -} diff --git a/internal/tools/transpile.go b/internal/tools/transpile.go index 4ff66de..9406348 100644 --- a/internal/tools/transpile.go +++ b/internal/tools/transpile.go @@ -1,11 +1,22 @@ package tools import ( + "fmt" + "os" "os/exec" "path/filepath" + "strings" ) // Transpile a Gno package: gno transpile . func Transpile(rootDir string) ([]byte, error) { - return exec.Command("gno", "transpile", "-skip-imports", filepath.Join(rootDir)).CombinedOutput() + cmd := exec.Command("gno", "transpile", "-skip-imports", "-gobuild", filepath.Join(rootDir)) + // FIXME(tb): See https://github.com/gnolang/gno/pull/1695/files#r1697255524 + const disableGoMod = "GO111MODULE=off" + cmd.Env = append(os.Environ(), disableGoMod) + bz, err := cmd.CombinedOutput() + if err != nil { + return bz, fmt.Errorf("running '%s': %w: %s", strings.Join(cmd.Args, " "), err, string(bz)) + } + return bz, nil } diff --git a/testdata/document_open.txtar b/testdata/document_open.txtar index 5cb76f0..337d4df 100644 --- a/testdata/document_open.txtar +++ b/testdata/document_open.txtar @@ -70,11 +70,11 @@ func Bye() { }, "end": { "line": 2, - "character": 4294967294 + "character": 6 } }, "severity": 1, - "code": "typecheck", + "code": "transpile", "source": "gnopls", "message": "undefined: X" }, @@ -86,11 +86,11 @@ func Bye() { }, "end": { "line": 5, - "character": 4294967294 + "character": 1 } }, "severity": 1, - "code": "typecheck", + "code": "transpile", "source": "gnopls", "message": "declared and not used: y" }