Skip to content

Commit

Permalink
Merge branch 'master' into feat/log
Browse files Browse the repository at this point in the history
  • Loading branch information
zivkovicmilos committed Jan 23, 2024
2 parents 795fe67 + 7339bdc commit 6779dc6
Show file tree
Hide file tree
Showing 27 changed files with 384 additions and 42 deletions.
38 changes: 37 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ To use *gofumpt* instead of *gofmt*, as hinted in the comment, you may either ha
cexpr system('go run -modfile </path/to/gno>/misc/devdeps/go.mod mvdan.cc/gofumpt -w ' . expand('%'))
```

##### ViM Linting Support

To integrate GNO linting in Vim, you can use Vim's `:make` command with a custom `makeprg` and `errorformat` to run the GNO linter and parse its output. Add the following configuration to your `.vimrc` file:

```vim
autocmd FileType gno setlocal makeprg=gno\ lint\ %
autocmd FileType gno setlocal errorformat=%f:%l:\ %m
" Optional: Key binding to run :make on the current file
autocmd FileType gno nnoremap <buffer> <F5> :make<CR>
```

### ViM Support (with LSP)

There is an experimental and unofficial [Gno Language Server](https://github.com/jdkato/gnols)
Expand Down Expand Up @@ -172,7 +184,31 @@ Additionally, it's not possible to use `gofumpt` for code formatting with
2. Add to your emacs configuration file:

```lisp
(add-to-list 'auto-mode-alist '("\\.gno\\'" . go-mode))
(define-derived-mode gno-mode go-mode "GNO"
"Major mode for GNO files, an alias for go-mode."
(setq-local tab-width 8))
(define-derived-mode gno-dot-mod-mode go-dot-mod-mode "GNO Mod"
"Major mode for GNO mod files, an alias for go-dot-mod-mode."
)
```

3. To integrate GNO linting with Flycheck, add the following to your Emacs configuration:
```lisp
(require 'flycheck)
(flycheck-define-checker gno-lint
"A GNO syntax checker using the gno lint tool."
:command ("gno" "lint" source-original)
:error-patterns (;; ./file.gno:32: error message (code=1)
(error line-start (file-name) ":" line ": " (message) " (code=" (id (one-or-more digit)) ")." line-end))
;; Ensure the file is saved, to work around
;; https://github.com/python/mypy/issues/4746.
:predicate (lambda ()
(and (not (bound-and-true-p polymode-mode))
(flycheck-buffer-saved-p)))
:modes gno-mode)
(add-to-list 'flycheck-checkers 'gno-lint)
```

#### Sublime Text
Expand Down
4 changes: 2 additions & 2 deletions contribs/gnodev/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ require (
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/dgraph-io/badger/v3 v3.2103.4 // indirect
github.com/dgraph-io/badger/v3 v3.2103.5 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/gnolang/goleveldb v0.0.9 // indirect
Expand All @@ -41,7 +41,7 @@ require (
github.com/klauspost/compress v1.12.3 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/linxGnu/grocksdb v1.8.5 // indirect
github.com/linxGnu/grocksdb v1.8.11 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/peterbourgon/ff/v3 v3.4.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
Expand Down
8 changes: 4 additions & 4 deletions contribs/gnodev/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions contribs/gnokeykc/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ require (
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/dgraph-io/badger/v3 v3.2103.4 // indirect
github.com/dgraph-io/badger/v3 v3.2103.5 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/gnolang/goleveldb v0.0.9 // indirect
Expand All @@ -33,7 +33,7 @@ require (
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/klauspost/compress v1.12.3 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/linxGnu/grocksdb v1.8.5 // indirect
github.com/linxGnu/grocksdb v1.8.11 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/peterbourgon/ff/v3 v3.4.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
Expand Down
8 changes: 4 additions & 4 deletions contribs/gnokeykc/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

124 changes: 121 additions & 3 deletions gnovm/cmd/gno/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ package main

import (
"context"
"errors"
"flag"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/gnolang/gno/gnovm/pkg/gnoenv"
gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
"github.com/gnolang/gno/gnovm/tests"
"github.com/gnolang/gno/tm2/pkg/commands"
osm "github.com/gnolang/gno/tm2/pkg/os"
)
Expand Down Expand Up @@ -37,8 +42,10 @@ func newLintCmd(io commands.IO) *commands.Command {
}

func (c *lintCfg) RegisterFlags(fs *flag.FlagSet) {
rootdir := gnoenv.RootDir()

fs.BoolVar(&c.verbose, "verbose", false, "verbose output when lintning")
fs.StringVar(&c.rootDir, "root-dir", "", "clone location of github.com/gnolang/gno (gno tries to guess it)")
fs.StringVar(&c.rootDir, "root-dir", rootdir, "clone location of github.com/gnolang/gno (gno tries to guess it)")
fs.IntVar(&c.setExitStatus, "set-exit-status", 1, "set exit status to 1 if any issues are found")
}

Expand Down Expand Up @@ -71,7 +78,7 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error {
fmt.Fprintf(io.Err(), "Linting %q...\n", pkgPath)
}

// 'gno.mod' exists?
// Check if 'gno.mod' exists
gnoModPath := filepath.Join(pkgPath, "gno.mod")
if !osm.FileExists(gnoModPath) {
addIssue(lintIssue{
Expand All @@ -82,20 +89,131 @@ func execLint(cfg *lintCfg, args []string, io commands.IO) error {
})
}

// TODO: add more checkers
// Handle runtime errors
catchRuntimeError(pkgPath, addIssue, func() {
stdout, stdin, stderr := io.Out(), io.In(), io.Err()
testStore := tests.TestStore(
rootDir, "",
stdin, stdout, stderr,
tests.ImportModeStdlibsOnly,
)

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

memPkg := gno.ReadMemPackage(targetPath, targetPath)
tm := tests.TestMachine(testStore, stdout, memPkg.Name)

// 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
}

// 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...)
})

// TODO: Add more checkers
}

if hasError && cfg.setExitStatus != 0 {
os.Exit(cfg.setExitStatus)
}

return nil
}

func guessSourcePath(pkg, source string) string {
if info, err := os.Stat(pkg); !os.IsNotExist(err) && !info.IsDir() {
pkg = filepath.Dir(pkg)
}

sourceJoin := filepath.Join(pkg, source)
if _, err := os.Stat(sourceJoin); !os.IsNotExist(err) {
return filepath.Clean(sourceJoin)
}

if _, err := os.Stat(source); !os.IsNotExist(err) {
return filepath.Clean(source)
}

return filepath.Clean(pkg)
}

// reParseRecover is a regex designed to parse error details from a string.
// It extracts the file location, line number, and error message from a formatted error string.
// XXX: Ideally, error handling should encapsulate location details within a dedicated error type.
var reParseRecover = regexp.MustCompile(`^([^:]+):(\d+)(?::\d+)?:? *(.*)$`)

func catchRuntimeError(pkgPath string, addIssue func(issue lintIssue), action func()) {
defer func() {
// Errors catched here mostly come from: gnovm/pkg/gnolang/preprocess.go
r := recover()
if r == nil {
return
}

var err error
switch verr := r.(type) {
case *gno.PreprocessError:
err = verr.Unwrap()
case error:
err = verr
case string:
err = errors.New(verr)
default:
panic(r)
}

var issue lintIssue
issue.Confidence = 1
issue.Code = lintGnoError

parsedError := strings.TrimSpace(err.Error())
parsedError = strings.TrimPrefix(parsedError, pkgPath+"/")

matches := reParseRecover.FindStringSubmatch(parsedError)
if len(matches) == 4 {
sourcepath := guessSourcePath(pkgPath, matches[1])
issue.Location = fmt.Sprintf("%s:%s", sourcepath, matches[2])
issue.Msg = strings.TrimSpace(matches[3])
} else {
issue.Location = fmt.Sprintf("%s:0", filepath.Clean(pkgPath))
issue.Msg = err.Error()
}

addIssue(issue)
}()

action()
}

type lintCode int

const (
lintUnknown lintCode = 0
lintNoGnoMod lintCode = iota
lintGnoError

// TODO: add new linter codes here.
)

Expand Down
7 changes: 7 additions & 0 deletions gnovm/cmd/gno/lint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ func TestLintApp(t *testing.T) {
}, {
args: []string{"lint", "--set-exit-status=0", "../../tests/integ/run-main/"},
stderrShouldContain: "./../../tests/integ/run-main: missing 'gno.mod' file (code=1).",
}, {
args: []string{"lint", "--set-exit-status=0", "../../tests/integ/undefined-variable-test/undefined_variables_test.gno"},
stderrShouldContain: "undefined_variables_test.gno:6: name toto not declared (code=2)",
}, {
args: []string{"lint", "--set-exit-status=0", "../../tests/integ/package-not-declared/main.gno"},
stderrShouldContain: "main.gno:4: name fmt not declared (code=2).",
}, {
args: []string{"lint", "--set-exit-status=0", "../../tests/integ/run-main/"},
stderrShouldContain: "./../../tests/integ/run-main: missing 'gno.mod' file (code=1).",
Expand All @@ -20,6 +26,7 @@ func TestLintApp(t *testing.T) {
args: []string{"lint", "--set-exit-status=0", "../../tests/integ/invalid-module-name/"},
// TODO: raise an error because gno.mod is invalid
},

// TODO: 'gno mod' is valid?
// TODO: is gno source valid?
// TODO: are dependencies valid?
Expand Down
4 changes: 4 additions & 0 deletions gnovm/cmd/gno/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ func TestRunApp(t *testing.T) {
args: []string{"run", "-expr", "WithArg(-255)", "../../tests/integ/run-package"},
stdoutShouldContain: "out of range!",
},
{
args: []string{"run", "../../tests/integ/undefined-variable-test/undefined_variables_test.gno"},
recoverShouldContain: "--- preprocess stack ---", // should contain preprocess debug stack trace
},
// TODO: a test file
// TODO: args
// TODO: nativeLibs VS stdlibs
Expand Down
6 changes: 5 additions & 1 deletion gnovm/cmd/gno/test_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package main

import (
"os"
"strconv"
"testing"

"github.com/gnolang/gno/gnovm/pkg/integration"
Expand All @@ -9,8 +11,10 @@ import (
)

func Test_ScriptsTest(t *testing.T) {
updateScripts, _ := strconv.ParseBool(os.Getenv("UPDATE_SCRIPTS"))
p := testscript.Params{
Dir: "testdata/gno_test",
UpdateScripts: updateScripts,
Dir: "testdata/gno_test",
}

if coverdir, ok := integration.ResolveCoverageDir(); ok {
Expand Down
19 changes: 19 additions & 0 deletions gnovm/cmd/gno/testdata/gno_test/lint_bad_import.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# testing gno lint command: bad import error

! gno lint ./bad_file.gno

cmp stdout stdout.golden
cmp stderr stderr.golden

-- bad_file.gno --
package main

import "python"

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

-- stdout.golden --
-- stderr.golden --
bad_file.gno:1: unknown import path python (code=2).
20 changes: 20 additions & 0 deletions gnovm/cmd/gno/testdata/gno_test/lint_file_error.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# gno lint: test file error

! gno lint ./i_have_error_test.gno

cmp stdout stdout.golden
cmp stderr stderr.golden

-- i_have_error_test.gno --
package main

import "fmt"

func TestIHaveSomeError() {
i := undefined_variable
fmt.Println("Hello", 42)
}

-- stdout.golden --
-- stderr.golden --
i_have_error_test.gno:6: name undefined_variable not declared (code=2).
Loading

0 comments on commit 6779dc6

Please sign in to comment.