Skip to content

Reduce per-file SourceCodeVisitor population overhead by caching selector-to-ruleId mappings across files #118

Description

@alanzabihi

Hypothesis

In runRules() (linter.js:557-682), for each file, the code iterates all 280 configured rules, calls ruleMapper to get the rule module, calls rule.create() to get listeners, then iterates the listener keys to call visitor.add(selector, handler) for each. This per-file loop processes ~280 rules × ~3 selectors each = ~840 visitor.add() calls per file. Across 388 files (METRIC_B): 326K rule.create() calls and 326K+ visitor.add() calls.

While individual operations are fast, the aggregate matters. The key insight: for files sharing the same flat config (which is most files in METRIC_B), the set of selectors registered is identical. Only the handler functions differ (because they capture per-file ruleContext). If we could pre-compute the selector set once and only register new handlers, we'd skip the per-file overhead of selector key iteration and string processing.

Proposed Change

Cache the result of rule iteration (rule module references, severity, options) on first file. On subsequent files with the same configuredRules identity (same object reference from flat config), skip ruleMapper calls, severity parsing, and options processing. Only call rule.create() with the per-file ruleContext and register the returned listeners.

Alternatively, since Object.keys(ruleListeners) always returns the same selectors for a given rule, cache the selector names per rule. This avoids the Object.keys allocation and iteration on subsequent files:

```js
// On first file: cache selectors per ruleId
const cachedRuleSelectors = new Map(); // ruleId -> string[]
// On subsequent files: iterate cached selectors directly
for (const selector of cachedRuleSelectors.get(ruleId)) {
visitor.add(selector, addRuleErrorHandler(ruleListeners[selector]));
}
```

Expected Impact

  • Eliminates 387 × 280 = 108K ruleMapper lookups (for cached rule refs)
  • Eliminates 387 × 280 = 108K Config.getRuleNumericSeverity calls
  • Eliminates 387 × ~840 = 326K Object.keys(ruleListeners) allocations
  • Conservative estimate: 3-8ms composite from reduced per-file setup
  • Risk: must handle files with different configuredRules (different flat config blocks)

Key Files

  • `lib/linter/linter.js` — runRules() lines 557-682 (rule iteration, listener registration)
  • `lib/linter/source-code-visitor.js` — SourceCodeVisitor.add()

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions