Skip to content

Architecture Overview

This document describes the major pieces that power @lapidist/design-lint.

mermaid
flowchart TD
  CLI[CLI] --> ConfigLoader
  ConfigLoader --> Linter
  Linter --> PluginLoader
  Linter --> FileScanner
  FileScanner --> Files
  Linter --> Rules
  Rules --> Formatter
  Linter --> Cache
  Formatter --> Output

Linter Core

The Linter class in src/core/linter.ts drives the linting process. On construction it registers built-in rules, loads any configured plugins, and then scans files using glob patterns while honoring ignore rules. Files are parsed according to their extensions—TypeScript and JavaScript via the TypeScript compiler, CSS with PostCSS, and single-file components like Vue or Svelte through their respective parsers. The engine then executes rule listeners for each relevant AST node or CSS declaration and can apply fixes when requested.

Rule Lifecycle

Rules expose a name, meta.description, and a create function. When linting begins, configuration enables specific rules with a severity and optional options. The engine calls each rule’s create function with a context providing tokens, options, filePath, and a report method. The returned listener hooks (onNode, onCSSDeclaration, etc.) run as the engine walks the parsed source. When a rule detects a problem it reports a LintMessage; fixes are collected and applied after all listeners finish.

Plugin Loading

Plugins extend the rule set by exporting an object like { rules: RuleModule[] }. plugin-loader.ts resolves each plugin relative to the config file and uses dynamic import when necessary. Each rule is validated for shape and uniqueness before being added to the rule map. Loading errors clearly identify the plugin and suggest remediation.

File Scanning and Ignoring

The file-scanner.ts module gathers target files based on glob patterns and consults ignore files via ignore.ts. This keeps the core linter focused on analysis while delegating filesystem concerns to dedicated helpers.

Configuration Resolution

Configuration is resolved through loadConfig. It searches upward from the current directory for designlint.config.* files, supporting JavaScript, TypeScript, JSON, and ESM/CJS variants. Loaded settings are merged with defaults, validated against a schema, and returned with absolute paths. The resulting object supplies tokens, rule settings, plugins, and ignore patterns consumed by the engine.

Caching Subsystem

To avoid reprocessing unchanged files, the engine accepts an optional cache map and location. When lintFiles runs it populates the map with each file’s modification time and LintResult, reading and writing to disk when cacheLocation is provided. Stale entries are pruned and results are persisted after every run. Cache serialization lives in src/core/cache.ts, orchestrated by the linter.

Formatting

Results are rendered through formatters such as stylish, json, or sarif. getFormatter in src/formatters/index.ts resolves a formatter by name and applies it to the collected LintResults to produce CLI output or machine-readable reports.

Watch Workflow

The CLI supports --watch mode to re-lint files on the fly. Using chokidar, it monitors target files, the configuration file, ignore files, and plugin modules. When any of these change, the CLI reloads configuration and plugins, clears caches, and invokes the engine again. Dynamic imports with cache-busting query strings ensure ESM plugins reload correctly. Implementation details can be found in src/cli/index.ts.