diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index e4d239e4f58..d18f1986ee9 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -47,4 +47,23 @@ jobs: - uses: actions/checkout@v3 - run: go install -v ./gnovm/cmd/gno - run: go run ./gnovm/cmd/gno test --verbose ./examples + lint: + strategy: + fail-fast: false + matrix: + go-version: [ "1.19.x", "1.20.x" ] + # unittests: TODO: matrix with contracts + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + - uses: actions/checkout@v3 + - run: go install -v ./gnovm/cmd/gno + # testing official directories, basically examples/ minus examples/.../x/. + - run: go run ./gnovm/cmd/gno lint --verbose ./examples/gno.land/p + - run: go run ./gnovm/cmd/gno lint --verbose ./examples/gno.land/r/demo + - run: go run ./gnovm/cmd/gno lint --verbose ./examples/gno.land/r/gnoland + - run: go run ./gnovm/cmd/gno lint --verbose ./examples/gno.land/r/system # TODO: track coverage diff --git a/examples/Makefile b/examples/Makefile index 59643b66ead..5075df198ac 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -1,3 +1,9 @@ +# Official packages: more reliable and tested modules, distinct from the experimentation area. +OFFICIAL_PACKAGES = ./gno.land/p +OFFICIAL_PACKAGES += ./gno.land/r/demo +OFFICIAL_PACKAGES += ./gno.land/r/gnoland +OFFICIAL_PACKAGES += ./gno.land/r/system + .PHONY: help help: @echo "Available make commands:" @@ -15,6 +21,10 @@ build: precompile test: go run ../gnovm/cmd/gno test --verbose . +.PHONY: lint +lint: + go run ../gnovm/cmd/gno lint $(OFFICIAL_PACKAGES) + .PHONY: test.sync test.sync: go run ../gnovm/cmd/gno test --verbose --update-golden-tests . diff --git a/examples/gno.land/p/demo/grc/exts/vault/gno.mod b/examples/gno.land/p/demo/grc/exts/vault/gno.mod new file mode 100644 index 00000000000..8b4d4524366 --- /dev/null +++ b/examples/gno.land/p/demo/grc/exts/vault/gno.mod @@ -0,0 +1,6 @@ +module gno.land/p/demo/grc/exts/vault + +require ( + "gno.land/p/demo/avl" v0.0.0-latest + "gno.land/p/demo/grc/grc20" v0.0.0-latest +) diff --git a/examples/gno.land/p/demo/merkle/gno.mod b/examples/gno.land/p/demo/merkle/gno.mod new file mode 100644 index 00000000000..ae70400aa6d --- /dev/null +++ b/examples/gno.land/p/demo/merkle/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/merkle diff --git a/examples/gno.land/p/demo/microblog/gno.mod b/examples/gno.land/p/demo/microblog/gno.mod new file mode 100644 index 00000000000..fe19b89f777 --- /dev/null +++ b/examples/gno.land/p/demo/microblog/gno.mod @@ -0,0 +1,7 @@ +module gno.land/p/demo/microblog + +require ( + "gno.land/p/demo/avl" v0.0.0-latest + "gno.land/p/demo/ufmt" v0.0.0-latest + "gno.land/r/demo/users" v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/microblog/gno.mod b/examples/gno.land/r/demo/microblog/gno.mod new file mode 100644 index 00000000000..3d79a38d5fd --- /dev/null +++ b/examples/gno.land/r/demo/microblog/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/demo/microblog + +require ( + "gno.land/p/demo/microblog" v0.0.0-latest + "gno.land/r/demo/users" v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/ui/gno.mod b/examples/gno.land/r/demo/ui/gno.mod new file mode 100644 index 00000000000..597c0f388a4 --- /dev/null +++ b/examples/gno.land/r/demo/ui/gno.mod @@ -0,0 +1,5 @@ +module gno.land/r/demo/ui + +require ( + "gno.land/p/demo/ui" v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/x/upgrade/gno.mod b/examples/gno.land/r/demo/x/upgrade/gno.mod deleted file mode 100644 index 52a6601da15..00000000000 --- a/examples/gno.land/r/demo/x/upgrade/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -// Draft - -module gno.land/r/demo/x/upgrade diff --git a/examples/gno.land/r/x/README.md b/examples/gno.land/r/x/README.md new file mode 100644 index 00000000000..a268ab0a9c0 --- /dev/null +++ b/examples/gno.land/r/x/README.md @@ -0,0 +1,19 @@ +# `x/` Experimentation Area + +Welcome to the `x/` directory, designed for experimentation. + +Code in this area is less reviewed and more subject to change or break over +time. It's a great place to publish experiments and proposals for significant +challenges before they become an MVP and move to another directory. + +While publishing code here provides core developers with more examples and edge +cases to work on through CI, consider alternative options such as working on +other repositories, keeping your work in a pull request, or exploring +[https://github.com/gnolang/hackerspace](https://github.com/gnolang/hackerspace) +for broader visibility and collaboration within the Gno community. + +Exercise caution as code in this directory may be less stable or secure due to +its experimental nature. + +Feel free to explore, experiment, and contribute to the exciting developments +happening in the `x/` directory. Together, we can shape the future of GnoVM. diff --git a/examples/gno.land/r/demo/x/outfmt/README.md b/examples/gno.land/r/x/manfred_outfmt/README.md similarity index 62% rename from examples/gno.land/r/demo/x/outfmt/README.md rename to examples/gno.land/r/x/manfred_outfmt/README.md index c90fcab7d37..aeea96ccdb1 100644 --- a/examples/gno.land/r/demo/x/outfmt/README.md +++ b/examples/gno.land/r/x/manfred_outfmt/README.md @@ -1,3 +1,3 @@ -# r/x/outfmt +# r/x/manfred_outfmt PoC of output formatting options. diff --git a/examples/gno.land/r/demo/x/outfmt/gno.mod b/examples/gno.land/r/x/manfred_outfmt/gno.mod similarity index 74% rename from examples/gno.land/r/demo/x/outfmt/gno.mod rename to examples/gno.land/r/x/manfred_outfmt/gno.mod index f8eaa296c71..eef8ec5956e 100644 --- a/examples/gno.land/r/demo/x/outfmt/gno.mod +++ b/examples/gno.land/r/x/manfred_outfmt/gno.mod @@ -1,4 +1,4 @@ -module gno.land/r/demo/x/outfmt +module gno.land/r/x/manfred_outfmt require ( "gno.land/p/demo/rand" v0.0.0-latest diff --git a/examples/gno.land/r/demo/x/outfmt/outfmt.gno b/examples/gno.land/r/x/manfred_outfmt/outfmt.gno similarity index 89% rename from examples/gno.land/r/demo/x/outfmt/outfmt.gno rename to examples/gno.land/r/x/manfred_outfmt/outfmt.gno index e9cb841c910..b01485e16dc 100644 --- a/examples/gno.land/r/demo/x/outfmt/outfmt.gno +++ b/examples/gno.land/r/x/manfred_outfmt/outfmt.gno @@ -51,9 +51,9 @@ func genResult() Result { func Render(path string) string { if path == "" { output := "" - output += "* [?fmt=stringer](/r/x/outfmt:?fmt=stringer)\n" - output += "* [?fmt=json](/r/x/outfmt:?fmt=json)\n" - output += "* [?fmt=jsonp](/r/x/outfmt:?fmt=jsonp)\n" + output += "* [?fmt=stringer](/r/x/manfred_outfmt:?fmt=stringer)\n" + output += "* [?fmt=json](/r/x/manfred_outfmt:?fmt=json)\n" + output += "* [?fmt=jsonp](/r/x/manfred_outfmt:?fmt=jsonp)\n" return output } diff --git a/examples/gno.land/r/demo/x/outfmt/outfmt_test.gno b/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno similarity index 84% rename from examples/gno.land/r/demo/x/outfmt/outfmt_test.gno rename to examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno index 1974c3eb357..c0c14139d52 100644 --- a/examples/gno.land/r/demo/x/outfmt/outfmt_test.gno +++ b/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno @@ -3,16 +3,16 @@ package outfmt import ( "testing" - "gno.land/r/demo/x/outfmt" + "gno.land/r/x/manfred_outfmt" ) func TestRender(t *testing.T) { // home { got := outfmt.Render("") - expected := `* [?fmt=stringer](/r/x/outfmt:?fmt=stringer) -* [?fmt=json](/r/x/outfmt:?fmt=json) -* [?fmt=jsonp](/r/x/outfmt:?fmt=jsonp) + expected := `* [?fmt=stringer](/r/x/manfred_outfmt:?fmt=stringer) +* [?fmt=json](/r/x/manfred_outfmt:?fmt=json) +* [?fmt=jsonp](/r/x/manfred_outfmt:?fmt=jsonp) ` if got != expected { t.Fatalf("expected %q, got %q.", expected, got) diff --git a/examples/gno.land/r/demo/x/upgrade/README.md b/examples/gno.land/r/x/manfred_upgrade_patterns/README.md similarity index 100% rename from examples/gno.land/r/demo/x/upgrade/README.md rename to examples/gno.land/r/x/manfred_upgrade_patterns/README.md diff --git a/examples/gno.land/r/x/manfred_upgrade_patterns/gno.mod b/examples/gno.land/r/x/manfred_upgrade_patterns/gno.mod new file mode 100644 index 00000000000..d6e97dc39b9 --- /dev/null +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/gno.mod @@ -0,0 +1,3 @@ +// Draft + +module gno.land/r/x/manfred_upgrade_patterns diff --git a/examples/gno.land/r/demo/x/upgrade/upgrade-a/integration_test.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-a/integration_test.gno similarity index 81% rename from examples/gno.land/r/demo/x/upgrade/upgrade-a/integration_test.gno rename to examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-a/integration_test.gno index 325559a5041..ea585cc344d 100644 --- a/examples/gno.land/r/demo/x/upgrade/upgrade-a/integration_test.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-a/integration_test.gno @@ -1,8 +1,8 @@ package upgradea import ( - v1 "gno.land/r/demo/x/upgrade/upgrade-a/v1" - v2 "gno.land/r/demo/x/upgrade/upgrade-a/v2" + v1 "gno.land/r/x/manfred_upgrade_patterns/upgrade-a/v1" + v2 "gno.land/r/x/manfred_upgrade_patterns/upgrade-a/v2" ) func main() { diff --git a/examples/gno.land/r/demo/x/upgrade/upgrade-a/v1/v1.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-a/v1/v1.gno similarity index 100% rename from examples/gno.land/r/demo/x/upgrade/upgrade-a/v1/v1.gno rename to examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-a/v1/v1.gno diff --git a/examples/gno.land/r/demo/x/upgrade/upgrade-a/v2/v2.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-a/v2/v2.gno similarity index 82% rename from examples/gno.land/r/demo/x/upgrade/upgrade-a/v2/v2.gno rename to examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-a/v2/v2.gno index ac5ab51cc2b..f5a598b2849 100644 --- a/examples/gno.land/r/demo/x/upgrade/upgrade-a/v2/v2.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-a/v2/v2.gno @@ -3,7 +3,7 @@ package upgradea import ( "strconv" - v1 "gno.land/r/demo/x/upgrade/upgrade-a/v1" + v1 "gno.land/r/x/manfred_upgrade_patterns/upgrade-a/v1" ) var counter int diff --git a/examples/gno.land/r/demo/x/upgrade/upgrade-b/v1/v1.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-b/v1/v1.gno similarity index 100% rename from examples/gno.land/r/demo/x/upgrade/upgrade-b/v1/v1.gno rename to examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-b/v1/v1.gno diff --git a/examples/gno.land/r/demo/x/upgrade/upgrade-b/v2/v2.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-b/v2/v2.gno similarity index 91% rename from examples/gno.land/r/demo/x/upgrade/upgrade-b/v2/v2.gno rename to examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-b/v2/v2.gno index 883f535a9b4..bc5862903a7 100644 --- a/examples/gno.land/r/demo/x/upgrade/upgrade-b/v2/v2.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-b/v2/v2.gno @@ -3,7 +3,7 @@ package upgradeb import ( "std" - v1 "gno.land/r/demo/x/upgrade/upgrade-b/v1" + v1 "gno.land/r/x/manfred_upgrade_patterns/upgrade-b/v1" ) const admin = "blahblah" diff --git a/examples/gno.land/r/demo/x/upgrade/upgrade-c/root/root.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-c/root/root.gno similarity index 78% rename from examples/gno.land/r/demo/x/upgrade/upgrade-c/root/root.gno rename to examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-c/root/root.gno index 6ec3225d997..6a51fe8001f 100644 --- a/examples/gno.land/r/demo/x/upgrade/upgrade-c/root/root.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-c/root/root.gno @@ -2,7 +2,7 @@ package root var ( counter int - currentImplementation = "gno.land/r/demo/x/upgrade/upgrade-c/v1" + currentImplementation = "gno.land/r/x/manfred_upgrade_patterns/upgrade-c/v1" ) func Inc() int { diff --git a/examples/gno.land/r/demo/x/upgrade/upgrade-c/v1/v1.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-c/v1/v1.gno similarity index 57% rename from examples/gno.land/r/demo/x/upgrade/upgrade-c/v1/v1.gno rename to examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-c/v1/v1.gno index e4c9b4a9c3d..cdacd4ac004 100644 --- a/examples/gno.land/r/demo/x/upgrade/upgrade-c/v1/v1.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-c/v1/v1.gno @@ -1,6 +1,6 @@ package v1 -import "gno.land/r/demo/x/upgrade/upgrade-c/root" +import "gno.land/r/x/manfred_upgrade_patterns/upgrade-c/root" func Inc() { root.Inc() diff --git a/examples/gno.land/r/demo/x/upgrade/upgrade-c/v2/v2.gno b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-c/v2/v2.gno similarity index 57% rename from examples/gno.land/r/demo/x/upgrade/upgrade-c/v2/v2.gno rename to examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-c/v2/v2.gno index 29b1eb850ef..995b5e3568d 100644 --- a/examples/gno.land/r/demo/x/upgrade/upgrade-c/v2/v2.gno +++ b/examples/gno.land/r/x/manfred_upgrade_patterns/upgrade-c/v2/v2.gno @@ -1,6 +1,6 @@ package v1 -import "gno.land/r/demo/x/upgrade/upgrade-c/root" +import "gno.land/r/x/manfred_upgrade_patterns/upgrade-c/root" func Inc() { root.Inc() diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go new file mode 100644 index 00000000000..8042a1f1703 --- /dev/null +++ b/gnovm/cmd/gno/lint.go @@ -0,0 +1,112 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "path/filepath" + + "github.com/gnolang/gno/tm2/pkg/commands" + osm "github.com/gnolang/gno/tm2/pkg/os" +) + +type lintCfg struct { + verbose bool + rootDir string + setExitStatus int + // min_confidence: minimum confidence of a problem to print it (default 0.8) + // auto-fix: apply suggested fixes automatically. +} + +func newLintCmd(io *commands.IO) *commands.Command { + cfg := &lintCfg{} + + return commands.NewCommand( + commands.Metadata{ + Name: "lint", + ShortUsage: "lint [flags] [...]", + ShortHelp: "Runs the linter for the specified packages", + }, + cfg, + func(_ context.Context, args []string) error { + return execLint(cfg, args, io) + }, + ) +} + +func (c *lintCfg) RegisterFlags(fs *flag.FlagSet) { + fs.BoolVar(&c.verbose, "verbose", false, "verbose output when lintning") + fs.StringVar(&c.rootDir, "root-dir", "", "clone location of github.com/gnolang/gno (gnodev tries to guess it)") + fs.IntVar(&c.setExitStatus, "set_exit_status", 1, "set exit status to 1 if any issues are found") +} + +func execLint(cfg *lintCfg, args []string, io *commands.IO) error { + if len(args) < 1 { + return flag.ErrHelp + } + + var ( + verbose = cfg.verbose + rootDir = cfg.rootDir + ) + if rootDir == "" { + rootDir = guessRootDir() + } + + pkgPaths, err := gnoPackagesFromArgs(args) + if err != nil { + return fmt.Errorf("list packages from args: %w", err) + } + + hasError := false + addIssue := func(issue lintIssue) { + hasError = true + fmt.Fprint(io.Err, issue.String()+"\n") + } + + for _, pkgPath := range pkgPaths { + if verbose { + fmt.Fprintf(io.Err, "Linting %q...\n", pkgPath) + } + + // 'gno.mod' exists? + gnoModPath := filepath.Join(pkgPath, "gno.mod") + if !osm.FileExists(gnoModPath) { + addIssue(lintIssue{ + Code: lintNoGnoMod, + Confidence: 1, + Location: pkgPath, + Msg: "missing 'gno.mod' file", + }) + } + + // TODO: add more checkers + } + + if hasError && cfg.setExitStatus != 0 { + os.Exit(cfg.setExitStatus) + } + return nil +} + +type lintCode int + +const ( + lintUnknown lintCode = 0 + lintNoGnoMod lintCode = iota + // 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) +} diff --git a/gnovm/cmd/gno/lint_test.go b/gnovm/cmd/gno/lint_test.go new file mode 100644 index 00000000000..ce200a1fedd --- /dev/null +++ b/gnovm/cmd/gno/lint_test.go @@ -0,0 +1,31 @@ +package main + +import "testing" + +func TestLintApp(t *testing.T) { + tc := []testMainCase{ + { + args: []string{"lint"}, + errShouldBe: "flag: help requested", + }, { + 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/run-main/"}, + stderrShouldContain: "./../../tests/integ/run-main: missing 'gno.mod' file (code=1).", + }, { + args: []string{"lint", "--set_exit_status=0", "../../tests/integ/minimalist-gnomod/"}, + // TODO: raise an error because there is a gno.mod, but no .gno files + }, { + 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? + // TODO: is gno source using unsafe/discouraged features? + // TODO: consider making `gno precompile; go lint *gen.go` + // TODO: check for imports of native libs from non _test.gno files + } + testMainCaseRun(t, tc) +} diff --git a/gnovm/cmd/gno/main.go b/gnovm/cmd/gno/main.go index acc64d9e8a7..433db703b90 100644 --- a/gnovm/cmd/gno/main.go +++ b/gnovm/cmd/gno/main.go @@ -29,11 +29,12 @@ func newGnocliCmd(io *commands.IO) *commands.Command { ) cmd.AddSubCommands( + newModCmd(io), + newTestCmd(io), + newLintCmd(io), newRunCmd(io), newBuildCmd(io), newPrecompileCmd(io), - newTestCmd(io), - newModCmd(io), newCleanCmd(io), newReplCmd(), newDocCmd(io), diff --git a/gnovm/docs/go-gno-compatibility.md b/gnovm/docs/go-gno-compatibility.md index 1162c7285aa..e35e9e5dd5d 100644 --- a/gnovm/docs/go-gno-compatibility.md +++ b/gnovm/docs/go-gno-compatibility.md @@ -369,3 +369,4 @@ Additional native types: | go tool | | | | go version | | | | go vet | | | +| golint | gno lint | same intention | diff --git a/misc/docker-integration/integration_test.go b/misc/docker-integration/integration_test.go index ebd4c0851a8..3af32226c96 100644 --- a/misc/docker-integration/integration_test.go +++ b/misc/docker-integration/integration_test.go @@ -53,7 +53,7 @@ func runSuite(t *testing.T, tempdir string) { var acc gnoland.GnoAccount dockerExec_gnokeyQuery(t, "auth/accounts/"+test1Addr, &acc) require.Equal(t, test1Addr, acc.Address.String(), "test1 account not found") - minCoins := std.MustParseCoins("9999900000000ugnot") + minCoins := std.MustParseCoins("9990000000000ugnot") // This value is chosen arbitrarily and may not be optimal. Feel free to update it to a more suitable amount require.True(t, acc.Coins.IsAllGTE(minCoins), "test1 account coins expected at least %s, got %s", minCoins, acc.Coins)