Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cmd/gno): perform type checking when calling linter #1730

Merged
merged 81 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 78 commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
85f62ba
refactor(gnovm): rename precompiler to transpiler, move to own package
thehowl Feb 22, 2024
9409285
move transpiler to pkg
thehowl Feb 22, 2024
cb63cf5
Merge branch 'master' into dev/morgan/precompile-refactor
thehowl Feb 22, 2024
3710ded
Merge branch 'dev/morgan/precompile-refactor' of github.com:gnolang/g…
thehowl Feb 22, 2024
37c9e66
fixup
thehowl Feb 22, 2024
59c5bb1
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/preco…
thehowl Feb 26, 2024
d39292b
Merge branch 'master' into dev/morgan/precompile-refactor
thehowl Feb 27, 2024
a25d076
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/preco…
thehowl Feb 28, 2024
a889dea
begin removing support for linked identifiers
thehowl Feb 22, 2024
aae89db
convert native bindings to never use linked types
thehowl Feb 23, 2024
fd2722e
remove InjectNativeMappings
thehowl Feb 23, 2024
8856225
fix errors in genstd
thehowl Feb 27, 2024
d6955d0
remove AddGo2GnoMapping, nolint on btReadonly
thehowl Feb 28, 2024
c3e8ff8
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/preco…
thehowl Feb 28, 2024
1de00fa
Merge branch 'dev/morgan/precompile-refactor' into dev/morgan/natbind…
thehowl Feb 28, 2024
c0581dd
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/natbi…
thehowl Feb 28, 2024
b3c7adb
fmt + bugfix
thehowl Feb 28, 2024
250e292
feat(gno.land): add go type checking to keeper
thehowl Feb 28, 2024
0d8596d
lint + test fixes
thehowl Feb 28, 2024
10c0434
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/natbi…
thehowl Mar 3, 2024
f641c58
Merge branch 'dev/morgan/natbind-no-linkedtype' into dev/morgan/go-ty…
thehowl Mar 3, 2024
9779ff3
use custom type instead of any in importer
thehowl Mar 3, 2024
7b239df
support multiple errors in type checker
thehowl Mar 3, 2024
1b323a2
correctly return error in vm keeper
thehowl Mar 3, 2024
df1679c
add tests to type checker
thehowl Mar 3, 2024
ee0448e
fixup
thehowl Mar 3, 2024
a169800
add integration test for gno.land
thehowl Mar 3, 2024
d2c3264
feat(cmd/gno): perform type checking when calling linter
thehowl Mar 4, 2024
b320155
fix testscript tests, remove duplicate code;
thehowl Mar 4, 2024
e53aa22
Merge branch 'master' into dev/morgan/go-types-typecheck
thehowl Mar 20, 2024
172e4ae
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/go-ty…
thehowl Mar 27, 2024
0fa34fa
remove newMemPackage
thehowl Mar 27, 2024
8688167
Merge branch 'dev/morgan/go-types-typecheck' of github.com:gnolang/gn…
thehowl Mar 27, 2024
89d9887
remove unused var
thehowl Mar 27, 2024
ccf0ba9
Merge branch 'master' into dev/morgan/go-types-typecheck
thehowl Apr 15, 2024
338454f
remove other usage in gnoclient
thehowl Apr 15, 2024
1c5c14f
Merge branch 'dev/morgan/go-types-typecheck' into dev/morgan/typechec…
thehowl Apr 15, 2024
c2917c6
s/--verbose/-v/g
thehowl Apr 15, 2024
6b4ed9e
remove unused import in test
thehowl Apr 15, 2024
593defa
Merge branch 'dev/morgan/go-types-typecheck' into dev/morgan/typechec…
thehowl Apr 15, 2024
0007e73
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/go-ty…
thehowl Apr 17, 2024
9ccb5ee
bump limits for issue-1786 test
thehowl Apr 17, 2024
e20ebbe
bump up to 4M
thehowl Apr 17, 2024
ad2f0b1
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/go-ty…
thehowl May 6, 2024
66fb953
changes from code review
thehowl May 6, 2024
38cba04
fix gas numbers
thehowl May 7, 2024
bee0f30
add transaction simulation in gnokey
thehowl May 7, 2024
3a82a98
fixup lint
thehowl May 7, 2024
a112b4a
Merge branch 'dev/morgan/go-types-typecheck' into dev/morgan/typechec…
thehowl May 7, 2024
39ae304
update docs
thehowl May 7, 2024
dbde296
partial revert of store changes
thehowl May 9, 2024
9ee3cc6
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/go-ty…
thehowl May 9, 2024
f843acb
fixup
thehowl May 9, 2024
2558b09
Revert "fixup"
thehowl May 10, 2024
cc9d45e
Revert "partial revert of store changes"
thehowl May 10, 2024
09f081c
fixup
thehowl May 10, 2024
38719cd
update docs
thehowl May 10, 2024
815c573
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/go-ty…
thehowl May 13, 2024
6154240
Merge branch 'dev/morgan/go-types-typecheck' into dev/morgan/typechec…
thehowl May 13, 2024
10992d2
please, ci, work
thehowl May 13, 2024
b0dbfe7
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/typec…
thehowl May 13, 2024
c5af2fe
mv
thehowl May 13, 2024
dd64ec1
mv
thehowl May 13, 2024
7d93ab1
codereview changes
thehowl May 13, 2024
0ffda3b
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/typec…
thehowl May 21, 2024
dcb9930
review comments
thehowl May 21, 2024
d5d166c
ignore errorlint on fmt.Errorf
thehowl May 21, 2024
b21a7f5
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/typec…
thehowl May 29, 2024
6530717
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/typec…
thehowl Jun 3, 2024
733fa73
adapt to new master changes
thehowl Jun 3, 2024
5982273
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/typec…
thehowl Dec 8, 2024
b5c2d8a
more changes
thehowl Dec 8, 2024
75249d6
update update
thehowl Dec 8, 2024
7fd597c
Merge branch 'master' of github.com:gnolang/gno into dev/morgan/typec…
thehowl Dec 8, 2024
2f2f106
cleanup cleanup
thehowl Dec 8, 2024
c536a6d
Merge branch 'master' into dev/morgan/typecheck-gno-lint
thehowl Dec 8, 2024
2e6a460
remove duplicate files
thehowl Dec 8, 2024
6db9b6c
Merge branch 'dev/morgan/typecheck-gno-lint' of github.com:gnolang/gn…
thehowl Dec 8, 2024
128a318
fixup
thehowl Dec 9, 2024
6e9fac6
Merge branch 'master' into dev/morgan/typecheck-gno-lint
thehowl Dec 9, 2024
77343fb
unused const
thehowl Dec 9, 2024
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
171 changes: 115 additions & 56 deletions gnovm/cmd/gno/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@
"flag"
"fmt"
"go/scanner"
"go/types"
"io"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/gnolang/gno/gnovm"
"github.com/gnolang/gno/gnovm/pkg/gnoenv"
gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
"github.com/gnolang/gno/gnovm/pkg/gnomod"
"github.com/gnolang/gno/gnovm/pkg/test"
"github.com/gnolang/gno/tm2/pkg/commands"
osm "github.com/gnolang/gno/tm2/pkg/os"
"go.uber.org/multierr"
)

