Skip to content

feat(gnovm): add extensible linting framework with AVL001 and GLOBAL001 rules#5068

Open
MikaelVallenet wants to merge 33 commits intognolang:masterfrom
MikaelVallenet:mikael/experiment/linter-archi
Open

feat(gnovm): add extensible linting framework with AVL001 and GLOBAL001 rules#5068
MikaelVallenet wants to merge 33 commits intognolang:masterfrom
MikaelVallenet:mikael/experiment/linter-archi

Conversation

@MikaelVallenet
Copy link
Member

@MikaelVallenet MikaelVallenet commented Jan 17, 2026

fix #4421 & fix #1042

Summary (PR description is mostly AI generated and then adapted by me)

This PR adds an extensible linting framework to gno lint. You can now write pluggable rules, choose different
output formats, and suppress warnings with //nolint comments.

What's New

New Lint Package (gnovm/pkg/lint/)

Core stuff:

  • Issue - represents a single lint finding (severity, location, message)
  • Severity - three levels: info, warning, error
  • Rule interface - implement this to create your own lint rules
  • RuleContext - gives rules access to the file, source code, and parent nodes
  • Registry - handles rule registration (rules self-register via init())
  • Config - controls mode and which rules to disable
  • NolintParser - reads //nolint comments to suppress specific issues

Reporters (how output gets formatted):

  • TextReporter - human-friendly output, sorted by file/line, with a summary at the end
  • JSONReporter - machine-readable JSON array for tooling integration
  • DirectReporter - prints issues immediately as they're found (used by gno test)

Built-in rules:

  • AVL001 - warns when you iterate an entire AVL tree without bounds (tree.Iterate("", "", ...))
  • GLOBAL001 - warns about exported package-level variables (like var Counter int)

CLI Changes (gnovm/cmd/gno/lint.go)

New flags:

  • --mode - choose between default, strict, or warn-only
  • --format - output as text or json
  • --list-rules - shows all available rules and exits
  • --disable-rules - skip specific rules (e.g., --disable-rules=AVL001,GLOBAL001)

Output format changed:
Before

  file.gno:10:5: some message (code=someCode)

After

  file.gno:10:5: warning: some message (AVL001)

Docs

  • Added docs/resources/gno-lint.md with usage examples, rule descriptions, and a guide for writing new rules

Tests

  • Unit tests for everything in gnovm/pkg/lint/
  • Integration tests (txtar format) covering AVL001, GLOBAL001, nolint directives, and mode flags

Breaking Changes

Output format - The lint output now includes severity and uses a different format (see above)

Exit codes - Now depend on the mode:

Mode Exits with 1 when...
default there are errors
strict there are any issues (warnings become errors)
warn-only never (errors become warnings)

@github-actions github-actions bot added 📖 documentation Improvements or additions to documentation 📦 🤖 gnovm Issues or PRs gnovm related labels Jan 17, 2026
@Gno2D2 Gno2D2 requested a review from a team January 17, 2026 16:19
@Gno2D2
Copy link
Collaborator

Gno2D2 commented Jan 17, 2026

🛠 PR Checks Summary

All Automated Checks passed. ✅

Manual Checks (for Reviewers):
  • IGNORE the bot requirements for this PR (force green CI check)
Read More

🤖 This bot helps streamline PR reviews by verifying automated checks and providing guidance for contributors and reviewers.

✅ Automated Checks (for Contributors):

🟢 Maintainers must be able to edit this pull request (more info)
🟢 Pending initial approval by a review team member, or review from tech-staff

☑️ Contributor Actions:
  1. Fix any issues flagged by automated checks.
  2. Follow the Contributor Checklist to ensure your PR is ready for review.
    • Add new tests, or document why they are unnecessary.
    • Provide clear examples/screenshots, if necessary.
    • Update documentation, if required.
    • Ensure no breaking changes, or include BREAKING CHANGE notes.
    • Link related issues/PRs, where applicable.
☑️ Reviewer Actions:
  1. Complete manual checks for the PR, including the guidelines and additional checks if applicable.
