Appearance
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 LintResult
s 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
.