Expand Down Expand Up @@ -50,6 +52,31 @@
fs.StringVar(&c.rootDir, "root-dir", rootdir, "clone location of github.com/gnolang/gno (gno tries to guess it)")
}

type lintCode int

const (
lintUnknown lintCode = iota
lintGnoMod
lintGnoError
lintParserError
lintTypeCheckError

// TODO: add new linter codes here.
)

type lintIssue struct {
Code lintCode
Msg string
Confidence float64 // 1 is 100%
zivkovicmilos marked this conversation as resolved.
Show resolved Hide resolved
Location string // file:line, or equivalent
// TODO: consider writing fix suggestions
}

func (i lintIssue) String() string {
// TODO: consider crafting a doc URL based on Code.
thehowl marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Sprintf("%s: %s (code=%d).", i.Location, i.Msg, i.Code)
thehowl marked this conversation as resolved.
Show resolved Hide resolved
}

func execLint(cfg *lintCfg, args []string, io commands.IO) error {
if len(args) < 1 {
return flag.ErrHelp
Expand All @@ -72,20 +99,25 @@

for _, pkgPath := range pkgPaths {
if verbose {
fmt.Fprintf(io.Err(), "Linting %q...\n", pkgPath)
io.ErrPrintln(pkgPath)
}

Check warning on line 103 in gnovm/cmd/gno/lint.go

View check run for this annotation

Codecov / codecov/patch

gnovm/cmd/gno/lint.go#L102-L103

Added lines #L102 - L103 were not covered by tests

info, err := os.Stat(pkgPath)
if err == nil && !info.IsDir() {
pkgPath = filepath.Dir(pkgPath)
}

// Check if 'gno.mod' exists
gnoModPath := filepath.Join(pkgPath, "gno.mod")
if !osm.FileExists(gnoModPath) {
hasError = true
gmFile, err := gnomod.ParseAt(pkgPath)
if err != nil {
issue := lintIssue{
Code: lintNoGnoMod,
Code: lintGnoMod,
Confidence: 1,
Location: pkgPath,
Msg: "missing 'gno.mod' file",
Msg: err.Error(),
}
fmt.Fprint(io.Err(), issue.String()+"\n")
io.ErrPrintln(issue)
hasError = true
}

// Handle runtime errors
Expand All @@ -96,42 +128,32 @@
stdin, stdout, stderr,
)

targetPath := pkgPath
info, err := os.Stat(pkgPath)
if err == nil && !info.IsDir() {
targetPath = filepath.Dir(pkgPath)
memPkg := gno.MustReadMemPackage(pkgPath, pkgPath)
thehowl marked this conversation as resolved.
Show resolved Hide resolved

// Run type checking
if gmFile == nil || !gmFile.Draft {
foundErr, err := lintTypeCheck(io, memPkg, testStore)
if err != nil {
io.ErrPrintln(err)
hasError = true

Check warning on line 138 in gnovm/cmd/gno/lint.go

View check run for this annotation

Codecov / codecov/patch

gnovm/cmd/gno/lint.go#L137-L138

Added lines #L137 - L138 were not covered by tests
} else {
hasError = foundErr || hasError
}
} else if verbose {
zivkovicmilos marked this conversation as resolved.
Show resolved Hide resolved
io.ErrPrintfln("%s: module is draft, skipping type check", pkgPath)

Check warning on line 143 in gnovm/cmd/gno/lint.go

View check run for this annotation

Codecov / codecov/patch

gnovm/cmd/gno/lint.go#L142-L143

Added lines #L142 - L143 were not covered by tests
}

memPkg := gno.MustReadMemPackage(targetPath, targetPath)
tm := test.Machine(testStore, stdout, memPkg.Path)
defer tm.Release()

// Check package
tm.RunMemPackage(memPkg, true)

// Check test files
testfiles := &gno.FileSet{}
for _, mfile := range memPkg.Files {
if !strings.HasSuffix(mfile.Name, ".gno") {
continue // Skip non-GNO files
}

n, _ := gno.ParseFile(mfile.Name, mfile.Body)
if n == nil {
continue // Skip empty files
}
testFiles := lintTestFiles(memPkg)

// XXX: package ending with `_test` is not supported yet
if strings.HasSuffix(mfile.Name, "_test.gno") && !strings.HasSuffix(string(n.PkgName), "_test") {
// Keep only test files
testfiles.AddFiles(n)
}
}

tm.RunFiles(testfiles.Files...)
tm.RunFiles(testFiles.Files...)
}) || hasError

// TODO: Add more checkers
}

if hasError {
Expand All @@ -141,6 +163,66 @@
return nil
}

func lintTypeCheck(io commands.IO, memPkg *gnovm.MemPackage, testStore gno.Store) (errorsFound bool, err error) {
tcErr := gno.TypeCheckMemPackageTest(memPkg, testStore)
if tcErr == nil {
return false, nil
}

errs := multierr.Errors(tcErr)
for _, err := range errs {
switch err := err.(type) {
case types.Error:
io.ErrPrintln(lintIssue{
Code: lintTypeCheckError,
Msg: err.Msg,
Confidence: 1,
Location: err.Fset.Position(err.Pos).String(),
})
case scanner.ErrorList:
for _, scErr := range err {
io.ErrPrintln(lintIssue{
Code: lintParserError,
Msg: scErr.Msg,
Confidence: 1,
Location: scErr.Pos.String(),
})
}
case scanner.Error:
io.ErrPrintln(lintIssue{
Code: lintParserError,
Msg: err.Msg,
Confidence: 1,
Location: err.Pos.String(),
})
default:
return false, fmt.Errorf("unexpected error type: %T", err)

Check warning on line 199 in gnovm/cmd/gno/lint.go

View check run for this annotation

Codecov / codecov/patch

gnovm/cmd/gno/lint.go#L191-L199

Added lines #L191 - L199 were not covered by tests
}
}
return true, nil
thehowl marked this conversation as resolved.
Show resolved Hide resolved
}

func lintTestFiles(memPkg *gnovm.MemPackage) *gno.FileSet {
testfiles := &gno.FileSet{}
for _, mfile := range memPkg.Files {
if !strings.HasSuffix(mfile.Name, ".gno") {
continue // Skip non-GNO files

Check warning on line 209 in gnovm/cmd/gno/lint.go

View check run for this annotation

Codecov / codecov/patch

gnovm/cmd/gno/lint.go#L209

Added line #L209 was not covered by tests
}

n, _ := gno.ParseFile(mfile.Name, mfile.Body)
if n == nil {
continue // Skip empty files

Check warning on line 214 in gnovm/cmd/gno/lint.go

View check run for this annotation

Codecov / codecov/patch

gnovm/cmd/gno/lint.go#L214

Added line #L214 was not covered by tests
}

// XXX: package ending with `_test` is not supported yet
if strings.HasSuffix(mfile.Name, "_test.gno") && !strings.HasSuffix(string(n.PkgName), "_test") {
// Keep only test files
testfiles.AddFiles(n)
}
thehowl marked this conversation as resolved.
Show resolved Hide resolved
}
return testfiles
}

func guessSourcePath(pkg, source string) string {
if info, err := os.Stat(pkg); !os.IsNotExist(err) && !info.IsDir() {
pkg = filepath.Dir(pkg)
Expand Down Expand Up @@ -198,29 +280,6 @@
return
}

type lintCode int

const (
lintUnknown lintCode = 0
lintNoGnoMod lintCode = iota
lintGnoError

// TODO: add new linter codes here.
)

type lintIssue struct {
Code lintCode
Msg string
Confidence float64 // 1 is 100%
Location string // file:line, or equivalent
// TODO: consider writing fix suggestions
}

func (i lintIssue) String() string {
// TODO: consider crafting a doc URL based on Code.
return fmt.Sprintf("%s: %s (code=%d).", i.Location, i.Msg, i.Code)
}

func issueFromError(pkgPath string, err error) lintIssue {
var issue lintIssue
issue.Confidence = 1
Expand Down
16 changes: 9 additions & 7 deletions gnovm/cmd/gno/lint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ func TestLintApp(t *testing.T) {
errShouldBe: "flag: help requested",
}, {
args: []string{"lint", "../../tests/integ/run_main/"},
stderrShouldContain: "./../../tests/integ/run_main: missing 'gno.mod' file (code=1).",
stderrShouldContain: "./../../tests/integ/run_main: gno.mod file not found in current or any parent directory (code=1).",
errShouldBe: "exit code: 1",
}, {
args: []string{"lint", "../../tests/integ/undefined_variable_test/undefined_variables_test.gno"},
Expand All @@ -27,23 +27,25 @@ func TestLintApp(t *testing.T) {
args: []string{"lint", "../../tests/integ/several-files-multiple-errors/main.gno"},
stderrShouldContain: "../../tests/integ/several-files-multiple-errors/file2.gno:3:5: expected 'IDENT', found '{' (code=2).\n../../tests/integ/several-files-multiple-errors/file2.gno:5:1: expected type, found '}' (code=2).\n../../tests/integ/several-files-multiple-errors/main.gno:5:5: expected ';', found example (code=2).\n../../tests/integ/several-files-multiple-errors/main.gno:6:2: expected '}', found 'EOF' (code=2).\n",
errShouldBe: "exit code: 1",
}, {
args: []string{"lint", "../../tests/integ/run_main/"},
stderrShouldContain: "./../../tests/integ/run_main: missing 'gno.mod' file (code=1).",
errShouldBe: "exit code: 1",
}, {
args: []string{"lint", "../../tests/integ/minimalist_gnomod/"},
// TODO: raise an error because there is a gno.mod, but no .gno files
}, {
args: []string{"lint", "../../tests/integ/invalid_module_name/"},
// TODO: raise an error because gno.mod is invalid
}, {
args: []string{"lint", "../../tests/integ/invalid_gno_file/"},
stderrShouldContain: "../../tests/integ/invalid_gno_file/invalid.gno:1:1: expected 'package', found packag (code=2)",
errShouldBe: "exit code: 1",
}, {
args: []string{"lint", "../../tests/integ/typecheck_missing_return/"},
stderrShouldContain: "../../tests/integ/typecheck_missing_return/main.gno:5:1: missing return (code=4).",
errShouldBe: "exit code: 1",
},

// TODO: 'gno mod' is valid?
// TODO: is gno source valid?
// TODO: are dependencies valid?
// TODO: is gno source using unsafe/discouraged features?
// TODO: consider making `gno transpile; go lint *gen.go`
// TODO: check for imports of native libs from non _test.gno files
}
testMainCaseRun(t, tc)
Expand Down
20 changes: 0 additions & 20 deletions gnovm/cmd/gno/testdata/gno_lint/file_error_txtar

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ package main
import "python"
thehowl marked this conversation as resolved.
Show resolved Hide resolved

func main() {
fmt.Println("Hello", 42)
println("Hello", 42)
}

-- gno.mod --
module gno.land/p/test

-- stdout.golden --
-- stderr.golden --
bad_file.gno:3:8: could not import python (import not found: python) (code=4).
bad_file.gno:3:8: unknown import path python (code=2).
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ func TestIHaveSomeError() {
fmt.Println("Hello", 42)
}

-- gno.mod --
module gno.land/p/test

-- stdout.golden --
-- stderr.golden --
i_have_error_test.gno:6:7: name undefined_variable not declared (code=2).
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
# testing simple gno lint command with any error

gno lint ./good_file.gno
gno lint ./good_file.gno

cmp stdout stdout.golden
cmp stdout stderr.golden

-- good_file.gno --
package main

import "fmt"

func main() {
fmt.Println("Hello", 42)
println("Hello", 42)
}

-- gno.mod --
module gno.land/p/demo/test

-- stdout.golden --
-- stderr.golden --
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ cmp stderr stderr.golden
-- good_file.gno --
package main

import "fmt"

func main() {
fmt.Println("Hello", 42)
println("Hello", 42)
}

-- stdout.golden --
-- stderr.golden --
./.: missing 'gno.mod' file (code=1).
./.: parsing gno.mod at ./.: gno.mod file not found in current or any parent directory (code=1).
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ cmp stderr stderr.golden
-- bad_file.gno --
package main

import "fmt"

func main() {
hello.Foo()
fmt.Println("Hello", 42)
hello.Foo()
println("Hello", 42)
}

-- gno.mod --
module gno.land/p/demo/hello

-- stdout.golden --
-- stderr.golden --
bad_file.gno:6:3: name hello not declared (code=2).
bad_file.gno:4:2: undefined: hello (code=4).
bad_file.gno:4:2: name hello not declared (code=2).
1 change: 0 additions & 1 deletion gnovm/cmd/gno/testdata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ func Test_Scripts(t *testing.T) {
}

name := dir.Name()
t.Logf("testing: %s", name)
t.Run(name, func(t *testing.T) {
updateScripts, _ := strconv.ParseBool(os.Getenv("UPDATE_SCRIPTS"))
p := testscript.Params{
Expand Down
Loading
Loading