📚 Resources:
Debug
Automated Checks
Maintainers must be able to edit this pull request (more info)

If

🟢 Condition met
└── 🟢 And
    ├── 🟢 The base branch matches this pattern: ^master$
    └── 🟢 The pull request was created from a fork (head branch repo: MikaelVallenet/gno)

Then

🟢 Requirement satisfied
└── 🟢 Maintainer can modify this pull request

Pending initial approval by a review team member, or review from tech-staff

If

🟢 Condition met
└── 🟢 And
    ├── 🟢 The base branch matches this pattern: ^master$
    └── 🟢 Not (🔴 Pull request author is a member of the team: tech-staff)

Then

🟢 Requirement satisfied
└── 🟢 If
    ├── 🟢 Condition
    │   └── 🟢 Or
    │       ├── 🟢 User davd-gzl already reviewed PR 5068 with state APPROVED
    │       ├── 🔴 At least 1 user(s) of the team tech-staff reviewed pull request
    │       └── 🔴 This pull request is a draft
    └── 🟢 Then
        └── 🟢 Not (🔴 This label is applied to pull request: review/triage-pending)

Manual Checks
**IGNORE** the bot requirements for this PR (force green CI check)

If

🟢 Condition met
└── 🟢 On every pull request

Can be checked by

  • Any user with comment edit permission

@codecov
Copy link

codecov bot commented Jan 17, 2026

@MikaelVallenet MikaelVallenet marked this pull request as ready for review January 20, 2026 13:10
@Gno2D2 Gno2D2 added the review/triage-pending PRs opened by external contributors that are waiting for the 1st review label Jan 20, 2026
@jefft0 jefft0 added this to the ⏭️Next after mainnet beta milestone Jan 28, 2026
@Kouteki Kouteki moved this from Triage to In Review in 🧙‍♂️Gno.land development Jan 28, 2026
@Kouteki Kouteki removed the request for review from a team January 28, 2026 21:58
Copy link
Member

@davd-gzl davd-gzl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall implementations seems working great from my testing, there's some nitpick and code can be simplified at some place but it seems like an efficient V1.
I need to do a second review to be sure!

@@ -0,0 +1,154 @@
# Gno Lint

This comment was marked as resolved.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## Command Line Options

| Flag | Default | Description |
|------|---------|-------------|

This comment was marked as resolved.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## Available Rules

- **AVL001** (warning): Unbounded AVL tree iteration - detects `avl.Tree.Iterate()` or `ReverseIterate()` with empty bounds (`"", ""`)
- **GLOBAL001** (warning): Exported package-level variable - detects exported (uppercase) `var` declarations at package level
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rule is triggered on the variables of the next code snippets, which I don't think should.
If it's possible with the AST you are working with, we would have better to trigger this warning only on dangerous exported variable (e.g.: structure containing public method returning object, not readonly tainted value, whatever else).

package grc1155

import "errors"

var (
	ErrInvalidAddress                         = errors.New("invalid address")
	ErrMismatchLength                         = errors.New("accounts and ids length mismatch")
	ErrCannotTransferToSelf                   = errors.New("cannot send transfer to self")
	ErrTransferToRejectedOrNonGRC1155Receiver = errors.New("transfer to rejected or non GRC1155Receiver implementer")
	ErrCallerIsNotOwnerOrApproved             = errors.New("caller is not token owner or approved")
	ErrInsufficientBalance                    = errors.New("insufficient balance for transfer")
	ErrBurnAmountExceedsBalance               = errors.New("burn amount exceeds balance")
	ErrInvalidAmount                          = errors.New("invalid amount")
)
davd@davd ~/P/g/e/g/p/d/t/grc1155 (mikael/experiment/linter-archi)> gno lint .
errors.gno:6:2: warning: exported package-level variable: ErrInvalidAddress (GLOBAL001)
errors.gno:7:2: warning: exported package-level variable: ErrMismatchLength (GLOBAL001)
errors.gno:8:2: warning: exported package-level variable: ErrCannotTransferToSelf (GLOBAL001)
errors.gno:9:2: warning: exported package-level variable: ErrTransferToRejectedOrNonGRC1155Receiver (GLOBAL001)
errors.gno:10:2: warning: exported package-level variable: ErrCallerIsNotOwnerOrApproved (GLOBAL001)
errors.gno:11:2: warning: exported package-level variable: ErrInsufficientBalance (GLOBAL001)
errors.gno:12:2: warning: exported package-level variable: ErrBurnAmountExceedsBalance (GLOBAL001)
errors.gno:13:2: warning: exported package-level variable: ErrInvalidAmount (GLOBAL001)

