# Architecture

How [`@hyperfrontend/linter`](https://www.hyperfrontend.dev/docs/libraries/linter.md) is put together: one configuration per run, one parse per file, every rule's listeners run in a single traversal, and services computed once and shared.

## System Overview

```mermaid
---
config:
  theme: base
  themeVariables:
    fontSize: 12px
---
flowchart LR
    CLI["bin/hf-lint.ts<br/>cli/run.ts<br/><i>arguments, exit code</i>"] --> Load["config/load.ts<br/><i>nearest lint.config.ts</i>"]
    Load --> Resolve["config/resolve.ts<br/><i>blocks to effective rules per file</i>"]
    CLI --> Files["files/enumerate.ts<br/><i>paths and globs to files</i>"]
    Files --> Lint["engine/lint-file.ts<br/><i>parse, traverse, directives, fix loop</i>"]
    Resolve --> Lint
    Registry["engine/registry.ts<br/><i>packaged rules and families, loaded on first use</i>"] --> Lint
    Lint --> Languages["languages/<br/><i>code, json, markdown, text</i>"]
    Languages --> Parsers["code/ markdown/<br/><i>own parsers</i>"]
    Lint --> Rules["rules/&lt;family&gt;/&lt;rule&gt;.ts"]
    Rules --> Services["scope/ flow/ jsdoc/ regex/<br/><i>shared, computed once</i>"]
    Lint --> Output["output/<br/><i>stylish, json</i>"]
```

The command, or [`lint`](https://www.hyperfrontend.dev/docs/libraries/linter/#api-lint) called from code, finds the configuration, registers the families its blocks name, enumerates the files, and lints them one at a time. For each file the engine picks the language by extension, parses once, loads the rules in effect for that file, and runs all of their listeners in one walk of the tree. It then drops what the directives suppress, applies fixes and lints again until no fix applies (at most ten passes), and prints. Nothing is kept between runs.

## Design Principles

1. **Behaviour is the specification.** A reimplemented rule keeps the upstream rule's messages, positions and fixes, and implements the options it documents, not the upstream rule's whole option surface.

   ```ts
   // ✅ the documented options, nothing else
   const { ignoredDependencies = [], ignoredFiles = [] } = context.options ?? {}
   // ❌ the upstream rule's whole option surface
   const options = { ...UPSTREAM_DEFAULTS, ...context.options }
   ```

2. **One parse, one traversal.** Rules return listeners keyed by node kind; the engine groups them and walks the tree once, so adding a rule adds listeners, not passes.

   ```ts
   // ✅
   create: (context) => ({
     TSEnumDeclaration(node) {
       context.report({ start: node.start, end: node.end, message })
     },
   })
   // ❌ a rule walking the tree itself
   create: (context) => ({
     Program(node) {
       walk(node, visitEverything)
     },
   })
   ```

3. **Services are lazy and shared.** Scope analysis and code paths are built on first request and memoised per tree; a rule asks, it never rebuilds.

   ```ts
   // ✅
   const analysis = scopeAnalysisOf(context)
   // ❌
   const analysis = analyseScopes(context.tree)
   ```

4. **Offsets in, positions out.** Rules report offsets; the engine turns them into lines and columns, applies directives by position and orders the messages, so no rule computes a line number.

   ```ts
   // ✅
   context.report({ start: node.start, end: node.end, message })
   // ❌
   context.report({ line: text.slice(0, node.start).split('\n').length, message })
   ```

5. **No external packages, and nothing run that a file does not need.** The parsers and the globals data are the linter's own; a rule's [`create`](https://www.hyperfrontend.dev/docs/libraries/linter/#api-RuleModule) runs only for a file whose configuration turns it on.

## Module Composition

| Module            | Responsibility                                                                                                                                                                                                           |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `api/`            | [`lint`](https://www.hyperfrontend.dev/docs/libraries/linter/#api-lint): configuration, file selection and linting as a function                                                                                         |
| `bin/`            | The `hf-lint` command                                                                                                                                                                                                    |
| `cli/`            | Arguments, the command's flow, exit codes                                                                                                                                                                                |
| `config/`         | Loading the nearest `lint.config.ts`, resolving blocks per file, directives                                                                                                                                              |
| `files/`          | Enumerating files from paths and globs, the glob matcher                                                                                                                                                                 |
| `engine/`         | The rule API, the registry of packaged rules and families, the per-file lint loop, traversal, fixes, report shapes                                                                                                       |
| `languages/`      | One adapter per language: parse, children table, comments                                                                                                                                                                |
| `code/`           | TypeScript, TSX and JavaScript scanner and parser, ESTree-shaped                                                                                                                                                         |
| `markdown/`       | Block and inline Markdown parser, mdast-shaped                                                                                                                                                                           |
| `text/`           | Line maps and positions                                                                                                                                                                                                  |
| `scope/`          | Scopes, variables, references and their resolution                                                                                                                                                                       |
| `flow/`           | Code-path segments for the rules that reason about reachability                                                                                                                                                          |
| `jsdoc/`          | JSDoc blocks, tags and type expressions, and their attachment to nodes                                                                                                                                                   |
| `regex/`          | Regular-expression patterns and their validity                                                                                                                                                                           |
| `output/`         | Stylish and JSON formatters                                                                                                                                                                                              |
| `presets/`        | The rule sets and globals a configuration is built from                                                                                                                                                                  |
| `helpers/`        | The rule-author helpers published as [`/helpers`](https://www.hyperfrontend.dev/docs/libraries/linter/helpers.md)                                                                                                          |
| `testing/`        | [`testRule`](https://www.hyperfrontend.dev/docs/libraries/linter/testing/#api-testRule), the spec helper every rule's spec uses, published as [`/testing`](https://www.hyperfrontend.dev/docs/libraries/linter/testing.md) |
| `rules/<family>/` | One module per rule, shared helpers under `shared/`; `catalogue.ts` and `ids.ts` list them                                                                                                                               |

## Data Flow

```mermaid
---
config:
  theme: base
  themeVariables:
    fontSize: 12px
---
sequenceDiagram
    participant Run as cli/run
    participant Config as config
    participant Engine as engine/lint-file
    participant Language as languages
    participant Rules as rules
    participant Directives as config/directives
    Run->>Config: findConfigFile(cwd), loadConfig
    Run->>Config: effectiveConfig(file)
    Run->>Rules: registry.load(ids in effect)
    Run->>Engine: lintFile(file, config, rules)
    loop until no fix applies, at most ten passes
        Engine->>Language: parse(text)
        Engine->>Rules: create(context) per rule
        Engine->>Engine: traverse once, listeners report
        Engine->>Directives: applyDirectives(reports, comments)
        Directives-->>Engine: kept problems, unused-directive warnings
        Engine->>Engine: apply non-overlapping fixes
    end
    Engine-->>Run: messages, fixed text
```

## Core Interfaces

```ts
/** A rule module's default export. */
interface RuleModule<Options = unknown, Node extends TreeNode = TreeNode> {
  readonly id: string
  readonly language: 'code' | 'json' | 'markdown' | 'text'
  readonly fixable: boolean
  create(context: RuleContext<Options, Node>): Listeners<Node>
}

/** What a rule receives. */
interface RuleContext<Options, Node extends TreeNode> {
  readonly file: RuleFile
  readonly tree: Node
  readonly options: Options
  readonly settings: Readonly<Record<string, unknown>>
  readonly language: LanguageSettings
  readonly report: (problem: Problem) => void
}

/** What a rule reports. */
interface Problem {
  readonly start: number
  readonly end?: number
  readonly at?: Location
  readonly message: string
  readonly fix?: { readonly start: number; readonly end: number; readonly text: string }
}

/** One configuration block. */
interface ConfigBlock {
  readonly files?: readonly string[]
  readonly ignores?: readonly string[]
  readonly rules?: Readonly<Record<string, 'off' | 'warn' | 'error' | readonly ['warn' | 'error', unknown]>>
  readonly settings?: Readonly<Record<string, unknown>>
  readonly language?: Partial<LanguageSettings>
  readonly families?: Readonly<Record<string, RuleFamily>>
}
```

## Links

- [README.md](https://www.hyperfrontend.dev/docs/libraries/linter.md): installing and running the linter
- [Rule catalogue](https://www.hyperfrontend.dev/docs/libraries/linter/rules.md): every packaged rule, with options and examples

---

Canonical page: https://www.hyperfrontend.dev/docs/libraries/linter/architecture/
This file: https://www.hyperfrontend.dev/docs/libraries/linter/architecture.md
Documentation index: https://www.hyperfrontend.dev/llms.txt
