@drskillissue/ganko 0.2.6 → 0.2.8

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/index.js CHANGED
@@ -2,9 +2,10 @@ import {
2
2
  RULES,
3
3
  RULES_BY_CATEGORY,
4
4
  getRule
5
- } from "./chunk-5OEDGKHL.js";
5
+ } from "./chunk-NFDA6LAI.js";
6
6
  import {
7
7
  CSSPlugin,
8
+ Level,
8
9
  SolidPlugin,
9
10
  analyzeInput,
10
11
  buildCSSGraph,
@@ -19,7 +20,7 @@ import {
19
20
  runSolidRules,
20
21
  scanDependencyCustomProperties,
21
22
  setActivePolicy
22
- } from "./chunk-FTIRRRQY.js";
23
+ } from "./chunk-SSLKXOHI.js";
23
24
  import "./chunk-EGRHWZRV.js";
24
25
 
25
26
  // src/runner.ts
@@ -87,7 +88,7 @@ var GraphCache = class {
87
88
  const key = canonicalPath(path);
88
89
  const cached = this.solids.get(key);
89
90
  const hit = cached !== void 0 && cached.version === version;
90
- if (this.log.enabled) this.log.debug(`hasSolidGraph: ${key} v=${version} cached=${cached?.version ?? "none"} hit=${hit} (${this.solids.size} total)`);
91
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`hasSolidGraph: ${key} v=${version} cached=${cached?.version ?? "none"} hit=${hit} (${this.solids.size} total)`);
91
92
  return hit;
92
93
  }
93
94
  /**
@@ -104,7 +105,7 @@ var GraphCache = class {
104
105
  const key = canonicalPath(path);
105
106
  this.solids.set(key, { version, graph });
106
107
  this.solidGeneration++;
107
- if (this.log.enabled) this.log.debug(`setSolidGraph: ${key} v=${version} (${this.solids.size} total) solidGen=${this.solidGeneration}`);
108
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`setSolidGraph: ${key} v=${version} (${this.solids.size} total) solidGen=${this.solidGeneration}`);
108
109
  }
109
110
  /**
110
111
  * Get a cached SolidGraph without building on miss.
@@ -120,10 +121,10 @@ var GraphCache = class {
120
121
  const key = canonicalPath(path);
121
122
  const cached = this.solids.get(key);
122
123
  if (cached !== void 0 && cached.version === version) {
123
- if (this.log.enabled) this.log.debug(`getCachedSolidGraph HIT: ${key} v=${version}`);
124
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getCachedSolidGraph HIT: ${key} v=${version}`);
124
125
  return cached.graph;
125
126
  }
126
- if (this.log.enabled) this.log.debug(`getCachedSolidGraph MISS: ${key} v=${version}`);
127
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getCachedSolidGraph MISS: ${key} v=${version}`);
127
128
  return null;
128
129
  }
129
130
  /**
@@ -140,15 +141,15 @@ var GraphCache = class {
140
141
  const key = canonicalPath(path);
141
142
  const cached = this.solids.get(key);
142
143
  if (cached !== void 0 && cached.version === version) {
143
- if (this.log.enabled) this.log.debug(`getSolidGraph HIT: ${key} v=${version}`);
144
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getSolidGraph HIT: ${key} v=${version}`);
144
145
  return cached.graph;
145
146
  }
146
- if (this.log.enabled) this.log.debug(`getSolidGraph MISS: ${key} v=${version} (was ${cached?.version ?? "uncached"})`);
147
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getSolidGraph MISS: ${key} v=${version} (was ${cached?.version ?? "uncached"})`);
147
148
  const t0 = performance.now();
148
149
  const graph = build();
149
150
  this.solids.set(key, { version, graph });
150
151
  this.solidGeneration++;
151
- if (this.log.enabled) this.log.debug(`getSolidGraph BUILT: ${key} v=${version} in ${performance.now() - t0}ms (${this.solids.size} total) solidGen=${this.solidGeneration}`);
152
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getSolidGraph BUILT: ${key} v=${version} in ${performance.now() - t0}ms (${this.solids.size} total) solidGen=${this.solidGeneration}`);
152
153
  return graph;
153
154
  }
154
155
  /**
@@ -162,14 +163,14 @@ var GraphCache = class {
162
163
  */