Found 8 issue(s): 0 error(s), 8 warning(s), 0 info

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about using the // nolint directive for these one
it's a way to acknowledge you know what you do IMO
will fix the others reviews asap

Copy link
Member

@davd-gzl davd-gzl Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's a good idea, it's gonna pollute the code base especially if we have to refactor everything.
I feel we would have better to set it deactivated by default with a warning on that flag, fix the flag with only problematic global variable, or remove it entirely as the false positive will be too high.

return false
})

//nolint:GLOBAL001
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to be able to inline this comment (or add an example if it's already implemented)

var Config = DefaultConfig() //nolint:GLOBAL001

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of scope IMO can be done in another PR


r.issues = append(r.issues, issue)

switch issue.Severity {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is duplicated

Copy link
Member Author

@MikaelVallenet MikaelVallenet Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

text and json implementations shared some common business logic, we could use a common base but i don't think it make sense here, just for 2 implem. can you developp your thinking

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This specific switch case is duplicated 4 times and is related to a constant behavior, I think this should be moved in a generic helper.
The sort function from the flush too, but I agree we don't have to use a common base in that case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// to allow reuse by other commands (run, test) without coupling them to the lint package.
type Reporter interface {
Report(issue Issue)
Flush() error

This comment was marked as resolved.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Gno2D2 Gno2D2 requested a review from a team January 29, 2026 19:26
Copy link
Member

@moonia moonia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, LGTM.
The implementation appears to be working as expected based on my testing. As Davphla mentioned, it might be worth adding this to docs/README.md

@thehowl
Copy link
Member

thehowl commented Feb 3, 2026

I'll review this when I can; can you make sure to include the additions you made in #4943 as well? :)

@jefft0 jefft0 moved this to Other dev assigned in 🤝🏻 Partner: Berty Feb 4, 2026
Copy link
Member

@davd-gzl davd-gzl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested every features, it all works great except the edge case of avl001


r.issues = append(r.issues, issue)

switch issue.Severity {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This specific switch case is duplicated 4 times and is related to a constant behavior, I think this should be moved in a generic helper.
The sort function from the flush too, but I agree we don't have to use a common base in that case.

}
}

func (AVL001) Check(ctx *lint.RuleContext, node gnolang.Node) []lint.Issue {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason this rule is not triggered on examples/gno.land/p/demo/microblog/microblog.gno:L39

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed: f0e468e

@Kouteki Kouteki requested review from notJoon and thehowl and removed request for a team February 15, 2026 22:04
@MikaelVallenet MikaelVallenet force-pushed the mikael/experiment/linter-archi branch from fdbe30b to b954995 Compare March 10, 2026 13:33
@MikaelVallenet MikaelVallenet force-pushed the mikael/experiment/linter-archi branch from b954995 to 484d008 Compare March 10, 2026 13:40
@jefft0
Copy link
Contributor

jefft0 commented Mar 19, 2026

@MikaelVallenet , the failed Lint CI check for "images/manifesto/boards2.jpeg" has been fixed in master. If you merge master again, then it should fix this CI check.

@Gno2D2 Gno2D2 removed the review/triage-pending PRs opened by external contributors that are waiting for the 1st review label Mar 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📖 documentation Improvements or additions to documentation 📦 🤖 gnovm Issues or PRs gnovm related

Projects

Status: No status
Status: Other dev assigned
Status: In Review

7 participants