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 ¶
- Variables
- func ApplyConfig(rule interface{}, d RuleDescriptor, cfg ConfigSource) (active bool)
- func ApplyConfigActiveOnly(d RuleDescriptor, cfg ConfigSource) (active bool)
- func ApplyResolvedExpressions(sink ExpressionFactSink, ...)
- func CollectExpressionPositions(rules []*Rule, files []*scanner.File) map[string][]ExpressionPosition
- func CompileAnchoredPattern(ruleName, field, pattern string) *regexp.Regexp
- func ContextFindings(ctx *Context) []scanner.Finding
- func DefaultInactiveSet(descs []RuleDescriptor) map[string]bool
- func NeedsJavaFacts(rules []*Rule) bool
- func Register(r *Rule)
- func RuleAppliesToLanguage(r *Rule, lang scanner.Language) bool
- func RuleLanguages(r *Rule) []scanner.Language
- func SetReporter(r *diag.Reporter)
- func TypedCustomApply[R any](apply func(*R, ConfigSource)) func(interface{}, ConfigSource)
- func ValidateAnchoredPattern(pattern string) error
- type Aggregate
- type BoolOptionSpec
- type Capabilities
- type ConfigOption
- func BoolOption[R any](s BoolOptionSpec[R]) ConfigOption
- func IntOption[R any](s IntOptionSpec[R]) ConfigOption
- func RegexOption[R any](s RegexOptionSpec[R]) ConfigOption
- func StringListOption[R any](s StringListOptionSpec[R]) ConfigOption
- func StringOption[R any](s StringOptionSpec[R]) ConfigOption
- type ConfigSource
- type Context
- type Deprecation
- type ExpressionFactSink
- type ExpressionPosition
- type ExpressionPositionSelector
- type ExpressionTypeResolver
- type FakeConfigSource
- func (f *FakeConfigSource) GetBool(ruleSet, rule, key string, def bool) bool
- func (f *FakeConfigSource) GetInt(ruleSet, rule, key string, def int) int
- func (f *FakeConfigSource) GetString(ruleSet, rule, key, def string) string
- func (f *FakeConfigSource) GetStringList(ruleSet, rule, key string) []string
- func (f *FakeConfigSource) HasKey(ruleSet, rule, key string) bool
- func (f *FakeConfigSource) IsRuleActive(ruleSet, rule string) *bool
- func (f *FakeConfigSource) IsRuleSetActive(ruleSet string) *bool
- func (f *FakeConfigSource) Set(ruleSet, rule, key string, value interface{})
- func (f *FakeConfigSource) SetRuleActive(ruleSet, rule string, active bool)
- func (f *FakeConfigSource) SetRuleSetActive(ruleSet string, active bool)
- type FakeOption
- func WithCheck(fn func(*Context)) FakeOption
- func WithConfidence(c float64) FakeOption
- func WithFix(level FixLevel) FakeOption
- func WithLexicalCalleeNames(names ...string) FakeOption
- func WithLibraryFacts() FakeOption
- func WithMaturity(m Maturity) FakeOption
- func WithNeeds(c Capabilities) FakeOption
- func WithNodeTypes(types ...string) FakeOption
- func WithOracle(f *OracleFilter) FakeOption
- func WithOracleCallTargets(f *OracleCallTargetFilter) FakeOption
- func WithSeverity(s Severity) FakeOption
- type FixLevel
- type IntOptionSpec
- type JavaFactProfile
- type LanguageSupport
- type LanguageSupportStatus
- type Maturity
- type MetaProvider
- type OptionType
- type OracleCallTargetFilter
- type OracleDeclarationProfile
- type OracleFilter
- type RegexOptionSpec
- type Rule
- type RuleDescriptor
- type Scope
- type Severity
- type StringListOptionSpec
- type StringOptionSpec
- type TypeInfoBackend
- type TypeInfoHint
Constants ¶
This section is empty.
Variables ¶
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 ¶
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 ¶
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 RuleAppliesToLanguage ¶
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 ¶
RuleLanguages returns the languages a rule applies to, falling back to the sensible default derived from Needs when Languages is nil.
func SetReporter ¶
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 ¶
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 )
const NeedsOracle Capabilities = NeedsOracleCallTargets | NeedsOracleSuspendMarkers | NeedsOracleExprType | NeedsOracleExprAnnotations | NeedsOracleSupertypes | NeedsOracleMembers | NeedsOracleMemberSignatures | NeedsOracleClassAnnotations | NeedsOracleMemberAnnotations | NeedsOracleDiagnostics | 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 ¶
FakeContext creates a minimal context for testing with the given file. A FindingCollector is pre-allocated; use ContextFindings(ctx) to read results.
func FakeContextWithNode ¶
FakeContextWithNode creates a context for testing with a specific node index.
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 ¶
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 ¶
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 WithConfidence ¶
func WithConfidence(c float64) FakeOption
WithConfidence sets the base confidence.
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 WithOracleCallTargets ¶
func WithOracleCallTargets(f *OracleCallTargetFilter) FakeOption
WithOracleCallTargets sets the oracle call-target filter.
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 )
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 ¶
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().
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 )
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.
Source Files
¶
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. |