From b7b258f94e97cab4bcd17060cd692222b03e8450 Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Tue, 25 Nov 2025 16:50:58 +0100 Subject: [PATCH 1/2] feat(lint): poc avl tree usage limit --- .../gno.land/r/demo/lintavltree/gnomod.toml | 2 + .../r/demo/lintavltree/lintavltree.gno | 36 +++++++++++++ gnovm/cmd/gno/lint.go | 10 ++-- gnovm/pkg/gnolang/machine.go | 12 ++++- gnovm/pkg/gnolang/nodes.go | 2 + gnovm/pkg/gnolang/preprocess.go | 52 +++++++++++++++++++ gnovm/pkg/test/imports.go | 4 +- 7 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 examples/gno.land/r/demo/lintavltree/gnomod.toml create mode 100644 examples/gno.land/r/demo/lintavltree/lintavltree.gno diff --git a/examples/gno.land/r/demo/lintavltree/gnomod.toml b/examples/gno.land/r/demo/lintavltree/gnomod.toml new file mode 100644 index 00000000000..3c9117e8802 --- /dev/null +++ b/examples/gno.land/r/demo/lintavltree/gnomod.toml @@ -0,0 +1,2 @@ +module = "gno.land/r/demo/lintavltree" +gno = "0.9" \ No newline at end of file diff --git a/examples/gno.land/r/demo/lintavltree/lintavltree.gno b/examples/gno.land/r/demo/lintavltree/lintavltree.gno new file mode 100644 index 00000000000..9971407c66d --- /dev/null +++ b/examples/gno.land/r/demo/lintavltree/lintavltree.gno @@ -0,0 +1,36 @@ +package lintavltree + +import "gno.land/p/nt/avl" + +var lintest *avl.Tree + +func init() { + lintest = avl.NewTree() + lintest.Set("key1", "value1") + lintest.Set("key2", "value2") + lintest.Set("key3", "value3") +} + +func JoinValues() string { + result := "" + //nolint + lintest.Iterate("", "", func(key string, value any) bool { + result += value.(string) + "," + return false + }) + return result +} + +func JoinValuesReverse() string { + result := "" + //nolint + lintest.ReverseIterate("", "", func(key string, value any) bool { + result += value.(string) + "," + return false + }) + return result +} + +func Render(path string) string { + return "Rendering AVL Tree at path: " + path +} diff --git a/gnovm/cmd/gno/lint.go b/gnovm/cmd/gno/lint.go index f3fd0fda853..6b917dde0fe 100644 --- a/gnovm/cmd/gno/lint.go +++ b/gnovm/cmd/gno/lint.go @@ -221,7 +221,7 @@ func execLint(cmd *lintCmd, args []string, io commands.IO) error { tmpkgType := tmpkg.Type.(gno.MemPackageType) m2.Store.AddMemPackage(tmpkg, tmpkgType) return m2.PreprocessFiles(tmpkg.Name, tmpkg.Path, - m2.ParseMemPackageAsType(tmpkg, tmpkgType), true, true, "") + m2.ParseMemPackageAsType(tmpkg, tmpkgType), true, true, "", false, tmpkg) } else { return tgetter(pkgPath, store) } @@ -284,7 +284,7 @@ func execLint(cmd *lintCmd, args []string, io commands.IO) error { // Preprocess fset files (no test files) tm.Store = newProdGnoStore() pn, _ := tm.PreprocessFiles( - mpkg.Name, mpkg.Path, fset, false, false, "") + mpkg.Name, mpkg.Path, fset, false, false, "", false, mpkg) ppkg.AddNormal(pn, fset) } { @@ -292,7 +292,7 @@ func execLint(cmd *lintCmd, args []string, io commands.IO) error { // Preprocess fset files (w/ some *_test.gno). tm.Store = newTestGnoStore(false) pn, _ := tm.PreprocessFiles( - mpkg.Name, mpkg.Path, tfset, false, false, "") + mpkg.Name, mpkg.Path, tfset, false, false, "", true, mpkg) ppkg.AddTest(pn, fset) } { @@ -300,7 +300,7 @@ func execLint(cmd *lintCmd, args []string, io commands.IO) error { // Preprocess _test files (all xxx_test *_test.gno). tm.Store = newTestGnoStore(true) pn, _ := tm.PreprocessFiles( - mpkg.Name+"_test", mpkg.Path+"_test", _tests, false, false, "") + mpkg.Name+"_test", mpkg.Path+"_test", _tests, false, false, "", false, mpkg) ppkg.AddUnderscoreTests(pn, _tests) } { @@ -318,7 +318,7 @@ func execLint(cmd *lintCmd, args []string, io commands.IO) error { continue } pkgName := string(fset.Files[0].PkgName) - pn, _ := tm.PreprocessFiles(pkgName, pkgPath, fset, false, false, "") + pn, _ := tm.PreprocessFiles(pkgName, pkgPath, fset, false, false, "", false, mpkg) ppkg.AddFileTest(pn, fset) } } diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 46e091dd18e..3003da5cd3f 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -492,7 +492,7 @@ func (m *Machine) RunFiles(fns ...*FileNode) { // the package getter for tests, e.g. from "gnovm/tests/files/extern/*", or from // "examples/*". // - fixFrom: the version of gno to fix from. -func (m *Machine) PreprocessFiles(pkgName, pkgPath string, fset *FileSet, save, withOverrides bool, fixFrom string) (*PackageNode, *PackageValue) { +func (m *Machine) PreprocessFiles(pkgName, pkgPath string, fset *FileSet, save, withOverrides bool, fixFrom string, lintMode bool, mpkg *std.MemPackage) (*PackageNode, *PackageValue) { if !withOverrides { if err := checkDuplicates(fset); err != nil { panic(fmt.Errorf("running package %q: %w", pkgName, err)) @@ -502,12 +502,22 @@ func (m *Machine) PreprocessFiles(pkgName, pkgPath string, fset *FileSet, save, if fixFrom != "" { pn.SetAttribute(ATTR_FIX_FROM, fixFrom) } + if lintMode { + pn.SetAttribute(ATTR_LINT_MODE, true) + } pv := pn.NewPackage(nilAllocator) pb := pv.GetBlock(m.Store) m.SetActivePackage(pv) m.Store.SetBlockNode(pn) PredefineFileSet(m.Store, pn, fset) for _, fn := range fset.Files { + if lintMode { + mf := mpkg.GetFile(fn.FileName) + if mf != nil { + fn.SetAttribute(ATTR_FILE_SOURCE, mf.Body) + } + } + fn = Preprocess(m.Store, pn, fn).(*FileNode) // After preprocessing, save blocknodes to store. SaveBlockNodes(m.Store, fn) diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index fb9a2b88860..3c8b05c8084 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -141,6 +141,8 @@ const ( ATTR_PACKAGE_DECL GnoAttribute = "ATTR_PACKAGE_DECL" ATTR_PACKAGE_PATH GnoAttribute = "ATTR_PACKAGE_PATH" // if name expr refers to package. ATTR_FIX_FROM GnoAttribute = "ATTR_FIX_FROM" // gno fix this version. + ATTR_LINT_MODE GnoAttribute = "ATTR_LINT_MODE" + ATTR_FILE_SOURCE GnoAttribute = "ATTR_FILE_SOURCE" // source code of file (for linting comments) ) // Embedded in each Node. diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 641963548a3..118460d80fe 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -524,6 +524,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { last := ctx stack := append(make([]BlockNode, 0, 32), last) ctxpn := packageOf(ctx) + var currentFile *FileNode // iterate over all nodes recursively nn := Transcribe(n, func(ns []Node, ftype TransField, index int, n Node, stage TransStage) (Node, TransCtrl) { @@ -886,6 +887,7 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { // TRANS_BLOCK ----------------------- case *FileNode: // only for imports. + currentFile = n pushInitBlock(n, &last, &stack) { // This logic supports out-of-order @@ -1846,6 +1848,56 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { checkOrConvertType(store, last, n, &n.Args[i], spts[i].Type, true) } } + if ctxpn.HasAttribute(ATTR_LINT_MODE) { + if sel, ok := n.Func.(*SelectorExpr); ok { + methodName := string(sel.Sel) + if methodName == "Iterate" || methodName == "ReverseIterate" { + recvT := evalStaticTypeOf(store, last, sel.X) + t := unwrapPointerType(recvT) + dt, ok := t.(*DeclaredType) + if ok && dt.PkgPath == "gno.land/p/nt/avl" && dt.Name == "Tree" { + if len(n.Args) < 3 { + return n, TRANS_CONTINUE + } + startArg := n.Args[0] + endArg := n.Args[1] + + isEmptyString := func(s Expr) bool { + if cs, ok := s.(*ConstExpr); ok { + if cs.T.Kind() == StringKind { + sv := cs.V.(StringValue) + return sv == "" + } + } + return false + } + + hasNoLint := false + body := currentFile.GetAttribute(ATTR_FILE_SOURCE) + if bodyStr, ok := body.(string); ok { + lines := strings.Split(bodyStr, "\n") + line := n.Pos.Line + + prevLineIndex := line - 2 + if prevLineIndex >= 0 && prevLineIndex < len(lines) { + prevLine := strings.TrimSpace(lines[prevLineIndex]) + if strings.HasPrefix(prevLine, "//nolint") { + hasNoLint = true + } + } + } + + startEmpty := isEmptyString(startArg) + endEmpty := isEmptyString(endArg) + + if startEmpty && endEmpty && !hasNoLint { + fmt.Printf("warning: calling %s.%s without start or end limit\n", dt.Name, methodName) + } + } + + } + } + } } default: panic(fmt.Sprintf( diff --git a/gnovm/pkg/test/imports.go b/gnovm/pkg/test/imports.go index 195b393700a..59e94b85276 100644 --- a/gnovm/pkg/test/imports.go +++ b/gnovm/pkg/test/imports.go @@ -143,7 +143,7 @@ func StoreWithOptions( return m.PreprocessFiles( mpkg.Name, mpkg.Path, m.ParseMemPackageAsType(mpkg, mptype), - save, false, opts.FixFrom) + save, false, opts.FixFrom, false, mpkg) } else { return m.RunMemPackage(mpkg, save) } @@ -314,7 +314,7 @@ func loadStdlib( }) if preprocessOnly { m2.Store.AddMemPackage(mpkg, mPkgType) - return m2.PreprocessFiles(mpkg.Name, mpkg.Path, m2.ParseMemPackageAsType(mpkg, mPkgType), true, true, "") + return m2.PreprocessFiles(mpkg.Name, mpkg.Path, m2.ParseMemPackageAsType(mpkg, mPkgType), true, true, "", false, mpkg) } // TODO: make this work when using gno lint. return m2.RunMemPackageWithOverrides(mpkg, true) From a134912cf9cfa6a63e88ee2cc60795a27875031c Mon Sep 17 00:00:00 2001 From: MikaelVallenet Date: Tue, 25 Nov 2025 17:17:39 +0100 Subject: [PATCH 2/2] feat(lint): poc avl tree usage limit --- examples/gno.land/r/demo/lintavltree/gnomod.toml | 2 +- gnovm/pkg/gnolang/preprocess.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/gno.land/r/demo/lintavltree/gnomod.toml b/examples/gno.land/r/demo/lintavltree/gnomod.toml index 3c9117e8802..9aa809202be 100644 --- a/examples/gno.land/r/demo/lintavltree/gnomod.toml +++ b/examples/gno.land/r/demo/lintavltree/gnomod.toml @@ -1,2 +1,2 @@ module = "gno.land/r/demo/lintavltree" -gno = "0.9" \ No newline at end of file +gno = "0.9" diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 118460d80fe..36135c26a25 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1894,7 +1894,6 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { fmt.Printf("warning: calling %s.%s without start or end limit\n", dt.Name, methodName) } } - } } }