/
build.go
142 lines (122 loc) · 3.44 KB
/
build.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package lsp
import (
"fmt"
"log/slog"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/harry-hov/gnopls/internal/tools"
)
type ErrorInfo struct {
FileName string
Line int
Column int
Span []int
Msg string
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)
err := copyDir(pkgDir, tmpDir)
if err != nil {
return nil, err
}
preOut, _ := tools.Transpile(tmpDir)
slog.Info(string(preOut))
if len(preOut) > 0 {
return parseErrors(file, string(preOut), "transpile")
}
buildOut, _ := tools.Build(tmpDir)
slog.Info(string(buildOut))
return parseErrors(file, string(buildOut), "build")
}
// 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?
var errorRe = regexp.MustCompile(`(?m)^([^#]+?):(\d+):(\d+):(.+)$`)
// parseErrors parses the output of the `gno build` command for errors.
//
// They look something like this:
//
// ```
// 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
// ```
func parseErrors(file *GnoFile, output, cmd string) ([]ErrorInfo, error) {
errors := []ErrorInfo{}
matches := errorRe.FindAllStringSubmatch(output, -1)
if len(matches) == 0 {
return errors, nil
}
for _, match := range matches {
line, err := strconv.Atoi(match[2])
if err != nil {
return nil, err
}
column, err := strconv.Atoi(match[3])
if err != nil {
return nil, 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)
}
return errors, nil
}
// 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 {
msg = strings.TrimSpace(msg)
// TODO: can be removed?
// see: https://github.com/gnolang/gno/pull/1670
if tool == "transpile" {
// fname parsed from transpile result can be incorrect
// e.g filename = `filename.gno: transpile: parse: tmp.gno`
parts := strings.Split(fname, ":")
fname = parts[0]
}
// Error messages are of the form:
//
// <token> <error> (<info>)
// <error>: <token>
//
// We want to strip the parens and find the token in the file.
parens := regexp.MustCompile(`\((.+)\)`)
needle := parens.ReplaceAllString(msg, "")
tokens := strings.Fields(needle)
errorInfo := ErrorInfo{
FileName: strings.TrimPrefix(GoToGnoFileName(filepath.Base(fname)), "."),
Line: line,
Column: col,
Span: []int{0, 0},
Msg: msg,
Tool: tool,
}
lines := strings.SplitAfter(string(file.Src), "\n")
for i, l := range lines {
if i != line-1 { // zero-indexed
continue
}
for _, token := range tokens {
tokRe := regexp.MustCompile(fmt.Sprintf(`\b%s\b`, regexp.QuoteMeta(token)))
if tokRe.MatchString(l) {
errorInfo.Line = i + 1
errorInfo.Span = []int{col, col + len(token)}
return errorInfo
}
}
}
// If we couldn't find the token, just return the original error + the
// full line.
errorInfo.Span = []int{col, col + 1}
return errorInfo
}