Documentation
¶
Overview ¶
Package lint is m-cli's M source linter. Rules come in two shapes, both over the m-parse tree:
- query rules carry a tree-sitter query + an OnMatch that turns each match into a finding (good for pattern detection, e.g. M-MOD-037);
- walk rules carry an Inspect that traverses the tree / scans the source and returns findings (good for metrics + structure, e.g. line length, nesting depth, argument count).
Rules are tagged; profiles select rules by tag (spec §3.1). This is the engine + a growing rule set — the dataflow/taint rules (M-MOD-011..036) need the flow-analysis infra, a later port.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var Profiles = []string{"default", "modern", "pythonic", "pedantic", "xindex", "sac", "vista", "all"}
Profiles is the set of recognized profile names (for the CLI enum).
default · modern · pythonic · pedantic — the M-MOD modernization track (tag "modern") xindex — rules ported from the VA VistA Toolkit ^XINDEX scanner (tag "xindex") sac — the subset of xindex rules mapping to a documented VA SAC requirement (tag "sac") vista — VistA-Kernel-specific rules (tag "vista"); pure false positives off VistA all — every registered rule
Functions ¶
func DefaultKernelLocals ¶
DefaultKernelLocals returns a copy of the built-in VistA Kernel auto-defined allowlist — the set [lint.vista] kernel_locals = "default" opts in to.
func DefaultTrustedRoutines ¶
DefaultTrustedRoutines returns the built-in VistA trusted-routine allowlist as an upper-cased set ([lint.vista] trusted_routines = "default").
func ResolveFilter ¶
ResolveFilter layers rule selection identically for every entry point — the CLI lint/watch commands and the LSP — so the editor and CI can never drift (G2 diagnostic parity): an explicitly-set non-default --profile flag wins; otherwise the config's lint rules; otherwise the "default" profile. Callers with no profile flag (the LSP) pass "".
Types ¶
type Finding ¶
type Finding struct {
Rule string `json:"rule"`
Severity Severity `json:"severity"`
Message string `json:"message"`
Line int `json:"line"`
Col int `json:"col"`
EndLine int `json:"endLine"`
EndCol int `json:"endCol"`
}
Finding is one lint result (1-based positions; End* mark the offending span).
type Linter ¶
type Linter struct {
// contains filtered or unexported fields
}
Linter compiles the query rules' queries once and runs the rule set over many sources.
func (*Linter) AttachWorkspace ¶
AttachWorkspace gives the linter a cross-routine index so InspectWorkspace rules (M-XINDX-007/008/049) can run. Without it those rules are skipped.
type Options ¶
type Options struct {
Thresholds Thresholds
Taint flow.TaintConfig
KernelLocals map[string]bool // M-MOD-024 allowlist; nil ⇒ strict (no allowlist)
TrustedRoutines map[string]bool // M-XINDX-007 allowlist; nil ⇒ strict (any unknown routine flagged)
SeverityOverrides map[string]Severity // [lint.severity]; re-stamps Rule.Severity at build
Disable map[string]bool // [lint] disable; rule ids dropped after selection
}
Options carries the resolved, config-derived inputs baked into rule construction. NewLinter takes the rule set built from these, so Lint's own signature stays unchanged — the config flows in at build time, not call time.
func DefaultOptions ¶
func DefaultOptions() Options
DefaultOptions is the built-in configuration when no config file is present. Faithful to the Python tool: the M-MOD-024 Kernel allowlist is OFF by default (KernelLocals is nil ⇒ strict) — opt in via [lint.vista] kernel_locals; taint treats formals as attack surface (flow.DefaultTaintConfig). With no config, DefaultOptions reproduces the previous hard-coded behavior except for the now-faithful strict-by-default Kernel allowlist.
func OptionsFromConfig ¶
OptionsFromConfig resolves a loaded project config into rule-build Options, layering config values over DefaultOptions. Validation already happened in the config package, so the only error path is an internally-inconsistent config (none today). Layering: thresholds merge over the defaults; taint formals/ sanitizers override the built-in TaintConfig; [lint.vista] kernel_locals selects strict / built-in / explicit; severity + disable carry through.
type Rule ¶
type Rule struct {
ID string
Severity Severity
Category string
Title string
Tags []string
// Query rule: Query is tree-sitter source; OnMatch is called per match and
// returns the finding message + whether to emit. The position is the start
// of the match's first capture.
Query string
OnMatch func(m parse.Match, src []byte) (string, bool)
// Walk rule: Inspect traverses root / scans src and returns findings with
// Line/Col/End* + Message set; the engine stamps Rule + Severity.
Inspect func(root parse.Node, src []byte) []Finding
// Name-aware walk rule: like Inspect but also receives the routine name
// (the file's base name without extension, as passed to LintNamed). Used by
// rules that compare against the routine identity (e.g. M-XINDX-017). When
// the name is "" (plain Lint, or a non-file source) the rule should no-op.
InspectNamed func(root parse.Node, src []byte, routine string) []Finding
// Cross-routine walk rule: also receives the routine name and the workspace
// index (labels + references across all linted files). Used by M-XINDX-007/
// 008/049. Runs only when a workspace is attached (Linter.AttachWorkspace);
// with no workspace these rules are skipped (faithful to the Python runner).
InspectWorkspace func(root parse.Node, src []byte, routine string, ws *workspace.Index) []Finding
}
Rule is a lint rule. Exactly one of Query (with OnMatch) or Inspect is set.
func All ¶
func All() []Rule
All returns every registered rule with the built-in default configuration.
func AllWith ¶
AllWith returns every registered rule, baking the resolved config into the rules that need it (thresholds, Kernel allowlist, taint config). The config- neutral rules are returned as-is.
func ProfileWith ¶
ProfileWith resolves a profile name to its rules, by tag (spec §3.1):
default = modern minus pedantic (the curated, low-noise set) modern = everything tagged "modern" pythonic = modern (alias for now) pedantic = everything tagged "pedantic" all = every rule
func Resolve ¶
Resolve turns a rule filter (a profile name, or a comma-list mixing profile names and rule IDs) plus build Options into the final rule set: profile/ID selection, then lint disable drops rules, then [lint.severity] re-stamps Rule.Severity. Faithful to the Python select_rules + post-selection layering.
func (Rule) NeedsWorkspace ¶
NeedsWorkspace reports whether the rule requires a cross-routine workspace index (so callers can skip building one when no selected rule uses it).
type Thresholds ¶
type Thresholds struct {
LineLength int // M-MOD-001
DotBlockDepth int // M-MOD-007
ArgumentCount int // M-MOD-008
CommandsLine int // M-MOD-009
}
Thresholds are the resolved numeric limits the metric rules enforce. Only the four the Go linter wires today live here; the config layer validates the full Python threshold set but silently ignores keys no rule consumes yet.
func DefaultThresholds ¶
func DefaultThresholds() Thresholds
DefaultThresholds mirrors the Python defaults (lint/thresholds.py).