api

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 8, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package api defines the krit rule contract: a single Rule struct that declares its dependencies via a Capabilities bitfield and provides a single Check function which receives a Context carrying everything the rule needs. Replaces the prior tangle of family-specific interfaces (FlatDispatchRule, LineRule, AggregateRule, CrossFileRule, ManifestRule, ResourceRule, GradleRule, TypeAwareRule, ConfidenceProvider, etc.) with one descriptor that the dispatcher classifies by Needs / NodeTypes.

Index

Constants

This section is empty.

Variables

View Source
var Registry []*Rule

Registry holds all registered v2 rules.

Functions

func ApplyConfig

func ApplyConfig(rule interface{}, d RuleDescriptor, cfg ConfigSource) (active bool)

ApplyConfig applies a config source to a rule via its descriptor.

The return value is the effective active state after ruleset + rule overrides:

  • If IsRuleSetActive(ruleSet) is non-nil and false, the rule is inactive and option overrides are NOT applied: disabling a ruleset short-circuits everything else.

  • Otherwise, if IsRuleActive(ruleSet, rule) is non-nil, it overrides d.DefaultActive. Options are still applied — a rule-level disable does not stop the option pass.

  • When no active override is present, d.DefaultActive is the effective state and options are applied.

For each option, ApplyConfig tries the primary Name first, then each alias in order. When a key is found (checked via HasKey), the value is read via the appropriate GetXxx method and passed to opt.Apply with the rule as the target. For OptRegex, the raw pattern is anchored and compiled via CompileAnchoredPattern before being passed to Apply; an invalid pattern logs to stderr and leaves the target field untouched.

ApplyConfig is pure: no globals are read or written. The caller owns the rule pointer and the config source.

func ApplyConfigActiveOnly

func ApplyConfigActiveOnly(d RuleDescriptor, cfg ConfigSource) (active bool)

ApplyConfigActiveOnly mirrors ApplyConfig but skips the Options loop. Use it when a rule publishes a Meta descriptor but the concrete struct pointer is unreachable. The ruleset-disable short-circuit and the rule-level active override are still honored.

Rules passed through this path MUST have no configurable options, since the Apply closures cannot run without a concrete target. ApplyConfig is the right choice when a concrete pointer is available.

func ApplyResolvedExpressions

func ApplyResolvedExpressions(sink ExpressionFactSink, results map[string]map[ExpressionPosition]*typeinfer.ResolvedType)

ApplyResolvedExpressions writes every entry from results into sink. Called after the targeted-resolution RPC completes and before the dispatcher runs, so rules see the new facts via their resolver.

func CollectExpressionPositions

func CollectExpressionPositions(rules []*Rule, files []*scanner.File) map[string][]ExpressionPosition

CollectExpressionPositions returns the union of every active rule's ExprPositions selector applied to every file, keyed by file.Path. Inner slices are deduplicated and sorted ascending by (line, col) so downstream consumers (RPC payload builders, fakes, snapshots) see a stable order.

Selectors that return nil or whose Rule has ExprPositions == nil contribute nothing. A file with zero requested positions is omitted from the result map entirely (callers should treat absence as "no work to do for this file").

func CompileAnchoredPattern

func CompileAnchoredPattern(ruleName, field, pattern string) *regexp.Regexp

CompileAnchoredPattern compiles a regex pattern, anchoring it with ^ and $ when those anchors are missing. Patterns are full-string matches rather than substring matches. Invalid patterns log a warning to stderr and return nil — callers must treat nil as "leave the existing field alone".

func ContextFindings

func ContextFindings(ctx *Context) []scanner.Finding

ContextFindings drains the context's collector and returns findings as a slice.

func DefaultInactiveSet

func DefaultInactiveSet(descs []RuleDescriptor) map[string]bool

