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

fix: transpile step #12

Merged
merged 2 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.Debug("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
12 changes: 11 additions & 1 deletion internal/lsp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ type server struct {
completionStore *CompletionStore
cache *Cache

initialized atomic.Bool
initialized atomic.Bool
workspaceFolder string
}

func BuildServerHandler(conn jsonrpc2.Conn, e *env.Env) jsonrpc2.Handler {
Expand Down Expand Up @@ -90,6 +91,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
Loading