Search/Write Static-Analysis Rules with Auto-Fixes That Never Change How Code Runs

Write Static-Analysis Rules with Auto-Fixes That Never Change How Code Runs

Use when creating a static-analysis (lint) rule. It targets common patterns, reuses existing helpers, adds an auto-fix only when it is safe, tests tricky syntax, and regenerates docs instead of hand-editing them.

To use this AI rule
## Target Common Patterns and Leave Rare Edge Cases Out Keep each rule conceptually simple. Target recurring, high-value patterns and leave rare edge cases unsupported when covering them would obscure the rule or make fixes risky. Define a stable rule contract with a creation function and metadata. In an event-based adapter, register entry and exit listeners through the provided hooks rather than returning a conventional visitor map. Listeners should return or yield problem objects; let the adapter perform reporting. Each problem should identify the relevant node and message, then add data, a fix, or suggestions only when needed. If the fixer API provides an abort mechanism, use it whenever a safe edit cannot be proven. Put default option values in metadata so the framework can merge them consistently instead of duplicating merge logic in the rule. ## Decide Which Preset Gets the Rule and Start Option Names with `check` Treat preset placement as a product decision: - Put uncontroversial rules in the safest preset and every broader preset. - Put useful but opinionated defaults only in the recommended preset and broader collections. - Keep niche or opt-in behavior out of default presets. Do not confuse “unopinionated” with “too opinionated.” When uncertain, state the proposed level and ask the maintainer to decide. Name public boolean options positively with a `check*` form so `true` consistently enables behavior. Lists of ignored patterns may still use conventional names such as `ignore`. Declare every supported language explicitly. If logic depends only on filenames, raw text, comments, or directives, support multiple languages where practical and use root and comment helpers that abstract over language-specific node types. ## Name Helpers by What They Return and Reuse Existing Utilities Name private helpers after their observable contract. `is*`, `has*`, `should*`, `can*`, and `needs*` must return booleans, preferably explicit `false` rather than an accidental undefined value. Use `get*Problem` for one optional problem and `get*Problems` for multiple problems. Reserve `report*` for helpers that directly report. Do not combine reporting or yielding with a predicate result. Build the problem separately, then make the boolean decision at the call site. Before adding a helper, inspect the repository’s AST, general utility, fixer, and shared-rule modules, then check trusted ecosystem utilities. Reuse barrel exports and established helpers for static evaluation, variable lookup, side-effect detection, token tests, reference resolution, parentheses, semicolons, and fix construction. Promote a helper only when it is clearly reusable; keep small rule-specific logic local. ## Use Control-Flow Analysis Only to Check Whether Code Always Exits Use the framework’s control-flow analysis when a rule must determine whether a branch or function always exits through constructs such as return, throw, break, continue, exhaustive selection, or an infinite loop. Manual AST walking is usually less accurate around exception handling, labels, nesting, and unreachable segments. For branch analysis, register the repository’s branch-exit tracker and query it after the enclosing conditional exits. For function analysis, maintain a segment set per code path so nested functions cannot contaminate their parents. Snapshot reachability at the relevant AST node’s exit; do not wait until code-path end if the framework has already cleared current segments. Use simpler syntax checks when the question is only whether the last statement has a particular type, where returns occur, or whether a local fixer ends in return or throw. Do not introduce control-flow machinery without a real reachability requirement. ## Regenerate Generated Indexes and Doc Headers Instead of Hand-Editing Them Identify generated files and generated regions before editing. Never hand-edit generated indexes, tables, or documentation headers. Run the owning generator after adding, removing, or renaming source rules. During conflict resolution, preserve source-of-truth entries and regenerate deterministic outputs rather than manually reconstructing them. Keep authored documentation below any generated header boundary. Use the repository’s preferred configuration syntax in examples; do not switch to quoted JSON-like examples unless the example is explicitly JSON. When shared rule patterns also exist in a related repository or package, consider whether the same change should be ported. Treat that as an explicit compatibility decision, not an automatic cross-project edit. ## Run Only the Rule's Tests While Coding and the Full Suite at the End Write focused tests before or alongside implementation. Prefer snapshot-style tests when the harness can capture diagnostics, fixes, and suggestions together, and use specialized parser modes only for cases that need them. During development, run only the targeted rule test. Update snapshots intentionally and remove temporary focused-test markers before completion. Do not run slow integration tests as part of the normal edit loop. Run dogfooding after implementing a new rule and again after each logic correction, because fixes often reveal violations elsewhere. At the end, run the repository’s actual linter, targeted tests, dogfooding, and then the full test suite once. Do not substitute a different lint tool merely because it reports clean results. ## Test Fixes Against Comments and Tricky Syntax Test behavior, non-matches, fixes, and suggestions without duplicating coverage. Include relevant cases from these families: - typed syntax, type assertions, generics, and optional chaining; - JSX, comments, extra parentheses, nested and chained expressions; - computed properties, templates, spread, and destructuring; - used versus unused return values; - syntax-only parsing versus full type information when the rule relies on types. Test the cases a rule intentionally ignores so the boundary is documented. Verify fixes under comments and automatic-semicolon-insertion hazards. If a case needs a different parser, configure it at the smallest test scope that expresses the requirement. ## Offer an Auto-Fix Only When It Can't Change How Code Runs, and Keep Comments Provide an automatic fix only when it cannot change runtime behavior. When behavior could change, prefer a suggestion. Treat comment preservation as mandatory: if a replacement or removal contains comments, abort the fix or use a range-aware edit that keeps them in place. Before emitting a fix, check whether a replacement needs parentheses, whether a leading bracket or parenthesis requires a semicolon, and whether adjacent tokens need spacing. Use generator fixes for multi-edit operations. Preserve optional-chain behavior and avoid unsafe edits when a value is consumed differently from a standalone expression. Do not make fixes disproportionately complex merely to preserve shadowed built-ins, accessor or proxy side effects, or sparse-array holes unless the repository already has a simple local safeguard. Report the issue without a fix when safety cannot be established. ## Name the Rule, Create It from the Scaffold, and Take It from Tests to Commit Choose names that describe the construct and intent: use prefixes such as `no-`, `prefer-`, `require-`, or `consistent-` when they clarify behavior. Name the target, not the proposed replacement, and avoid vague names. Use the repository scaffold to create the implementation, test, documentation, and generated index entries. Then follow this sequence: 1. Add failing or representative tests. 2. Implement the smallest rule logic that satisfies them. 3. Document behavior and intentional exclusions. 4. Run targeted tests and the authoritative lint command. 5. Run dogfooding, then the full suite at the end. 6. Confirm generated files are current and scratch files are gone. Use the project’s commit-message convention, including required punctuation around rule and option names. Delivery is complete only when source, tests, documentation, generated surfaces, and validation results agree.