@drskillissue/ganko 0.1.25 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-KVZ56NZ5.js → chunk-5OEDGKHL.js} +3 -3
- package/dist/{chunk-KVZ56NZ5.js.map → chunk-5OEDGKHL.js.map} +1 -1
- package/dist/{chunk-64TMAKYI.js → chunk-CAKVXEYV.js} +5075 -4449
- package/dist/chunk-CAKVXEYV.js.map +1 -0
- package/dist/eslint-plugin.cjs +5091 -4416
- package/dist/eslint-plugin.cjs.map +1 -1
- package/dist/eslint-plugin.js +14 -5
- package/dist/eslint-plugin.js.map +1 -1
- package/dist/index.cjs +5200 -4567
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -205
- package/dist/index.d.ts +138 -205
- package/dist/index.js +10 -9
- package/dist/index.js.map +1 -1
- package/dist/rules-manifest.cjs +2 -2
- package/dist/rules-manifest.cjs.map +1 -1
- package/dist/rules-manifest.js +1 -1
- package/package.json +3 -5
- package/dist/chunk-64TMAKYI.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
RULES,
|
|
3
3
|
RULES_BY_CATEGORY,
|
|
4
4
|
getRule
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-5OEDGKHL.js";
|
|
6
6
|
import {
|
|
7
7
|
CSSPlugin,
|
|
8
8
|
SolidPlugin,
|
|
@@ -12,16 +12,14 @@ import {
|
|
|
12
12
|
buildSolidGraph,
|
|
13
13
|
canonicalPath,
|
|
14
14
|
classifyFile,
|
|
15
|
+
createSolidInput,
|
|
15
16
|
noopLogger,
|
|
16
|
-
parseContent,
|
|
17
|
-
parseContentWithProgram,
|
|
18
|
-
parseFile,
|
|
19
17
|
resolveTailwindValidator,
|
|
20
18
|
runCrossFileRules,
|
|
21
19
|
runSolidRules,
|
|
22
20
|
scanDependencyCustomProperties,
|
|
23
21
|
setActivePolicy
|
|
24
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-CAKVXEYV.js";
|
|
25
23
|
import "./chunk-EGRHWZRV.js";
|
|
26
24
|
|
|
27
25
|
// src/runner.ts
|
|
@@ -42,19 +40,24 @@ function createOverrideEmit(target, overrides) {
|
|
|
42
40
|
}
|
|
43
41
|
function createRunner(config) {
|
|
44
42
|
let overrides = config.rules ?? {};
|
|
43
|
+
let program = config.program;
|
|
45
44
|
return {
|
|
46
45
|
run(files) {
|
|
47
46
|
const diagnostics = [];
|
|
48
47
|
const raw = (d) => diagnostics.push(d);
|
|
49
48
|
const hasOverrides = Object.keys(overrides).length > 0;
|
|
50
49
|
const emit = hasOverrides ? createOverrideEmit(raw, overrides) : raw;
|
|
50
|
+
const context = program ? { program } : void 0;
|
|
51
51
|
for (const plugin of config.plugins) {
|
|
52
|
-
plugin.analyze(files, emit);
|
|
52
|
+
plugin.analyze(files, emit, context);
|
|
53
53
|
}
|
|
54
54
|
return diagnostics;
|
|
55
55
|
},
|
|
56
56
|
setRuleOverrides(next) {
|
|
57
57
|
overrides = next;
|
|
58
|
+
},
|
|
59
|
+
setProgram(next) {
|
|
60
|
+
program = next;
|
|
58
61
|
}
|
|
59
62
|
};
|
|
60
63
|
}
|
|
@@ -368,10 +371,8 @@ export {
|
|
|
368
371
|
buildSolidGraph,
|
|
369
372
|
createOverrideEmit,
|
|
370
373
|
createRunner,
|
|
374
|
+
createSolidInput,
|
|
371
375
|
getRule,
|
|
372
|
-
parseContent,
|
|
373
|
-
parseContentWithProgram,
|
|
374
|
-
parseFile,
|
|
375
376
|
resolveTailwindValidator,
|
|
376
377
|
runCrossFileRules,
|
|
377
378
|
runSolidRules,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runner.ts","../src/cache.ts"],"sourcesContent":["/**\n * Runner - Plugin-agnostic runner for ganko\n *\n * The runner takes a configuration with plugins and runs them against files.\n * It never stores typed graphs - plugins handle their own graph building\n * and rule execution internally via the analyze() method.\n *\n * Rule overrides allow callers (LSP, CLI) to disable rules or remap their\n * severity without modifying rule definitions. The runner intercepts the\n * Emit callback to enforce overrides before diagnostics reach the caller.\n */\nimport type { Diagnostic } from \"./diagnostic\"\nimport type { Plugin, Emit } from \"./graph\"\nimport type { RuleOverrides } from \"@drskillissue/ganko-shared\"\n\n/** Runner configuration */\nexport interface RunnerConfig {\n readonly plugins: readonly Plugin<string>[]\n readonly rules?: RuleOverrides\n}\n\n/** Runner interface */\nexport interface Runner {\n run(files: readonly string[]): readonly Diagnostic[]\n /** Replace rule overrides. Takes effect on the next run() call. */\n setRuleOverrides(overrides: RuleOverrides): void\n}\n\n/**\n * Build an Emit wrapper that enforces rule overrides.\n *\n * @param target - The underlying emit that collects diagnostics\n * @param overrides - Current rule override map\n * @returns Wrapped emit that suppresses/remaps per overrides\n */\nexport function createOverrideEmit(target: Emit, overrides: RuleOverrides): Emit {\n return (d) => {\n const override = overrides[d.rule]\n if (override === undefined) { target(d); return }\n if (override === \"off\") return\n if (override !== d.severity) {\n target({ ...d, severity: override })\n return\n }\n target(d)\n }\n}\n\n/**\n * Create a runner from configuration.\n *\n * @example\n * ```ts\n * const runner = createRunner({\n * plugins: [SolidPlugin, CSSPlugin, CrossFilePlugin],\n * rules: { \"missing-jsdoc-comments\": \"off\", \"signal-call\": \"warn\" },\n * })\n * const diagnostics = runner.run(projectFiles)\n * ```\n */\nexport function createRunner(config: RunnerConfig): Runner {\n let overrides: RuleOverrides = config.rules ?? {}\n\n return {\n run(files) {\n const diagnostics: Diagnostic[] = []\n const raw: Emit = (d) => diagnostics.push(d)\n const hasOverrides = Object.keys(overrides).length > 0\n const emit = hasOverrides ? createOverrideEmit(raw, overrides) : raw\n for (const plugin of config.plugins) {\n plugin.analyze(files, emit)\n }\n return diagnostics\n },\n\n setRuleOverrides(next) {\n overrides = next\n },\n }\n}\n","/**\n * GraphCache — Versioned cache for SolidGraph, CSSGraph, and LayoutGraph instances.\n *\n * Eliminates redundant graph construction in the LSP server by caching\n * per-file SolidGraphs (keyed by path + script version), a single\n * CSSGraph invalidated via a monotonic generation counter, and a\n * LayoutGraph invalidated by Solid/CSS signatures.\n *\n * The cache does not perform I/O or parsing — callers supply builder\n * functions that are invoked only on cache miss.\n */\nimport { canonicalPath, classifyFile, noopLogger } from \"@drskillissue/ganko-shared\"\nimport type { Logger } from \"@drskillissue/ganko-shared\"\nimport type { Diagnostic } from \"./diagnostic\"\nimport type { SolidGraph } from \"./solid/impl\"\nimport type { CSSGraph } from \"./css/impl\"\nimport type { LayoutGraph } from \"./cross-file/layout/graph\"\n\ninterface CachedSolid {\n readonly version: string\n readonly graph: SolidGraph\n}\n\ninterface CachedCSS {\n readonly generation: number\n readonly graph: CSSGraph\n}\n\ninterface CachedLayout {\n readonly solidGeneration: number\n readonly cssGeneration: number\n readonly graph: LayoutGraph\n}\n\ninterface CachedCrossFileResults {\n readonly solidGeneration: number\n readonly cssGeneration: number\n readonly byFile: ReadonlyMap<string, readonly Diagnostic[]>\n}\n\n/**\n * Versioned cache for SolidGraph, CSSGraph, and LayoutGraph instances.\n *\n * SolidGraphs are cached per file path with a version string.\n * The CSSGraph is a single instance covering all CSS files,\n * invalidated via a monotonic generation counter bumped by\n * `invalidate()` or `invalidateAll()`.\n */\nexport class GraphCache {\n private readonly log: Logger\n private readonly solids = new Map<string, CachedSolid>()\n private readonly crossFileDiagnostics = new Map<string, readonly Diagnostic[]>()\n private crossFileResults: CachedCrossFileResults | null = null\n private css: CachedCSS | null = null\n private solidGeneration = 0\n private cssGeneration = 0\n private layout: CachedLayout | null = null\n\n constructor(log?: Logger) {\n this.log = log ?? noopLogger\n }\n\n /**\n * Check if a SolidGraph is cached and current for a file path.\n *\n * Allows callers to skip builder allocation when the cache is warm.\n *\n * @param path Absolute file path\n * @param version Script version string from the TS project service\n */\n hasSolidGraph(path: string, version: string): boolean {\n const key = canonicalPath(path)\n const cached = this.solids.get(key)\n const hit = cached !== undefined && cached.version === version\n if (this.log.enabled) this.log.debug(`hasSolidGraph: ${key} v=${version} cached=${cached?.version ?? \"none\"} hit=${hit} (${this.solids.size} total)`)\n return hit\n }\n\n /**\n * Store a pre-built SolidGraph in the cache.\n *\n * Used by the CLI lint command which builds graphs during single-file\n * analysis and pre-populates the cache for cross-file reuse.\n *\n * @param path Absolute file path\n * @param version Script version string from the TS project service\n * @param graph Pre-built SolidGraph\n */\n setSolidGraph(path: string, version: string, graph: SolidGraph): void {\n const key = canonicalPath(path)\n this.solids.set(key, { version, graph })\n this.solidGeneration++\n if (this.log.enabled) this.log.debug(`setSolidGraph: ${key} v=${version} (${this.solids.size} total) solidGen=${this.solidGeneration}`)\n }\n\n /**\n * Get a cached SolidGraph without building on miss.\n *\n * Returns the cached graph if the version matches, null otherwise.\n * Use when the caller has already confirmed the entry exists via\n * `hasSolidGraph` and wants to avoid allocating a builder closure.\n *\n * @param path Absolute file path\n * @param version Script version string from the TS project service\n */\n getCachedSolidGraph(path: string, version: string): SolidGraph | null {\n const key = canonicalPath(path)\n const cached = this.solids.get(key)\n if (cached !== undefined && cached.version === version) {\n if (this.log.enabled) this.log.debug(`getCachedSolidGraph HIT: ${key} v=${version}`)\n return cached.graph\n }\n if (this.log.enabled) this.log.debug(`getCachedSolidGraph MISS: ${key} v=${version}`)\n return null\n }\n\n /**\n * Get or build a SolidGraph for a file path.\n *\n * Returns the cached graph if the version matches.\n * Otherwise invokes the builder, caches the result, and returns it.\n *\n * @param path Absolute file path\n * @param version Script version string from the TS project service\n * @param build Builder function invoked on cache miss\n */\n getSolidGraph(path: string, version: string, build: () => SolidGraph): SolidGraph {\n const key = canonicalPath(path)\n const cached = this.solids.get(key)\n if (cached !== undefined && cached.version === version) {\n if (this.log.enabled) this.log.debug(`getSolidGraph HIT: ${key} v=${version}`)\n return cached.graph\n }\n\n if (this.log.enabled) this.log.debug(`getSolidGraph MISS: ${key} v=${version} (was ${cached?.version ?? \"uncached\"})`)\n const t0 = performance.now()\n const graph = build()\n this.solids.set(key, { version, graph })\n this.solidGeneration++\n if (this.log.enabled) this.log.debug(`getSolidGraph BUILT: ${key} v=${version} in ${performance.now() - t0}ms (${this.solids.size} total) solidGen=${this.solidGeneration}`)\n return graph\n }\n\n /**\n * Get the cached CSSGraph, or rebuild it.\n *\n * Returns the cached graph if the generation matches the current\n * CSS generation counter. Otherwise invokes the builder, caches\n * the result at the current generation, and returns it.\n *\n * @param build Builder function invoked on cache miss\n */\n getCSSGraph(build: () => CSSGraph): CSSGraph {\n if (this.css !== null && this.css.generation === this.cssGeneration) {\n if (this.log.enabled) this.log.debug(`getCSSGraph HIT: gen=${this.cssGeneration}`)\n return this.css.graph\n }\n\n if (this.log.enabled) this.log.debug(`getCSSGraph MISS: currentGen=${this.cssGeneration} cachedGen=${this.css?.generation ?? \"none\"}`)\n const t0 = performance.now()\n const graph = build()\n this.css = { generation: this.cssGeneration, graph }\n if (this.log.enabled) this.log.debug(`getCSSGraph BUILT: gen=${this.cssGeneration} in ${performance.now() - t0}ms`)\n return graph\n }\n\n /**\n * Get or build a LayoutGraph for current Solid/CSS cache state.\n *\n * Returns cached LayoutGraph when both Solid signature (path+version)\n * and CSS generation match. Otherwise invokes the builder.\n *\n * @param build Builder function invoked on cache miss\n */\n getLayoutGraph(build: () => LayoutGraph): LayoutGraph {\n const solidGen = this.solidGeneration\n const cssGen = this.cssGeneration\n\n if (\n this.layout !== null\n && this.layout.solidGeneration === solidGen\n && this.layout.cssGeneration === cssGen\n ) {\n if (this.log.enabled) this.log.debug(`getLayoutGraph HIT: solidGen=${solidGen} cssGen=${cssGen}`)\n return this.layout.graph\n }\n\n if (this.log.enabled) this.log.debug(\n `getLayoutGraph MISS: solidGen=${solidGen} cssGen=${cssGen} `\n + `cached=${this.layout !== null}`,\n )\n\n const t0 = performance.now()\n const graph = build()\n this.layout = {\n solidGeneration: solidGen,\n cssGeneration: cssGen,\n graph,\n }\n if (this.log.enabled) this.log.debug(`getLayoutGraph BUILT: in ${performance.now() - t0}ms`)\n return graph\n }\n\n /**\n * Invalidate cached graphs affected by a file change.\n *\n * Classifies the path and invalidates the appropriate cache:\n * solid files evict their per-file SolidGraph, CSS files bump\n * the CSSGraph generation counter.\n *\n * @param path Absolute file path that changed\n */\n invalidate(path: string): void {\n const key = canonicalPath(path)\n const kind = classifyFile(key)\n if (this.log.enabled) this.log.debug(`invalidate: ${key} kind=${kind} solids=${this.solids.size} hasCrossFileResults=${this.crossFileResults !== null} hasLayout=${this.layout !== null}`)\n if (kind === \"solid\") {\n const had = this.solids.has(key)\n this.solids.delete(key)\n this.crossFileDiagnostics.delete(key)\n this.crossFileResults = null\n this.solidGeneration++\n this.layout = null\n if (this.log.enabled) this.log.debug(`invalidate SOLID: ${key} wasInCache=${had} solids=${this.solids.size} solidGen=${this.solidGeneration}`)\n }\n if (kind === \"css\") {\n this.crossFileDiagnostics.delete(key)\n this.crossFileResults = null\n this.cssGeneration++\n this.css = null\n this.layout = null\n if (this.log.enabled) this.log.debug(`invalidate CSS: ${key} newCssGen=${this.cssGeneration}`)\n }\n }\n\n /**\n * Invalidate all cached graphs.\n *\n * Called on workspace-level events like config changes.\n */\n invalidateAll(): void {\n if (this.log.enabled) this.log.debug(`invalidateAll: solids=${this.solids.size} solidGen=${this.solidGeneration} cssGen=${this.cssGeneration}`)\n this.solids.clear()\n this.crossFileDiagnostics.clear()\n this.crossFileResults = null\n this.solidGeneration++\n this.cssGeneration++\n this.css = null\n this.layout = null\n }\n\n /**\n * Get all cached SolidGraphs.\n *\n * Returns a snapshot array of all currently-cached graphs.\n * Used by cross-file analysis which needs all SolidGraphs.\n */\n getAllSolidGraphs(): readonly SolidGraph[] {\n if (this.log.enabled) this.log.debug(`getAllSolidGraphs: ${this.solids.size} graphs`)\n const out: SolidGraph[] = new Array(this.solids.size)\n let i = 0\n for (const entry of this.solids.values()) {\n out[i++] = entry.graph\n }\n return out\n }\n\n /**\n * Get the cached CSSGraph, or null if not cached.\n */\n getCachedCSSGraph(): CSSGraph | null {\n return this.css?.graph ?? null\n }\n\n /**\n * Get the cached LayoutGraph, or null if not cached.\n */\n getCachedLayoutGraph(): LayoutGraph | null {\n return this.layout?.graph ?? null\n }\n\n /**\n * Get cached cross-file diagnostics for a file path.\n *\n * Returns the previous cross-file results so single-file-only\n * re-analysis (during typing) can merge them without re-running\n * cross-file rules.\n *\n * @param path Absolute file path\n */\n getCachedCrossFileDiagnostics(path: string): readonly Diagnostic[] {\n return this.crossFileDiagnostics.get(canonicalPath(path)) ?? []\n }\n\n /**\n * Store cross-file diagnostics for a file path.\n *\n * @param path Absolute file path\n * @param diagnostics Cross-file diagnostics for this path\n */\n setCachedCrossFileDiagnostics(path: string, diagnostics: readonly Diagnostic[]): void {\n this.crossFileDiagnostics.set(canonicalPath(path), diagnostics)\n }\n\n /**\n * Get workspace-level cross-file results if the underlying graphs haven't changed.\n *\n * Returns the full per-file map when the solid signature and CSS generation\n * match, meaning no graphs were rebuilt since the last run. Returns null\n * when results are stale and `runCrossFileRules` must re-execute.\n */\n getCachedCrossFileResults(): ReadonlyMap<string, readonly Diagnostic[]> | null {\n if (this.crossFileResults === null) {\n this.log.debug(\"getCachedCrossFileResults: null (no cached results)\")\n return null\n }\n const solidMatch = this.crossFileResults.solidGeneration === this.solidGeneration\n const cssMatch = this.crossFileResults.cssGeneration === this.cssGeneration\n if (solidMatch && cssMatch) {\n if (this.log.enabled) this.log.debug(`getCachedCrossFileResults HIT: ${this.crossFileResults?.byFile.size} files`)\n return this.crossFileResults.byFile\n }\n if (this.log.enabled) this.log.debug(\n `getCachedCrossFileResults MISS: solidMatch=${solidMatch} cssMatch=${cssMatch} `\n + `cachedSolidGen=${this.crossFileResults?.solidGeneration} currentSolidGen=${this.solidGeneration} `\n + `cachedCssGen=${this.crossFileResults?.cssGeneration} currentCssGen=${this.cssGeneration}`,\n )\n return null\n }\n\n /**\n * Store workspace-level cross-file results bucketed by file.\n *\n * Called after `runCrossFileRules` completes. Captures the current\n * solid signature and CSS generation so subsequent lookups are O(1)\n * until a graph changes.\n *\n * @param allDiagnostics All cross-file diagnostics from the workspace run\n */\n setCachedCrossFileResults(allDiagnostics: readonly Diagnostic[]): void {\n const byFile = new Map<string, Diagnostic[]>()\n for (let i = 0, len = allDiagnostics.length; i < len; i++) {\n const d = allDiagnostics[i]\n if (!d) continue\n let arr = byFile.get(d.file)\n if (!arr) {\n arr = []\n byFile.set(d.file, arr)\n }\n arr.push(d)\n }\n this.crossFileResults = {\n solidGeneration: this.solidGeneration,\n cssGeneration: this.cssGeneration,\n byFile,\n }\n if (this.log.enabled) this.log.debug(\n `setCachedCrossFileResults: ${allDiagnostics.length} diags across ${byFile.size} files `\n + `solidGen=${this.solidGeneration} cssGen=${this.cssGeneration}`,\n )\n /* Also update the per-file cache used during typing (when cross-file\n analysis is skipped and previous results are reused). */\n for (const [file, diagnostics] of byFile) {\n this.crossFileDiagnostics.set(file, diagnostics)\n }\n }\n\n /** Number of cached SolidGraphs. */\n get solidCount(): number {\n return this.solids.size\n }\n\n /** The logger instance used by this cache. */\n get logger(): Logger {\n return this.log\n }\n\n\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/runner.ts","../src/cache.ts"],"sourcesContent":["/**\n * Runner - Plugin-agnostic runner for ganko\n *\n * The runner takes a configuration with plugins and runs them against files.\n * It never stores typed graphs - plugins handle their own graph building\n * and rule execution internally via the analyze() method.\n *\n * Rule overrides allow callers (LSP, CLI) to disable rules or remap their\n * severity without modifying rule definitions. The runner intercepts the\n * Emit callback to enforce overrides before diagnostics reach the caller.\n */\nimport type ts from \"typescript\"\nimport type { Diagnostic } from \"./diagnostic\"\nimport type { Plugin, Emit } from \"./graph\"\nimport type { RuleOverrides } from \"@drskillissue/ganko-shared\"\n\n/** Runner configuration */\nexport interface RunnerConfig {\n readonly plugins: readonly Plugin<string>[]\n readonly rules?: RuleOverrides\n readonly program?: ts.Program\n}\n\n/** Runner interface */\nexport interface Runner {\n run(files: readonly string[]): readonly Diagnostic[]\n /** Replace rule overrides. Takes effect on the next run() call. */\n setRuleOverrides(overrides: RuleOverrides): void\n /** Replace the TypeScript program. Takes effect on the next run() call. */\n setProgram(program: ts.Program): void\n}\n\n/**\n * Build an Emit wrapper that enforces rule overrides.\n *\n * @param target - The underlying emit that collects diagnostics\n * @param overrides - Current rule override map\n * @returns Wrapped emit that suppresses/remaps per overrides\n */\nexport function createOverrideEmit(target: Emit, overrides: RuleOverrides): Emit {\n return (d) => {\n const override = overrides[d.rule]\n if (override === undefined) { target(d); return }\n if (override === \"off\") return\n if (override !== d.severity) {\n target({ ...d, severity: override })\n return\n }\n target(d)\n }\n}\n\n/**\n * Create a runner from configuration.\n */\nexport function createRunner(config: RunnerConfig): Runner {\n let overrides: RuleOverrides = config.rules ?? {}\n let program: ts.Program | undefined = config.program\n\n return {\n run(files) {\n const diagnostics: Diagnostic[] = []\n const raw: Emit = (d) => diagnostics.push(d)\n const hasOverrides = Object.keys(overrides).length > 0\n const emit = hasOverrides ? createOverrideEmit(raw, overrides) : raw\n const context = program ? { program } : undefined\n for (const plugin of config.plugins) {\n plugin.analyze(files, emit, context)\n }\n return diagnostics\n },\n\n setRuleOverrides(next) {\n overrides = next\n },\n\n setProgram(next) {\n program = next\n },\n }\n}\n","/**\n * GraphCache — Versioned cache for SolidGraph, CSSGraph, and LayoutGraph instances.\n *\n * Eliminates redundant graph construction in the LSP server by caching\n * per-file SolidGraphs (keyed by path + script version), a single\n * CSSGraph invalidated via a monotonic generation counter, and a\n * LayoutGraph invalidated by Solid/CSS signatures.\n *\n * The cache does not perform I/O or parsing — callers supply builder\n * functions that are invoked only on cache miss.\n */\nimport { canonicalPath, classifyFile, noopLogger } from \"@drskillissue/ganko-shared\"\nimport type { Logger } from \"@drskillissue/ganko-shared\"\nimport type { Diagnostic } from \"./diagnostic\"\nimport type { SolidGraph } from \"./solid/impl\"\nimport type { CSSGraph } from \"./css/impl\"\nimport type { LayoutGraph } from \"./cross-file/layout/graph\"\n\ninterface CachedSolid {\n readonly version: string\n readonly graph: SolidGraph\n}\n\ninterface CachedCSS {\n readonly generation: number\n readonly graph: CSSGraph\n}\n\ninterface CachedLayout {\n readonly solidGeneration: number\n readonly cssGeneration: number\n readonly graph: LayoutGraph\n}\n\ninterface CachedCrossFileResults {\n readonly solidGeneration: number\n readonly cssGeneration: number\n readonly byFile: ReadonlyMap<string, readonly Diagnostic[]>\n}\n\n/**\n * Versioned cache for SolidGraph, CSSGraph, and LayoutGraph instances.\n *\n * SolidGraphs are cached per file path with a version string.\n * The CSSGraph is a single instance covering all CSS files,\n * invalidated via a monotonic generation counter bumped by\n * `invalidate()` or `invalidateAll()`.\n */\nexport class GraphCache {\n private readonly log: Logger\n private readonly solids = new Map<string, CachedSolid>()\n private readonly crossFileDiagnostics = new Map<string, readonly Diagnostic[]>()\n private crossFileResults: CachedCrossFileResults | null = null\n private css: CachedCSS | null = null\n private solidGeneration = 0\n private cssGeneration = 0\n private layout: CachedLayout | null = null\n\n constructor(log?: Logger) {\n this.log = log ?? noopLogger\n }\n\n /**\n * Check if a SolidGraph is cached and current for a file path.\n *\n * Allows callers to skip builder allocation when the cache is warm.\n *\n * @param path Absolute file path\n * @param version Script version string from the TS project service\n */\n hasSolidGraph(path: string, version: string): boolean {\n const key = canonicalPath(path)\n const cached = this.solids.get(key)\n const hit = cached !== undefined && cached.version === version\n if (this.log.enabled) this.log.debug(`hasSolidGraph: ${key} v=${version} cached=${cached?.version ?? \"none\"} hit=${hit} (${this.solids.size} total)`)\n return hit\n }\n\n /**\n * Store a pre-built SolidGraph in the cache.\n *\n * Used by the CLI lint command which builds graphs during single-file\n * analysis and pre-populates the cache for cross-file reuse.\n *\n * @param path Absolute file path\n * @param version Script version string from the TS project service\n * @param graph Pre-built SolidGraph\n */\n setSolidGraph(path: string, version: string, graph: SolidGraph): void {\n const key = canonicalPath(path)\n this.solids.set(key, { version, graph })\n this.solidGeneration++\n if (this.log.enabled) this.log.debug(`setSolidGraph: ${key} v=${version} (${this.solids.size} total) solidGen=${this.solidGeneration}`)\n }\n\n /**\n * Get a cached SolidGraph without building on miss.\n *\n * Returns the cached graph if the version matches, null otherwise.\n * Use when the caller has already confirmed the entry exists via\n * `hasSolidGraph` and wants to avoid allocating a builder closure.\n *\n * @param path Absolute file path\n * @param version Script version string from the TS project service\n */\n getCachedSolidGraph(path: string, version: string): SolidGraph | null {\n const key = canonicalPath(path)\n const cached = this.solids.get(key)\n if (cached !== undefined && cached.version === version) {\n if (this.log.enabled) this.log.debug(`getCachedSolidGraph HIT: ${key} v=${version}`)\n return cached.graph\n }\n if (this.log.enabled) this.log.debug(`getCachedSolidGraph MISS: ${key} v=${version}`)\n return null\n }\n\n /**\n * Get or build a SolidGraph for a file path.\n *\n * Returns the cached graph if the version matches.\n * Otherwise invokes the builder, caches the result, and returns it.\n *\n * @param path Absolute file path\n * @param version Script version string from the TS project service\n * @param build Builder function invoked on cache miss\n */\n getSolidGraph(path: string, version: string, build: () => SolidGraph): SolidGraph {\n const key = canonicalPath(path)\n const cached = this.solids.get(key)\n if (cached !== undefined && cached.version === version) {\n if (this.log.enabled) this.log.debug(`getSolidGraph HIT: ${key} v=${version}`)\n return cached.graph\n }\n\n if (this.log.enabled) this.log.debug(`getSolidGraph MISS: ${key} v=${version} (was ${cached?.version ?? \"uncached\"})`)\n const t0 = performance.now()\n const graph = build()\n this.solids.set(key, { version, graph })\n this.solidGeneration++\n if (this.log.enabled) this.log.debug(`getSolidGraph BUILT: ${key} v=${version} in ${performance.now() - t0}ms (${this.solids.size} total) solidGen=${this.solidGeneration}`)\n return graph\n }\n\n /**\n * Get the cached CSSGraph, or rebuild it.\n *\n * Returns the cached graph if the generation matches the current\n * CSS generation counter. Otherwise invokes the builder, caches\n * the result at the current generation, and returns it.\n *\n * @param build Builder function invoked on cache miss\n */\n getCSSGraph(build: () => CSSGraph): CSSGraph {\n if (this.css !== null && this.css.generation === this.cssGeneration) {\n if (this.log.enabled) this.log.debug(`getCSSGraph HIT: gen=${this.cssGeneration}`)\n return this.css.graph\n }\n\n if (this.log.enabled) this.log.debug(`getCSSGraph MISS: currentGen=${this.cssGeneration} cachedGen=${this.css?.generation ?? \"none\"}`)\n const t0 = performance.now()\n const graph = build()\n this.css = { generation: this.cssGeneration, graph }\n if (this.log.enabled) this.log.debug(`getCSSGraph BUILT: gen=${this.cssGeneration} in ${performance.now() - t0}ms`)\n return graph\n }\n\n /**\n * Get or build a LayoutGraph for current Solid/CSS cache state.\n *\n * Returns cached LayoutGraph when both Solid signature (path+version)\n * and CSS generation match. Otherwise invokes the builder.\n *\n * @param build Builder function invoked on cache miss\n */\n getLayoutGraph(build: () => LayoutGraph): LayoutGraph {\n const solidGen = this.solidGeneration\n const cssGen = this.cssGeneration\n\n if (\n this.layout !== null\n && this.layout.solidGeneration === solidGen\n && this.layout.cssGeneration === cssGen\n ) {\n if (this.log.enabled) this.log.debug(`getLayoutGraph HIT: solidGen=${solidGen} cssGen=${cssGen}`)\n return this.layout.graph\n }\n\n if (this.log.enabled) this.log.debug(\n `getLayoutGraph MISS: solidGen=${solidGen} cssGen=${cssGen} `\n + `cached=${this.layout !== null}`,\n )\n\n const t0 = performance.now()\n const graph = build()\n this.layout = {\n solidGeneration: solidGen,\n cssGeneration: cssGen,\n graph,\n }\n if (this.log.enabled) this.log.debug(`getLayoutGraph BUILT: in ${performance.now() - t0}ms`)\n return graph\n }\n\n /**\n * Invalidate cached graphs affected by a file change.\n *\n * Classifies the path and invalidates the appropriate cache:\n * solid files evict their per-file SolidGraph, CSS files bump\n * the CSSGraph generation counter.\n *\n * @param path Absolute file path that changed\n */\n invalidate(path: string): void {\n const key = canonicalPath(path)\n const kind = classifyFile(key)\n if (this.log.enabled) this.log.debug(`invalidate: ${key} kind=${kind} solids=${this.solids.size} hasCrossFileResults=${this.crossFileResults !== null} hasLayout=${this.layout !== null}`)\n if (kind === \"solid\") {\n const had = this.solids.has(key)\n this.solids.delete(key)\n this.crossFileDiagnostics.delete(key)\n this.crossFileResults = null\n this.solidGeneration++\n this.layout = null\n if (this.log.enabled) this.log.debug(`invalidate SOLID: ${key} wasInCache=${had} solids=${this.solids.size} solidGen=${this.solidGeneration}`)\n }\n if (kind === \"css\") {\n this.crossFileDiagnostics.delete(key)\n this.crossFileResults = null\n this.cssGeneration++\n this.css = null\n this.layout = null\n if (this.log.enabled) this.log.debug(`invalidate CSS: ${key} newCssGen=${this.cssGeneration}`)\n }\n }\n\n /**\n * Invalidate all cached graphs.\n *\n * Called on workspace-level events like config changes.\n */\n invalidateAll(): void {\n if (this.log.enabled) this.log.debug(`invalidateAll: solids=${this.solids.size} solidGen=${this.solidGeneration} cssGen=${this.cssGeneration}`)\n this.solids.clear()\n this.crossFileDiagnostics.clear()\n this.crossFileResults = null\n this.solidGeneration++\n this.cssGeneration++\n this.css = null\n this.layout = null\n }\n\n /**\n * Get all cached SolidGraphs.\n *\n * Returns a snapshot array of all currently-cached graphs.\n * Used by cross-file analysis which needs all SolidGraphs.\n */\n getAllSolidGraphs(): readonly SolidGraph[] {\n if (this.log.enabled) this.log.debug(`getAllSolidGraphs: ${this.solids.size} graphs`)\n const out: SolidGraph[] = new Array(this.solids.size)\n let i = 0\n for (const entry of this.solids.values()) {\n out[i++] = entry.graph\n }\n return out\n }\n\n /**\n * Get the cached CSSGraph, or null if not cached.\n */\n getCachedCSSGraph(): CSSGraph | null {\n return this.css?.graph ?? null\n }\n\n /**\n * Get the cached LayoutGraph, or null if not cached.\n */\n getCachedLayoutGraph(): LayoutGraph | null {\n return this.layout?.graph ?? null\n }\n\n /**\n * Get cached cross-file diagnostics for a file path.\n *\n * Returns the previous cross-file results so single-file-only\n * re-analysis (during typing) can merge them without re-running\n * cross-file rules.\n *\n * @param path Absolute file path\n */\n getCachedCrossFileDiagnostics(path: string): readonly Diagnostic[] {\n return this.crossFileDiagnostics.get(canonicalPath(path)) ?? []\n }\n\n /**\n * Store cross-file diagnostics for a file path.\n *\n * @param path Absolute file path\n * @param diagnostics Cross-file diagnostics for this path\n */\n setCachedCrossFileDiagnostics(path: string, diagnostics: readonly Diagnostic[]): void {\n this.crossFileDiagnostics.set(canonicalPath(path), diagnostics)\n }\n\n /**\n * Get workspace-level cross-file results if the underlying graphs haven't changed.\n *\n * Returns the full per-file map when the solid signature and CSS generation\n * match, meaning no graphs were rebuilt since the last run. Returns null\n * when results are stale and `runCrossFileRules` must re-execute.\n */\n getCachedCrossFileResults(): ReadonlyMap<string, readonly Diagnostic[]> | null {\n if (this.crossFileResults === null) {\n this.log.debug(\"getCachedCrossFileResults: null (no cached results)\")\n return null\n }\n const solidMatch = this.crossFileResults.solidGeneration === this.solidGeneration\n const cssMatch = this.crossFileResults.cssGeneration === this.cssGeneration\n if (solidMatch && cssMatch) {\n if (this.log.enabled) this.log.debug(`getCachedCrossFileResults HIT: ${this.crossFileResults?.byFile.size} files`)\n return this.crossFileResults.byFile\n }\n if (this.log.enabled) this.log.debug(\n `getCachedCrossFileResults MISS: solidMatch=${solidMatch} cssMatch=${cssMatch} `\n + `cachedSolidGen=${this.crossFileResults?.solidGeneration} currentSolidGen=${this.solidGeneration} `\n + `cachedCssGen=${this.crossFileResults?.cssGeneration} currentCssGen=${this.cssGeneration}`,\n )\n return null\n }\n\n /**\n * Store workspace-level cross-file results bucketed by file.\n *\n * Called after `runCrossFileRules` completes. Captures the current\n * solid signature and CSS generation so subsequent lookups are O(1)\n * until a graph changes.\n *\n * @param allDiagnostics All cross-file diagnostics from the workspace run\n */\n setCachedCrossFileResults(allDiagnostics: readonly Diagnostic[]): void {\n const byFile = new Map<string, Diagnostic[]>()\n for (let i = 0, len = allDiagnostics.length; i < len; i++) {\n const d = allDiagnostics[i]\n if (!d) continue\n let arr = byFile.get(d.file)\n if (!arr) {\n arr = []\n byFile.set(d.file, arr)\n }\n arr.push(d)\n }\n this.crossFileResults = {\n solidGeneration: this.solidGeneration,\n cssGeneration: this.cssGeneration,\n byFile,\n }\n if (this.log.enabled) this.log.debug(\n `setCachedCrossFileResults: ${allDiagnostics.length} diags across ${byFile.size} files `\n + `solidGen=${this.solidGeneration} cssGen=${this.cssGeneration}`,\n )\n /* Also update the per-file cache used during typing (when cross-file\n analysis is skipped and previous results are reused). */\n for (const [file, diagnostics] of byFile) {\n this.crossFileDiagnostics.set(file, diagnostics)\n }\n }\n\n /** Number of cached SolidGraphs. */\n get solidCount(): number {\n return this.solids.size\n }\n\n /** The logger instance used by this cache. */\n get logger(): Logger {\n return this.log\n }\n\n\n}\n"],"mappings":"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","names":[]}
|
package/dist/rules-manifest.cjs
CHANGED
|
@@ -715,7 +715,7 @@ var RULES = [
|
|
|
715
715
|
"category": "css-layout",
|
|
716
716
|
"plugin": "cross-file",
|
|
717
717
|
"messages": {
|
|
718
|
-
"misalignedSibling": "
|
|
718
|
+
"misalignedSibling": "Vertically misaligned '{{subject}}' in '{{parent}}'.{{fix}}{{offsetClause}}"
|
|
719
719
|
}
|
|
720
720
|
},
|
|
721
721
|
{
|
|
@@ -1958,7 +1958,7 @@ var RULES_BY_CATEGORY = {
|
|
|
1958
1958
|
"css-animation": [{ "id": "css-no-discrete-transition", "severity": "error", "description": "Disallow transitions on discrete CSS properties.", "fixable": false, "category": "css-animation", "plugin": "css", "messages": { "discreteTransition": "Property `{{property}}` is discrete and should not be transitioned." } }, { "id": "css-no-empty-keyframes", "severity": "error", "description": "Disallow empty @keyframes rules.", "fixable": false, "category": "css-animation", "plugin": "css", "messages": { "emptyKeyframes": "@keyframes `{{name}}` has no effective keyframes." } }, { "id": "no-layout-property-animation", "severity": "warn", "description": "Disallow animating layout-affecting properties.", "fixable": false, "category": "css-animation", "plugin": "css", "messages": { "avoidLayoutAnimation": "Avoid animating layout property `{{property}}`. Prefer transform or opacity to reduce layout thrashing." } }, { "id": "no-transition-all", "severity": "warn", "description": "Disallow transition: all and transition-property: all.", "fixable": false, "category": "css-animation", "plugin": "css", "messages": { "avoidTransitionAll": "Avoid `transition: all`. Transition specific properties to reduce unnecessary style and paint work." } }, { "id": "no-unknown-animation-name", "severity": "error", "description": "Disallow animation names that do not match declared keyframes.", "fixable": false, "category": "css-animation", "plugin": "css", "messages": { "unknownAnimationName": "Animation name `{{name}}` in `{{property}}` does not match any declared @keyframes." } }, { "id": "no-unused-keyframes", "severity": "warn", "description": "Disallow unused @keyframes declarations.", "fixable": false, "category": "css-animation", "plugin": "css", "messages": { "unusedKeyframes": "@keyframes `{{name}}` is never referenced by animation declarations." } }],
|
|
1959
1959
|
"css-cascade": [{ "id": "declaration-no-overridden-within-rule", "severity": "warn", "description": "Disallow duplicate declarations of the same property within a single rule block.", "fixable": false, "category": "css-cascade", "plugin": "css", "messages": { "overriddenWithinRule": "Declaration `{{property}}` is overridden later in the same rule. Keep one final declaration per property." } }, { "id": "media-query-overlap-conflict", "severity": "warn", "description": "Disallow conflicting declarations in partially overlapping media queries.", "fixable": false, "category": "css-cascade", "plugin": "css", "messages": { "mediaOverlapConflict": "Overlapping media queries set different `{{property}}` values for `{{selector}}` in the same overlap range." } }, { "id": "no-descending-specificity-conflict", "severity": "warn", "description": "Disallow lower-specificity selectors after higher-specificity selectors for the same property.", "fixable": false, "category": "css-cascade", "plugin": "css", "messages": { "descendingSpecificity": "Lower-specificity selector `{{laterSelector}}` appears after `{{earlierSelector}}` for `{{property}}`, creating brittle cascade behavior." } }, { "id": "no-layer-order-inversion", "severity": "warn", "description": "Disallow source-order assumptions that are inverted by layer precedence.", "fixable": false, "category": "css-cascade", "plugin": "css", "messages": { "layerOrderInversion": "Declaration for `{{property}}` in selector `{{selector}}` appears later but is overridden by an earlier declaration due to @layer precedence." } }, { "id": "no-redundant-override-pairs", "severity": "warn", "description": "Disallow declarations that are deterministically overridden in the same selector context.", "fixable": false, "category": "css-cascade", "plugin": "css", "messages": { "redundantOverride": "Declaration `{{property}}` is always overridden later by the same selector in the same cascade context." } }],
|
|
1960
1960
|
"css-jsx": [{ "id": "css-no-unreferenced-component-class", "severity": "warn", "description": "Detect CSS classes that are never referenced by static JSX class attributes.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "unreferencedClass": "CSS class '{{className}}' is defined but not referenced by static JSX class attributes" } }, { "id": "jsx-classlist-boolean-values", "severity": "error", "description": "Require classList values to be boolean-like expressions.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "nonBooleanValue": "classList value for `{{name}}` must be boolean." } }, { "id": "jsx-classlist-no-accessor-reference", "severity": "error", "description": "Disallow passing accessor references directly as classList values.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "accessorReference": "Signal accessor `{{name}}` must be called in classList value (use {{name}}())." } }, { "id": "jsx-classlist-no-constant-literals", "severity": "warn", "description": "Disallow classList entries with constant true/false values.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "constantEntry": "classList entry `{{name}}: {{value}}` is constant; move it to static class." } }, { "id": "jsx-classlist-static-keys", "severity": "error", "description": "Require classList keys to be static and non-computed.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "nonStaticKey": "classList key must be statically known for reliable class mapping." } }, { "id": "jsx-layout-classlist-geometry-toggle", "severity": "warn", "description": "Flag classList-driven class toggles that map to layout-affecting CSS geometry changes.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "classListGeometryToggle": "classList toggles '{{className}}', and matching CSS changes layout-affecting '{{property}}', which can cause CLS." } }, { "id": "jsx-layout-fill-image-parent-must-be-sized", "severity": "warn", "description": "Require stable parent size and positioning for fill-image component usage.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "unsizedFillParent": "Fill-image component '{{component}}' is inside a parent without stable size/position; add parent sizing (height/min-height/aspect-ratio) and non-static position to avoid CLS." } }, { "id": "jsx-layout-picture-source-ratio-consistency", "severity": "warn", "description": "Require consistent intrinsic aspect ratios across <picture> sources and fallback image.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "inconsistentPictureRatio": "`<picture>` source ratio {{sourceRatio}} differs from fallback img ratio {{imgRatio}}, which can cause reserved-space mismatch and CLS." } }, { "id": "jsx-layout-unstable-style-toggle", "severity": "warn", "description": "Flag dynamic inline style values on layout-sensitive properties that can trigger CLS.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "unstableLayoutStyleToggle": "Dynamic style value for '{{property}}' can toggle layout geometry at runtime and cause CLS." } }, { "id": "jsx-no-duplicate-class-token-class-classlist", "severity": "warn", "description": "Disallow duplicate class tokens between class and classList on the same JSX element.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "duplicateClassToken": "Class token `{{name}}` appears in both class and classList." } }, { "id": "jsx-no-undefined-css-class", "severity": "error", "description": "Detect undefined CSS class names in JSX", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "undefinedClass": "CSS class '{{className}}' is not defined in project CSS files" } }, { "id": "jsx-style-kebab-case-keys", "severity": "error", "description": "Require kebab-case keys in JSX style object literals.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "kebabStyleKey": "Style key `{{name}}` should be `{{kebab}}` in Solid style objects." } }, { "id": "jsx-style-no-function-values", "severity": "error", "description": "Disallow function values in JSX style objects.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "functionStyleValue": "Style value for `{{name}}` is a function; pass computed value instead." } }, { "id": "jsx-style-no-unused-custom-prop", "severity": "warn", "description": "Detect inline style custom properties that are never consumed by CSS var() references.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "unusedInlineVar": "Inline custom property `{{name}}` is never read via var({{name}})." } }, { "id": "jsx-style-policy", "severity": "warn", "description": "Enforce accessibility policy thresholds on inline JSX style objects.", "fixable": false, "category": "css-jsx", "plugin": "cross-file", "messages": { "fontTooSmall": "Inline style `{{prop}}: {{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for policy `{{policy}}`.", "lineHeightTooSmall": "Inline style `line-height: {{value}}` is below the minimum `{{min}}` for policy `{{policy}}`.", "heightTooSmall": "Inline style `{{prop}}: {{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for interactive elements in policy `{{policy}}`.", "letterSpacingTooSmall": "Inline style `letter-spacing: {{value}}` ({{resolved}}em) is below the minimum `{{min}}em` for policy `{{policy}}`.", "wordSpacingTooSmall": "Inline style `word-spacing: {{value}}` ({{resolved}}em) is below the minimum `{{min}}em` for policy `{{policy}}`." } }],
|
|
1961
|
-
"css-layout": [{ "id": "css-layout-animation-layout-property", "severity": "warn", "description": "Disallow keyframe animations that mutate layout-affecting properties and can trigger CLS.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "animationLayoutProperty": "Animation '{{animation}}' mutates layout-affecting '{{property}}', which can trigger CLS. Prefer transform/opacity or reserve geometry." } }, { "id": "css-layout-box-sizing-toggle-with-chrome", "severity": "warn", "description": "Disallow conditional box-sizing mode toggles when box chrome contributes to geometry shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "boxSizingToggleWithChrome": "Conditional `box-sizing` toggle on '{{tag}}' combines with non-zero padding/border, which can shift layout and trigger CLS." } }, { "id": "css-layout-conditional-display-collapse", "severity": "warn", "description": "Disallow conditional display collapse in flow without reserved geometry.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "conditionalDisplayCollapse": "Conditional display sets '{{display}}' on '{{tag}}' without stable reserved space, which can collapse/expand layout and cause CLS." } }, { "id": "css-layout-conditional-offset-shift", "severity": "warn", "description": "Disallow conditional non-zero block-axis offsets that can trigger layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "conditionalOffsetShift": "Conditional style applies non-zero '{{property}}' offset ({{value}}), which can cause layout shifts when conditions toggle." } }, { "id": "css-layout-conditional-white-space-wrap-shift", "severity": "warn", "description": "Disallow conditional white-space wrapping mode toggles that can trigger CLS.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "conditionalWhiteSpaceShift": "Conditional white-space '{{whiteSpace}}' on '{{tag}}' can reflow text and shift siblings; keep wrapping behavior stable or reserve geometry." } }, { "id": "css-layout-content-visibility-no-intrinsic-size", "severity": "warn", "description": "Require intrinsic size reservation when using content-visibility auto to avoid late layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "missingIntrinsicSize": "`content-visibility: auto` on '{{tag}}' lacks intrinsic size reservation (`contain-intrinsic-size`/min-height/height/aspect-ratio), which can cause CLS." } }, { "id": "css-layout-dynamic-slot-no-reserved-space", "severity": "warn", "description": "Require reserved block space for dynamic content containers to avoid layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "dynamicSlotNoReservedSpace": "Dynamic content container '{{tag}}' does not reserve block space (min-height/height/aspect-ratio/contain-intrinsic-size), which can cause CLS." } }, { "id": "css-layout-font-swap-instability", "severity": "warn", "description": "Require metric overrides for swapping webfonts to reduce layout shifts during font load.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "unstableFontSwap": "`@font-face` for '{{family}}' uses `font-display: {{display}}` without metric overrides (for example `size-adjust`), which can cause CLS when the webfont swaps in." } }, { "id": "css-layout-overflow-anchor-instability", "severity": "warn", "description": "Disallow overflow-anchor none on dynamic or scrollable containers prone to visible layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "unstableOverflowAnchor": "Element '{{tag}}' sets `overflow-anchor: none` on a {{context}} container; disabling scroll anchoring can amplify visible layout shifts." } }, { "id": "css-layout-overflow-mode-toggle-instability", "severity": "warn", "description": "Disallow conditional overflow mode switches that can introduce scrollbar-induced layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "overflowModeToggle": "Conditional overflow mode changes scrolling ('{{overflow}}') on '{{tag}}' without `scrollbar-gutter: stable`, which can trigger CLS." } }, { "id": "css-layout-scrollbar-gutter-instability", "severity": "warn", "description": "Require stable scrollbar gutters for scrollable containers to reduce layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "missingScrollbarGutter": "Scrollable container '{{tag}}' uses overflow auto/scroll without `scrollbar-gutter: stable`, which can trigger CLS when scrollbars appear." } }, { "id": "css-layout-sibling-alignment-outlier", "severity": "warn", "description": "Detect vertical alignment outliers between sibling elements in shared layout containers.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "misalignedSibling": "
|
|
1961
|
+
"css-layout": [{ "id": "css-layout-animation-layout-property", "severity": "warn", "description": "Disallow keyframe animations that mutate layout-affecting properties and can trigger CLS.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "animationLayoutProperty": "Animation '{{animation}}' mutates layout-affecting '{{property}}', which can trigger CLS. Prefer transform/opacity or reserve geometry." } }, { "id": "css-layout-box-sizing-toggle-with-chrome", "severity": "warn", "description": "Disallow conditional box-sizing mode toggles when box chrome contributes to geometry shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "boxSizingToggleWithChrome": "Conditional `box-sizing` toggle on '{{tag}}' combines with non-zero padding/border, which can shift layout and trigger CLS." } }, { "id": "css-layout-conditional-display-collapse", "severity": "warn", "description": "Disallow conditional display collapse in flow without reserved geometry.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "conditionalDisplayCollapse": "Conditional display sets '{{display}}' on '{{tag}}' without stable reserved space, which can collapse/expand layout and cause CLS." } }, { "id": "css-layout-conditional-offset-shift", "severity": "warn", "description": "Disallow conditional non-zero block-axis offsets that can trigger layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "conditionalOffsetShift": "Conditional style applies non-zero '{{property}}' offset ({{value}}), which can cause layout shifts when conditions toggle." } }, { "id": "css-layout-conditional-white-space-wrap-shift", "severity": "warn", "description": "Disallow conditional white-space wrapping mode toggles that can trigger CLS.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "conditionalWhiteSpaceShift": "Conditional white-space '{{whiteSpace}}' on '{{tag}}' can reflow text and shift siblings; keep wrapping behavior stable or reserve geometry." } }, { "id": "css-layout-content-visibility-no-intrinsic-size", "severity": "warn", "description": "Require intrinsic size reservation when using content-visibility auto to avoid late layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "missingIntrinsicSize": "`content-visibility: auto` on '{{tag}}' lacks intrinsic size reservation (`contain-intrinsic-size`/min-height/height/aspect-ratio), which can cause CLS." } }, { "id": "css-layout-dynamic-slot-no-reserved-space", "severity": "warn", "description": "Require reserved block space for dynamic content containers to avoid layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "dynamicSlotNoReservedSpace": "Dynamic content container '{{tag}}' does not reserve block space (min-height/height/aspect-ratio/contain-intrinsic-size), which can cause CLS." } }, { "id": "css-layout-font-swap-instability", "severity": "warn", "description": "Require metric overrides for swapping webfonts to reduce layout shifts during font load.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "unstableFontSwap": "`@font-face` for '{{family}}' uses `font-display: {{display}}` without metric overrides (for example `size-adjust`), which can cause CLS when the webfont swaps in." } }, { "id": "css-layout-overflow-anchor-instability", "severity": "warn", "description": "Disallow overflow-anchor none on dynamic or scrollable containers prone to visible layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "unstableOverflowAnchor": "Element '{{tag}}' sets `overflow-anchor: none` on a {{context}} container; disabling scroll anchoring can amplify visible layout shifts." } }, { "id": "css-layout-overflow-mode-toggle-instability", "severity": "warn", "description": "Disallow conditional overflow mode switches that can introduce scrollbar-induced layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "overflowModeToggle": "Conditional overflow mode changes scrolling ('{{overflow}}') on '{{tag}}' without `scrollbar-gutter: stable`, which can trigger CLS." } }, { "id": "css-layout-scrollbar-gutter-instability", "severity": "warn", "description": "Require stable scrollbar gutters for scrollable containers to reduce layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "missingScrollbarGutter": "Scrollable container '{{tag}}' uses overflow auto/scroll without `scrollbar-gutter: stable`, which can trigger CLS when scrollbars appear." } }, { "id": "css-layout-sibling-alignment-outlier", "severity": "warn", "description": "Detect vertical alignment outliers between sibling elements in shared layout containers.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "misalignedSibling": "Vertically misaligned '{{subject}}' in '{{parent}}'.{{fix}}{{offsetClause}}" } }, { "id": "css-layout-stateful-box-model-shift", "severity": "warn", "description": "Disallow stateful selector changes that alter element geometry and trigger layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "statefulBoxModelShift": "State selector '{{selector}}' changes layout-affecting '{{property}}'. Keep geometry stable across states to avoid CLS." } }, { "id": "css-layout-transition-layout-property", "severity": "warn", "description": "Disallow transitions that animate layout-affecting geometry properties.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "transitionLayoutProperty": "Transition '{{property}}' in '{{declaration}}' animates layout-affecting geometry. Prefer transform/opacity to avoid CLS." } }, { "id": "css-layout-unsized-replaced-element", "severity": "warn", "description": "Require stable reserved geometry for replaced media elements to prevent layout shifts.", "fixable": false, "category": "css-layout", "plugin": "cross-file", "messages": { "unsizedReplacedElement": "Replaced element '{{tag}}' has no stable reserved size (width/height or aspect-ratio with a dimension), which can cause CLS." } }],
|
|
1962
1962
|
"css-property": [{ "id": "css-no-custom-property-cycle", "severity": "error", "description": "Disallow cycles in custom property references.", "fixable": false, "category": "css-property", "plugin": "css", "messages": { "variableCycle": "Custom property cycle detected involving `{{name}}`." } }, { "id": "css-no-hardcoded-z-index", "severity": "warn", "description": "Disallow hardcoded positive z-index literals.", "fixable": false, "category": "css-property", "plugin": "css", "messages": { "hardcodedZ": "Use a z-index token variable instead of literal `{{value}}`." } }, { "id": "css-no-legacy-vh-100", "severity": "warn", "description": "Disallow 100vh in viewport sizing declarations.", "fixable": false, "category": "css-property", "plugin": "css", "messages": { "avoidLegacyVh": "Use 100dvh/100svh instead of `100vh` for mobile-safe viewport sizing." } }, { "id": "css-z-index-requires-positioned-context", "severity": "warn", "description": "Require positioned context when using z-index.", "fixable": false, "category": "css-property", "plugin": "css", "messages": { "zIndexNoContext": "`z-index` has no guaranteed effect without a positioned context." } }, { "id": "no-important", "severity": "warn", "description": "Disallow !important declarations.", "fixable": false, "category": "css-property", "plugin": "css", "messages": { "avoidImportant": "Avoid `!important` on `{{property}}`. It increases override cost and usually signals specificity debt." } }, { "id": "no-unresolved-custom-properties", "severity": "error", "description": "Disallow unresolved custom property references.", "fixable": false, "category": "css-property", "plugin": "css", "messages": { "unresolvedCustomProperty": "Custom property reference `{{name}}` is unresolved in `{{property}}`. Define it or provide a fallback value." } }, { "id": "no-unused-custom-properties", "severity": "warn", "description": "Disallow unused CSS custom properties.", "fixable": false, "category": "css-property", "plugin": "css", "messages": { "unusedCustomProperty": "Custom property `{{name}}` is never referenced within the project CSS." } }],
|
|
1963
1963
|
"css-selector": [{ "id": "no-complex-selectors", "severity": "warn", "description": "Disallow deep selectors that are expensive to match.", "fixable": false, "category": "css-selector", "plugin": "css", "messages": { "selectorTooDeep": "Selector `{{selector}}` has depth {{depth}}. Deep selectors increase style recalculation cost and are fragile across component rerenders." } }, { "id": "no-duplicate-selectors", "severity": "warn", "description": "Disallow duplicate selector blocks.", "fixable": false, "category": "css-selector", "plugin": "css", "messages": { "duplicateSelector": "Selector `{{selector}}` is duplicated {{count}} times. Merge declarations to avoid cascade ambiguity." } }, { "id": "no-id-selectors", "severity": "warn", "description": "Disallow ID selectors.", "fixable": false, "category": "css-selector", "plugin": "css", "messages": { "avoidId": "Avoid ID selector in `{{selector}}`. IDs raise specificity and make component-level styling harder to maintain." } }, { "id": "selector-max-attribute-and-universal", "severity": "off", "description": "Disallow selectors with attribute or universal selectors beyond configured limits.", "fixable": false, "category": "css-selector", "plugin": "css", "messages": { "tooManyAttributes": "Selector `{{selector}}` uses {{count}} attribute selector(s). Maximum allowed is {{max}}.", "tooManyUniversals": "Selector `{{selector}}` uses {{count}} universal selector(s). Maximum allowed is {{max}}." } }, { "id": "selector-max-specificity", "severity": "warn", "description": "Disallow selectors that exceed a specificity threshold.", "fixable": false, "category": "css-selector", "plugin": "css", "messages": { "maxSpecificity": "Selector `{{selector}}` specificity {{specificity}} exceeds max {{max}}. Reduce selector weight to keep the cascade predictable." } }],
|
|
1964
1964
|
"css-structure": [{ "id": "css-no-empty-rule", "severity": "warn", "description": "Disallow empty CSS rules.", "fixable": false, "category": "css-structure", "plugin": "css", "messages": { "emptyRule": "Empty rule `{{selector}}` should be removed." } }, { "id": "css-no-unknown-container-name", "severity": "error", "description": "Disallow unknown named containers in @container queries.", "fixable": false, "category": "css-structure", "plugin": "css", "messages": { "unknownContainer": "Unknown container name `{{name}}` in @container query." } }, { "id": "css-no-unused-container-name", "severity": "warn", "description": "Disallow unused named containers.", "fixable": false, "category": "css-structure", "plugin": "css", "messages": { "unusedContainer": "Container name `{{name}}` is declared but never queried." } }, { "id": "layer-requirement-for-component-rules", "severity": "warn", "description": "Require style rules to be inside @layer when the file defines layers.", "fixable": false, "category": "css-structure", "plugin": "css", "messages": { "missingLayer": "Rule `{{selector}}` is not inside any @layer block while this file uses @layer. Place component rules inside an explicit layer." } }],
|