163
164
  getCSSGraph(build) {
164
165
  if (this.css !== null && this.css.generation === this.cssGeneration) {
165
- if (this.log.enabled) this.log.debug(`getCSSGraph HIT: gen=${this.cssGeneration}`);
166
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getCSSGraph HIT: gen=${this.cssGeneration}`);
166
167
  return this.css.graph;
167
168
  }
168
- if (this.log.enabled) this.log.debug(`getCSSGraph MISS: currentGen=${this.cssGeneration} cachedGen=${this.css?.generation ?? "none"}`);
169
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getCSSGraph MISS: currentGen=${this.cssGeneration} cachedGen=${this.css?.generation ?? "none"}`);
169
170
  const t0 = performance.now();
170
171
  const graph = build();
171
172
  this.css = { generation: this.cssGeneration, graph };
172
- if (this.log.enabled) this.log.debug(`getCSSGraph BUILT: gen=${this.cssGeneration} in ${performance.now() - t0}ms`);
173
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getCSSGraph BUILT: gen=${this.cssGeneration} in ${performance.now() - t0}ms`);
173
174
  return graph;
174
175
  }
175
176
  /**
@@ -184,10 +185,10 @@ var GraphCache = class {
184
185
  const solidGen = this.solidGeneration;
185
186
  const cssGen = this.cssGeneration;
186
187
  if (this.layout !== null && this.layout.solidGeneration === solidGen && this.layout.cssGeneration === cssGen) {
187
- if (this.log.enabled) this.log.debug(`getLayoutGraph HIT: solidGen=${solidGen} cssGen=${cssGen}`);
188
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getLayoutGraph HIT: solidGen=${solidGen} cssGen=${cssGen}`);
188
189
  return this.layout.graph;
189
190
  }
190
- if (this.log.enabled) this.log.debug(
191
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(
191
192
  `getLayoutGraph MISS: solidGen=${solidGen} cssGen=${cssGen} cached=${this.layout !== null}`
192
193
  );
193
194
  const t0 = performance.now();
@@ -197,7 +198,7 @@ var GraphCache = class {
197
198
  cssGeneration: cssGen,
198
199
  graph
199
200
  };
200
- if (this.log.enabled) this.log.debug(`getLayoutGraph BUILT: in ${performance.now() - t0}ms`);
201
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getLayoutGraph BUILT: in ${performance.now() - t0}ms`);
201
202
  return graph;
202
203
  }
203
204
  /**
@@ -212,7 +213,7 @@ var GraphCache = class {
212
213
  invalidate(path) {
213
214
  const key = canonicalPath(path);
214
215
  const kind = classifyFile(key);
215
- if (this.log.enabled) this.log.debug(`invalidate: ${key} kind=${kind} solids=${this.solids.size} hasCrossFileResults=${this.crossFileResults !== null} hasLayout=${this.layout !== null}`);
216
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`invalidate: ${key} kind=${kind} solids=${this.solids.size} hasCrossFileResults=${this.crossFileResults !== null} hasLayout=${this.layout !== null}`);
216
217
  if (kind === "solid") {
217
218
  const had = this.solids.has(key);
218
219
  this.solids.delete(key);
@@ -220,7 +221,7 @@ var GraphCache = class {
220
221
  this.crossFileResults = null;
221
222
  this.solidGeneration++;
222
223
  this.layout = null;
223
- if (this.log.enabled) this.log.debug(`invalidate SOLID: ${key} wasInCache=${had} solids=${this.solids.size} solidGen=${this.solidGeneration}`);
224
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`invalidate SOLID: ${key} wasInCache=${had} solids=${this.solids.size} solidGen=${this.solidGeneration}`);
224
225
  }
225
226
  if (kind === "css") {
226
227
  this.crossFileDiagnostics.delete(key);
@@ -228,7 +229,7 @@ var GraphCache = class {
228
229
  this.cssGeneration++;
229
230
  this.css = null;
230
231
  this.layout = null;
231
- if (this.log.enabled) this.log.debug(`invalidate CSS: ${key} newCssGen=${this.cssGeneration}`);
232
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`invalidate CSS: ${key} newCssGen=${this.cssGeneration}`);
232
233
  }
233
234
  }
234
235
  /**
@@ -237,7 +238,7 @@ var GraphCache = class {
237
238
  * Called on workspace-level events like config changes.
238
239
  */
239
240
  invalidateAll() {
240
- if (this.log.enabled) this.log.debug(`invalidateAll: solids=${this.solids.size} solidGen=${this.solidGeneration} cssGen=${this.cssGeneration}`);
241
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`invalidateAll: solids=${this.solids.size} solidGen=${this.solidGeneration} cssGen=${this.cssGeneration}`);
241
242
  this.solids.clear();
242
243
  this.crossFileDiagnostics.clear();
243
244
  this.crossFileResults = null;
@@ -253,7 +254,7 @@ var GraphCache = class {
253
254
  * Used by cross-file analysis which needs all SolidGraphs.
254
255
  */
255
256
  getAllSolidGraphs() {
256
- if (this.log.enabled) this.log.debug(`getAllSolidGraphs: ${this.solids.size} graphs`);
257
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getAllSolidGraphs: ${this.solids.size} graphs`);
257
258
  const out = new Array(this.solids.size);
