Skip to content

Commit f24690e

Browse files
authored
feat(gnovm/cmd): printing all the errors from goparser (#2011)
<!-- please provide a detailed description of the changes made in this pull request. --> This PR aims to print all the errors that are found by the go/parser package. Currently the `parser.ParseFile` function returns an `scanner.ErrorList` object that only prints the first error and a count of the remaining errors. #1933 everything is better explained here. **For the changes this PR introduces**: - Create **issueWithError** function. This function will wrap the lines of code that processed an error (before this PR changes) in order to convert it on an **lintIssue** structure. This existent code applies a regex to localize the parse error more precisely. - The PR changes the behavior of run, test, and lint commands. Not they exit with a non-zero status code as soon as we found some error while parsing the files. This in order to not having the whole stacktrace for some parsing file error, improving visibility. - Add a new case on `gnovm/cmd/gno/lint.go.catchRuntimeError` to handle and print all errors of an error with type `scanner.ErrorList` - remove some wrapping during panic calls. This was done by simplicity some of the calls were doing `panic(errors.Wrap(err, "parsing file "+mfile.Name))` here err has the type scanner.ErrorList but as we're wrapping it it would be more complex and difficult to read on **catchRuntimeError** to identify an error with the type ErrorList **Results:** (Using issue example) - lint: ```cmd -- NOW ./.: missing 'gno.mod' file (code=1). test.gno:6: expected ';', found example (code=2). test.gno:7: expected '}', found 'EOF' (code=2). exit status 1 ---- BEFORE ./.: missing 'gno.mod' file (code=1). test.gno:6: expected ';', found example (and 1 more errors) (code=2). ``` - run: ```cmd -- NOW test.gno:6: expected ';', found example (code=2). test.gno:7: expected '}', found 'EOF' (code=2). exit status 1 ---- BEFORE panic: test.gno:6:5: expected ';', found example (and 1 more errors) goroutine 1 [running]: github.com/gnolang/gno/gnovm/pkg/gnolang.MustReadFile(...) /Users/miguel/gnorigin/gnovm/pkg/gnolang/go2gno.go:49 main.parseFiles({0x140003b1720, 0x1, 0x1400031fb48?}) /Users/miguel/gnorigin/gnovm/cmd/gno/run.go:132 +0x27c main.parseFiles({0x140003b09a0, 0x1, 0x0?}) /Users/miguel/gnorigin/gnovm/cmd/gno/run.go:121 +0x158 main.execRun(0x14000373230, {0x140003b09a0, 0x1, 0x1}, {0x100ca3e68, 0x140003b3bd0}) /Users/miguel/gnorigin/gnovm/cmd/gno/run.go:89 +0x174 main.newRunCmd.func1({0x0?, 0x1400019c140?}, {0x140003b09a0?, 0x0?, 0x0?}) /Users/miguel/gnorigin/gnovm/cmd/gno/run.go:35 +0x3c github.com/gnolang/gno/tm2/pkg/commands.(*Command).Run(0x1400031fdc8?, {0x100c9af18?, 0x101249700?}) /Users/miguel/gnorigin/tm2/pkg/commands/command.go:247 +0x17c github.com/gnolang/gno/tm2/pkg/commands.(*Command).Run(0x140003ae2c0?, {0x100c9af18?, 0x101249700?}) /Users/miguel/gnorigin/tm2/pkg/commands/command.go:251 +0x12c github.com/gnolang/gno/tm2/pkg/commands.(*Command).ParseAndRun(0x140003ae6e0?, {0x100c9af18, 0x101249700}, {0x1400019c130?, 0x140003ae790?, 0x140003ae840?}) /Users/miguel/gnorigin/tm2/pkg/commands/command.go:132 +0x4c github.com/gnolang/gno/tm2/pkg/commands.(*Command).Execute(0x100ca3e68?, {0x100c9af18?, 0x101249700?}, {0x1400019c130?, 0x1011a5ef8?, 0x140000021a0?}) /Users/miguel/gnorigin/tm2/pkg/commands/command.go:114 +0x28 main.main() /Users/miguel/gnorigin/gnovm/cmd/gno/main.go:13 +0x6c ``` <details><summary>Contributors' checklist...</summary> - [ ] Added new tests, or not needed, or not feasible - [ ] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [ ] Updated the official documentation or not needed - [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [ ] Added references to related issues and PRs - [ ] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md). </details>
1 parent f3ddc44 commit f24690e

File tree

7 files changed

+73
-40
lines changed

7 files changed

+73
-40
lines changed

gnovm/cmd/gno/lint.go

+39-33
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"errors"
66
"flag"
77
"fmt"
8+
"go/scanner"
9+
"io"
810
"os"
911
"path/filepath"
1012
"regexp"
@@ -68,10 +70,6 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error {
6870
}
6971

7072
hasError := false
71-
addIssue := func(issue lintIssue) {
72-
hasError = true
73-
fmt.Fprint(io.Err(), issue.String()+"\n")
74-
}
7573

7674
for _, pkgPath := range pkgPaths {
7775
if verbose {
@@ -81,16 +79,18 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error {
8179
// Check if 'gno.mod' exists
8280
gnoModPath := filepath.Join(pkgPath, "gno.mod")
8381
if !osm.FileExists(gnoModPath) {
84-
addIssue(lintIssue{
82+
hasError = true
83+
issue := lintIssue{
8584
Code: lintNoGnoMod,
8685
Confidence: 1,
8786
Location: pkgPath,
8887
Msg: "missing 'gno.mod' file",
89-
})
88+
}
89+
fmt.Fprint(io.Err(), issue.String()+"\n")
9090
}
9191

9292
// Handle runtime errors
93-
catchRuntimeError(pkgPath, addIssue, func() {
93+
hasError = catchRuntimeError(pkgPath, io.Err(), func() {
9494
stdout, stdin, stderr := io.Out(), io.In(), io.Err()
9595
testStore := tests.TestStore(
9696
rootDir, "",
@@ -130,7 +130,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error {
130130
}
131131

132132
tm.RunFiles(testfiles.Files...)
133-
})
133+
}) || hasError
134134

135135
// TODO: Add more checkers
136136
}
@@ -164,47 +164,33 @@ func guessSourcePath(pkg, source string) string {
164164
// XXX: Ideally, error handling should encapsulate location details within a dedicated error type.
165165
var reParseRecover = regexp.MustCompile(`^([^:]+):(\d+)(?::\d+)?:? *(.*)$`)
166166

167-
func catchRuntimeError(pkgPath string, addIssue func(issue lintIssue), action func()) {
167+
func catchRuntimeError(pkgPath string, stderr io.WriteCloser, action func()) (hasError bool) {
168168
defer func() {
169169
// Errors catched here mostly come from: gnovm/pkg/gnolang/preprocess.go
170170
r := recover()
171171
if r == nil {
172172
return
173173
}
174-
175-
var err error
174+
hasError = true
176175
switch verr := r.(type) {
177176
case *gno.PreprocessError:
178-
err = verr.Unwrap()
177+
err := verr.Unwrap()
178+
fmt.Fprint(stderr, issueFromError(pkgPath, err).String()+"\n")
179+
case scanner.ErrorList:
180+
for _, err := range verr {
181+
fmt.Fprint(stderr, issueFromError(pkgPath, err).String()+"\n")
182+
}
179183
case error:
180-
err = verr
184+
fmt.Fprint(stderr, issueFromError(pkgPath, verr).String()+"\n")
181185
case string:
182-
err = errors.New(verr)
186+
fmt.Fprint(stderr, issueFromError(pkgPath, errors.New(verr)).String()+"\n")
183187
default:
184188
panic(r)
185189
}
186-
187-
var issue lintIssue
188-
issue.Confidence = 1
189-
issue.Code = lintGnoError
190-
191-
parsedError := strings.TrimSpace(err.Error())
192-
parsedError = strings.TrimPrefix(parsedError, pkgPath+"/")
193-
194-
matches := reParseRecover.FindStringSubmatch(parsedError)
195-
if len(matches) == 4 {
196-
sourcepath := guessSourcePath(pkgPath, matches[1])
197-
issue.Location = fmt.Sprintf("%s:%s", sourcepath, matches[2])
198-
issue.Msg = strings.TrimSpace(matches[3])
199-
} else {
200-
issue.Location = fmt.Sprintf("%s:0", filepath.Clean(pkgPath))
201-
issue.Msg = err.Error()
202-
}
203-
204-
addIssue(issue)
205190
}()
206191

207192
action()
193+
return
208194
}
209195

210196
type lintCode int
@@ -229,3 +215,23 @@ func (i lintIssue) String() string {
229215
// TODO: consider crafting a doc URL based on Code.
230216
return fmt.Sprintf("%s: %s (code=%d).", i.Location, i.Msg, i.Code)
231217
}
218+
219+
func issueFromError(pkgPath string, err error) lintIssue {
220+
var issue lintIssue
221+
issue.Confidence = 1
222+
issue.Code = lintGnoError
223+
224+
parsedError := strings.TrimSpace(err.Error())
225+
parsedError = strings.TrimPrefix(parsedError, pkgPath+"/")
226+
227+
matches := reParseRecover.FindStringSubmatch(parsedError)
228+
if len(matches) == 4 {
229+
sourcepath := guessSourcePath(pkgPath, matches[1])
230+
issue.Location = fmt.Sprintf("%s:%s", sourcepath, matches[2])
231+
issue.Msg = strings.TrimSpace(matches[3])
232+
} else {
233+
issue.Location = fmt.Sprintf("%s:0", filepath.Clean(pkgPath))
234+
issue.Msg = err.Error()
235+
}
236+
return issue
237+
}

gnovm/cmd/gno/lint_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ func TestLintApp(t *testing.T) {
1616
}, {
1717
args: []string{"lint", "--set-exit-status=0", "../../tests/integ/package_not_declared/main.gno"},
1818
stderrShouldContain: "main.gno:4: name fmt not declared (code=2).",
19+
}, {
20+
args: []string{"lint", "--set-exit-status=0", "../../tests/integ/several-lint-errors/main.gno"},
21+
stderrShouldContain: "../../tests/integ/several-lint-errors/main.gno:5: expected ';', found example (code=2).\n../../tests/integ/several-lint-errors/main.gno:6",
1922
}, {
2023
args: []string{"lint", "--set-exit-status=0", "../../tests/integ/run_main/"},
2124
stderrShouldContain: "./../../tests/integ/run_main: missing 'gno.mod' file (code=1).",

gnovm/cmd/gno/run.go

+13-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"flag"
77
"fmt"
8+
"io"
89
"os"
910
"path/filepath"
1011
"strings"
@@ -102,7 +103,7 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error {
102103
}
103104

104105
// read files
105-
files, err := parseFiles(args)
106+
files, err := parseFiles(args, stderr)
106107
if err != nil {
107108
return err
108109
}
@@ -135,15 +136,16 @@ func execRun(cfg *runCfg, args []string, io commands.IO) error {
135136
return nil
136137
}
137138

138-
func parseFiles(fnames []string) ([]*gno.FileNode, error) {
139+
func parseFiles(fnames []string, stderr io.WriteCloser) ([]*gno.FileNode, error) {
139140
files := make([]*gno.FileNode, 0, len(fnames))
141+
var hasError bool
140142
for _, fname := range fnames {
141143
if s, err := os.Stat(fname); err == nil && s.IsDir() {
142144
subFns, err := listNonTestFiles(fname)
143145
if err != nil {
144146
return nil, err
145147
}
146-
subFiles, err := parseFiles(subFns)
148+
subFiles, err := parseFiles(subFns, stderr)
147149
if err != nil {
148150
return nil, err
149151
}
@@ -154,7 +156,14 @@ func parseFiles(fnames []string) ([]*gno.FileNode, error) {
154156
// in either case not a file we can parse.
155157
return nil, err
156158
}
157-
files = append(files, gno.MustReadFile(fname))
159+
160+
hasError = catchRuntimeError(fname, stderr, func() {
161+
files = append(files, gno.MustReadFile(fname))
162+
})
163+
}
164+
165+
if hasError {
166+
os.Exit(1)
158167
}
159168
return files, nil
160169
}

gnovm/cmd/gno/test.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,15 @@ func gnoTestPkg(
325325
memPkg := gno.ReadMemPackage(pkgPath, gnoPkgPath)
326326

327327
// tfiles, ifiles := gno.ParseMemPackageTests(memPkg)
328-
tfiles, ifiles := parseMemPackageTests(memPkg)
328+
var tfiles, ifiles *gno.FileSet
329+
330+
hasError := catchRuntimeError(gnoPkgPath, stderr, func() {
331+
tfiles, ifiles = parseMemPackageTests(memPkg)
332+
})
333+
334+
if hasError {
335+
os.Exit(1)
336+
}
329337
testPkgName := getPkgNameFromFileset(ifiles)
330338

331339
// run test files in pkg
@@ -639,7 +647,7 @@ func parseMemPackageTests(memPkg *std.MemPackage) (tset, itset *gno.FileSet) {
639647
}
640648
n, err := gno.ParseFile(mfile.Name, mfile.Body)
641649
if err != nil {
642-
panic(errors.Wrap(err, "parsing file "+mfile.Name))
650+
panic(err)
643651
}
644652
if n == nil {
645653
panic("should not happen")

gnovm/pkg/gnolang/nodes.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1179,7 +1179,7 @@ func ParseMemPackage(memPkg *std.MemPackage) (fset *FileSet) {
11791179
}
11801180
n, err := ParseFile(mfile.Name, mfile.Body)
11811181
if err != nil {
1182-
panic(errors.Wrap(err, "parsing file "+mfile.Name))
1182+
panic(err)
11831183
}
11841184
if memPkg.Name != string(n.PkgName) {
11851185
panic(fmt.Sprintf(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module gno.land/tests/severalerrors
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package main
2+
3+
func main() {
4+
for {
5+
_ example
6+
}

0 commit comments

Comments
 (0)