Skip to content
This repository has been archived by the owner on Jan 31, 2025. It is now read-only.

Commit

Permalink
fix: transpile step
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
tbruyelle committed Dec 17, 2024
1 parent 0847953 commit 9c14478
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 59 deletions.
75 changes: 46 additions & 29 deletions internal/lsp/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
// <file>:20:9: undefined: strin
//
// <pkg_path>: build pkg: std go compiler: exit status 1
//
// 1 go build errors
// <file.gno>:<line>:<col>: <error>
// ```
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)
Expand All @@ -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 {
Expand Down
30 changes: 19 additions & 11 deletions internal/lsp/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
14 changes: 12 additions & 2 deletions internal/lsp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -94,6 +95,15 @@ func (s *server) Initialize(ctx context.Context, reply jsonrpc2.Replier, req jso
if err := json.Unmarshal(req.Params(), &params); 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{
Expand Down
12 changes: 0 additions & 12 deletions internal/tools/build.go

This file was deleted.

13 changes: 12 additions & 1 deletion internal/tools/transpile.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
package tools

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)

// Transpile a Gno package: gno transpile <dir>.
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
}
8 changes: 4 additions & 4 deletions testdata/document_open.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ func Bye() {
},
"end": {
"line": 2,
"character": 4294967294
"character": 6
}
},
"severity": 1,
"code": "typecheck",
"code": "transpile",
"source": "gnopls",
"message": "undefined: X"
},
Expand All @@ -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"
}
Expand Down

0 comments on commit 9c14478

Please sign in to comment.