258
259
  let i = 0;
259
260
  for (const entry of this.solids.values()) {
@@ -309,10 +310,10 @@ var GraphCache = class {
309
310
  const solidMatch = this.crossFileResults.solidGeneration === this.solidGeneration;
310
311
  const cssMatch = this.crossFileResults.cssGeneration === this.cssGeneration;
311
312
  if (solidMatch && cssMatch) {
312
- if (this.log.enabled) this.log.debug(`getCachedCrossFileResults HIT: ${this.crossFileResults?.byFile.size} files`);
313
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getCachedCrossFileResults HIT: ${this.crossFileResults?.byFile.size} files`);
313
314
  return this.crossFileResults.byFile;
314
315
  }
315
- if (this.log.enabled) this.log.debug(
316
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(
316
317
  `getCachedCrossFileResults MISS: solidMatch=${solidMatch} cssMatch=${cssMatch} cachedSolidGen=${this.crossFileResults?.solidGeneration} currentSolidGen=${this.solidGeneration} cachedCssGen=${this.crossFileResults?.cssGeneration} currentCssGen=${this.cssGeneration}`
317
318
  );
318
319
  return null;
@@ -343,7 +344,7 @@ var GraphCache = class {
343
344
  cssGeneration: this.cssGeneration,
344
345
  byFile
345
346
  };
346
- if (this.log.enabled) this.log.debug(
347
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(
347
348
  `setCachedCrossFileResults: ${allDiagnostics.length} diags across ${byFile.size} files solidGen=${this.solidGeneration} cssGen=${this.cssGeneration}`
348
349
  );
349
350
  this.crossFileDiagnostics.clear();
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 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 /* Replace the per-file cache used during typing (when cross-file\n analysis is skipped and previous results are reused). Must clear\n first — files that previously had cross-file diagnostics but no\n longer do must not retain stale entries. */\n this.crossFileDiagnostics.clear()\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, Level } 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.isLevelEnabled(Level.Debug)) 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.isLevelEnabled(Level.Debug)) 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.isLevelEnabled(Level.Debug)) this.log.debug(`getCachedSolidGraph HIT: ${key} v=${version}`)\n return cached.graph\n }\n if (this.log.isLevelEnabled(Level.Debug)) 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.isLevelEnabled(Level.Debug)) this.log.debug(`getSolidGraph HIT: ${key} v=${version}`)\n return cached.graph\n }\n\n if (this.log.isLevelEnabled(Level.Debug)) 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.isLevelEnabled(Level.Debug)) 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.isLevelEnabled(Level.Debug)) this.log.debug(`getCSSGraph HIT: gen=${this.cssGeneration}`)\n return this.css.graph\n }\n\n if (this.log.isLevelEnabled(Level.Debug)) 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.isLevelEnabled(Level.Debug)) 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.isLevelEnabled(Level.Debug)) this.log.debug(`getLayoutGraph HIT: solidGen=${solidGen} cssGen=${cssGen}`)\n return this.layout.graph\n }\n\n if (this.log.isLevelEnabled(Level.Debug)) 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.isLevelEnabled(Level.Debug)) 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.isLevelEnabled(Level.Debug)) 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.isLevelEnabled(Level.Debug)) 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.isLevelEnabled(Level.Debug)) 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.isLevelEnabled(Level.Debug)) 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.isLevelEnabled(Level.Debug)) 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.isLevelEnabled(Level.Debug)) this.log.debug(`getCachedCrossFileResults HIT: ${this.crossFileResults?.byFile.size} files`)\n return this.crossFileResults.byFile\n }\n if (this.log.isLevelEnabled(Level.Debug)) 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.isLevelEnabled(Level.Debug)) this.log.debug(\n `setCachedCrossFileResults: ${allDiagnostics.length} diags across ${byFile.size} files `\n + `solidGen=${this.solidGeneration} cssGen=${this.cssGeneration}`,\n )\n /* Replace the per-file cache used during typing (when cross-file\n analysis is skipped and previous results are reused). Must clear\n first — files that previously had cross-file diagnostics but no\n longer do must not retain stale entries. */\n this.crossFileDiagnostics.clear()\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":[]}
@@ -260,19 +260,6 @@ var RULES = [
260
260
  "paragraphSpacingTooSmall": "Paragraph spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` ({{minMultiplier}}\xD7 font-size) for policy `{{policy}}`."
261
261
  }
262
262
  },
263
- {
264
- "id": "css-policy-touch-target",
265
- "severity": "warn",
266
- "description": "Enforce minimum interactive element sizes per accessibility policy.",
267
- "fixable": false,
268
- "category": "css-a11y",
269
- "plugin": "css",
270
- "messages": {
271
- "heightTooSmall": "`{{property}}` of `{{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for `{{element}}` elements in policy `{{policy}}`.",
272
- "widthTooSmall": "`{{property}}` of `{{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for `{{element}}` elements in policy `{{policy}}`.",
273
- "paddingTooSmall": "Horizontal padding `{{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for `{{element}}` elements in policy `{{policy}}`."
274
- }
275
- },
276
263
  {
277
264
  "id": "css-policy-typography",
278
265
  "severity": "warn",
@@ -296,6 +283,21 @@ var RULES = [
296
283
  "missingReducedMotion": "Animated selector `{{selector}}` lacks prefers-reduced-motion override."
297
284
  }
298
285
  },
286
+ {
287
+ "id": "jsx-layout-policy-touch-target",
288
+ "severity": "warn",
289
+ "description": "Enforce minimum interactive element sizes per accessibility policy via resolved layout signals.",
290
+ "fixable": false,
291
+ "category": "css-a11y",
292
+ "plugin": "cross-file",
293
+ "messages": {
294
+ "heightTooSmall": "`{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.",
295
+ "widthTooSmall": "`{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.",
296
+ "paddingTooSmall": "Horizontal padding `{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.",
297
+ "noReservedBlockSize": "Interactive element `<{{tag}}>` has no declared height (minimum `{{min}}px` required by policy `{{policy}}`). The element is content-sized and may not meet the touch-target threshold.",
298
+ "noReservedInlineSize": "Interactive element `<{{tag}}>` has no declared width (minimum `{{min}}px` required by policy `{{policy}}`). The element is content-sized and may not meet the touch-target threshold."
299
+ }
300
+ },
299
301
  {
300
302
  "id": "css-no-discrete-transition",
301
303
  "severity": "error",
@@ -1954,7 +1956,7 @@ var RULES = [
1954
1956
  ];
1955
1957
  var RULES_BY_CATEGORY = {
1956
1958
  "correctness": [{ "id": "avoid-conditional-spreads", "severity": "error", "description": "Disallow conditional spread operators that create empty objects. Patterns like `...(condition ? {...} : {})` are fragile and create unnecessary object creations.", "fixable": false, "category": "correctness", "plugin": "solid", "messages": { "avoidConditionalSpread": "Avoid conditional spread with empty object fallback. Instead of `...(cond ? {...} : {})`, build the object first with conditional property assignment, then spread once.", "avoidLogicalAndSpread": "Avoid logical AND spread pattern. Instead of `...(cond && {...})`, use explicit conditional property assignment for clarity." } }, { "id": "avoid-non-null-assertions", "severity": "error", "description": "Disallow non-null assertion operator (`!`). Use optional chaining, nullish coalescing, or proper type narrowing instead.", "fixable": true, "category": "correctness", "plugin": "solid", "messages": { "avoidNonNull": 'Avoid non-null assertion on "{{name}}". Non-null assertions bypass type safety. Use optional chaining (`?.`), nullish coalescing (`??`), or proper type narrowing instead.' } }, { "id": "avoid-object-assign", "severity": "error", "description": "Disallow Object.assign(). Prefer object spread syntax or structuredClone() for copying objects.", "fixable": true, "category": "correctness", "plugin": "solid", "messages": { "avoidMerge": "Avoid Object.assign() for merging. Use object spread syntax { ...obj } instead.", "avoidMutation": "Avoid Object.assign() for mutation. Consider immutable patterns like { ...existing, ...props }." } }, { "id": "avoid-object-spread", "severity": "error", "description": "Disallow object spread operators that break Solid's fine-grained reactivity.", "fixable": true, "category": "correctness", "plugin": "solid", "messages": { "avoidObjectCopy": "Avoid object spread for copying. Use direct property access.", "avoidObjectMerge": "Avoid object spread for merging. Use mergeProps() from 'solid-js'.", "avoidObjectUpdate": "Avoid object spread for updates. Use produce() or direct assignment.", "avoidJsxSpread": "Avoid JSX prop spreading. Use splitProps() to separate props.", "avoidRestDestructure": "Avoid rest destructuring. Use splitProps() from 'solid-js'.", "avoidPropsSpread": "Spreading props breaks reactivity. Use splitProps() to separate known props.", "avoidStoreSpread": "Spreading store creates a static snapshot. Access properties directly.", "avoidSignalSpread": "Spreading signal result captures current value. Wrap in createMemo().", "avoidClassListSpread": "Spreading in classList breaks reactivity. Wrap in createMemo().", "avoidStyleSpread": "Spreading in style breaks reactivity. Wrap in createMemo().", "unnecessarySplitProps": "Unnecessary splitProps with empty array. Remove it and use {{source}} directly." } }, { "id": "avoid-type-casting", "severity": "error", "description": "Disallow type casting methods that bypass TypeScript's type safety. Includes unnecessary casts, double assertions, casting to any, type predicates, and unsafe generic assertions.", "fixable": true, "category": "correctness", "plugin": "solid", "messages": { "unnecessaryCast": `Unnecessary type assertion: "{{name}}" is already of type "{{exprType}}", which is assignable to "{{type}}". Remove the cast - it adds noise and suggests you don't understand the types.`, "doubleAssertion": 'Double assertion detected: "{{name}}" is cast through unknown/any to "{{type}}". This bypasses type safety. You are creating sloppy architecture.', "castToAny": 'Casting "{{name}}" to `any` disables all type checking. Use `unknown` with proper type guards, or fix the underlying type issue.', "castToUnknown": "Casting to `unknown` requires runtime type checks before use. You are creating sloppy architecture.", "simpleAssertion": 'Type assertion on "{{name}}" to "{{type}}" bypasses type checking. Why are you doing this? Do you EVEN need this? This is sloppy architecture.', "assertionInLoop": 'Type assertion on "{{name}}" inside a loop. Repeated casts to "{{type}}" without validation can mask type errors. Consider validating the type once before the loop.', "importAssertion": 'Type assertion on dynamic import to "{{type}}". Import types should be validated at runtime or use proper module type declarations.', "typePredicate": 'Type predicate function asserts "{{param}}" is "{{type}}". Why are you doing this? Do you EVEN need this? This is sloppy architecture.', "unsafeGeneric": 'Casting to generic type parameter "{{typeParam}}" without runtime validation. The function returns an unverified type. This is sloppy architecture.' } }, { "id": "avoid-unsafe-type-annotations", "severity": "error", "description": "Disallow `any` and `unknown` in value-level type annotation positions (parameters, returns, variables, properties)", "fixable": false, "category": "correctness", "plugin": "solid", "messages": { "anyParameter": "Parameter '{{name}}' is typed `any`{{inFunction}}. This disables type checking for all callers. Use a specific type, a generic, or `unknown` with proper type narrowing.", "anyReturn": "Function '{{name}}' returns `any`. This disables type checking for all callers. Use a specific return type.", "anyVariable": "Variable '{{name}}' is typed `any`. This disables all type checking on this variable. Use a specific type or `unknown` with type narrowing.", "anyProperty": "Property '{{name}}' is typed `any`. This disables type checking for all accesses. Use a specific type.", "unknownParameter": "Parameter '{{name}}' is typed `unknown`{{inFunction}}. Callers can pass anything and the function body requires type narrowing on every use. Use a specific type or a generic constraint.", "unknownReturn": "Function '{{name}}' returns `unknown`. Callers must narrow the return value before use. Use a specific return type or a generic.", "unknownVariable": "Variable '{{name}}' is typed `unknown`. Every use requires type narrowing. Use a specific type or parse the value at the boundary.", "unknownProperty": "Property '{{name}}' is typed `unknown`. Every access requires type narrowing. Use a specific type." } }, { "id": "event-handlers", "severity": "error", "description": "Enforce naming DOM element event handlers consistently and prevent Solid's analysis from misunderstanding whether a prop should be an event handler.", "fixable": true, "category": "correctness", "plugin": "solid", "messages": { "detectedAttr": 'The "{{name}}" prop looks like an event handler but has a static value ({{staticValue}}), so Solid will treat it as an attribute instead of attaching an event listener. Use attr:{{name}} to make this explicit, or provide a function value.', "naming": 'The "{{name}}" prop is ambiguous. Solid cannot determine if this is an event handler or an attribute. Use {{handlerName}} for an event handler, or {{attrName}} for an attribute.', "capitalization": 'The "{{name}}" prop should be {{fixedName}} for Solid to recognize it as an event handler. Event handlers use camelCase with an uppercase letter after "on".', "nonstandard": 'The "{{name}}" prop uses a nonstandard event name. Use {{fixedName}} instead, which is the standard DOM event name that Solid recognizes.', "makeHandler": "Change {{name}} to {{handlerName}} (event handler).", "makeAttr": "Change {{name}} to {{attrName}} (attribute).", "spreadHandler": 'The "{{name}}" prop is being spread into JSX, which prevents Solid from attaching it as an event listener. Add it directly as a JSX attribute instead: {{name}}={...}.' } }, { "id": "missing-jsdoc-comments", "severity": "error", "description": "Require JSDoc comments on functions with appropriate tags for parameters, return values, and throws.", "fixable": false, "category": "correctness", "plugin": "solid", "messages": { "missingJsdoc": "Function '{{name}}' is missing a JSDoc comment.", "missingParam": "JSDoc for '{{name}}' is missing @param tag for '{{param}}'.", "missingReturn": "JSDoc for '{{name}}' is missing @returns tag.", "missingThrows": "JSDoc for '{{name}}' is missing @throws tag.", "missingExample": "JSDoc for '{{name}}' is missing @example tag.", "missingClassJsdoc": "Class '{{name}}' is missing a JSDoc comment.", "missingPropertyJsdoc": "Property '{{name}}' is missing a JSDoc comment." } }, { "id": "no-ai-slop-comments", "severity": "error", "description": "Disallow comments containing specified forbidden words or phrases. Useful for enforcing comment style guidelines and detecting AI-generated boilerplate.", "fixable": true, "category": "correctness", "plugin": "solid", "messages": { "forbiddenWord": "Comment contains forbidden word '{{word}}'." } }, { "id": "no-array-handlers", "severity": "error", "description": "Disallow array handlers in JSX event properties.", "fixable": false, "category": "correctness", "plugin": "solid", "messages": { "noArrayHandlers": 'Passing an array to "{{handlerName}}" is type-unsafe. The array syntax `[handler, data]` passes data as the first argument, making the event object the second argument. Use a closure instead: `{{handlerName}}={() => handler(data)}`.' } }, { "id": "no-banner-comments", "severity": "error", "description": "Disallow banner-style comments with repeated separator characters.", "fixable": true, "category": "correctness", "plugin": "solid", "messages": { "banner": "Avoid banner-style comments with repeated separator characters. Use simple comments instead." } }, { "id": "no-destructure", "severity": "error", "description": "Disallow destructuring props in Solid components. Props must be accessed via property access (props.x) to preserve reactivity.", "fixable": false, "category": "correctness", "plugin": "solid", "messages": { "noDestructure": "Destructuring component props breaks Solid's reactivity. Props are reactive getters, so `{ a }` captures the value at component creation time and won't update. Use `props.a` to access props reactively.", "noDestructureWithDefaults": "Destructuring component props breaks Solid's reactivity. For default values, use `mergeProps({ a: defaultValue }, props)` instead of `{ a = defaultValue }`.", "noDestructureWithRest": "Destructuring component props breaks Solid's reactivity. For rest patterns, use `splitProps(props, ['a', 'b'])` instead of `{ a, b, ...rest }`.", "noDestructureWithBoth": "Destructuring component props breaks Solid's reactivity. For default values with rest, use `splitProps(mergeProps({ a: defaultValue }, props), ['a'])` to combine both patterns." } }, { "id": "no-inline-imports", "severity": "error", "description": "Disallow inline type imports. Import types at the top of the file for clarity and maintainability.", "fixable": false, "category": "correctness", "plugin": "solid", "messages": { "inlineImport": "Avoid inline imports. Import `{{specifier}}` at the top of the file instead." } }, { "id": "string-concat-in-loop", "severity": "error", "description": "Disallow string concatenation with += inside loops. Use array.push() and .join() instead.", "fixable": false, "category": "correctness", "plugin": "solid", "messages": { "stringConcatInLoop": "Avoid string concatenation with += inside loops. Use an array with .push() and .join() instead." } }],
1957
- "css-a11y": [{ "id": "css-no-outline-none-without-focus-visible", "severity": "error", "description": "Disallow removing outline without explicit focus-visible replacement.", "fixable": false, "category": "css-a11y", "plugin": "css", "messages": { "missingFocusVisible": "Focus outline removed without matching `:focus-visible` replacement." } }, { "id": "css-policy-contrast", "severity": "warn", "description": "Enforce minimum contrast ratio between foreground and background colors per accessibility policy.", "fixable": false, "category": "css-a11y", "plugin": "css", "messages": { "insufficientContrast": "Contrast ratio `{{ratio}}:1` between `{{fg}}` and `{{bg}}` is below the minimum `{{min}}:1` for `{{textSize}}` text in policy `{{policy}}`." } }, { "id": "css-policy-spacing", "severity": "warn", "description": "Enforce minimum letter-spacing, word-spacing, and paragraph spacing per accessibility policy.", "fixable": false, "category": "css-a11y", "plugin": "css", "messages": { "letterSpacingTooSmall": "Letter spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` for policy `{{policy}}`.", "wordSpacingTooSmall": "Word spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` for policy `{{policy}}`.", "paragraphSpacingTooSmall": "Paragraph spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` ({{minMultiplier}}\xD7 font-size) for policy `{{policy}}`." } }, { "id": "css-policy-touch-target", "severity": "warn", "description": "Enforce minimum interactive element sizes per accessibility policy.", "fixable": false, "category": "css-a11y", "plugin": "css", "messages": { "heightTooSmall": "`{{property}}` of `{{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for `{{element}}` elements in policy `{{policy}}`.", "widthTooSmall": "`{{property}}` of `{{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for `{{element}}` elements in policy `{{policy}}`.", "paddingTooSmall": "Horizontal padding `{{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for `{{element}}` elements in policy `{{policy}}`." } }, { "id": "css-policy-typography", "severity": "warn", "description": "Enforce minimum font sizes and line heights per accessibility policy.", "fixable": false, "category": "css-a11y", "plugin": "css", "messages": { "fontTooSmall": "Font size `{{value}}` ({{resolved}}px) is below the `{{context}}` minimum of `{{min}}px` for policy `{{policy}}`.", "lineHeightTooSmall": "Line height `{{value}}` is below the `{{context}}` minimum of `{{min}}` for policy `{{policy}}`." } }, { "id": "css-require-reduced-motion-override", "severity": "warn", "description": "Require reduced-motion override for animated selectors.", "fixable": false, "category": "css-a11y", "plugin": "css", "messages": { "missingReducedMotion": "Animated selector `{{selector}}` lacks prefers-reduced-motion override." } }],
1959
+ "css-a11y": [{ "id": "css-no-outline-none-without-focus-visible", "severity": "error", "description": "Disallow removing outline without explicit focus-visible replacement.", "fixable": false, "category": "css-a11y", "plugin": "css", "messages": { "missingFocusVisible": "Focus outline removed without matching `:focus-visible` replacement." } }, { "id": "css-policy-contrast", "severity": "warn", "description": "Enforce minimum contrast ratio between foreground and background colors per accessibility policy.", "fixable": false, "category": "css-a11y", "plugin": "css", "messages": { "insufficientContrast": "Contrast ratio `{{ratio}}:1` between `{{fg}}` and `{{bg}}` is below the minimum `{{min}}:1` for `{{textSize}}` text in policy `{{policy}}`." } }, { "id": "css-policy-spacing", "severity": "warn", "description": "Enforce minimum letter-spacing, word-spacing, and paragraph spacing per accessibility policy.", "fixable": false, "category": "css-a11y", "plugin": "css", "messages": { "letterSpacingTooSmall": "Letter spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` for policy `{{policy}}`.", "wordSpacingTooSmall": "Word spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` for policy `{{policy}}`.", "paragraphSpacingTooSmall": "Paragraph spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` ({{minMultiplier}}\xD7 font-size) for policy `{{policy}}`." } }, { "id": "css-policy-typography", "severity": "warn", "description": "Enforce minimum font sizes and line heights per accessibility policy.", "fixable": false, "category": "css-a11y", "plugin": "css", "messages": { "fontTooSmall": "Font size `{{value}}` ({{resolved}}px) is below the `{{context}}` minimum of `{{min}}px` for policy `{{policy}}`.", "lineHeightTooSmall": "Line height `{{value}}` is below the `{{context}}` minimum of `{{min}}` for policy `{{policy}}`." } }, { "id": "css-require-reduced-motion-override", "severity": "warn", "description": "Require reduced-motion override for animated selectors.", "fixable": false, "category": "css-a11y", "plugin": "css", "messages": { "missingReducedMotion": "Animated selector `{{selector}}` lacks prefers-reduced-motion override." } }, { "id": "jsx-layout-policy-touch-target", "severity": "warn", "description": "Enforce minimum interactive element sizes per accessibility policy via resolved layout signals.", "fixable": false, "category": "css-a11y", "plugin": "cross-file", "messages": { "heightTooSmall": "`{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.", "widthTooSmall": "`{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.", "paddingTooSmall": "Horizontal padding `{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.", "noReservedBlockSize": "Interactive element `<{{tag}}>` has no declared height (minimum `{{min}}px` required by policy `{{policy}}`). The element is content-sized and may not meet the touch-target threshold.", "noReservedInlineSize": "Interactive element `<{{tag}}>` has no declared width (minimum `{{min}}px` required by policy `{{policy}}`). The element is content-sized and may not meet the touch-target threshold." } }],
1958
1960
  "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
1961
  "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
1962
  "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}}`." } }],