DefaultInactiveSet returns the set of rule IDs that are opt-in based on the given descriptor slice. The returned map is safe for the caller to mutate (it's not shared).

func NeedsJavaFacts

func NeedsJavaFacts(rules []*Rule) bool

func Register

func Register(r *Rule)

Register adds a rule to the global rule registry.

func RuleAppliesToLanguage

func RuleAppliesToLanguage(r *Rule, lang scanner.Language) bool

RuleAppliesToLanguage reports whether a rule should run on a file of the given language. Used by the dispatcher to filter rules per file.

func RuleLanguages

func RuleLanguages(r *Rule) []scanner.Language

RuleLanguages returns the languages a rule applies to, falling back to the sensible default derived from Needs when Languages is nil.

func SetReporter

func SetReporter(r *diag.Reporter)

SetReporter installs r as the package-level Reporter for rule metadata diagnostics (e.g. invalid regex in config). Passing nil restores the default warnings-only Reporter.

func TypedCustomApply

func TypedCustomApply[R any](apply func(*R, ConfigSource)) func(interface{}, ConfigSource)

TypedCustomApply wraps a typed CustomApply closure for use in RuleDescriptor.CustomApply. It downcasts the target to *R before invoking the supplied function.

func ValidateAnchoredPattern

func ValidateAnchoredPattern(pattern string) error

ValidateAnchoredPattern validates a regex pattern with the same implicit full-string anchoring used by CompileAnchoredPattern, but without logging.

Types

type Aggregate

type Aggregate struct {
	// Collect is invoked for each matching node. The rule accumulates
	// state internally via this callback (typically via a closure over
	// fields in its adapter).
	Collect func(ctx *Context)
	// Finalize is invoked after the walk completes for a file. It
	// should report findings via ctx.Emit / ctx.EmitAt.
	Finalize func(ctx *Context)
	// Reset clears any per-file state. Called before Collect for the
	// next file.
	Reset func()
}

Aggregate describes the three-phase lifecycle of an aggregate rule: Collect is called for each matching node during the AST walk; Finalize is called once per file after the walk to produce findings; Reset is called between files to clear any accumulated state.

type BoolOptionSpec

type BoolOptionSpec[R any] struct {
	Name        string
	Aliases     []string
	Default     bool
	Description string
	Apply       func(*R, bool)
}

BoolOptionSpec describes a bool-valued option on rule type R.

type Capabilities

type Capabilities uint32

Capabilities is a bitfield carrying both the rule's primary scope (which dispatcher bucket it lands in) and its orthogonal aspects (resolver/oracle wiring, concurrency safety, etc.).

Conceptually the bits split into two groups:

  • Scope bits (mutually exclusive in practice — the dispatcher classifier picks at most one): NeedsCrossFile, NeedsModuleIndex, NeedsParsedFiles, NeedsManifest, NeedsResources, NeedsGradle, NeedsAggregate, NeedsLinePass. A rule may also leave all of these unset and supply NodeTypes — that lands in the per-file AST dispatch path.

  • Aspect bits (orthogonal, additive): NeedsResolver, NeedsOracle, NeedsConcurrent. These layer on top of any scope.

New rules can also set Rule.Scope explicitly to declare the primary scope as a typed value; when Scope is unset the dispatcher derives it from the bits above. See the Scope type for the enumeration.

const (
	// NeedsResolver requests a TypeResolver in Context. (Aspect.)
	NeedsResolver Capabilities = 1 << iota
	// NeedsModuleIndex requests a PerModuleIndex in Context. (Scope.)
	NeedsModuleIndex
	// NeedsCrossFile requests a CodeIndex in Context. (Scope.)
	NeedsCrossFile
	// NeedsLinePass marks this rule as a line-scanning rule (receives
	// Lines, not nodes). (Scope.)
	NeedsLinePass
	// NeedsParsedFiles marks this rule as needing all parsed files
	// (project-scope). (Scope.)
	NeedsParsedFiles
	// NeedsManifest marks this rule as needing AndroidManifest.xml data.
	// (Scope.)
	NeedsManifest
	// NeedsResources marks this rule as needing the Android resource
	// index. (Scope.)
	NeedsResources
	// NeedsGradle marks this rule as needing Gradle build config data.
	// (Scope.)
	NeedsGradle
	// NeedsAggregate marks this rule as having an aggregate lifecycle
	// (Collect per node, Finalize per file, Reset between files).
	// (Scope.)
	NeedsAggregate

	// NeedsConcurrent marks this rule as safe to execute in parallel
	// across worker goroutines at a phase boundary. The dispatcher
	// hands each concurrent rule its own Context carrying a worker-
	// local FindingCollector; collectors are serially merged into the
	// phase's output after all workers stop appending.
	//
	// Rules declaring this bit must not rely on package-level mutable
	// state or shared maps. They read the phase's immutable inputs
	// (CodeIndex, ParsedFiles, ModuleIndex) and emit only through
	// ctx.Emit / ctx.EmitAt. Finding order is recovered by the phase
	// owner after the merge (SortByFileLine), so worker interleavings
	// do not affect JSON / SARIF output. (Aspect.)
	NeedsConcurrent

	// NeedsOracleCallTargets requests resolved overload FQN / receiver
	// type for call expressions selected by OracleCallTargetFilter.
	// Pair with a non-nil OracleCallTargets to narrow the JVM-side scan.
	NeedsOracleCallTargets
	// NeedsOracleSuspendMarkers requests the suspend / inline / operator
	// properties on the resolved callable. Independent of declaration
	// extraction.
	NeedsOracleSuspendMarkers
	// NeedsOracleExprType requests expression type / nullability at
	// selected positions (LookupExpression / ExprPositions).
	NeedsOracleExprType
	// NeedsOracleExprAnnotations requests annotation FQNs at expression
	// positions (e.g. @CheckResult on the resolved callable).
	NeedsOracleExprAnnotations
	// NeedsOracleSupertypes requests the supertype walk for declaration
	// symbols. Implies ClassShell when projected onto the declaration
	// profile.
	NeedsOracleSupertypes
	// NeedsOracleMembers requests the member list for declaration
	// symbols. Implied by NeedsOracleMemberSignatures and
	// NeedsOracleMemberAnnotations.
	NeedsOracleMembers
	// NeedsOracleMemberSignatures requests parameter / return-type
	// rendering for members. Implies NeedsOracleMembers when projected.
	NeedsOracleMemberSignatures
	// NeedsOracleClassAnnotations requests class-level annotation FQNs.
	NeedsOracleClassAnnotations
	// NeedsOracleMemberAnnotations requests member-level annotation
	// FQNs. Implies NeedsOracleMembers when projected.
	NeedsOracleMemberAnnotations
	// NeedsOracleDiagnostics requests KAA compiler diagnostics
	// (UNREACHABLE_CODE, USELESS_ELVIS, etc.). Skipped JVM-side when no
	// active rule declares this bit.
	NeedsOracleDiagnostics
	// NeedsOracleLibraryClasses requests the JAR / library closure
	// (Dependencies map) — required for resolving against types that
	// do not appear in source. Skipped JVM-side when no active rule
	// declares this bit.
	NeedsOracleLibraryClasses
)

NeedsOracle is the back-compat umbrella: the OR of every narrow oracle fact bit. Rules that declare it consent to the broadest JVM workload. New rules should declare only the narrow bits they read in their Check function so the bridge can compute a tight union across the active rule set.

const NeedsTypeInfo Capabilities = NeedsResolver

NeedsTypeInfo is a source type-information alias for NeedsResolver only: rules that need KAA must declare NeedsOracle or explicit oracle metadata (Oracle, OracleCallTargets, OracleDeclarationNeeds, diagnostics). This keeps source-level AT/typeinfer rules from accidentally widening the Kotlin Analysis API workload.

Prefer NeedsResolver for new rules unless the implementation consumes KAA-only facts.

func (Capabilities) Has

func (c Capabilities) Has(flag Capabilities) bool

Has reports whether c includes all bits in flag.

func (Capabilities) HasAny

func (c Capabilities) HasAny(flag Capabilities) bool

HasAny reports whether c includes any bit in flag. Useful for umbrella unions like NeedsOracle, where Has would require every constituent bit and miss rules that declared only narrow bits.

func (Capabilities) IsPerFile

func (c Capabilities) IsPerFile() bool

IsPerFile reports whether this rule runs per-file (dispatch or line pass). Aggregate rules are per-file (they collect during per-file walks and finalize after each file) so they are considered per-file here.

type ConfigOption

type ConfigOption struct {
	// Name is the primary YAML key for this option.
	Name string

	// Aliases are alternate YAML keys accepted for back-compat (e.g.
	// "threshold" aliasing allowedLines). Checked in order after Name.
	Aliases []string

	// Type declares how the ConfigSource should read the value.
	Type OptionType

	// Default is the value used when no override is present. Retained
	// for schema generation; the runtime does not re-apply the default
	// (the rule struct literal already carries the default).
	Default interface{}

	// Description is the schema-level documentation for this option.
	Description string

	// Apply is invoked with the target rule and the parsed value when a
	// config override is present. The closure downcasts target to the
	// rule's concrete struct type and assigns the field. For OptRegex
	// the value is *regexp.Regexp (compiled via CompileAnchoredPattern);
	// for all other types the value matches Type's Go representation.
	Apply func(target interface{}, value interface{})
}

ConfigOption describes a single configurable field on a rule.

Apply closures downcast the target interface to the concrete rule struct and assign the parsed value. At runtime ApplyConfig iterates the descriptor's options, reads each from the ConfigSource (primary Name first, then each alias in order), and invokes Apply when a value is present.

func BoolOption

func BoolOption[R any](s BoolOptionSpec[R]) ConfigOption

BoolOption builds an OptBool ConfigOption from a typed spec.

func IntOption

func IntOption[R any](s IntOptionSpec[R]) ConfigOption

IntOption builds an OptInt ConfigOption from a typed spec.

func RegexOption

func RegexOption[R any](s RegexOptionSpec[R]) ConfigOption

RegexOption builds an OptRegex ConfigOption from a typed spec.

func StringListOption

func StringListOption[R any](s StringListOptionSpec[R]) ConfigOption

StringListOption builds an OptStringList ConfigOption from a typed spec.

func StringOption

func StringOption[R any](s StringOptionSpec[R]) ConfigOption

StringOption builds an OptString ConfigOption from a typed spec.

type ConfigSource

type ConfigSource interface {
	// GetInt reads an int override. If the key is not set, def is returned.
	GetInt(ruleSet, rule, key string, def int) int
	// GetBool reads a bool override. If the key is not set, def is returned.
	GetBool(ruleSet, rule, key string, def bool) bool
	// GetString reads a string override. If the key is not set, def is returned.
	GetString(ruleSet, rule, key, def string) string
	// GetStringList reads a []string override. Returns nil when not set.
	GetStringList(ruleSet, rule, key string) []string
	// HasKey reports whether the given key is present in the config.
	HasKey(ruleSet, rule, key string) bool
	// IsRuleActive returns the explicit active override for a rule, or nil when
	// the config does not override. Non-nil true enables a default-inactive rule;
	// non-nil false disables a default-active rule.
	IsRuleActive(ruleSet, rule string) *bool
	// IsRuleSetActive returns the explicit active override for a ruleset, or nil
	// when the config does not override. Non-nil false disables every rule in the
	// set regardless of rule-level overrides.
	IsRuleSetActive(ruleSet string) *bool
}

ConfigSource is the abstraction the rule metadata runtime uses to read configuration values. The real implementation lives in internal/config; tests use FakeConfigSource in the compatibility registry package.

The GetXxx methods all accept a default value and return it when the requested key is not present. HasKey exists so ApplyConfig can distinguish "present but empty list" from "not configured", which is needed to implement the existing "apply only if present" semantics for string-list and regex options.

type Context

type Context struct {
	// Always available for per-file rules:
	File *scanner.File
	Node *scanner.FlatNode // nil for line-pass rules
	Idx  uint32            // flat tree index of Node (0 for line-pass rules)

	// Rule is the rule whose Check is currently executing. Populated by
	// the dispatcher before invoking Check so Emit can stamp
	// Rule/RuleSet/Severity/Confidence defaults without the rule body
	// having to know them.
	Rule *Rule

	// DefaultConfidence is the family-level fallback confidence applied
	// to findings emitted through Emit when the rule doesn't set its own
	// Confidence. Set by the dispatcher (0.95 node-dispatch, 0.75 line).
	DefaultConfidence float64

	// Collector receives findings written via Emit/EmitAt in columnar form.
	// Must be set before Check is called.
	Collector *scanner.FindingCollector

	// Populated only when the rule declares NeedsResolver:
	Resolver typeinfer.TypeResolver

	// Populated only when the rule declares NeedsModuleIndex:
	ModuleIndex *module.PerModuleIndex

	// Populated only when the rule declares NeedsCrossFile:
	CodeIndex *scanner.CodeIndex

	// Populated only when the rule declares NeedsParsedFiles:
	ParsedFiles []*scanner.File

	// Populated only when the rule declares NeedsManifest. Now strongly
	// typed: the rule-facing manifest model lives in the leaf
	// internal/manifest package, so v2 can import it without creating a
	// cycle through internal/rules.
	Manifest *manifest.Manifest

	// Populated only when the rule declares NeedsResources:
	ResourceIndex *android.ResourceIndex

	// Populated only when the rule declares AndroidDepIcons:
	IconIndex *android.IconIndex

	// Populated only when the rule declares NeedsGradle:
	GradlePath    string
	GradleContent string
	GradleConfig  *android.BuildConfig

	// Populated for Java source files with cheap source-level facts
	// derived from the parsed Java file. JavaSourceIndex is populated
	// when the dispatcher has a project/source file set; single-file
	// dispatch receives a one-file index.
	JavaFacts       *javafacts.JavaFileFacts
	JavaSourceIndex *javafacts.SourceIndex
	// Populated when at least one enabled Java rule requested optional
	// javac-backed semantic facts and the helper was available.
	JavaSemanticFacts *javafacts.Facts

	// Project-wide library and platform facts derived from Gradle where
	// available. Rules should use this instead of baking library-version
	// assumptions directly into AST heuristics.
	LibraryFacts *librarymodel.Facts

	// Facts is the per-run shared cache of derived per-file facts
	// (imports, references, declaration summaries). Always non-nil for
	// contexts produced by the dispatcher; helpers that build their own
	// mini-contexts may leave it nil — filefacts accessors are nil-safe
	// and recompute without caching in that case.
	Facts *filefacts.Cache
}

Context carries everything a rule could need. Fields are populated conditionally based on the rule's declared Capabilities.

func FakeContext

func FakeContext(file *scanner.File) *Context

FakeContext creates a minimal context for testing with the given file. A FindingCollector is pre-allocated; use ContextFindings(ctx) to read results.

func FakeContextWithNode

func FakeContextWithNode(file *scanner.File, idx uint32) *Context

FakeContextWithNode creates a context for testing with a specific node index.

func (*Context) Emit

func (c *Context) Emit(f scanner.Finding)

Emit reports a finding. The finding is stamped with rule metadata and written to the Collector.

func (*Context) EmitAt

func (c *Context) EmitAt(line, col int, msg string)

EmitAt is a convenience for emitting a finding at a specific location.

type Deprecation

type Deprecation struct {
	// Since is the krit version in which this rule was deprecated.
	// Empty string means the deprecation predates version tracking.
	Since string

	// ReplacedBy is the canonical ID of the rule users should migrate
	// to. Empty string means the rule was retired without a direct
	// replacement (the user should remove their suppression entirely).
	ReplacedBy string

	// Reason is a one-line explanation surfaced to users alongside the
	// deprecation. Keep terse — not a full migration guide.
	Reason string
}

Deprecation marks a rule as scheduled for removal and points users at a migration target.

Treat the struct as immutable after construction so descriptors stay safe to copy and share.

type ExpressionFactSink

type ExpressionFactSink interface {
	SetExpressionFact(filePath string, line, col int, t *typeinfer.ResolvedType)
}

ExpressionFactSink accepts resolved expression facts for injection into an oracle. The production sink (PR C) adapts oracle.Oracle's expressions map; tests use a local fake. Kept as an interface here so this package does not import oracle (which would be circular).

type ExpressionPosition

type ExpressionPosition struct {
	Line int
	Col  int
}

ExpressionPosition identifies a single oracle expression-type query by 1-based line and column. Mirrors the key shape used by oracle.LookupExpression so resolved facts drop into the existing Expressions map at the same key.

type ExpressionPositionSelector

type ExpressionPositionSelector func(file *scanner.File) []uint32

ExpressionPositionSelector returns FlatNode indices in file whose oracle expression type a rule wants resolved before dispatch. See Rule.ExprPositions for the contract and lifecycle.

type ExpressionTypeResolver

type ExpressionTypeResolver interface {
	Resolve(positions map[string][]ExpressionPosition) (map[string]map[ExpressionPosition]*typeinfer.ResolvedType, error)
}

ExpressionTypeResolver resolves a batched set of (file → positions) queries to (file → position → type). Implementations may dispatch to the krit-types daemon, a one-shot JVM call, or a test fake.

A resolver that returns nil for an entry signals "no fact at this position" (vs. a present-but-nullable type). Callers must tolerate missing entries — selectors over-approximate, so not every requested position will have a fact.

type FakeConfigSource

type FakeConfigSource struct {
	// Values stores the configured overrides, keyed as
	// Values[ruleSet][rule][key]. Nil is safe to read from but not
	// safe to write to; use Set to populate.
	Values map[string]map[string]map[string]interface{}

	// RuleActive stores explicit rule-level active overrides.
	// Presence of the key indicates an override; the bool is the
	// override value. Absence means "no override".
	RuleActive map[string]bool

	// RuleSetActive stores explicit ruleset-level active overrides.
	// Same presence semantics as RuleActive.
	RuleSetActive map[string]bool
}

FakeConfigSource is an in-memory ConfigSource for tests. It is also a useful starting point for rule authors experimenting with the rule metadata runtime — it has no YAML dependency.

The three-level Values map is keyed as Values[ruleSet][rule][key] so tests can store arbitrary scalar values (int, bool, string, []string). The GetXxx methods perform best-effort coercion: int/bool require the matching Go type; GetString accepts string values; GetStringList accepts either []string or []interface{} (the shape produced by a generic YAML decoder) so tests can set values with the same types they would get from the real config loader.

func NewFakeConfigSource

func NewFakeConfigSource() *FakeConfigSource

NewFakeConfigSource returns a FakeConfigSource with empty maps ready to be populated via Set.

func (*FakeConfigSource) GetBool

func (f *FakeConfigSource) GetBool(ruleSet, rule, key string, def bool) bool

GetBool implements ConfigSource.

func (*FakeConfigSource) GetInt

func (f *FakeConfigSource) GetInt(ruleSet, rule, key string, def int) int

GetInt implements ConfigSource.

func (*FakeConfigSource) GetString

func (f *FakeConfigSource) GetString(ruleSet, rule, key, def string) string

GetString implements ConfigSource.

func (*FakeConfigSource) GetStringList

func (f *FakeConfigSource) GetStringList(ruleSet, rule, key string) []string

GetStringList implements ConfigSource. Accepts either []string or []interface{} (the shape a generic YAML decoder produces).

func (*FakeConfigSource) HasKey

func (f *FakeConfigSource) HasKey(ruleSet, rule, key string) bool

HasKey implements ConfigSource.

func (*FakeConfigSource) IsRuleActive

func (f *FakeConfigSource) IsRuleActive(ruleSet, rule string) *bool

IsRuleActive implements ConfigSource.

func (*FakeConfigSource) IsRuleSetActive

func (f *FakeConfigSource) IsRuleSetActive(ruleSet string) *bool

IsRuleSetActive implements ConfigSource.

func (*FakeConfigSource) Set

func (f *FakeConfigSource) Set(ruleSet, rule, key string, value interface{})

Set records an override value for (ruleSet, rule, key). Pass the Go type matching the option type — int for OptInt, bool for OptBool, string for OptString and OptRegex, []string for OptStringList.

func (*FakeConfigSource) SetRuleActive

func (f *FakeConfigSource) SetRuleActive(ruleSet, rule string, active bool)

SetRuleActive records an explicit rule-level active override.

func (*FakeConfigSource) SetRuleSetActive

func (f *FakeConfigSource) SetRuleSetActive(ruleSet string, active bool)

SetRuleSetActive records an explicit ruleset-level active override.

type FakeOption

type FakeOption func(*Rule)

FakeOption configures a FakeRule.

func WithCheck

func WithCheck(fn func(*Context)) FakeOption

WithCheck sets the check function.

func WithConfidence

func WithConfidence(c float64) FakeOption

WithConfidence sets the base confidence.

func WithFix

func WithFix(level FixLevel) FakeOption

WithFix sets the fix level.

func WithLexicalCalleeNames

func WithLexicalCalleeNames(names ...string) FakeOption

WithLexicalCalleeNames narrows call_expression dispatch by syntactic callee name.

func WithLibraryFacts

func WithLibraryFacts() FakeOption

WithLibraryFacts marks the fake rule as reading project library facts.

func WithMaturity

func WithMaturity(m Maturity) FakeOption

WithMaturity sets the rule's lifecycle stage.

func WithNeeds

func WithNeeds(c Capabilities) FakeOption

WithNeeds sets the capabilities bitfield.

func WithNodeTypes

func WithNodeTypes(types ...string) FakeOption

WithNodeTypes sets the node types the rule dispatches on.

func WithOracle

func WithOracle(f *OracleFilter) FakeOption

WithOracle sets the oracle filter.

func WithOracleCallTargets

func WithOracleCallTargets(f *OracleCallTargetFilter) FakeOption

WithOracleCallTargets sets the oracle call-target filter.

func WithSeverity

func WithSeverity(s Severity) FakeOption

WithSeverity sets the severity.

type FixLevel

type FixLevel int

FixLevel indicates how safe an auto-fix is.

const (
	FixNone      FixLevel = 0
	FixCosmetic  FixLevel = 1
	FixIdiomatic FixLevel = 2
	FixSemantic  FixLevel = 3
)

func (FixLevel) String

func (l FixLevel) String() string

type IntOptionSpec

type IntOptionSpec[R any] struct {
	Name        string
	Aliases     []string
	Default     int
	Description string
	Apply       func(*R, int)
}

IntOptionSpec describes an int-valued option on rule type R.

type JavaFactProfile

type JavaFactProfile struct {
	ReceiverTypesForCallees []string
	ReturnTypesForCallees   []string
	ClassSupertypes         []string
	Annotations             []string
	DeclarationNames        []string
}

JavaFactProfile declares optional javac-backed facts a Java-aware rule can consume. The pipeline may use this profile to run the Java helper for a narrow set of sites; rules must still keep conservative source-only fallbacks when facts are unavailable.

type LanguageSupport

type LanguageSupport struct {
	Status   LanguageSupportStatus `json:"status" yaml:"status"`
	Reason   string                `json:"reason,omitempty" yaml:"reason,omitempty"`
	Issue    int                   `json:"issue,omitempty" yaml:"issue,omitempty"`
	Evidence []string              `json:"evidence,omitempty" yaml:"evidence,omitempty"`
	Fixtures []string              `json:"fixtures,omitempty" yaml:"fixtures,omitempty"`
}

LanguageSupport captures the source-of-truth support classification and the evidence used to justify it.

type LanguageSupportStatus

type LanguageSupportStatus string

LanguageSupportStatus is the stable support classification for a rule or ruleset in a source language. These values are intended to be serialized in docs and tooling, so prefer adding values over renaming existing ones.

const (
	// LanguageSupportSupported means the language path is implemented and
	// covered by evidence or fixtures.
	LanguageSupportSupported LanguageSupportStatus = "supported"
	// LanguageSupportPartial means important coverage exists, but known gaps
	// remain before the rule or ruleset can be counted as complete support.
	LanguageSupportPartial LanguageSupportStatus = "partial"
	// LanguageSupportPending means the language path has not yet been reviewed
	// or implemented.
	LanguageSupportPending LanguageSupportStatus = "pending"
	// LanguageSupportNotApplicable means the rule intentionally does not apply
	// to the language.
	LanguageSupportNotApplicable LanguageSupportStatus = "not-applicable"
	// LanguageSupportNeedsDesign means applicability is plausible, but the
	// implementation approach needs design before work can be estimated.
	LanguageSupportNeedsDesign LanguageSupportStatus = "needs-design"
)

func (LanguageSupportStatus) Valid

func (s LanguageSupportStatus) Valid() bool

Valid reports whether s is one of the known support statuses.

type Maturity

type Maturity uint8

Maturity describes a rule's lifecycle stage. The default zero value is MaturityStable so existing rule registrations remain unchanged.

Experimental rules ship dark: they are filtered out of the default-active set and only run when the user opts in via --experimental, the `experimental: true` top-level config key, --all-rules, or by naming the rule explicitly with --enable-rules.

Deprecated rules are also default-inactive and never re-enabled by the experimental flag — the only way to run them is to name them explicitly with --enable-rules. This gives rule authors a one-release deprecation window without surprising default users.

const (
	// MaturityStable is the zero value: the rule is part of the supported
	// built-in rule set and runs by default unless DefaultActive=false.
	MaturityStable Maturity = iota
	// MaturityExperimental marks a rule that has not yet soaked under
	// real-world usage. It is default-inactive and only runs when the
	// user explicitly enables experimental rules.
	MaturityExperimental
	// MaturityDeprecated marks a rule that is on its way out. It is
	// default-inactive and is NOT re-enabled by --experimental; users who
	// still want it must pass --enable-rules <ID> or set it in config.
	MaturityDeprecated
)

func (Maturity) String

func (m Maturity) String() string

String returns a human-readable label for the Maturity value.

type MetaProvider

type MetaProvider interface {
	Meta() RuleDescriptor
}

MetaProvider is the interface rules satisfy when they publish metadata for config, defaults, schema, and registry validation.

type OptionType

type OptionType int

OptionType declares the kind of value a ConfigOption carries.

const (
	// OptInt is a scalar int read via ConfigSource.GetInt.
	OptInt OptionType = iota
	// OptBool is a scalar bool read via ConfigSource.GetBool.
	OptBool
	// OptString is a scalar string read via ConfigSource.GetString.
	OptString
	// OptStringList is a []string read via ConfigSource.GetStringList.
	OptStringList
	// OptRegex is a pattern string that is anchored and compiled to
	// *regexp.Regexp before being passed to Apply.
	OptRegex
)

func (OptionType) String

func (t OptionType) String() string

String returns a human-readable name for the option type. Used by the schema generator and by test diagnostics.

type OracleCallTargetFilter

type OracleCallTargetFilter struct {
	AllCalls bool
	// DiscardedOnly means the rule only needs call targets for calls whose
	// result is used as a standalone statement.
	DiscardedOnly bool
	// TargetFQNs are fully-qualified callable targets the rule may query.
	// The Go bridge derives their simple names for the JVM-side lexical
	// callee filter.
	TargetFQNs []string
	// CalleeNames are lexical callee names the rule may query directly.
	CalleeNames []string
	// LexicalHintsByCallee optionally adds cheap file-level evidence that
	// must be present before the JVM resolves broad call names. Absence keeps
	// name-only behavior for that callee.
	LexicalHintsByCallee map[string][]string
	// LexicalSkipByCallee optionally declares cheap receiver evidence where
	// the Go rule can classify the call structurally and does not need a JVM
	// call target for that site.
	LexicalSkipByCallee map[string][]string
	// AnnotatedIdentifiers asks the bridge to derive CalleeNames from
	// source declarations annotated with these annotation identifiers.
	// This is for rules that call LookupCallTarget only so they can call
	// LookupAnnotations on annotated symbols; it avoids resolving every
	// call expression when the annotated declaration names are knowable
	// from raw source.
	AnnotatedIdentifiers []string
}

OracleCallTargetFilter declares which call targets a rule may ask the JVM oracle to resolve via LookupCallTarget. Nil means the rule does not contribute call-target interest. AllCalls is conservative and disables callee-name filtering when the rule is enabled.

type OracleDeclarationProfile

type OracleDeclarationProfile struct {
	ClassShell              bool
	Supertypes              bool
	ClassAnnotations        bool
	Members                 bool
	MemberSignatures        bool
	MemberAnnotations       bool
	SourceDependencyClosure bool
}

OracleDeclarationProfile narrows which KAA symbol fields the JVM extracts for this rule. The pipeline takes the union across all active rules before passing --declaration-profile to krit-types; a nil value means "no opinion" (the rule does not constrain extraction). If every active oracle rule supplies a non-nil profile, the JVM skips fields outside the union and may run significantly faster.

Rules that only use LookupCallTarget or LookupExpression (expression-level APIs) and never walk the declarations map should set an empty struct OracleDeclarationProfile{} — this signals the rule contributes no declaration interest and allows the union to stay narrow.

Fields mirror oracle.DeclarationProfile (no import needed here; the bridge in oracle_filter_bridge.go converts).

type OracleFilter

type OracleFilter struct {
	Identifiers []string
	AllFiles    bool
}

OracleFilter declares when a rule needs oracle type information.

func (*OracleFilter) NeverNeedsOracle

func (f *OracleFilter) NeverNeedsOracle() bool

NeverNeedsOracle returns true when the filter declares the rule is purely tree-sitter and will never consult the oracle.

type RegexOptionSpec

type RegexOptionSpec[R any] struct {
	Name        string
	Aliases     []string
	Default     string
	Description string
	Apply       func(*R, *regexp.Regexp)
}

RegexOptionSpec describes a regex-valued option on rule type R.

Default is the documentation default published in the schema; the runtime only invokes Apply when a config override is present and successfully compiles. Default is intentionally a raw pattern string to match the schema's "default" emission shape.

type Rule

type Rule struct {
	// Identity
	ID          string
	Category    string
	Description string
	Sev         Severity

	// Aliases are legacy or alternate IDs for this rule. They do NOT
	// appear in the registry as separate rules; they only affect
	// suppression: @Suppress("<alias>") (and inline `// krit:ignore[<alias>]`)
	// silences findings emitted under the canonical ID. Use when
	// renaming a rule so existing user suppressions keep working.
	Aliases []string

	// EnabledByDefaultSince records the krit version in which this rule
	// became default-active (DefaultActive transitioned from false to
	// true). Empty string means the rule has been default-active since
	// inception, or the version was not recorded. Used by docs and
	// release-note generation; the runtime does not key behavior on it.
	EnabledByDefaultSince string

	// Deprecated, when non-nil, marks the rule as scheduled for removal.
	// Consumers (docs, output formatters, CI gates) read this to surface
	// migration guidance. The dispatcher does NOT skip deprecated rules
	// — they continue to fire so existing baselines stay valid until the
	// user migrates.
	Deprecated *Deprecation

	// Dispatch routing.
	//
	// Scope, when set, declares the rule's primary dispatcher bucket
	// directly. When ScopeUnset (the zero value), the dispatcher
	// derives the scope from the bits Needs carries plus the shape of
	// NodeTypes. Aspects (NeedsResolver, NeedsOracle, NeedsConcurrent)
	// remain on Needs and layer on top of any scope.
	Scope     Scope
	NodeTypes []string     // nil + no NeedsLinePass means every AST node; nil + NeedsLinePass means line rule
	Needs     Capabilities // zero → no extra deps

	// LexicalCalleeNames narrows call_expression dispatch to calls whose
	// syntactic callee name matches one of these names. It is intentionally
	// lexical and AST-only; semantic call-target filtering for the Kotlin
	// oracle remains in OracleCallTargets below.
	LexicalCalleeNames []string

	// Languages declares which source languages this rule applies to.
	// When nil the effective default is computed from Needs:
	//   NeedsManifest   → [LangXML]
	//   NeedsResources  → [LangXML]
	//   NeedsGradle     → [LangGradle]
	//   otherwise       → [LangKotlin]
	// The dispatcher uses RuleLanguages() to skip rules whose language
	// list does not include the current file's Language.
	Languages []scanner.Language

	// Maturity is the rule's lifecycle stage. The zero value is
	// MaturityStable. Experimental and deprecated rules are
	// default-inactive; see the Maturity type docs for the full contract.
	Maturity Maturity

	// RunAfter declares rule IDs that must run before this rule within
	// the dispatcher. The dispatcher topologically sorts active rules by
	// these constraints once at construction; rules that name a non-active
	// dependency are unconstrained (the missing dependency is silently
	// ignored). Cycles among active rules are a programmer error and
	// cause NewDispatcher to panic with the offending rule IDs.
	//
	// Use cases:
	//   - A fixable rule whose autofix output another rule reads.
	//   - Two rules emitting findings on the same node where downstream
	//     tooling expects a stable ordering.
	//
	// Most rules do not need RunAfter and should leave it nil.
	RunAfter []string

	// Fix metadata
	Fix FixLevel // FixNone → not fixable

	// Confidence tier (0 = use family default)
	Confidence float64

	// Oracle filtering (nil = conservative AllFiles default)
	Oracle *OracleFilter

	// OracleCallTargets optionally narrows JVM call-target resolution to
	// lexical callees this rule can consume. Broad consumers set
	// AllCalls=true, which disables the optimization for the active rule set.
	OracleCallTargets *OracleCallTargetFilter

	// OracleDeclarationNeeds, when non-nil, declares which declaration
	// fields this rule reads from the JVM oracle. The pipeline unions these
	// across all active rules to compute the --declaration-profile flag
	// passed to krit-types. A nil value means the rule has not opted in to
	// narrowing (conservative: treated as needing all fields for the union).
	// Rules that only use expression-level APIs (LookupCallTarget,
	// LookupExpression) and never touch the declarations map should set
	// &OracleDeclarationProfile{} (empty, no fields needed).
	OracleDeclarationNeeds *OracleDeclarationProfile

	// TypeInfo carries per-rule routing hints for type-information
	// lookups. Zero value (PreferAny, Required=false) is backwards
	// compatible — the dispatcher wires the composite resolver just
	// as it did before the hint existed. See TypeInfoHint.
	TypeInfo TypeInfoHint

	// JavaFacts optionally requests javac-backed facts for Java files.
	// This does not make javac mandatory; unavailable facts are a warning
	// and rules must fall back to source AST/index evidence.
	JavaFacts *JavaFactProfile

	// NeedsLibraryFacts declares that the rule reads Context.LibraryFacts
	// and should receive project-derived library facts instead of the
	// conservative defaults when Gradle metadata is available.
	NeedsLibraryFacts bool

	// Check is the rule's analysis function. It receives a Context
	// populated according to the rule's Needs bitfield. Rules report
	// findings by calling ctx.Emit or ctx.EmitAt.
	Check func(*Context)

	// AndroidDeps carries the AndroidDataDependency bitfield (stored as
	// uint32 to avoid an import cycle with the rules package) for Android
	// project-data rules. Zero means "not an Android data rule".
	AndroidDeps uint32

	// Implementation stores the concrete rule instance captured by this v2
	// registration. Config descriptors apply option overrides to this value,
	// and tests may type-assert it to inspect configured fields.
	//
	// Typed as any because the registry is heterogeneous: every rule has a
	// different concrete struct type, but they all share the same Rule
	// metadata shape. Consumers that need a typed pointer assert through
	// the Implementation field (e.g. r.Implementation.(*MyRule)).
	Implementation any

	// Aggregate carries the collect/finalize/reset lifecycle hooks
	// for rules declared with NeedsAggregate. Non-aggregate rules
	// leave this nil.
	Aggregate *Aggregate

	// ExprPositions optionally returns FlatNode indices whose oracle
	// expression types this rule wants resolved before dispatch runs.
	// Used by the targeted-resolution pre-pass under --depth=thorough:
	// the pipeline walks every file once, accumulates the union of
	// requested positions across rules, sends them to krit-types in a
	// single batched RPC, then injects the resulting types into the
	// oracle's expression map. Rules then query ctx.Resolver normally
	// during dispatch and find the precomputed facts via
	// CompositeResolver.ResolveByNameFlat.
	//
	// Selectors must be cheap (AST-only, no resolver calls) — they are
	// invoked for every file regardless of whether any positions match.
	// Returning nil or an empty slice skips the file. A nil ExprPositions
	// means the rule does not participate in targeted resolution.
	ExprPositions ExpressionPositionSelector

	// DefaultActive reports whether the rule runs by default. Rules with
	// DefaultActive == false are opt-in (must be enabled via config or
	// --all-rules).
	DefaultActive bool

	// Options are the configurable fields the rule exposes via YAML.
	Options []ConfigOption

	// CustomApply is an optional escape hatch for rules whose config cannot
	// be expressed as a list of Options. See RuleDescriptor.CustomApply for
	// the contract.
	CustomApply func(target interface{}, cfg ConfigSource)

	// LanguageSupport records per-source-language support status for a rule.
	// See RuleDescriptor.LanguageSupport.
	LanguageSupport map[string]LanguageSupport
}

Rule is the unified rule descriptor. Every analysis rule in krit is represented as a single Rule value. Dependencies and capabilities are declared via the Needs bitfield rather than through interface implementations.

func FakeRule

func FakeRule(id string, opts ...FakeOption) *Rule

FakeRule creates a minimal rule for testing. The check function receives the context and can emit findings via ctx.Emit().

func (*Rule) Name

func (r *Rule) Name() string

Name returns the rule ID.

type RuleDescriptor

type RuleDescriptor struct {
	// ID is the stable rule identifier (matches the rule's Name()).
	ID string

	// Aliases are legacy or alternate IDs honored by suppression. See
	// Rule.Aliases for the full contract; the descriptor field exists so
	// rules implementing MetaProvider can publish aliases the same way
	// they publish other metadata.
	Aliases []string

	// EnabledByDefaultSince mirrors Rule.EnabledByDefaultSince — the
	// krit version in which this rule became default-active. Empty
	// string means inception or unrecorded.
	EnabledByDefaultSince string

	// Deprecated mirrors Rule.Deprecated. Non-nil means the rule is
	// scheduled for removal; consumers should surface migration
	// guidance via ReplacedBy / Reason.
	Deprecated *Deprecation

	// RuleSet is the configuration group this rule belongs to
	// (e.g. "complexity", "naming", "performance").
	RuleSet string

	// Severity is "error", "warning", or "info".
	Severity string

	// Description is the human-readable rule summary.
	Description string

	// DefaultActive reports whether the rule runs by default. Rules with
	// DefaultActive == false are opt-in (must be enabled via config or
	// --all-rules).
	DefaultActive bool

	// FixLevel is "", "cosmetic", "idiomatic", or "semantic".
	// Empty string means the rule does not provide an auto-fix.
	FixLevel string

	// Confidence is the base confidence tier (0 = use family default).
	Confidence float64

	// LanguageSupport records per-source-language support status for a rule.
	// It is product/support metadata rather than dispatcher routing: Languages
	// on Rule controls where a rule runs, while LanguageSupport explains whether
	// that behavior counts as full, partial, pending, or inapplicable support.
	LanguageSupport map[string]LanguageSupport

	// Options are the configurable fields the rule exposes via YAML.
	Options []ConfigOption

	// CustomApply is an optional escape hatch for rules whose config cannot
	// be expressed as a list of Options. It runs AFTER the Options loop, so
	// it can override option-applied fields if needed. Rules that can be
	// fully expressed via Options leave this nil.
	//
	// A common use case is a rule that needs to read the whole config tree
	// (e.g. LayerDependencyViolationRule.LayerConfig) rather than a single
	// scalar key. Such hooks typically assert on a concrete ConfigSource
	// implementation (e.g. the real ConfigAdapter) and no-op on the fake
	// sources used by unit tests.
	CustomApply func(target interface{}, cfg ConfigSource)
}

RuleDescriptor is the metadata a rule publishes via its Meta method.

A descriptor is metadata rather than rule state, and carries no runtime behavior other than the per-option Apply closures. Treat map and slice fields as immutable after construction so descriptors remain safe to copy and share.

type Scope

type Scope int8

Scope is the typed enumeration of the dispatcher buckets a rule can land in. Exactly one Scope applies to each rule; the dispatcher's classifier picks one based on Rule.Scope, falling back to a derivation over Capabilities and NodeTypes when Scope is ScopeUnset.

Scope is intentionally separate from Capabilities: aspects like NeedsResolver, NeedsOracle, and NeedsConcurrent layer on top of any scope and remain on the Capabilities bitfield.

const (
	// ScopeUnset means the rule has not declared a Scope and the
	// dispatcher should derive one from Capabilities / NodeTypes. This
	// is the zero value, so existing rule registrations remain valid
	// without changes.
	ScopeUnset Scope = iota
	// ScopePerFileNode runs during the per-file AST walk, dispatched by
	// FlatNode.Type via NodeTypes. The hot path of the analyzer.
	ScopePerFileNode
	// ScopePerFileAllNodes runs during the per-file AST walk and
	// receives every node (NodeTypes is nil and no other scope flag is
	// set).
	ScopePerFileAllNodes
	// ScopeLinePass runs once per file over file.Lines after the AST
	// walk completes.
	ScopeLinePass
	// ScopeAggregate runs per file with a Collect/Finalize/Reset
	// lifecycle. Collect is called for each matching node; Finalize
	// produces findings after the walk; Reset clears state between
	// files.
	ScopeAggregate
	// ScopeCrossFile runs at project scope after the cross-file index
	// is built; the rule receives ctx.CodeIndex.
	ScopeCrossFile
	// ScopeModuleIndex runs at project scope after the per-module
	// dependency graph is built; the rule receives ctx.ModuleIndex.
	ScopeModuleIndex
	// ScopeParsedFiles runs at project scope and receives the full
	// ctx.ParsedFiles slice without an index pass.
	ScopeParsedFiles
	// ScopeManifest runs once per parsed AndroidManifest.xml; the rule
	// receives ctx.Manifest.
	ScopeManifest
	// ScopeResource runs once per merged Android res/ ResourceIndex;
	// XML-targeted.
	ScopeResource
	// ScopeResourceSource is a Kotlin/Java source rule that consults
	// the merged Android ResourceIndex. It dispatches per-file via
	// NodeTypes but is deferred from the per-file phase because the
	// resource index is assembled later in the Android phase.
	ScopeResourceSource
	// ScopeIcons runs once per parsed Android IconIndex.
	ScopeIcons
	// ScopeGradle runs once per parsed Gradle build script; the rule
	// receives ctx.GradleConfig.
	ScopeGradle
)

func (Scope) String

func (s Scope) String() string

String returns a human-readable label for the Scope, used in diagnostics and verbose logging.

type Severity

type Severity string

Severity levels.

const (
	SeverityError   Severity = "error"
	SeverityWarning Severity = "warning"
	SeverityInfo    Severity = "info"
)

type StringListOptionSpec

type StringListOptionSpec[R any] struct {
	Name        string
	Aliases     []string
	Default     []string
	Description string
	Apply       func(*R, []string)
}

StringListOptionSpec describes a []string option on rule type R.

A nil Default is preserved as nil in the descriptor (the schema generator suppresses empty defaults). Use a non-nil zero-length slice if you need the schema to publish an explicit empty default.

type StringOptionSpec

type StringOptionSpec[R any] struct {
	Name        string
	Aliases     []string
	Default     string
	Description string
	Apply       func(*R, string)
}

StringOptionSpec describes a string-valued option on rule type R.

type TypeInfoBackend

type TypeInfoBackend uint8

TypeInfoBackend is a per-rule hint telling the dispatcher which type-information backend to prefer when both the in-process resolver and the JVM oracle are wired. The zero value is PreferAny — rules that do not care get the composite resolver (oracle-first) just as they did before the hint was introduced.

Decision matrix for rule authors:

PreferResolver — cheap source-level lookups: imports, local
  declarations, type hierarchy/nullability. No resolved overload,
  external annotation, compiler diagnostic, or suspend-call truth.
PreferOracle   — requires dependency metadata the in-process
  resolver cannot see: full FQN resolution, call-target / overload
  resolution, annotation-argument lookups against library types.
PreferAny      — don't care / equally well served (default).
const (
	// PreferAny leaves routing to the dispatcher — the composite
	// resolver is wired and backends answer in oracle > source order.
	PreferAny TypeInfoBackend = iota
	// PreferResolver asks the dispatcher to wire the in-process
	// source-level resolver only. Cheaper, avoids oracle IPC.
	PreferResolver
	// PreferOracle asks the dispatcher to route through the oracle-
	// backed composite. Strictly equivalent to PreferAny when the
	// composite is wired today; declared explicitly so rule authors
	// can document intent and the linter can cross-check usage.
	PreferOracle
)

type TypeInfoHint

type TypeInfoHint struct {
	// PreferBackend names the backend the rule would rather use when
	// the dispatcher has both wired.
	PreferBackend TypeInfoBackend
	// Required controls what happens when PreferBackend is set but
	// the preferred backend is NOT available:
	//
	//   false (default): skip the rule silently — the rule author
	//     asserts that running against the non-preferred backend
	//     would produce wrong or misleading findings.
	//   true           : fall through to whatever backend IS wired;
	//     the preference was a perf hint, not a correctness lever.
	//
	// PreferAny ignores Required — any wired backend satisfies the
	// rule.
	Required bool
}

TypeInfoHint groups the per-rule type-information routing hints on Rule. The zero value (PreferAny, Required=false) is backwards compatible — existing rules see identical behaviour.

Directories

Path Synopsis
Package evidence is a thin facade over scanner.File + the wired type resolvers, exposing only the structured questions a rule should ask.
Package evidence is a thin facade over scanner.File + the wired type resolvers, exposing only the structured questions a rule should ask.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL