@drskillissue/ganko 0.2.61 → 0.2.72

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.cjs CHANGED
@@ -151,9 +151,29 @@ function canonicalPath(path) {
151
151
  return canonical;
152
152
  }
153
153
  var LOG_LEVELS = ["trace", "debug", "info", "warning", "error", "critical", "off"];
154
+ var Level = {
155
+ Trace: 0,
156
+ Debug: 1,
157
+ Info: 2,
158
+ Warning: 3,
159
+ Error: 4,
160
+ Critical: 5,
161
+ Off: 6
162
+ };
163
+ var LOG_LEVEL_ORDER = {
164
+ trace: Level.Trace,
165
+ debug: Level.Debug,
166
+ info: Level.Info,
167
+ warning: Level.Warning,
168
+ error: Level.Error,
169
+ critical: Level.Critical,
170
+ off: Level.Off
171
+ };
154
172
  var noopLogger = {
155
- enabled: false,
156
173
  level: "off",
174
+ isLevelEnabled() {
175
+ return false;
176
+ },
157
177
  trace() {
158
178
  },
159
179
  debug() {
@@ -2790,7 +2810,7 @@ var GraphCache = class {
2790
2810
  const key = canonicalPath(path);
2791
2811
  const cached = this.solids.get(key);
2792
2812
  const hit = cached !== void 0 && cached.version === version;
2793
- if (this.log.enabled) this.log.debug(`hasSolidGraph: ${key} v=${version} cached=${cached?.version ?? "none"} hit=${hit} (${this.solids.size} total)`);
2813
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`hasSolidGraph: ${key} v=${version} cached=${cached?.version ?? "none"} hit=${hit} (${this.solids.size} total)`);
2794
2814
  return hit;
2795
2815
  }
2796
2816
  /**
@@ -2807,7 +2827,7 @@ var GraphCache = class {
2807
2827
  const key = canonicalPath(path);
2808
2828
  this.solids.set(key, { version, graph });
2809
2829
  this.solidGeneration++;
2810
- if (this.log.enabled) this.log.debug(`setSolidGraph: ${key} v=${version} (${this.solids.size} total) solidGen=${this.solidGeneration}`);
2830
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`setSolidGraph: ${key} v=${version} (${this.solids.size} total) solidGen=${this.solidGeneration}`);
2811
2831
  }
2812
2832
  /**
2813
2833
  * Get a cached SolidGraph without building on miss.
@@ -2823,10 +2843,10 @@ var GraphCache = class {
2823
2843
  const key = canonicalPath(path);
2824
2844
  const cached = this.solids.get(key);
2825
2845
  if (cached !== void 0 && cached.version === version) {
2826
- if (this.log.enabled) this.log.debug(`getCachedSolidGraph HIT: ${key} v=${version}`);
2846
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getCachedSolidGraph HIT: ${key} v=${version}`);
2827
2847
  return cached.graph;
2828
2848
  }
2829
- if (this.log.enabled) this.log.debug(`getCachedSolidGraph MISS: ${key} v=${version}`);
2849
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getCachedSolidGraph MISS: ${key} v=${version}`);
2830
2850
  return null;
2831
2851
  }
2832
2852
  /**
@@ -2843,15 +2863,15 @@ var GraphCache = class {
2843
2863
  const key = canonicalPath(path);
2844
2864
  const cached = this.solids.get(key);
2845
2865
  if (cached !== void 0 && cached.version === version) {
2846
- if (this.log.enabled) this.log.debug(`getSolidGraph HIT: ${key} v=${version}`);
2866
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getSolidGraph HIT: ${key} v=${version}`);
2847
2867
  return cached.graph;
2848
2868
  }
2849
- if (this.log.enabled) this.log.debug(`getSolidGraph MISS: ${key} v=${version} (was ${cached?.version ?? "uncached"})`);
2869
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getSolidGraph MISS: ${key} v=${version} (was ${cached?.version ?? "uncached"})`);
2850
2870
  const t0 = performance.now();
2851
2871
  const graph = build();
2852
2872
  this.solids.set(key, { version, graph });
2853
2873
  this.solidGeneration++;
2854
- if (this.log.enabled) this.log.debug(`getSolidGraph BUILT: ${key} v=${version} in ${performance.now() - t0}ms (${this.solids.size} total) solidGen=${this.solidGeneration}`);
2874
+ 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}`);
2855
2875
  return graph;
2856
2876
  }
2857
2877
  /**
@@ -2865,14 +2885,14 @@ var GraphCache = class {
2865
2885
  */
2866
2886
  getCSSGraph(build) {
2867
2887
  if (this.css !== null && this.css.generation === this.cssGeneration) {
2868
- if (this.log.enabled) this.log.debug(`getCSSGraph HIT: gen=${this.cssGeneration}`);
2888
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getCSSGraph HIT: gen=${this.cssGeneration}`);
2869
2889
  return this.css.graph;
2870
2890
  }
2871
- if (this.log.enabled) this.log.debug(`getCSSGraph MISS: currentGen=${this.cssGeneration} cachedGen=${this.css?.generation ?? "none"}`);
2891
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getCSSGraph MISS: currentGen=${this.cssGeneration} cachedGen=${this.css?.generation ?? "none"}`);
2872
2892
  const t0 = performance.now();
2873
2893
  const graph = build();
2874
2894
  this.css = { generation: this.cssGeneration, graph };
2875
- if (this.log.enabled) this.log.debug(`getCSSGraph BUILT: gen=${this.cssGeneration} in ${performance.now() - t0}ms`);
2895
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getCSSGraph BUILT: gen=${this.cssGeneration} in ${performance.now() - t0}ms`);
2876
2896
  return graph;
2877
2897
  }
2878
2898
  /**
@@ -2887,10 +2907,10 @@ var GraphCache = class {
2887
2907
  const solidGen = this.solidGeneration;
2888
2908
  const cssGen = this.cssGeneration;
2889
2909
  if (this.layout !== null && this.layout.solidGeneration === solidGen && this.layout.cssGeneration === cssGen) {
2890
- if (this.log.enabled) this.log.debug(`getLayoutGraph HIT: solidGen=${solidGen} cssGen=${cssGen}`);
2910
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getLayoutGraph HIT: solidGen=${solidGen} cssGen=${cssGen}`);
2891
2911
  return this.layout.graph;
2892
2912
  }
2893
- if (this.log.enabled) this.log.debug(
2913
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(
2894
2914
  `getLayoutGraph MISS: solidGen=${solidGen} cssGen=${cssGen} cached=${this.layout !== null}`
2895
2915
  );
2896
2916
  const t0 = performance.now();
@@ -2900,7 +2920,7 @@ var GraphCache = class {
2900
2920
  cssGeneration: cssGen,
2901
2921
  graph
2902
2922
  };
2903
- if (this.log.enabled) this.log.debug(`getLayoutGraph BUILT: in ${performance.now() - t0}ms`);
2923
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getLayoutGraph BUILT: in ${performance.now() - t0}ms`);
2904
2924
  return graph;
2905
2925
  }
2906
2926
  /**
@@ -2915,7 +2935,7 @@ var GraphCache = class {
2915
2935
  invalidate(path) {
2916
2936
  const key = canonicalPath(path);
2917
2937
  const kind = classifyFile(key);
2918
- if (this.log.enabled) this.log.debug(`invalidate: ${key} kind=${kind} solids=${this.solids.size} hasCrossFileResults=${this.crossFileResults !== null} hasLayout=${this.layout !== null}`);
2938
+ 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}`);
2919
2939
  if (kind === "solid") {
2920
2940
  const had = this.solids.has(key);
2921
2941
  this.solids.delete(key);
@@ -2923,7 +2943,7 @@ var GraphCache = class {
2923
2943
  this.crossFileResults = null;
2924
2944
  this.solidGeneration++;
2925
2945
  this.layout = null;
2926
- if (this.log.enabled) this.log.debug(`invalidate SOLID: ${key} wasInCache=${had} solids=${this.solids.size} solidGen=${this.solidGeneration}`);
2946
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`invalidate SOLID: ${key} wasInCache=${had} solids=${this.solids.size} solidGen=${this.solidGeneration}`);
2927
2947
  }
2928
2948
  if (kind === "css") {
2929
2949
  this.crossFileDiagnostics.delete(key);
@@ -2931,7 +2951,7 @@ var GraphCache = class {
2931
2951
  this.cssGeneration++;
2932
2952
  this.css = null;
2933
2953
  this.layout = null;
2934
- if (this.log.enabled) this.log.debug(`invalidate CSS: ${key} newCssGen=${this.cssGeneration}`);
2954
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`invalidate CSS: ${key} newCssGen=${this.cssGeneration}`);
2935
2955
  }
2936
2956
  }
2937
2957
  /**
@@ -2940,7 +2960,7 @@ var GraphCache = class {
2940
2960
  * Called on workspace-level events like config changes.
2941
2961
  */
2942
2962
  invalidateAll() {
2943
- if (this.log.enabled) this.log.debug(`invalidateAll: solids=${this.solids.size} solidGen=${this.solidGeneration} cssGen=${this.cssGeneration}`);
2963
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`invalidateAll: solids=${this.solids.size} solidGen=${this.solidGeneration} cssGen=${this.cssGeneration}`);
2944
2964
  this.solids.clear();
2945
2965
  this.crossFileDiagnostics.clear();
2946
2966
  this.crossFileResults = null;
@@ -2956,7 +2976,7 @@ var GraphCache = class {
2956
2976
  * Used by cross-file analysis which needs all SolidGraphs.
2957
2977
  */
2958
2978
  getAllSolidGraphs() {
2959
- if (this.log.enabled) this.log.debug(`getAllSolidGraphs: ${this.solids.size} graphs`);
2979
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getAllSolidGraphs: ${this.solids.size} graphs`);
2960
2980
  const out = new Array(this.solids.size);
2961
2981
  let i = 0;
2962
2982
  for (const entry of this.solids.values()) {
@@ -3012,10 +3032,10 @@ var GraphCache = class {
3012
3032
  const solidMatch = this.crossFileResults.solidGeneration === this.solidGeneration;
3013
3033
  const cssMatch = this.crossFileResults.cssGeneration === this.cssGeneration;
3014
3034
  if (solidMatch && cssMatch) {
3015
- if (this.log.enabled) this.log.debug(`getCachedCrossFileResults HIT: ${this.crossFileResults?.byFile.size} files`);
3035
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(`getCachedCrossFileResults HIT: ${this.crossFileResults?.byFile.size} files`);
3016
3036
  return this.crossFileResults.byFile;
3017
3037
  }
3018
- if (this.log.enabled) this.log.debug(
3038
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(
3019
3039
  `getCachedCrossFileResults MISS: solidMatch=${solidMatch} cssMatch=${cssMatch} cachedSolidGen=${this.crossFileResults?.solidGeneration} currentSolidGen=${this.solidGeneration} cachedCssGen=${this.crossFileResults?.cssGeneration} currentCssGen=${this.cssGeneration}`
3020
3040
  );
3021
3041
  return null;
@@ -3046,7 +3066,7 @@ var GraphCache = class {
3046
3066
  cssGeneration: this.cssGeneration,
3047
3067
  byFile
3048
3068
  };
3049
- if (this.log.enabled) this.log.debug(
3069
+ if (this.log.isLevelEnabled(Level.Debug)) this.log.debug(
3050
3070
  `setCachedCrossFileResults: ${allDiagnostics.length} diags across ${byFile.size} files solidGen=${this.solidGeneration} cssGen=${this.cssGeneration}`
3051
3071
  );
3052
3072
  this.crossFileDiagnostics.clear();
@@ -3358,6 +3378,35 @@ var TypeResolver = class {
3358
3378
  return false;
3359
3379
  }
3360
3380
  }
3381
+ /**
3382
+ * Strict array check: only matches Array<T>, T[], ReadonlyArray<T>, and tuples.
3383
+ * Does NOT match typed arrays (Uint8Array, Buffer, etc.) or other types with
3384
+ * a numeric index signature.
3385
+ */
3386
+ isStrictArrayType(node) {
3387
+ try {
3388
+ const tsType = this.checker.getTypeAtLocation(node);
3389
+ return this.checkIsStrictArrayType(tsType);
3390
+ } catch {
3391
+ return false;
3392
+ }
3393
+ }
3394
+ checkIsStrictArrayType(tsType) {
3395
+ if (tsType.flags & 524288) {
3396
+ const objFlags = getObjectFlags(tsType);
3397
+ if (objFlags & 8) return true;
3398
+ if (objFlags & 4) {
3399
+ const name = tsType.getSymbol()?.getName();
3400
+ if (name === "Array" || name === "ReadonlyArray") return true;
3401
+ }
3402
+ }
3403
+ if (tsType.isUnion()) {
3404
+ for (const t of tsType.types) {
3405
+ if (this.checkIsStrictArrayType(t)) return true;
3406
+ }
3407
+ }
3408
+ return false;
3409
+ }
3361
3410
  checkIsArrayType(tsType) {
3362
3411
  if (tsType.flags & 524288) {
3363
3412
  const objFlags = getObjectFlags(tsType);
@@ -3409,7 +3458,7 @@ var TypeResolver = class {
3409
3458
  if (exprTsType.flags & import_typescript2.default.TypeFlags.Any) return false;
3410
3459
  if (targetTsType.flags & import_typescript2.default.TypeFlags.Any) return false;
3411
3460
  const result = this.checker.isTypeAssignableTo(exprTsType, targetTsType);
3412
- if (this.logger.enabled) {
3461
+ if (this.logger.isLevelEnabled(Level.Debug)) {
3413
3462
  const exprStr = this.checker.typeToString(exprTsType);
3414
3463
  const targetStr = this.checker.typeToString(targetTsType);
3415
3464
  this.logger.debug(`isUnnecessaryCast: expr="${exprStr.slice(0, 120)}" target="${targetStr.slice(0, 120)}" assignable=${result} exprFlags=${exprTsType.flags} targetFlags=${targetTsType.flags}`);
@@ -3926,6 +3975,7 @@ function getStaticStringFromJSXValue(node) {
3926
3975
  const expression = node.expression;
3927
3976
  if (!expression) return null;
3928
3977
  if (import_typescript4.default.isStringLiteral(expression)) return expression.text;
3978
+ if (import_typescript4.default.isNumericLiteral(expression)) return expression.text;
3929
3979
  if (import_typescript4.default.isNoSubstitutionTemplateLiteral(expression)) return expression.text;
3930
3980
  if (import_typescript4.default.isTemplateExpression(expression) && expression.templateSpans.length === 0) {
3931
3981
  return expression.head.text;
@@ -10373,6 +10423,9 @@ function typeIncludesString(graph, node) {
10373
10423
  function typeIsArray(graph, node) {
10374
10424
  return graph.typeResolver.isArrayType(node);
10375
10425
  }
10426
+ function typeIsStrictArray(graph, node) {
10427
+ return graph.typeResolver.isStrictArrayType(node);
10428
+ }
10376
10429
  function getArrayElementKind(graph, node) {
10377
10430
  return graph.typeResolver.getArrayElementKind(node);
10378
10431
  }
@@ -20168,6 +20221,7 @@ var preferSetLookupInLoop = defineSolidRule({
20168
20221
  options: options77,
20169
20222
  check(graph, emit) {
20170
20223
  const reported = /* @__PURE__ */ new Set();
20224
+ const graphHasTypes = hasTypeInfo(graph);
20171
20225
  for (const method of LINEAR_SEARCH_METHODS) {
20172
20226
  const calls = getCallsByMethodName(graph, method);
20173
20227
  for (let i = 0, len = calls.length; i < len; i++) {
@@ -20185,6 +20239,7 @@ var preferSetLookupInLoop = defineSolidRule({
20185
20239
  const loop = getEnclosingLoop(call.node);
20186
20240
  if (!loop) continue;
20187
20241
  if (!isDeclaredOutsideLoop2(loop, variable)) continue;
20242
+ if (graphHasTypes && !typeIsStrictArray(graph, callee.expression)) continue;
20188
20243
  if (isStringLikeReceiver(graph, callee.expression, variable)) continue;
20189
20244
  const key = `${loop.pos}:var:${variable.id}`;
20190
20245
  if (reported.has(key)) continue;
@@ -29780,148 +29835,8 @@ function checkParagraphSpacing(graph, emit, policy, name) {
29780
29835
  }
29781
29836
  }
29782
29837
 
29783
- // src/css/rules/a11y/css-policy-touch-target.ts
29784
- var messages125 = {
29785
- heightTooSmall: "`{{property}}` of `{{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for `{{element}}` elements in policy `{{policy}}`.",
29786
- widthTooSmall: "`{{property}}` of `{{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for `{{element}}` elements in policy `{{policy}}`.",
29787
- paddingTooSmall: "Horizontal padding `{{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for `{{element}}` elements in policy `{{policy}}`."
29788
- };
29789
- function classifyRule(rule) {
29790
- if (rule.elementKinds.has("button")) return "button";
29791
- if (rule.elementKinds.has("input")) return "input";
29792
- return null;
29793
- }
29794
- var HEIGHT_PROPERTIES = /* @__PURE__ */ new Set(["height", "min-height"]);
29795
- var WIDTH_PROPERTIES = /* @__PURE__ */ new Set(["width", "min-width"]);
29796
- var HPADDING_PROPERTIES = /* @__PURE__ */ new Set(["padding-left", "padding-right", "padding-inline", "padding-inline-start", "padding-inline-end"]);
29797
- var cssPolicyTouchTarget = defineCSSRule({
29798
- id: "css-policy-touch-target",
29799
- severity: "warn",
29800
- messages: messages125,
29801
- meta: {
29802
- description: "Enforce minimum interactive element sizes per accessibility policy.",
29803
- fixable: false,
29804
- category: "css-a11y"
29805
- },
29806
- options: {},
29807
- check(graph, emit) {
29808
- const policy = getActivePolicy();
29809
- if (policy === null) return;
29810
- const name = getActivePolicyName() ?? "";
29811
- const decls = graph.declarationsForProperties(
29812
- "height",
29813
- "min-height",
29814
- "width",
29815
- "min-width",
29816
- "padding-left",
29817
- "padding-right",
29818
- "padding-inline",
29819
- "padding-inline-start",
29820
- "padding-inline-end"
29821
- );
29822
- for (let i = 0; i < decls.length; i++) {
29823
- const d = decls[i];
29824
- if (!d) continue;
29825
- if (!d.rule) continue;
29826
- const prop = d.property.toLowerCase();
29827
- const kind = classifyRule(d.rule);
29828
- if (!kind) continue;
29829
- if (isVisuallyHiddenInput(d.rule)) continue;
29830
- const px = parsePxValue(d.value);
29831
- if (px === null) continue;
29832
- if (HEIGHT_PROPERTIES.has(prop)) {
29833
- const min = kind === "button" ? policy.minButtonHeight : policy.minInputHeight;
29834
- if (px >= min) continue;
29835
- emitCSSDiagnostic(
29836
- emit,
29837
- d.file.path,
29838
- d.startLine,
29839
- d.startColumn,
29840
- cssPolicyTouchTarget,
29841
- "heightTooSmall",
29842
- resolveMessage(messages125.heightTooSmall, {
29843
- property: d.property,
29844
- value: d.value.trim(),
29845
- resolved: String(Math.round(px * 100) / 100),
29846
- min: String(min),
29847
- element: kind,
29848
- policy: name
29849
- })
29850
- );
29851
- continue;
29852
- }
29853
- if (WIDTH_PROPERTIES.has(prop)) {
29854
- const min = kind === "button" ? policy.minButtonWidth : policy.minTouchTarget;
29855
- if (px >= min) continue;
29856
- emitCSSDiagnostic(
29857
- emit,
29858
- d.file.path,
29859
- d.startLine,
29860
- d.startColumn,
29861
- cssPolicyTouchTarget,
29862
- "widthTooSmall",
29863
- resolveMessage(messages125.widthTooSmall, {
29864
- property: d.property,
29865
- value: d.value.trim(),
29866
- resolved: String(Math.round(px * 100) / 100),
29867
- min: String(min),
29868
- element: kind,
29869
- policy: name
29870
- })
29871
- );
29872
- continue;
29873
- }
29874
- if (kind === "button" && HPADDING_PROPERTIES.has(prop)) {
29875
- if (px >= policy.minButtonHorizontalPadding) continue;
29876
- emitCSSDiagnostic(
29877
- emit,
29878
- d.file.path,
29879
- d.startLine,
29880
- d.startColumn,
29881
- cssPolicyTouchTarget,
29882
- "paddingTooSmall",
29883
- resolveMessage(messages125.paddingTooSmall, {
29884
- value: d.value.trim(),
29885
- resolved: String(Math.round(px * 100) / 100),
29886
- min: String(policy.minButtonHorizontalPadding),
29887
- element: kind,
29888
- policy: name
29889
- })
29890
- );
29891
- }
29892
- }
29893
- }
29894
- });
29895
- function isVisuallyHiddenInput(rule) {
29896
- if (!hasPositionAbsoluteOrFixed(rule)) return false;
29897
- if (!hasOpacityZero(rule)) return false;
29898
- return true;
29899
- }
29900
- function hasPositionAbsoluteOrFixed(rule) {
29901
- const positionDecls = rule.declarationIndex.get("position");
29902
- if (!positionDecls || positionDecls.length === 0) return false;
29903
- for (let i = 0; i < positionDecls.length; i++) {
29904
- const decl = positionDecls[i];
29905
- if (!decl) continue;
29906
- const v = decl.value.trim().toLowerCase();
29907
- if (v === "absolute" || v === "fixed") return true;
29908
- }
29909
- return false;
29910
- }
29911
- function hasOpacityZero(rule) {
29912
- const opacityDecls = rule.declarationIndex.get("opacity");
29913
- if (!opacityDecls || opacityDecls.length === 0) return false;
29914
- for (let i = 0; i < opacityDecls.length; i++) {
29915
- const decl = opacityDecls[i];
29916
- if (!decl) continue;
29917
- const v = decl.value.trim();
29918
- if (v === "0") return true;
29919
- }
29920
- return false;
29921
- }
29922
-
29923
29838
  // src/css/rules/a11y/css-policy-typography.ts
29924
- var messages126 = {
29839
+ var messages125 = {
29925
29840
  fontTooSmall: "Font size `{{value}}` ({{resolved}}px) is below the `{{context}}` minimum of `{{min}}px` for policy `{{policy}}`.",
29926
29841
  lineHeightTooSmall: "Line height `{{value}}` is below the `{{context}}` minimum of `{{min}}` for policy `{{policy}}`."
29927
29842
  };
@@ -29957,7 +29872,7 @@ function resolveLineHeightContext(d, p) {
29957
29872
  var cssPolicyTypography = defineCSSRule({
29958
29873
  id: "css-policy-typography",
29959
29874
  severity: "warn",
29960
- messages: messages126,
29875
+ messages: messages125,
29961
29876
  meta: {
29962
29877
  description: "Enforce minimum font sizes and line heights per accessibility policy.",
29963
29878
  fixable: false,
@@ -29984,7 +29899,7 @@ var cssPolicyTypography = defineCSSRule({
29984
29899
  d.startColumn,
29985
29900
  cssPolicyTypography,
29986
29901
  "fontTooSmall",
29987
- resolveMessage(messages126.fontTooSmall, {
29902
+ resolveMessage(messages125.fontTooSmall, {
29988
29903
  value: d.value.trim(),
29989
29904
  resolved: String(Math.round(px * 100) / 100),
29990
29905
  context,
@@ -30011,7 +29926,7 @@ var cssPolicyTypography = defineCSSRule({
30011
29926
  d.startColumn,
30012
29927
  cssPolicyTypography,
30013
29928
  "lineHeightTooSmall",
30014
- resolveMessage(messages126.lineHeightTooSmall, {
29929
+ resolveMessage(messages125.lineHeightTooSmall, {
30015
29930
  value: d.value.trim(),
30016
29931
  context,
30017
29932
  min: String(min),
@@ -30029,7 +29944,7 @@ var ZERO_S = /(^|\s|,)0s($|\s|,)/;
30029
29944
  var TIME_VALUE_G = /([0-9]*\.?[0-9]+)(ms|s)/g;
30030
29945
  var AMPERSAND_G = /&/g;
30031
29946
  var WHITESPACE_G = /\s+/g;
30032
- var messages127 = {
29947
+ var messages126 = {
30033
29948
  missingReducedMotion: "Animated selector `{{selector}}` lacks prefers-reduced-motion override."
30034
29949
  };
30035
29950
  function isAnimationDecl(p) {
@@ -30133,7 +30048,7 @@ function normalizeSelector2(s) {
30133
30048
  var cssRequireReducedMotionOverride = defineCSSRule({
30134
30049
  id: "css-require-reduced-motion-override",
30135
30050
  severity: "warn",
30136
- messages: messages127,
30051
+ messages: messages126,
30137
30052
  meta: {
30138
30053
  description: "Require reduced-motion override for animated selectors.",
30139
30054
  fixable: false,
@@ -30196,20 +30111,20 @@ var cssRequireReducedMotionOverride = defineCSSRule({
30196
30111
  d.startColumn,
30197
30112
  cssRequireReducedMotionOverride,
30198
30113
  "missingReducedMotion",
30199
- resolveMessage(messages127.missingReducedMotion, { selector: r.selectorText })
30114
+ resolveMessage(messages126.missingReducedMotion, { selector: r.selectorText })
30200
30115
  );
30201
30116
  }
30202
30117
  }
30203
30118
  });
30204
30119
 
30205
30120
  // src/css/rules/structure/css-no-empty-rule.ts
30206
- var messages128 = {
30121
+ var messages127 = {
30207
30122
  emptyRule: "Empty rule `{{selector}}` should be removed."
30208
30123
  };
30209
30124
  var cssNoEmptyRule = defineCSSRule({
30210
30125
  id: "css-no-empty-rule",
30211
30126
  severity: "warn",
30212
- messages: messages128,
30127
+ messages: messages127,
30213
30128
  meta: {
30214
30129
  description: "Disallow empty CSS rules.",
30215
30130
  fixable: false,
@@ -30227,20 +30142,20 @@ var cssNoEmptyRule = defineCSSRule({
30227
30142
  rule.startColumn,
30228
30143
  cssNoEmptyRule,
30229
30144
  "emptyRule",
30230
- resolveMessage(messages128.emptyRule, { selector: rule.selectorText })
30145
+ resolveMessage(messages127.emptyRule, { selector: rule.selectorText })
30231
30146
  );
30232
30147
  }
30233
30148
  }
30234
30149
  });
30235
30150
 
30236
30151
  // src/css/rules/structure/css-no-unknown-container-name.ts
30237
- var messages129 = {
30152
+ var messages128 = {
30238
30153
  unknownContainer: "Unknown container name `{{name}}` in @container query."
30239
30154
  };
30240
30155
  var cssNoUnknownContainerName = defineCSSRule({
30241
30156
  id: "css-no-unknown-container-name",
30242
30157
  severity: "error",
30243
- messages: messages129,
30158
+ messages: messages128,
30244
30159
  meta: {
30245
30160
  description: "Disallow unknown named containers in @container queries.",
30246
30161
  fixable: false,
@@ -30260,20 +30175,20 @@ var cssNoUnknownContainerName = defineCSSRule({
30260
30175
  1,
30261
30176
  cssNoUnknownContainerName,
30262
30177
  "unknownContainer",
30263
- resolveMessage(messages129.unknownContainer, { name })
30178
+ resolveMessage(messages128.unknownContainer, { name })
30264
30179
  );
30265
30180
  }
30266
30181
  }
30267
30182
  });
30268
30183
 
30269
30184
  // src/css/rules/structure/css-no-unused-container-name.ts
30270
- var messages130 = {
30185
+ var messages129 = {
30271
30186
  unusedContainer: "Container name `{{name}}` is declared but never queried."
30272
30187
  };
30273
30188
  var cssNoUnusedContainerName = defineCSSRule({
30274
30189
  id: "css-no-unused-container-name",
30275
30190
  severity: "warn",
30276
- messages: messages130,
30191
+ messages: messages129,
30277
30192
  meta: {
30278
30193
  description: "Disallow unused named containers.",
30279
30194
  fixable: false,
@@ -30296,7 +30211,7 @@ var cssNoUnusedContainerName = defineCSSRule({
30296
30211
  d.startColumn,
30297
30212
  cssNoUnusedContainerName,
30298
30213
  "unusedContainer",
30299
- resolveMessage(messages130.unusedContainer, { name })
30214
+ resolveMessage(messages129.unusedContainer, { name })
30300
30215
  );
30301
30216
  }
30302
30217
  }
@@ -30304,13 +30219,13 @@ var cssNoUnusedContainerName = defineCSSRule({
30304
30219
  });
30305
30220
 
30306
30221
  // src/css/rules/structure/layer-requirement-for-component-rules.ts
30307
- var messages131 = {
30222
+ var messages130 = {
30308
30223
  missingLayer: "Rule `{{selector}}` is not inside any @layer block while this file uses @layer. Place component rules inside an explicit layer."
30309
30224
  };
30310
30225
  var layerRequirementForComponentRules = defineCSSRule({
30311
30226
  id: "layer-requirement-for-component-rules",
30312
30227
  severity: "warn",
30313
- messages: messages131,
30228
+ messages: messages130,
30314
30229
  meta: {
30315
30230
  description: "Require style rules to be inside @layer when the file defines layers.",
30316
30231
  fixable: false,
@@ -30329,7 +30244,7 @@ var layerRequirementForComponentRules = defineCSSRule({
30329
30244
  rule.startColumn,
30330
30245
  layerRequirementForComponentRules,
30331
30246
  "missingLayer",
30332
- resolveMessage(messages131.missingLayer, {
30247
+ resolveMessage(messages130.missingLayer, {
30333
30248
  selector: rule.selectorText
30334
30249
  })
30335
30250
  );
@@ -30369,7 +30284,6 @@ var rules2 = [
30369
30284
  selectorMaxAttributeAndUniversal,
30370
30285
  selectorMaxSpecificity,
30371
30286
  cssPolicyTypography,
30372
- cssPolicyTouchTarget,
30373
30287
  cssPolicySpacing,
30374
30288
  cssPolicyContrast
30375
30289
  ];
@@ -30706,21 +30620,34 @@ function detectTailwindEntry(files) {
30706
30620
  }
30707
30621
  return themeOnlyFallback;
30708
30622
  }
30709
- async function resolveTailwindValidator(files) {
30623
+ async function resolveTailwindValidator(files, logger) {
30710
30624
  const entry = detectTailwindEntry(files);
30711
- if (!entry) return null;
30625
+ if (!entry) {
30626
+ logger?.info('tailwind: no entry file detected (no @import "tailwindcss" or @theme block found in CSS files)');
30627
+ return null;
30628
+ }
30629
+ logger?.info(`tailwind: entry file detected: ${entry.path}`);
30712
30630
  try {
30713
30631
  const base = (0, import_node_path2.dirname)(entry.path);
30714
30632
  const resolved = resolveTailwindNodePath(base);
30715
- if (resolved === null) return null;
30633
+ if (resolved === null) {
30634
+ logger?.warning(`tailwind: @tailwindcss/node not resolvable walking up from ${base}`);
30635
+ return null;
30636
+ }
30637
+ logger?.info(`tailwind: @tailwindcss/node resolved to ${resolved}`);
30716
30638
  const mod = await import(resolved);
30717
- if (typeof mod.__unstable__loadDesignSystem !== "function") return null;
30639
+ if (typeof mod.__unstable__loadDesignSystem !== "function") {
30640
+ logger?.warning("tailwind: @tailwindcss/node module missing __unstable__loadDesignSystem export");
30641
+ return null;
30642
+ }
30718
30643
  const design = await mod.__unstable__loadDesignSystem(
30719
30644
  entry.content,
30720
30645
  { base }
30721
30646
  );
30647
+ logger?.info("tailwind: design system loaded successfully");
30722
30648
  return createLiveValidator(design);
30723
- } catch {
30649
+ } catch (err) {
30650
+ logger?.warning(`tailwind: failed to load design system: ${err instanceof Error ? err.message : String(err)}`);
30724
30651
  return null;
30725
30652
  }
30726
30653
  }
@@ -31740,6 +31667,9 @@ function readBaselineOffsetFacts(graph, node) {
31740
31667
  function readElementRef(graph, node) {
31741
31668
  return readElementRefById(graph, node.solidFile, node.elementId);
31742
31669
  }
31670
+ function readHostElementRef(graph, node) {
31671
+ return graph.hostElementRefsByNode.get(node) ?? null;
31672
+ }
31743
31673
  function readElementRefById(graph, solidFile, elementId) {
31744
31674
  const refs = graph.elementRefsBySolidFileAndId.get(solidFile);
31745
31675
  if (!refs) return null;
@@ -32805,7 +32735,7 @@ function publishLayoutPerfStatsForTest(stats) {
32805
32735
  }
32806
32736
  function maybeLogLayoutPerf(stats, log) {
32807
32737
  if (process.env["SOLID_LINT_LAYOUT_PROFILE"] !== "1") return;
32808
- if (!log || !log.enabled) return;
32738
+ if (!log || !log.isLevelEnabled(Level.Debug)) return;
32809
32739
  const view = snapshotLayoutPerfStats(stats);
32810
32740
  log.debug(
32811
32741
  `[layout] elements=${view.elementsScanned} candidates=${view.selectorCandidatesChecked} compiledSelectors=${view.compiledSelectorCount} unsupportedSelectors=${view.selectorsRejectedUnsupported} conditionalSelectors=${view.selectorsGuardedConditional} ancestryChecks=${view.ancestryChecks} edges=${view.matchEdgesCreated} collected=${view.casesCollected} cases=${view.casesScored} rejectLowEvidence=${view.casesRejectedLowEvidence} rejectThreshold=${view.casesRejectedThreshold} rejectUndecidable=${view.casesRejectedUndecidable} rejectIdentifiability=${view.casesRejectedIdentifiability} undecidableInterval=${view.undecidableInterval} conditionalSignalRatio=${Math.round(view.conditionalSignalRatio * 1e3) / 1e3} conditionalSignals=${view.conditionalSignals} totalSignals=${view.totalSignals} cohortUnimodalFalse=${view.cohortUnimodalFalse} factorCoverageMean=${Math.round(view.factorCoverageMean * 1e3) / 1e3} posteriorWidthP95=${Math.round(view.posteriorWidthP95 * 1e3) / 1e3} uncertaintyEscalations=${view.uncertaintyEscalations} snapshots=${view.signalSnapshotsBuilt} snapshotHits=${view.signalSnapshotCacheHits} measurementIndexHits=${view.measurementIndexHits} contexts=${view.contextsClassified} diagnostics=${view.diagnosticsEmitted} selectorIndexMs=${Math.round(view.selectorIndexMs * 100) / 100} selectorMatchMs=${Math.round(view.selectorMatchMs * 100) / 100} cascadeBuildMs=${Math.round(view.cascadeBuildMs * 100) / 100} caseBuildMs=${Math.round(view.caseBuildMs * 100) / 100} scoringMs=${Math.round(view.scoringMs * 100) / 100} elapsedMs=${Math.round(view.elapsedMs * 100) / 100}`
@@ -32853,17 +32783,17 @@ function createLayoutComponentHostResolver(solids, moduleResolver, logger = noop
32853
32783
  if (cached !== void 0) return cached;
32854
32784
  const binding = resolveTagBinding(normalizedFile, tag);
32855
32785
  if (binding === null) {
32856
- if (logger.enabled) logger.trace(`[component-host] resolveHost(${tag}): binding=null`);
32786
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[component-host] resolveHost(${tag}): binding=null`);
32857
32787
  hostByTagCache.set(cacheKey, null);
32858
32788
  return null;
32859
32789
  }
32860
32790
  if (binding.kind === "component") {
32861
- if (logger.enabled) logger.trace(`[component-host] resolveHost(${tag}): component, tagName=${binding.host.tagName}, attrs=[${[...binding.host.staticAttributes.keys()]}]`);
32791
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[component-host] resolveHost(${tag}): component, tagName=${binding.host.descriptor.tagName}, attrs=[${[...binding.host.descriptor.staticAttributes.keys()]}]`);
32862
32792
  hostByTagCache.set(cacheKey, binding.host);
32863
32793
  return binding.host;
32864
32794
  }
32865
32795
  const host = binding.base ? binding.base.host : null;
32866
- if (logger.enabled) logger.trace(`[component-host] resolveHost(${tag}): namespace, base=${host?.tagName ?? "null"}`);
32796
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[component-host] resolveHost(${tag}): namespace, base=${host?.descriptor.tagName ?? "null"}`);
32867
32797
  hostByTagCache.set(cacheKey, host);
32868
32798
  return host;
32869
32799
  },
@@ -32876,26 +32806,26 @@ function createLayoutComponentHostResolver(solids, moduleResolver, logger = noop
32876
32806
  }
32877
32807
  };
32878
32808
  function resolveComponentHostEntry(entry) {
32879
- if (entry.resolution === "resolved") return entry.descriptor;
32880
- if (logger.enabled) logger.trace(`[component-host] resolveComponentHostEntry: deferred innerTag=${entry.innerTag}, file=${entry.filePath}, attrs=[${[...entry.staticAttributes.keys()]}]`);
32809
+ if (entry.resolution === "resolved") {
32810
+ return { descriptor: entry.descriptor, hostElementRef: entry.hostElementRef };
32811
+ }
32812
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[component-host] resolveComponentHostEntry: deferred innerTag=${entry.innerTag}, file=${entry.filePath}, attrs=[${[...entry.staticAttributes.keys()]}]`);
32881
32813
  const innerBinding = resolveLocalIdentifierBinding(entry.filePath, entry.innerTag);
32882
- if (logger.enabled) logger.trace(`[component-host] innerBinding=${innerBinding === null ? "null" : innerBinding.kind}`);
32814
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[component-host] innerBinding=${innerBinding === null ? "null" : innerBinding.kind}`);
32883
32815
  const innerHost = extractHostFromBinding(innerBinding);
32884
- if (logger.enabled) logger.trace(`[component-host] innerHost=${innerHost === null ? "null" : `tagName=${innerHost.tagName}, attrs=[${[...innerHost.staticAttributes.keys()]}]`}`);
32885
- let tagName = innerHost !== null ? innerHost.tagName : null;
32816
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[component-host] innerHost=${innerHost === null ? "null" : `tagName=${innerHost.descriptor.tagName}, attrs=[${[...innerHost.descriptor.staticAttributes.keys()]}]`}`);
32817
+ let tagName = innerHost !== null ? innerHost.descriptor.tagName : null;
32886
32818
  if (tagName === null) {
32887
32819
  tagName = resolveTagNameFromPolymorphicProp(entry.staticAttributes);
32888
- if (logger.enabled) logger.trace(`[component-host] polymorphic fallback: tagName=${tagName}`);
32820
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[component-host] polymorphic fallback: tagName=${tagName}`);
32889
32821
  }
32890
- const staticAttributes = innerHost !== null ? mergeStaticAttributes(entry.staticAttributes, innerHost.staticAttributes) : entry.staticAttributes;
32891
- const staticClassTokens = innerHost !== null ? mergeStaticClassTokens(entry.staticClassTokens, innerHost.staticClassTokens) : entry.staticClassTokens;
32892
- const forwardsChildren = entry.forwardsChildren || innerHost !== null && innerHost.forwardsChildren;
32893
- if (logger.enabled) logger.trace(`[component-host] resolved: tagName=${tagName}, attrs=[${[...staticAttributes.keys()]}], classes=[${staticClassTokens}]`);
32822
+ const staticAttributes = innerHost !== null ? mergeStaticAttributes(entry.staticAttributes, innerHost.descriptor.staticAttributes) : entry.staticAttributes;
32823
+ const staticClassTokens = innerHost !== null ? mergeStaticClassTokens(entry.staticClassTokens, innerHost.descriptor.staticClassTokens) : entry.staticClassTokens;
32824
+ const forwardsChildren = entry.forwardsChildren || innerHost !== null && innerHost.descriptor.forwardsChildren;
32825
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[component-host] resolved: tagName=${tagName}, attrs=[${[...staticAttributes.keys()]}], classes=[${staticClassTokens}]`);
32894
32826
  return {
32895
- tagName,
32896
- staticAttributes,
32897
- staticClassTokens,
32898
- forwardsChildren
32827
+ descriptor: { tagName, staticAttributes, staticClassTokens, forwardsChildren },
32828
+ hostElementRef: innerHost?.hostElementRef ?? null
32899
32829
  };
32900
32830
  }
32901
32831
  function extractHostFromBinding(binding) {
@@ -32936,10 +32866,7 @@ function createLayoutComponentHostResolver(solids, moduleResolver, logger = noop
32936
32866
  if (hostEntry) {
32937
32867
  const resolved = resolveComponentHostEntry(hostEntry);
32938
32868
  if (resolved !== null) {
32939
- const binding = {
32940
- kind: "component",
32941
- host: resolved
32942
- };
32869
+ const binding = { kind: "component", host: resolved };
32943
32870
  localBindingCache.set(key, binding);
32944
32871
  resolvingLocal.delete(key);
32945
32872
  return binding;
@@ -33000,7 +32927,7 @@ function createLayoutComponentHostResolver(solids, moduleResolver, logger = noop
33000
32927
  const baseExpression = toExpressionArgument(firstArg);
33001
32928
  if (baseExpression === null) return null;
33002
32929
  const baseBinding = resolveBindingFromExpression(filePath, baseExpression);
33003
- if (logger.enabled) logger.trace(`[component-host] Object.assign base: ${baseBinding === null ? "null" : baseBinding.kind}${baseBinding?.kind === "component" ? `, tagName=${baseBinding.host.tagName}` : ""}`);
32930
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[component-host] Object.assign base: ${baseBinding === null ? "null" : baseBinding.kind}${baseBinding?.kind === "component" ? `, tagName=${baseBinding.host.descriptor.tagName}` : ""}`);
33004
32931
  let baseComponent = null;
33005
32932
  const members = /* @__PURE__ */ new Map();
33006
32933
  if (baseBinding && baseBinding.kind === "component") {
@@ -33026,7 +32953,7 @@ function createLayoutComponentHostResolver(solids, moduleResolver, logger = noop
33026
32953
  if (!import_typescript126.default.isObjectLiteralExpression(argument)) continue;
33027
32954
  appendObjectExpressionMembers(filePath, argument, members);
33028
32955
  }
33029
- if (logger.enabled) logger.trace(`[component-host] Object.assign result: base=${baseComponent === null ? "null" : `tagName=${baseComponent.host.tagName}`}, members=[${[...members.keys()]}]`);
32956
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[component-host] Object.assign result: base=${baseComponent === null ? "null" : `tagName=${baseComponent.host.descriptor.tagName}`}, members=[${[...members.keys()]}]`);
33030
32957
  if (baseComponent === null && members.size === 0) return null;
33031
32958
  return {
33032
32959
  kind: "namespace",
@@ -33068,7 +32995,7 @@ function createLayoutComponentHostResolver(solids, moduleResolver, logger = noop
33068
32995
  }
33069
32996
  function resolveBindingFromImport(filePath, importBinding) {
33070
32997
  const resolvedModule = moduleResolver.resolveSolid(filePath, importBinding.source) ?? resolveAndIndexExternalModule(filePath, importBinding.source);
33071
- if (logger.enabled) logger.trace(`[component-host] resolveBindingFromImport: source=${importBinding.source}, kind=${importBinding.kind}, resolvedModule=${resolvedModule}`);
32998
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[component-host] resolveBindingFromImport: source=${importBinding.source}, kind=${importBinding.kind}, resolvedModule=${resolvedModule}`);
33072
32999
  if (resolvedModule === null) return null;
33073
33000
  const normalized = (0, import_node_path4.resolve)(resolvedModule);
33074
33001
  if (importBinding.kind === "namespace") {
@@ -33077,7 +33004,7 @@ function createLayoutComponentHostResolver(solids, moduleResolver, logger = noop
33077
33004
  const exportName = importBinding.kind === "default" ? "default" : importBinding.importedName;
33078
33005
  if (exportName === null) return null;
33079
33006
  const result = resolveExportBinding(normalized, exportName);
33080
- if (logger.enabled) logger.trace(`[component-host] export ${exportName}: ${result === null ? "null" : result.kind}`);
33007
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[component-host] export ${exportName}: ${result === null ? "null" : result.kind}`);
33081
33008
  return result;
33082
33009
  }
33083
33010
  function resolveAndIndexExternalModule(importerFile, importSource) {
@@ -33271,6 +33198,7 @@ function collectComponentHosts(graph) {
33271
33198
  }
33272
33199
  function resolveComponentHostEntryForFunction(graph, fn) {
33273
33200
  let entry = null;
33201
+ let hostElementRefAgreed = true;
33274
33202
  const bodyEntry = resolveHostEntryFromFunctionBody(graph, fn);
33275
33203
  if (bodyEntry !== null) {
33276
33204
  entry = bodyEntry;
@@ -33286,9 +33214,17 @@ function resolveComponentHostEntryForFunction(graph, fn) {
33286
33214
  entry = returnEntry;
33287
33215
  continue;
33288
33216
  }
33289
- if (areComponentHostEntriesEqual(entry, returnEntry)) continue;
33217
+ if (areComponentHostEntriesEqual(entry, returnEntry)) {
33218
+ if (hostElementRefAgreed && entry.resolution === "resolved" && returnEntry.resolution === "resolved" && entry.hostElementRef !== returnEntry.hostElementRef) {
33219
+ hostElementRefAgreed = false;
33220
+ }
33221
+ continue;
33222
+ }
33290
33223
  return null;
33291
33224
  }
33225
+ if (!hostElementRefAgreed && entry !== null && entry.resolution === "resolved") {
33226
+ return { resolution: "resolved", descriptor: entry.descriptor, hostElementRef: null };
33227
+ }
33292
33228
  return entry;
33293
33229
  }
33294
33230
  function resolveHostEntryFromFunctionBody(graph, fn) {
@@ -33316,7 +33252,8 @@ function resolveHostEntryFromJSXElement(graph, node) {
33316
33252
  staticAttributes: collectStaticAttributes(element),
33317
33253
  staticClassTokens: getStaticClassTokensForElementEntity(graph, element),
33318
33254
  forwardsChildren: detectChildrenForwarding(element)
33319
- }
33255
+ },
33256
+ hostElementRef: { solid: graph, element }
33320
33257
  };
33321
33258
  }
33322
33259
  if (isContextProviderTag(element.tag)) {
@@ -34026,7 +33963,7 @@ function matchesChain(matcher, node, index, perf, fileRootElements, logger) {
34026
33963
  ancestor = ancestor.parentElementNode;
34027
33964
  }
34028
33965
  if (fileRootElements !== null) {
34029
- if (logger.enabled) {
33966
+ if (logger.isLevelEnabled(Level.Trace)) {
34030
33967
  const compoundDesc = describeCompound(targetCompound);
34031
33968
  logger.trace(`[selector-match] fallback: node=${node.key} tag=${node.tagName} checking ${fileRootElements.length} roots for compound=${compoundDesc}`);
34032
33969
  }
@@ -34037,11 +33974,11 @@ function matchesChain(matcher, node, index, perf, fileRootElements, logger) {
34037
33974
  if (root.solidFile !== node.solidFile) continue;
34038
33975
  perf.ancestryChecks++;
34039
33976
  const compoundResult = matchesCompound(root, targetCompound);
34040
- if (logger.enabled && compoundResult === "no-match") {
33977
+ if (logger.isLevelEnabled(Level.Trace) && compoundResult === "no-match") {
34041
33978
  logger.trace(`[selector-match] fallback MISS: root=${root.key} tag=${root.tagName} attrs=[${[...root.attributes.entries()].map(([k, v]) => `${k}=${v}`).join(",")}]`);
34042
33979
  }
34043
33980
  if (compoundResult !== "no-match") {
34044
- if (logger.enabled) {
33981
+ if (logger.isLevelEnabled(Level.Debug)) {
34045
33982
  const compoundDesc = describeCompound(targetCompound);
34046
33983
  logger.debug(`[selector-match] fallback HIT: node=${node.key} tag=${node.tagName} matched root=${root.key} tag=${root.tagName} compound=${compoundDesc} isFinal=${isFinal}`);
34047
33984
  }
@@ -37243,7 +37180,7 @@ function appendMatchingEdgesFromSelectorIds(ctx, selectorIds, node, applies, app
37243
37180
  };
37244
37181
  applies.push(edge);
37245
37182
  ctx.perf.matchEdgesCreated++;
37246
- if (ctx.logger.enabled) {
37183
+ if (ctx.logger.isLevelEnabled(Level.Trace)) {
37247
37184
  ctx.logger.trace(
37248
37185
  `[cascade] edge node=${node.key} selector=${selector.id} match=${matchResult} conditional=${edge.conditionalMatch} selector-raw=${selector.raw.slice(0, 80)}`
37249
37186
  );
@@ -37686,7 +37623,7 @@ function getTextualContentState(element, memo, compositionMetaByElementId, logge
37686
37623
  if (!child) continue;
37687
37624
  if (child.kind === "expression") {
37688
37625
  if (isStructuralExpression(child.node)) {
37689
- if (logger.enabled) logger.trace(`[textual-content] element=${element.tagName ?? element.tag}#${element.id} \u2192 unknown (structural expression child)`);
37626
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[textual-content] element=${element.tagName ?? element.tag}#${element.id} \u2192 unknown (structural expression child)`);
37690
37627
  memo.set(element.id, 2 /* Unknown */);
37691
37628
  return 2 /* Unknown */;
37692
37629
  }
@@ -37708,11 +37645,11 @@ function getTextualContentState(element, memo, compositionMetaByElementId, logge
37708
37645
  if (!child.isDomElement) {
37709
37646
  const childMeta = compositionMetaByElementId.get(child.id);
37710
37647
  if (childMeta !== void 0 && isControlTag(childMeta.tagName)) {
37711
- if (logger.enabled) logger.trace(`[textual-content] element=${element.tagName ?? element.tag}#${element.id}: non-DOM child ${child.tag}#${child.id} resolves to control tag=${childMeta.tagName}, skipping`);
37648
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[textual-content] element=${element.tagName ?? element.tag}#${element.id}: non-DOM child ${child.tag}#${child.id} resolves to control tag=${childMeta.tagName}, skipping`);
37712
37649
  continue;
37713
37650
  }
37714
37651
  if (childState !== 1 /* No */) {
37715
- if (logger.enabled) logger.trace(`[textual-content] element=${element.tagName ?? element.tag}#${element.id}: non-DOM child ${child.tag ?? child.id}#${child.id} has state=${childState} \u2192 childHasUnknown`);
37652
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[textual-content] element=${element.tagName ?? element.tag}#${element.id}: non-DOM child ${child.tag ?? child.id}#${child.id} has state=${childState} \u2192 childHasUnknown`);
37716
37653
  childHasUnknown = true;
37717
37654
  }
37718
37655
  continue;
@@ -37725,12 +37662,12 @@ function getTextualContentState(element, memo, compositionMetaByElementId, logge
37725
37662
  if (childState === 3 /* DynamicText */) childHasDynamicText = true;
37726
37663
  }
37727
37664
  if (childHasUnknown) {
37728
- if (logger.enabled) logger.trace(`[textual-content] element=${element.tagName ?? element.tag}#${element.id} \u2192 unknown (child has unknown)`);
37665
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[textual-content] element=${element.tagName ?? element.tag}#${element.id} \u2192 unknown (child has unknown)`);
37729
37666
  memo.set(element.id, 2 /* Unknown */);
37730
37667
  return 2 /* Unknown */;
37731
37668
  }
37732
37669
  if (hasTextOnlyExpression || childHasDynamicText) {
37733
- if (logger.enabled) logger.trace(`[textual-content] element=${element.tagName ?? element.tag}#${element.id} \u2192 dynamic-text`);
37670
+ if (logger.isLevelEnabled(Level.Trace)) logger.trace(`[textual-content] element=${element.tagName ?? element.tag}#${element.id} \u2192 dynamic-text`);
37734
37671
  memo.set(element.id, 3 /* DynamicText */);
37735
37672
  return 3 /* DynamicText */;
37736
37673
  }
@@ -37752,15 +37689,16 @@ function collectLayoutElementRecordsForSolid(solid, selectorRequirements, inline
37752
37689
  const meta = compositionMetaByElementId.get(element.id);
37753
37690
  if (!meta || !meta.participates) continue;
37754
37691
  const localClassTokens = selectorRequirements.needsClassTokens ? getStaticClassTokensForElementEntity(solid, element) : EMPTY_STRING_LIST3;
37755
- const classTokens = mergeClassTokens(localClassTokens, meta.hostDescriptor?.staticClassTokens);
37692
+ const classTokens = mergeClassTokens(localClassTokens, meta.resolvedHost?.descriptor.staticClassTokens);
37756
37693
  const classTokenSet = classTokens.length === 0 ? EMPTY_CLASS_TOKEN_SET : createClassTokenSet(classTokens);
37757
37694
  const inlineStyleKeys = getStaticStyleKeysForElement(solid, element.id);
37758
37695
  const localAttributes = selectorRequirements.needsAttributes ? collectStaticAttributes(element) : EMPTY_ATTRIBUTES2;
37759
- const attributes = mergeAttributes(localAttributes, meta.hostDescriptor?.staticAttributes);
37696
+ const attributes = mergeAttributes(localAttributes, meta.resolvedHost?.descriptor.staticAttributes);
37760
37697
  const selectorDispatchKeys = buildSelectorDispatchKeys(attributes, classTokens);
37761
37698
  const inlineStyleValues = inlineStyleValuesByElementId.get(element.id) ?? EMPTY_INLINE_STYLE_VALUES;
37762
37699
  const textualContent = getTextualContentState(element, textContentMemo, compositionMetaByElementId, logger);
37763
37700
  const parentElementId = resolveComposedParentElementId(element, compositionMetaByElementId);
37701
+ const hostElementRef = meta.resolvedHost?.hostElementRef ?? null;
37764
37702
  out.push({
37765
37703
  element,
37766
37704
  key: toLayoutElementKey(solid.file, element.id),
@@ -37773,7 +37711,8 @@ function collectLayoutElementRecordsForSolid(solid, selectorRequirements, inline
37773
37711
  selectorDispatchKeys,
37774
37712
  inlineStyleValues,
37775
37713
  textualContent,
37776
- parentElementId
37714
+ parentElementId,
37715
+ hostElementRef
37777
37716
  });
37778
37717
  }
37779
37718
  return out;
@@ -37783,7 +37722,7 @@ function collectCompositionMetaByElementId(solid, componentHostResolver) {
37783
37722
  for (let i = 0; i < solid.jsxElements.length; i++) {
37784
37723
  const element = solid.jsxElements[i];
37785
37724
  if (!element) continue;
37786
- const hostDescriptor = resolveHostDescriptorForElement(
37725
+ const resolvedHost = resolveHostForElement(
37787
37726
  componentHostResolver,
37788
37727
  solid.file,
37789
37728
  element
@@ -37792,30 +37731,30 @@ function collectCompositionMetaByElementId(solid, componentHostResolver) {
37792
37731
  componentHostResolver,
37793
37732
  solid.file,
37794
37733
  element,
37795
- hostDescriptor
37734
+ resolvedHost
37796
37735
  );
37797
37736
  const participates = element.tag !== null && !isTransparentPrimitive;
37798
- const tag = resolveEffectiveTag(element, hostDescriptor);
37737
+ const tag = resolveEffectiveTag(element, resolvedHost?.descriptor ?? null);
37799
37738
  const tagName = tag ? tag.toLowerCase() : null;
37800
37739
  out.set(element.id, {
37801
37740
  element,
37802
37741
  participates,
37803
37742
  tag,
37804
37743
  tagName,
37805
- hostDescriptor
37744
+ resolvedHost
37806
37745
  });
37807
37746
  }
37808
37747
  return out;
37809
37748
  }
37810
- function resolveHostDescriptorForElement(componentHostResolver, solidFile, element) {
37749
+ function resolveHostForElement(componentHostResolver, solidFile, element) {
37811
37750
  if (element.tag === null) return null;
37812
37751
  if (element.isDomElement) return null;
37813
37752
  return componentHostResolver.resolveHost(solidFile, element.tag);
37814
37753
  }
37815
- function resolveTransparentPrimitiveStatus(componentHostResolver, solidFile, element, hostDescriptor) {
37754
+ function resolveTransparentPrimitiveStatus(componentHostResolver, solidFile, element, resolvedHost) {
37816
37755
  if (element.tag === null) return false;
37817
37756
  if (element.isDomElement) return false;
37818
- if (hostDescriptor !== null) return false;
37757
+ if (resolvedHost !== null) return false;
37819
37758
  return componentHostResolver.isTransparentPrimitive(solidFile, element.tag);
37820
37759
  }
37821
37760
  function resolveEffectiveTag(element, hostDescriptor) {
@@ -37933,6 +37872,7 @@ function buildLayoutGraph(solids, css, logger = noopLogger) {
37933
37872
  const childrenByParentNodeMutable = /* @__PURE__ */ new Map();
37934
37873
  const elementBySolidFileAndIdMutable = /* @__PURE__ */ new Map();
37935
37874
  const elementRefsBySolidFileAndIdMutable = /* @__PURE__ */ new Map();
37875
+ const hostElementRefsByNodeMutable = /* @__PURE__ */ new Map();
37936
37876
  const appliesByElementNodeMutable = /* @__PURE__ */ new Map();
37937
37877
  const selectorsById = /* @__PURE__ */ new Map();
37938
37878
  const monitoredDeclarationsBySelectorId = /* @__PURE__ */ new Map();
@@ -37969,7 +37909,7 @@ function buildLayoutGraph(solids, css, logger = noopLogger) {
37969
37909
  const moduleResolver = createLayoutModuleResolver(solids, css);
37970
37910
  const componentHostResolver = createLayoutComponentHostResolver(solids, moduleResolver, logger);
37971
37911
  const cssScopeBySolidFile = collectCSSScopeBySolidFile(solids, css, moduleResolver);
37972
- if (logger.enabled) {
37912
+ if (logger.isLevelEnabled(Level.Trace)) {
37973
37913
  for (const [solidFile, scopePaths] of cssScopeBySolidFile) {
37974
37914
  if (scopePaths.length > 0) {
37975
37915
  let names = "";
@@ -38064,6 +38004,9 @@ function buildLayoutGraph(solids, css, logger = noopLogger) {
38064
38004
  isControl: isControlTag(record.tagName),
38065
38005
  isReplaced: isReplacedTag(record.tagName)
38066
38006
  };
38007
+ if (record.hostElementRef !== null) {
38008
+ hostElementRefsByNodeMutable.set(node, record.hostElementRef);
38009
+ }
38067
38010
  elements.push(node);
38068
38011
  elementById.set(record.element.id, node);
38069
38012
  nodeByElementId.set(record.element.id, node);
@@ -38085,7 +38028,7 @@ function buildLayoutGraph(solids, css, logger = noopLogger) {
38085
38028
  }
38086
38029
  }
38087
38030
  }
38088
- if (logger.enabled) {
38031
+ if (logger.isLevelEnabled(Level.Debug)) {
38089
38032
  for (const [file, roots] of rootElementsByFile) {
38090
38033
  const descs = roots.map((r) => `${r.key}(tag=${r.tagName}, attrs=[${[...r.attributes.entries()].map(([k, v]) => `${k}=${v}`).join(",")}])`);
38091
38034
  logger.debug(`[build] rootElementsByFile file=${file} count=${roots.length}: ${descs.join(", ")}`);
@@ -38128,7 +38071,7 @@ function buildLayoutGraph(solids, css, logger = noopLogger) {
38128
38071
  appliesByNode.set(node, edges);
38129
38072
  }
38130
38073
  perf.cascadeBuildMs = performance.now() - cascadeStartedAt;
38131
- if (logger.enabled) {
38074
+ if (logger.isLevelEnabled(Level.Trace)) {
38132
38075
  for (let i = 0; i < elements.length; i++) {
38133
38076
  const node = elements[i];
38134
38077
  if (!node) continue;
@@ -38184,6 +38127,7 @@ function buildLayoutGraph(solids, css, logger = noopLogger) {
38184
38127
  childrenByParentNode: childrenByParentNodeMutable,
38185
38128
  elementBySolidFileAndId: elementBySolidFileAndIdMutable,
38186
38129
  elementRefsBySolidFileAndId: elementRefsBySolidFileAndIdMutable,
38130
+ hostElementRefsByNode: hostElementRefsByNodeMutable,
38187
38131
  appliesByNode,
38188
38132
  selectorCandidatesByNode,
38189
38133
  selectorsById,
@@ -38584,7 +38528,7 @@ function computeFlowParticipationFact(snapshot) {
38584
38528
  }
38585
38529
  function buildContextIndex(childrenByParentNode, snapshotByElementNode, perf, logger) {
38586
38530
  const out = /* @__PURE__ */ new Map();
38587
- const trace = logger.enabled;
38531
+ const trace = logger.isLevelEnabled(Level.Trace);
38588
38532
  for (const [parent, children] of childrenByParentNode) {
38589
38533
  if (children.length < 2) continue;
38590
38534
  const snapshot = snapshotByElementNode.get(parent);
@@ -39403,7 +39347,7 @@ function runLayoutDetector(context, detector) {
39403
39347
  result.evidence.posteriorLower,
39404
39348
  result.evidence.posteriorUpper
39405
39349
  );
39406
- if (log.enabled) {
39350
+ if (log.isLevelEnabled(Level.Debug)) {
39407
39351
  log.debug(
39408
39352
  `[${detector.id}] accept case=${i} severity=${result.evidence.severity.toFixed(2)} confidence=${result.evidence.confidence.toFixed(2)} posterior=[${result.evidence.posteriorLower.toFixed(3)},${result.evidence.posteriorUpper.toFixed(3)}] evidenceMass=${result.evidence.evidenceMass.toFixed(3)} context=${result.evidence.contextKind} offset=${result.evidence.estimatedOffsetPx?.toFixed(2) ?? "null"} topFactors=[${result.evidence.topFactors.join(",")}] causes=[${result.evidence.causes.join("; ")}]`
39409
39353
  );
@@ -39412,7 +39356,7 @@ function runLayoutDetector(context, detector) {
39412
39356
  continue;
39413
39357
  }
39414
39358
  recordPolicyMetrics(context, result.evidenceMass, result.posteriorLower, result.posteriorUpper);
39415
- if (log.enabled) {
39359
+ if (log.isLevelEnabled(Level.Debug)) {
39416
39360
  log.debug(
39417
39361
  `[${detector.id}] reject case=${i} reason=${result.reason} detail=${result.detail ?? "none"} posterior=[${result.posteriorLower.toFixed(3)},${result.posteriorUpper.toFixed(3)}] evidenceMass=${result.evidenceMass.toFixed(3)}`
39418
39362
  );
@@ -39508,13 +39452,13 @@ function emitLayoutDiagnostic(layout, node, emit, ruleId, messageId, template, s
39508
39452
  }
39509
39453
 
39510
39454
  // src/cross-file/rules/undefined-css-class.ts
39511
- var messages132 = {
39455
+ var messages131 = {
39512
39456
  undefinedClass: "CSS class '{{className}}' is not defined in project CSS files"
39513
39457
  };
39514
39458
  var jsxNoUndefinedCssClass = defineCrossRule({
39515
39459
  id: "jsx-no-undefined-css-class",
39516
39460
  severity: "error",
39517
- messages: messages132,
39461
+ messages: messages131,
39518
39462
  meta: {
39519
39463
  description: "Detect undefined CSS class names in JSX",
39520
39464
  fixable: false,
@@ -39533,7 +39477,7 @@ var jsxNoUndefinedCssClass = defineCrossRule({
39533
39477
  ref.solid.sourceFile,
39534
39478
  jsxNoUndefinedCssClass.id,
39535
39479
  "undefinedClass",
39536
- resolveMessage(messages132.undefinedClass, { className: item.className }),
39480
+ resolveMessage(messages131.undefinedClass, { className: item.className }),
39537
39481
  "error"
39538
39482
  ));
39539
39483
  }
@@ -39541,13 +39485,13 @@ var jsxNoUndefinedCssClass = defineCrossRule({
39541
39485
  });
39542
39486
 
39543
39487
  // src/cross-file/rules/unreferenced-css-class.ts
39544
- var messages133 = {
39488
+ var messages132 = {
39545
39489
  unreferencedClass: "CSS class '{{className}}' is defined but not referenced by static JSX class attributes"
39546
39490
  };
39547
39491
  var cssNoUnreferencedComponentClass = defineCrossRule({
39548
39492
  id: "css-no-unreferenced-component-class",
39549
39493
  severity: "warn",
39550
- messages: messages133,
39494
+ messages: messages132,
39551
39495
  meta: {
39552
39496
  description: "Detect CSS classes that are never referenced by static JSX class attributes.",
39553
39497
  fixable: false,
@@ -39571,7 +39515,7 @@ var cssNoUnreferencedComponentClass = defineCrossRule({
39571
39515
  },
39572
39516
  cssNoUnreferencedComponentClass.id,
39573
39517
  "unreferencedClass",
39574
- resolveMessage(messages133.unreferencedClass, { className }),
39518
+ resolveMessage(messages132.unreferencedClass, { className }),
39575
39519
  "warn"
39576
39520
  )
39577
39521
  );
@@ -39580,13 +39524,13 @@ var cssNoUnreferencedComponentClass = defineCrossRule({
39580
39524
  });
39581
39525
 
39582
39526
  // src/cross-file/rules/jsx-no-duplicate-class-token-class-classlist.ts
39583
- var messages134 = {
39527
+ var messages133 = {
39584
39528
  duplicateClassToken: "Class token `{{name}}` appears in both class and classList."
39585
39529
  };
39586
39530
  var jsxNoDuplicateClassTokenClassClasslist = defineCrossRule({
39587
39531
  id: "jsx-no-duplicate-class-token-class-classlist",
39588
39532
  severity: "warn",
39589
- messages: messages134,
39533
+ messages: messages133,
39590
39534
  meta: {
39591
39535
  description: "Disallow duplicate class tokens between class and classList on the same JSX element.",
39592
39536
  fixable: false,
@@ -39628,7 +39572,7 @@ var jsxNoDuplicateClassTokenClassClasslist = defineCrossRule({
39628
39572
  solid.sourceFile,
39629
39573
  jsxNoDuplicateClassTokenClassClasslist.id,
39630
39574
  "duplicateClassToken",
39631
- resolveMessage(messages134.duplicateClassToken, { name: token }),
39575
+ resolveMessage(messages133.duplicateClassToken, { name: token }),
39632
39576
  "warn"
39633
39577
  ));
39634
39578
  }
@@ -39639,13 +39583,13 @@ var jsxNoDuplicateClassTokenClassClasslist = defineCrossRule({
39639
39583
 
39640
39584
  // src/cross-file/rules/jsx-classlist-static-keys.ts
39641
39585
  var import_typescript128 = __toESM(require("typescript"), 1);
39642
- var messages135 = {
39586
+ var messages134 = {
39643
39587
  nonStaticKey: "classList key must be statically known for reliable class mapping."
39644
39588
  };
39645
39589
  var jsxClasslistStaticKeys = defineCrossRule({
39646
39590
  id: "jsx-classlist-static-keys",
39647
39591
  severity: "error",
39648
- messages: messages135,
39592
+ messages: messages134,
39649
39593
  meta: {
39650
39594
  description: "Require classList keys to be static and non-computed.",
39651
39595
  fixable: false,
@@ -39656,26 +39600,26 @@ var jsxClasslistStaticKeys = defineCrossRule({
39656
39600
  forEachClassListPropertyAcross(solids, (solid, p) => {
39657
39601
  if (import_typescript128.default.isSpreadAssignment(p)) return;
39658
39602
  if (!import_typescript128.default.isPropertyAssignment(p)) {
39659
- emit(createDiagnostic(solid.file, p, solid.sourceFile, jsxClasslistStaticKeys.id, "nonStaticKey", resolveMessage(messages135.nonStaticKey), "error"));
39603
+ emit(createDiagnostic(solid.file, p, solid.sourceFile, jsxClasslistStaticKeys.id, "nonStaticKey", resolveMessage(messages134.nonStaticKey), "error"));
39660
39604
  return;
39661
39605
  }
39662
39606
  if (import_typescript128.default.isComputedPropertyName(p.name)) return;
39663
39607
  if (import_typescript128.default.isIdentifier(p.name)) return;
39664
39608
  if (import_typescript128.default.isStringLiteral(p.name)) return;
39665
- emit(createDiagnostic(solid.file, p.name, solid.sourceFile, jsxClasslistStaticKeys.id, "nonStaticKey", resolveMessage(messages135.nonStaticKey), "error"));
39609
+ emit(createDiagnostic(solid.file, p.name, solid.sourceFile, jsxClasslistStaticKeys.id, "nonStaticKey", resolveMessage(messages134.nonStaticKey), "error"));
39666
39610
  });
39667
39611
  }
39668
39612
  });
39669
39613
 
39670
39614
  // src/cross-file/rules/jsx-classlist-no-constant-literals.ts
39671
39615
  var import_typescript129 = __toESM(require("typescript"), 1);
39672
- var messages136 = {
39616
+ var messages135 = {
39673
39617
  constantEntry: "classList entry `{{name}}: {{value}}` is constant; move it to static class."
39674
39618
  };
39675
39619
  var jsxClasslistNoConstantLiterals = defineCrossRule({
39676
39620
  id: "jsx-classlist-no-constant-literals",
39677
39621
  severity: "warn",
39678
- messages: messages136,
39622
+ messages: messages135,
39679
39623
  meta: {
39680
39624
  description: "Disallow classList entries with constant true/false values.",
39681
39625
  fixable: false,
@@ -39695,7 +39639,7 @@ var jsxClasslistNoConstantLiterals = defineCrossRule({
39695
39639
  solid.sourceFile,
39696
39640
  jsxClasslistNoConstantLiterals.id,
39697
39641
  "constantEntry",
39698
- resolveMessage(messages136.constantEntry, { name: n, value: val.kind === import_typescript129.default.SyntaxKind.TrueKeyword ? "true" : "false" }),
39642
+ resolveMessage(messages135.constantEntry, { name: n, value: val.kind === import_typescript129.default.SyntaxKind.TrueKeyword ? "true" : "false" }),
39699
39643
  "warn"
39700
39644
  ));
39701
39645
  });
@@ -39730,13 +39674,13 @@ function isDefinitelyNonBooleanType(solid, node) {
39730
39674
  }
39731
39675
 
39732
39676
  // src/cross-file/rules/jsx-classlist-boolean-values.ts
39733
- var messages137 = {
39677
+ var messages136 = {
39734
39678
  nonBooleanValue: "classList value for `{{name}}` must be boolean."
39735
39679
  };
39736
39680
  var jsxClasslistBooleanValues = defineCrossRule({
39737
39681
  id: "jsx-classlist-boolean-values",
39738
39682
  severity: "error",
39739
- messages: messages137,
39683
+ messages: messages136,
39740
39684
  meta: {
39741
39685
  description: "Require classList values to be boolean-like expressions.",
39742
39686
  fixable: false,
@@ -39750,25 +39694,25 @@ var jsxClasslistBooleanValues = defineCrossRule({
39750
39694
  if (!n) return;
39751
39695
  if (isBooleanish(p.initializer)) return;
39752
39696
  if (isDefinitelyNonBoolean(p.initializer)) {
39753
- emit(createDiagnostic(solid.file, p.initializer, solid.sourceFile, jsxClasslistBooleanValues.id, "nonBooleanValue", resolveMessage(messages137.nonBooleanValue, { name: n }), "error"));
39697
+ emit(createDiagnostic(solid.file, p.initializer, solid.sourceFile, jsxClasslistBooleanValues.id, "nonBooleanValue", resolveMessage(messages136.nonBooleanValue, { name: n }), "error"));
39754
39698
  return;
39755
39699
  }
39756
39700
  if (isBooleanType(solid, p.initializer)) return;
39757
39701
  if (!isDefinitelyNonBooleanType(solid, p.initializer)) return;
39758
- emit(createDiagnostic(solid.file, p.initializer, solid.sourceFile, jsxClasslistBooleanValues.id, "nonBooleanValue", resolveMessage(messages137.nonBooleanValue, { name: n }), "error"));
39702
+ emit(createDiagnostic(solid.file, p.initializer, solid.sourceFile, jsxClasslistBooleanValues.id, "nonBooleanValue", resolveMessage(messages136.nonBooleanValue, { name: n }), "error"));
39759
39703
  });
39760
39704
  }
39761
39705
  });
39762
39706
 
39763
39707
  // src/cross-file/rules/jsx-classlist-no-accessor-reference.ts
39764
39708
  var import_typescript131 = __toESM(require("typescript"), 1);
39765
- var messages138 = {
39709
+ var messages137 = {
39766
39710
  accessorReference: "Signal accessor `{{name}}` must be called in classList value (use {{name}}())."
39767
39711
  };
39768
39712
  var jsxClasslistNoAccessorReference = defineCrossRule({
39769
39713
  id: "jsx-classlist-no-accessor-reference",
39770
39714
  severity: "error",
39771
- messages: messages138,
39715
+ messages: messages137,
39772
39716
  meta: {
39773
39717
  description: "Disallow passing accessor references directly as classList values.",
39774
39718
  fixable: false,
@@ -39800,7 +39744,7 @@ var jsxClasslistNoAccessorReference = defineCrossRule({
39800
39744
  solid.sourceFile,
39801
39745
  jsxClasslistNoAccessorReference.id,
39802
39746
  "accessorReference",
39803
- resolveMessage(messages138.accessorReference, { name: v.text }),
39747
+ resolveMessage(messages137.accessorReference, { name: v.text }),
39804
39748
  "error"
39805
39749
  ));
39806
39750
  });
@@ -39809,13 +39753,13 @@ var jsxClasslistNoAccessorReference = defineCrossRule({
39809
39753
 
39810
39754
  // src/cross-file/rules/jsx-style-kebab-case-keys.ts
39811
39755
  var import_typescript132 = __toESM(require("typescript"), 1);
39812
- var messages139 = {
39756
+ var messages138 = {
39813
39757
  kebabStyleKey: "Style key `{{name}}` should be `{{kebab}}` in Solid style objects."
39814
39758
  };
39815
39759
  var jsxStyleKebabCaseKeys = defineCrossRule({
39816
39760
  id: "jsx-style-kebab-case-keys",
39817
39761
  severity: "error",
39818
- messages: messages139,
39762
+ messages: messages138,
39819
39763
  meta: {
39820
39764
  description: "Require kebab-case keys in JSX style object literals.",
39821
39765
  fixable: false,
@@ -39836,7 +39780,7 @@ var jsxStyleKebabCaseKeys = defineCrossRule({
39836
39780
  solid.sourceFile,
39837
39781
  jsxStyleKebabCaseKeys.id,
39838
39782
  "kebabStyleKey",
39839
- resolveMessage(messages139.kebabStyleKey, { name: n, kebab }),
39783
+ resolveMessage(messages138.kebabStyleKey, { name: n, kebab }),
39840
39784
  "error"
39841
39785
  ));
39842
39786
  });
@@ -39845,13 +39789,13 @@ var jsxStyleKebabCaseKeys = defineCrossRule({
39845
39789
 
39846
39790
  // src/cross-file/rules/jsx-style-no-function-values.ts
39847
39791
  var import_typescript133 = __toESM(require("typescript"), 1);
39848
- var messages140 = {
39792
+ var messages139 = {
39849
39793
  functionStyleValue: "Style value for `{{name}}` is a function; pass computed value instead."
39850
39794
  };
39851
39795
  var jsxStyleNoFunctionValues = defineCrossRule({
39852
39796
  id: "jsx-style-no-function-values",
39853
39797
  severity: "error",
39854
- messages: messages140,
39798
+ messages: messages139,
39855
39799
  meta: {
39856
39800
  description: "Disallow function values in JSX style objects.",
39857
39801
  fixable: false,
@@ -39865,11 +39809,11 @@ var jsxStyleNoFunctionValues = defineCrossRule({
39865
39809
  if (!n) return;
39866
39810
  const v = p.initializer;
39867
39811
  if (import_typescript133.default.isArrowFunction(v) || import_typescript133.default.isFunctionExpression(v)) {
39868
- emit(createDiagnostic(solid.file, v, solid.sourceFile, jsxStyleNoFunctionValues.id, "functionStyleValue", resolveMessage(messages140.functionStyleValue, { name: n }), "error"));
39812
+ emit(createDiagnostic(solid.file, v, solid.sourceFile, jsxStyleNoFunctionValues.id, "functionStyleValue", resolveMessage(messages139.functionStyleValue, { name: n }), "error"));
39869
39813
  return;
39870
39814
  }
39871
39815
  if (import_typescript133.default.isIdentifier(v) && solid.typeResolver.isCallableType(v)) {
39872
- emit(createDiagnostic(solid.file, v, solid.sourceFile, jsxStyleNoFunctionValues.id, "functionStyleValue", resolveMessage(messages140.functionStyleValue, { name: n }), "error"));
39816
+ emit(createDiagnostic(solid.file, v, solid.sourceFile, jsxStyleNoFunctionValues.id, "functionStyleValue", resolveMessage(messages139.functionStyleValue, { name: n }), "error"));
39873
39817
  }
39874
39818
  });
39875
39819
  }
@@ -39877,13 +39821,13 @@ var jsxStyleNoFunctionValues = defineCrossRule({
39877
39821
 
39878
39822
  // src/cross-file/rules/jsx-style-no-unused-custom-prop.ts
39879
39823
  var import_typescript134 = __toESM(require("typescript"), 1);
39880
- var messages141 = {
39824
+ var messages140 = {
39881
39825
  unusedInlineVar: "Inline custom property `{{name}}` is never read via var({{name}})."
39882
39826
  };
39883
39827
  var jsxStyleNoUnusedCustomProp = defineCrossRule({
39884
39828
  id: "jsx-style-no-unused-custom-prop",
39885
39829
  severity: "warn",
39886
- messages: messages141,
39830
+ messages: messages140,
39887
39831
  meta: {
39888
39832
  description: "Detect inline style custom properties that are never consumed by CSS var() references.",
39889
39833
  fixable: false,
@@ -39924,7 +39868,7 @@ var jsxStyleNoUnusedCustomProp = defineCrossRule({
39924
39868
  solid.sourceFile,
39925
39869
  jsxStyleNoUnusedCustomProp.id,
39926
39870
  "unusedInlineVar",
39927
- resolveMessage(messages141.unusedInlineVar, { name: n }),
39871
+ resolveMessage(messages140.unusedInlineVar, { name: n }),
39928
39872
  "warn"
39929
39873
  ));
39930
39874
  }
@@ -39934,7 +39878,7 @@ var jsxStyleNoUnusedCustomProp = defineCrossRule({
39934
39878
 
39935
39879
  // src/cross-file/rules/jsx-style-policy.ts
39936
39880
  var import_typescript135 = __toESM(require("typescript"), 1);
39937
- var messages142 = {
39881
+ var messages141 = {
39938
39882
  fontTooSmall: "Inline style `{{prop}}: {{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for policy `{{policy}}`.",
39939
39883
  lineHeightTooSmall: "Inline style `line-height: {{value}}` is below the minimum `{{min}}` for policy `{{policy}}`.",
39940
39884
  heightTooSmall: "Inline style `{{prop}}: {{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for interactive elements in policy `{{policy}}`.",
@@ -39945,28 +39889,54 @@ var INLINE_TOUCH_TARGET_KEYS = /* @__PURE__ */ new Set([
39945
39889
  "height",
39946
39890
  "min-height",
39947
39891
  "width",
39948
- "min-width",
39949
- "padding-left",
39950
- "padding-right",
39951
- "padding-inline",
39952
- "padding-inline-start",
39953
- "padding-inline-end"
39892
+ "min-width"
39893
+ ]);
39894
+ var INTERACTIVE_HTML_TAGS = /* @__PURE__ */ new Set(["button", "a", "input", "select", "textarea", "label", "summary"]);
39895
+ var INTERACTIVE_ARIA_ROLES = /* @__PURE__ */ new Set([
39896
+ "button",
39897
+ "link",
39898
+ "checkbox",
39899
+ "radio",
39900
+ "combobox",
39901
+ "listbox",
39902
+ "menuitem",
39903
+ "menuitemcheckbox",
39904
+ "menuitemradio",
39905
+ "option",
39906
+ "switch",
39907
+ "tab"
39954
39908
  ]);
39909
+ function isInteractiveElement(solid, element, hostElementRef) {
39910
+ if (element.tagName !== null && INTERACTIVE_HTML_TAGS.has(element.tagName)) return true;
39911
+ const roleAttr = getJSXAttributeEntity(solid, element, "role");
39912
+ if (roleAttr !== null && roleAttr.valueNode !== null) {
39913
+ const role = getStaticStringFromJSXValue(roleAttr.valueNode);
39914
+ if (role !== null && INTERACTIVE_ARIA_ROLES.has(role)) return true;
39915
+ }
39916
+ if (hostElementRef !== null && hostElementRef.element.tagName !== null) {
39917
+ if (INTERACTIVE_HTML_TAGS.has(hostElementRef.element.tagName)) return true;
39918
+ }
39919
+ return false;
39920
+ }
39921
+ function readNodeHostElementRef(layout, solid, element) {
39922
+ const node = layout.elementBySolidFileAndId.get(solid.file)?.get(element.id) ?? null;
39923
+ return node !== null ? readHostElementRef(layout, node) : null;
39924
+ }
39955
39925
  var jsxStylePolicy = defineCrossRule({
39956
39926
  id: "jsx-style-policy",
39957
39927
  severity: "warn",
39958
- messages: messages142,
39928
+ messages: messages141,
39959
39929
  meta: {
39960
39930
  description: "Enforce accessibility policy thresholds on inline JSX style objects.",
39961
39931
  fixable: false,
39962
39932
  category: "css-jsx"
39963
39933
  },
39964
39934
  check(context, emit) {
39965
- const { solids } = context;
39935
+ const { solids, layout } = context;
39966
39936
  const policy = getActivePolicy();
39967
39937
  if (policy === null) return;
39968
39938
  const name = getActivePolicyName() ?? "";
39969
- forEachStylePropertyAcross(solids, (solid, p) => {
39939
+ forEachStylePropertyAcross(solids, (solid, p, element) => {
39970
39940
  if (!import_typescript135.default.isPropertyAssignment(p)) return;
39971
39941
  const key = objectKeyName(p.name);
39972
39942
  if (!key) return;
@@ -39982,7 +39952,7 @@ var jsxStylePolicy = defineCrossRule({
39982
39952
  solid.sourceFile,
39983
39953
  jsxStylePolicy.id,
39984
39954
  "fontTooSmall",
39985
- resolveMessage(messages142.fontTooSmall, {
39955
+ resolveMessage(messages141.fontTooSmall, {
39986
39956
  prop: key,
39987
39957
  value: strVal,
39988
39958
  resolved: formatRounded(px),
@@ -40004,7 +39974,7 @@ var jsxStylePolicy = defineCrossRule({
40004
39974
  solid.sourceFile,
40005
39975
  jsxStylePolicy.id,
40006
39976
  "lineHeightTooSmall",
40007
- resolveMessage(messages142.lineHeightTooSmall, {
39977
+ resolveMessage(messages141.lineHeightTooSmall, {
40008
39978
  value: String(lh),
40009
39979
  min: String(policy.minLineHeight),
40010
39980
  policy: name
@@ -40014,6 +39984,8 @@ var jsxStylePolicy = defineCrossRule({
40014
39984
  return;
40015
39985
  }
40016
39986
  if (INLINE_TOUCH_TARGET_KEYS.has(normalizedKey)) {
39987
+ const hostRef = readNodeHostElementRef(layout, solid, element);
39988
+ if (!isInteractiveElement(solid, element, hostRef)) return;
40017
39989
  const strVal = getStaticStringValue(p.initializer);
40018
39990
  if (!strVal) return;
40019
39991
  const px = parsePxValue(strVal);
@@ -40026,7 +39998,7 @@ var jsxStylePolicy = defineCrossRule({
40026
39998
  solid.sourceFile,
40027
39999
  jsxStylePolicy.id,
40028
40000
  "heightTooSmall",
40029
- resolveMessage(messages142.heightTooSmall, {
40001
+ resolveMessage(messages141.heightTooSmall, {
40030
40002
  prop: key,
40031
40003
  value: strVal,
40032
40004
  resolved: formatRounded(px),
@@ -40048,7 +40020,7 @@ var jsxStylePolicy = defineCrossRule({
40048
40020
  solid.sourceFile,
40049
40021
  jsxStylePolicy.id,
40050
40022
  "letterSpacingTooSmall",
40051
- resolveMessage(messages142.letterSpacingTooSmall, {
40023
+ resolveMessage(messages141.letterSpacingTooSmall, {
40052
40024
  value: strVal,
40053
40025
  resolved: String(em),
40054
40026
  min: String(policy.minLetterSpacing),
@@ -40069,7 +40041,7 @@ var jsxStylePolicy = defineCrossRule({
40069
40041
  solid.sourceFile,
40070
40042
  jsxStylePolicy.id,
40071
40043
  "wordSpacingTooSmall",
40072
- resolveMessage(messages142.wordSpacingTooSmall, {
40044
+ resolveMessage(messages141.wordSpacingTooSmall, {
40073
40045
  value: strVal,
40074
40046
  resolved: String(em),
40075
40047
  min: String(policy.minWordSpacing),
@@ -40083,7 +40055,7 @@ var jsxStylePolicy = defineCrossRule({
40083
40055
  });
40084
40056
 
40085
40057
  // src/cross-file/rules/css-layout-sibling-alignment-outlier.ts
40086
- var messages143 = {
40058
+ var messages142 = {
40087
40059
  misalignedSibling: "Vertically misaligned '{{subject}}' in '{{parent}}'.{{fix}}{{offsetClause}}"
40088
40060
  };
40089
40061
  var MIN_CONFIDENCE_THRESHOLD = 0.48;
@@ -40092,7 +40064,7 @@ var siblingAlignmentDetector = {
40092
40064
  id: "sibling-alignment-outlier",
40093
40065
  collect: collectAlignmentCases,
40094
40066
  evaluate(input, context) {
40095
- if (context.logger.enabled) {
40067
+ if (context.logger.isLevelEnabled(Level.Trace)) {
40096
40068
  const ctx = input.context;
40097
40069
  context.logger.trace(
40098
40070
  `[sibling-alignment] evaluate subject=${input.subject.elementKey} tag=${input.subject.tag} parent=${ctx.parentElementKey} parentTag=${ctx.parentTag} context.kind=${ctx.kind} certainty=${ctx.certainty} display=${ctx.parentDisplay} alignItems=${ctx.parentAlignItems} crossAxisIsBlockAxis=${ctx.crossAxisIsBlockAxis} crossAxisCertainty=${ctx.crossAxisIsBlockAxisCertainty} baseline=${ctx.baselineRelevance}`
@@ -40131,7 +40103,7 @@ var siblingAlignmentDetector = {
40131
40103
  var cssLayoutSiblingAlignmentOutlier = defineCrossRule({
40132
40104
  id: "css-layout-sibling-alignment-outlier",
40133
40105
  severity: "warn",
40134
- messages: messages143,
40106
+ messages: messages142,
40135
40107
  meta: {
40136
40108
  description: "Detect vertical alignment outliers between sibling elements in shared layout containers.",
40137
40109
  fixable: false,
@@ -40141,7 +40113,7 @@ var cssLayoutSiblingAlignmentOutlier = defineCrossRule({
40141
40113
  const log = context.logger;
40142
40114
  const detections = runLayoutDetector(context, siblingAlignmentDetector);
40143
40115
  const uniqueDetections = dedupeDetectionsBySubject(detections);
40144
- if (log.enabled) {
40116
+ if (log.isLevelEnabled(Level.Debug)) {
40145
40117
  log.debug(
40146
40118
  `[sibling-alignment] raw=${detections.length} deduped=${uniqueDetections.length}`
40147
40119
  );
@@ -40155,7 +40127,7 @@ var cssLayoutSiblingAlignmentOutlier = defineCrossRule({
40155
40127
  const subjectId = detection.caseData.subject.elementId;
40156
40128
  const logPrefix = `[sibling-alignment] <${subjectTag}> in <${parentTag}> (${subjectFile}#${subjectId})`;
40157
40129
  if (detection.evidence.confidence < MIN_CONFIDENCE_THRESHOLD) {
40158
- if (log.enabled) {
40130
+ if (log.isLevelEnabled(Level.Debug)) {
40159
40131
  log.debug(
40160
40132
  `${logPrefix} SKIP: confidence=${detection.evidence.confidence.toFixed(2)} < threshold=${MIN_CONFIDENCE_THRESHOLD}`
40161
40133
  );
@@ -40164,7 +40136,7 @@ var cssLayoutSiblingAlignmentOutlier = defineCrossRule({
40164
40136
  }
40165
40137
  const estimatedOffset = detection.evidence.estimatedOffsetPx;
40166
40138
  if (estimatedOffset !== null && Math.abs(estimatedOffset) < MIN_OFFSET_PX_THRESHOLD && !hasNonOffsetPrimaryEvidence(detection.evidence.topFactors)) {
40167
- if (log.enabled) {
40139
+ if (log.isLevelEnabled(Level.Debug)) {
40168
40140
  log.debug(
40169
40141
  `${logPrefix} SKIP: offset=${estimatedOffset.toFixed(2)}px < ${MIN_OFFSET_PX_THRESHOLD}px (no non-offset primary evidence, topFactors=[${detection.evidence.topFactors.join(",")}])`
40170
40142
  );
@@ -40176,7 +40148,7 @@ var cssLayoutSiblingAlignmentOutlier = defineCrossRule({
40176
40148
  detection.caseData.cohort.parentElementKey,
40177
40149
  detection.caseData.subject.solidFile
40178
40150
  )) {
40179
- if (log.enabled) {
40151
+ if (log.isLevelEnabled(Level.Debug)) {
40180
40152
  log.debug(`${logPrefix} SKIP: out-of-flow ancestor`);
40181
40153
  }
40182
40154
  continue;
@@ -40187,7 +40159,7 @@ var cssLayoutSiblingAlignmentOutlier = defineCrossRule({
40187
40159
  detection.caseData.subject.elementId
40188
40160
  );
40189
40161
  if (!subjectRef) {
40190
- if (log.enabled) {
40162
+ if (log.isLevelEnabled(Level.Debug)) {
40191
40163
  log.debug(`${logPrefix} SKIP: no node ref`);
40192
40164
  }
40193
40165
  continue;
@@ -40203,7 +40175,7 @@ var cssLayoutSiblingAlignmentOutlier = defineCrossRule({
40203
40175
  const primaryFix = detection.evidence.primaryFix;
40204
40176
  const firstChar = primaryFix.length > 0 ? primaryFix[0] : void 0;
40205
40177
  const fix = firstChar !== void 0 ? ` ${firstChar.toUpperCase()}${primaryFix.slice(1)}.` : "";
40206
- if (log.enabled) {
40178
+ if (log.isLevelEnabled(Level.Debug)) {
40207
40179
  log.debug(
40208
40180
  `${logPrefix} EMIT: severity=${severity} confidence=${confidence} offset=${offset?.toFixed(2) ?? "null"} posterior=[${detection.evidence.posteriorLower.toFixed(3)},${detection.evidence.posteriorUpper.toFixed(3)}] evidenceMass=${detection.evidence.evidenceMass.toFixed(3)} topFactors=[${detection.evidence.topFactors.join(",")}] causes=[${causes}]`
40209
40181
  );
@@ -40215,7 +40187,7 @@ var cssLayoutSiblingAlignmentOutlier = defineCrossRule({
40215
40187
  subjectRef.solid.sourceFile,
40216
40188
  cssLayoutSiblingAlignmentOutlier.id,
40217
40189
  "misalignedSibling",
40218
- resolveMessage(messages143.misalignedSibling, {
40190
+ resolveMessage(messages142.misalignedSibling, {
40219
40191
  subject,
40220
40192
  parent,
40221
40193
  fix,
@@ -40299,13 +40271,13 @@ function hasNonOffsetPrimaryEvidence(topFactors) {
40299
40271
  }
40300
40272
 
40301
40273
  // src/cross-file/rules/css-layout-transition-layout-property.ts
40302
- var messages144 = {
40274
+ var messages143 = {
40303
40275
  transitionLayoutProperty: "Transition '{{property}}' in '{{declaration}}' animates layout-affecting geometry. Prefer transform/opacity to avoid CLS."
40304
40276
  };
40305
40277
  var cssLayoutTransitionLayoutProperty = defineCrossRule({
40306
40278
  id: "css-layout-transition-layout-property",
40307
40279
  severity: "warn",
40308
- messages: messages144,
40280
+ messages: messages143,
40309
40281
  meta: {
40310
40282
  description: "Disallow transitions that animate layout-affecting geometry properties.",
40311
40283
  fixable: false,
@@ -40329,7 +40301,7 @@ var cssLayoutTransitionLayoutProperty = defineCrossRule({
40329
40301
  },
40330
40302
  cssLayoutTransitionLayoutProperty.id,
40331
40303
  "transitionLayoutProperty",
40332
- resolveMessage(messages144.transitionLayoutProperty, {
40304
+ resolveMessage(messages143.transitionLayoutProperty, {
40333
40305
  property: target,
40334
40306
  declaration: declaration.property
40335
40307
  }),
@@ -40344,13 +40316,13 @@ function findLayoutTransitionTarget(raw) {
40344
40316
  }
40345
40317
 
40346
40318
  // src/cross-file/rules/css-layout-animation-layout-property.ts
40347
- var messages145 = {
40319
+ var messages144 = {
40348
40320
  animationLayoutProperty: "Animation '{{animation}}' mutates layout-affecting '{{property}}', which can trigger CLS. Prefer transform/opacity or reserve geometry."
40349
40321
  };
40350
40322
  var cssLayoutAnimationLayoutProperty = defineCrossRule({
40351
40323
  id: "css-layout-animation-layout-property",
40352
40324
  severity: "warn",
40353
- messages: messages145,
40325
+ messages: messages144,
40354
40326
  meta: {
40355
40327
  description: "Disallow keyframe animations that mutate layout-affecting properties and can trigger CLS.",
40356
40328
  fixable: false,
@@ -40378,7 +40350,7 @@ var cssLayoutAnimationLayoutProperty = defineCrossRule({
40378
40350
  },
40379
40351
  cssLayoutAnimationLayoutProperty.id,
40380
40352
  "animationLayoutProperty",
40381
- resolveMessage(messages145.animationLayoutProperty, {
40353
+ resolveMessage(messages144.animationLayoutProperty, {
40382
40354
  animation: match.name,
40383
40355
  property: match.property
40384
40356
  }),
@@ -40448,13 +40420,13 @@ function firstRiskyAnimationName(names, riskyKeyframes) {
40448
40420
  }
40449
40421
 
40450
40422
  // src/cross-file/rules/css-layout-stateful-box-model-shift.ts
40451
- var messages146 = {
40423
+ var messages145 = {
40452
40424
  statefulBoxModelShift: "State selector '{{selector}}' changes layout-affecting '{{property}}'. Keep geometry stable across states to avoid CLS."
40453
40425
  };
40454
40426
  var cssLayoutStatefulBoxModelShift = defineCrossRule({
40455
40427
  id: "css-layout-stateful-box-model-shift",
40456
40428
  severity: "warn",
40457
- messages: messages146,
40429
+ messages: messages145,
40458
40430
  meta: {
40459
40431
  description: "Disallow stateful selector changes that alter element geometry and trigger layout shifts.",
40460
40432
  fixable: false,
@@ -40495,7 +40467,7 @@ var cssLayoutStatefulBoxModelShift = defineCrossRule({
40495
40467
  },
40496
40468
  cssLayoutStatefulBoxModelShift.id,
40497
40469
  "statefulBoxModelShift",
40498
- resolveMessage(messages146.statefulBoxModelShift, {
40470
+ resolveMessage(messages145.statefulBoxModelShift, {
40499
40471
  selector: match.raw,
40500
40472
  property: declaration.property
40501
40473
  }),
@@ -40589,14 +40561,14 @@ function lookupBaseByProperty(baseValueIndex, selectorKeys) {
40589
40561
 
40590
40562
  // src/cross-file/rules/css-layout-unsized-replaced-element.ts
40591
40563
  var import_typescript136 = __toESM(require("typescript"), 1);
40592
- var messages147 = {
40564
+ var messages146 = {
40593
40565
  unsizedReplacedElement: "Replaced element '{{tag}}' has no stable reserved size (width/height or aspect-ratio with a dimension), which can cause CLS."
40594
40566
  };
40595
40567
  var REPLACED_MEDIA_TAGS = /* @__PURE__ */ new Set(["img", "video", "iframe", "canvas", "svg"]);
40596
40568
  var cssLayoutUnsizedReplacedElement = defineCrossRule({
40597
40569
  id: "css-layout-unsized-replaced-element",
40598
40570
  severity: "warn",
40599
- messages: messages147,
40571
+ messages: messages146,
40600
40572
  meta: {
40601
40573
  description: "Require stable reserved geometry for replaced media elements to prevent layout shifts.",
40602
40574
  fixable: false,
@@ -40612,7 +40584,8 @@ var cssLayoutUnsizedReplacedElement = defineCrossRule({
40612
40584
  const ref = readNodeRef(context.layout, node);
40613
40585
  if (!ref) continue;
40614
40586
  const reservedSpace = readReservedSpaceFact(context.layout, node);
40615
- if (hasReservedSize(ref.solid, node.attributes, ref.element, reservedSpace)) continue;
40587
+ const hostRef = readHostElementRef(context.layout, node);
40588
+ if (hasReservedSize(ref.solid, node.attributes, ref.element, reservedSpace, hostRef)) continue;
40616
40589
  emit(
40617
40590
  createDiagnostic(
40618
40591
  ref.solid.file,
@@ -40620,22 +40593,24 @@ var cssLayoutUnsizedReplacedElement = defineCrossRule({
40620
40593
  ref.solid.sourceFile,
40621
40594
  cssLayoutUnsizedReplacedElement.id,
40622
40595
  "unsizedReplacedElement",
40623
- resolveMessage(messages147.unsizedReplacedElement, { tag }),
40596
+ resolveMessage(messages146.unsizedReplacedElement, { tag }),
40624
40597
  "warn"
40625
40598
  )
40626
40599
  );
40627
40600
  }
40628
40601
  }
40629
40602
  });
40630
- function hasReservedSize(solid, attributes, element, reservedSpaceFact) {
40603
+ function hasReservedSize(solid, attributes, element, reservedSpaceFact, hostElementRef) {
40631
40604
  if (reservedSpaceFact.hasReservedSpace) return true;
40632
40605
  const attrWidth = parsePositiveLength(attributes.get("width"));
40633
40606
  const attrHeight = parsePositiveLength(attributes.get("height"));
40634
40607
  const jsxAttrWidth = readPositiveJsxAttribute(solid, element, "width");
40635
40608
  const jsxAttrHeight = readPositiveJsxAttribute(solid, element, "height");
40636
- if (attrWidth && attrHeight || jsxAttrWidth && jsxAttrHeight) return true;
40637
- const hasAnyWidth = attrWidth || jsxAttrWidth || reservedSpaceFact.hasUsableInlineDimension;
40638
- const hasAnyHeight = attrHeight || jsxAttrHeight || reservedSpaceFact.hasUsableBlockDimension || reservedSpaceFact.hasContainIntrinsicSize;
40609
+ const hostJsxWidth = hostElementRef !== null ? readPositiveJsxAttribute(hostElementRef.solid, hostElementRef.element, "width") : false;
40610
+ const hostJsxHeight = hostElementRef !== null ? readPositiveJsxAttribute(hostElementRef.solid, hostElementRef.element, "height") : false;
40611
+ if (attrWidth && attrHeight || jsxAttrWidth && jsxAttrHeight || hostJsxWidth && hostJsxHeight) return true;
40612
+ const hasAnyWidth = attrWidth || jsxAttrWidth || hostJsxWidth || reservedSpaceFact.hasUsableInlineDimension;
40613
+ const hasAnyHeight = attrHeight || jsxAttrHeight || hostJsxHeight || reservedSpaceFact.hasUsableBlockDimension || reservedSpaceFact.hasContainIntrinsicSize;
40639
40614
  if (reservedSpaceFact.hasUsableAspectRatio && (hasAnyWidth || hasAnyHeight)) return true;
40640
40615
  if (reservedSpaceFact.hasContainIntrinsicSize && (hasAnyWidth || hasAnyHeight)) return true;
40641
40616
  return false;
@@ -40671,7 +40646,7 @@ function readPositiveJsxAttribute(solid, element, name) {
40671
40646
  }
40672
40647
 
40673
40648
  // src/cross-file/rules/css-layout-dynamic-slot-no-reserved-space.ts
40674
- var messages148 = {
40649
+ var messages147 = {
40675
40650
  dynamicSlotNoReservedSpace: "Dynamic content container '{{tag}}' does not reserve block space (min-height/height/aspect-ratio/contain-intrinsic-size), which can cause CLS."
40676
40651
  };
40677
40652
  var INLINE_DISPLAYS = /* @__PURE__ */ new Set(["inline", "contents"]);
@@ -40693,7 +40668,7 @@ function hasOutOfFlowAncestor(layout, node) {
40693
40668
  var cssLayoutDynamicSlotNoReservedSpace = defineCrossRule({
40694
40669
  id: "css-layout-dynamic-slot-no-reserved-space",
40695
40670
  severity: "warn",
40696
- messages: messages148,
40671
+ messages: messages147,
40697
40672
  meta: {
40698
40673
  description: "Require reserved block space for dynamic content containers to avoid layout shifts.",
40699
40674
  fixable: false,
@@ -40716,19 +40691,19 @@ var cssLayoutDynamicSlotNoReservedSpace = defineCrossRule({
40716
40691
  const reservedSpace = readReservedSpaceFact(context.layout, node);
40717
40692
  if (reservedSpace.hasReservedSpace) continue;
40718
40693
  if (hasBlockAxisPadding(snapshot)) continue;
40719
- if (!emitLayoutDiagnostic(context.layout, node, emit, cssLayoutDynamicSlotNoReservedSpace.id, "dynamicSlotNoReservedSpace", messages148.dynamicSlotNoReservedSpace, cssLayoutDynamicSlotNoReservedSpace.severity)) continue;
40694
+ if (!emitLayoutDiagnostic(context.layout, node, emit, cssLayoutDynamicSlotNoReservedSpace.id, "dynamicSlotNoReservedSpace", messages147.dynamicSlotNoReservedSpace, cssLayoutDynamicSlotNoReservedSpace.severity)) continue;
40720
40695
  }
40721
40696
  }
40722
40697
  });
40723
40698
 
40724
40699
  // src/cross-file/rules/css-layout-scrollbar-gutter-instability.ts
40725
- var messages149 = {
40700
+ var messages148 = {
40726
40701
  missingScrollbarGutter: "Scrollable container '{{tag}}' uses overflow auto/scroll without `scrollbar-gutter: stable`, which can trigger CLS when scrollbars appear."
40727
40702
  };
40728
40703
  var cssLayoutScrollbarGutterInstability = defineCrossRule({
40729
40704
  id: "css-layout-scrollbar-gutter-instability",
40730
40705
  severity: "warn",
40731
- messages: messages149,
40706
+ messages: messages148,
40732
40707
  meta: {
40733
40708
  description: "Require stable scrollbar gutters for scrollable containers to reduce layout shifts.",
40734
40709
  fixable: false,
@@ -40747,19 +40722,19 @@ var cssLayoutScrollbarGutterInstability = defineCrossRule({
40747
40722
  if (scrollbarWidth === "none") continue;
40748
40723
  const gutter = readKnownNormalized(snapshot, "scrollbar-gutter");
40749
40724
  if (gutter !== null && gutter.startsWith("stable")) continue;
40750
- if (!emitLayoutDiagnostic(context.layout, node, emit, cssLayoutScrollbarGutterInstability.id, "missingScrollbarGutter", messages149.missingScrollbarGutter, cssLayoutScrollbarGutterInstability.severity)) continue;
40725
+ if (!emitLayoutDiagnostic(context.layout, node, emit, cssLayoutScrollbarGutterInstability.id, "missingScrollbarGutter", messages148.missingScrollbarGutter, cssLayoutScrollbarGutterInstability.severity)) continue;
40751
40726
  }
40752
40727
  }
40753
40728
  });
40754
40729
 
40755
40730
  // src/cross-file/rules/css-layout-overflow-anchor-instability.ts
40756
- var messages150 = {
40731
+ var messages149 = {
40757
40732
  unstableOverflowAnchor: "Element '{{tag}}' sets `overflow-anchor: none` on a {{context}} container; disabling scroll anchoring can amplify visible layout shifts."
40758
40733
  };
40759
40734
  var cssLayoutOverflowAnchorInstability = defineCrossRule({
40760
40735
  id: "css-layout-overflow-anchor-instability",
40761
40736
  severity: "warn",
40762
- messages: messages150,
40737
+ messages: messages149,
40763
40738
  meta: {
40764
40739
  description: "Disallow overflow-anchor none on dynamic or scrollable containers prone to visible layout shifts.",
40765
40740
  fixable: false,
@@ -40777,20 +40752,20 @@ var cssLayoutOverflowAnchorInstability = defineCrossRule({
40777
40752
  const isDynamicContainer = isDynamicContainerLike(node);
40778
40753
  if (!isScrollable && !isDynamicContainer) continue;
40779
40754
  const containerContext = isScrollable ? "scrollable" : "dynamic";
40780
- if (!emitLayoutDiagnostic(context.layout, node, emit, cssLayoutOverflowAnchorInstability.id, "unstableOverflowAnchor", messages150.unstableOverflowAnchor, cssLayoutOverflowAnchorInstability.severity, { context: containerContext })) continue;
40755
+ if (!emitLayoutDiagnostic(context.layout, node, emit, cssLayoutOverflowAnchorInstability.id, "unstableOverflowAnchor", messages149.unstableOverflowAnchor, cssLayoutOverflowAnchorInstability.severity, { context: containerContext })) continue;
40781
40756
  }
40782
40757
  }
40783
40758
  });
40784
40759
 
40785
40760
  // src/cross-file/rules/css-layout-font-swap-instability.ts
40786
- var messages151 = {
40761
+ var messages150 = {
40787
40762
  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."
40788
40763
  };
40789
40764
  var SWAP_DISPLAYS = /* @__PURE__ */ new Set(["swap", "fallback"]);
40790
40765
  var cssLayoutFontSwapInstability = defineCrossRule({
40791
40766
  id: "css-layout-font-swap-instability",
40792
40767
  severity: "warn",
40793
- messages: messages151,
40768
+ messages: messages150,
40794
40769
  meta: {
40795
40770
  description: "Require metric overrides for swapping webfonts to reduce layout shifts during font load.",
40796
40771
  fixable: false,
@@ -40837,7 +40812,7 @@ var cssLayoutFontSwapInstability = defineCrossRule({
40837
40812
  },
40838
40813
  cssLayoutFontSwapInstability.id,
40839
40814
  "unstableFontSwap",
40840
- resolveMessage(messages151.unstableFontSwap, {
40815
+ resolveMessage(messages150.unstableFontSwap, {
40841
40816
  family,
40842
40817
  display: report.display
40843
40818
  }),
@@ -40850,14 +40825,14 @@ var cssLayoutFontSwapInstability = defineCrossRule({
40850
40825
  });
40851
40826
 
40852
40827
  // src/cross-file/rules/css-layout-conditional-display-collapse.ts
40853
- var messages152 = {
40828
+ var messages151 = {
40854
40829
  conditionalDisplayCollapse: "Conditional display sets '{{display}}' on '{{tag}}' without stable reserved space, which can collapse/expand layout and cause CLS."
40855
40830
  };
40856
40831
  var COLLAPSING_DISPLAYS = /* @__PURE__ */ new Set(["none", "contents"]);
40857
40832
  var cssLayoutConditionalDisplayCollapse = defineCrossRule({
40858
40833
  id: "css-layout-conditional-display-collapse",
40859
40834
  severity: "warn",
40860
- messages: messages152,
40835
+ messages: messages151,
40861
40836
  meta: {
40862
40837
  description: "Disallow conditional display collapse in flow without reserved geometry.",
40863
40838
  fixable: false,
@@ -40878,13 +40853,13 @@ var cssLayoutConditionalDisplayCollapse = defineCrossRule({
40878
40853
  if (!isFlowRelevantBySiblingsOrText(node, snapshot.node.textualContent)) continue;
40879
40854
  const reservedSpace = readReservedSpaceFact(context.layout, node);
40880
40855
  if (reservedSpace.hasReservedSpace) continue;
40881
- if (!emitLayoutDiagnostic(context.layout, node, emit, cssLayoutConditionalDisplayCollapse.id, "conditionalDisplayCollapse", messages152.conditionalDisplayCollapse, cssLayoutConditionalDisplayCollapse.severity, { display })) continue;
40856
+ if (!emitLayoutDiagnostic(context.layout, node, emit, cssLayoutConditionalDisplayCollapse.id, "conditionalDisplayCollapse", messages151.conditionalDisplayCollapse, cssLayoutConditionalDisplayCollapse.severity, { display })) continue;
40882
40857
  }
40883
40858
  }
40884
40859
  });
40885
40860
 
40886
40861
  // src/cross-file/rules/css-layout-conditional-white-space-wrap-shift.ts
40887
- var messages153 = {
40862
+ var messages152 = {
40888
40863
  conditionalWhiteSpaceShift: "Conditional white-space '{{whiteSpace}}' on '{{tag}}' can reflow text and shift siblings; keep wrapping behavior stable or reserve geometry."
40889
40864
  };
40890
40865
  var WRAP_SHIFT_VALUES = /* @__PURE__ */ new Set(["nowrap", "pre"]);
@@ -40893,7 +40868,7 @@ var BLOCK_SIZE_PROPERTIES = ["height", "min-height"];
40893
40868
  var cssLayoutConditionalWhiteSpaceWrapShift = defineCrossRule({
40894
40869
  id: "css-layout-conditional-white-space-wrap-shift",
40895
40870
  severity: "warn",
40896
- messages: messages153,
40871
+ messages: messages152,
40897
40872
  meta: {
40898
40873
  description: "Disallow conditional white-space wrapping mode toggles that can trigger CLS.",
40899
40874
  fixable: false,
@@ -40916,7 +40891,7 @@ var cssLayoutConditionalWhiteSpaceWrapShift = defineCrossRule({
40916
40891
  if (!flow.inFlow) continue;
40917
40892
  if (!isFlowRelevantBySiblingsOrText(node, snapshot.node.textualContent)) continue;
40918
40893
  if (hasStableTextShell(snapshot)) continue;
40919
- if (!emitLayoutDiagnostic(context.layout, node, emit, cssLayoutConditionalWhiteSpaceWrapShift.id, "conditionalWhiteSpaceShift", messages153.conditionalWhiteSpaceShift, cssLayoutConditionalWhiteSpaceWrapShift.severity, { whiteSpace })) continue;
40894
+ if (!emitLayoutDiagnostic(context.layout, node, emit, cssLayoutConditionalWhiteSpaceWrapShift.id, "conditionalWhiteSpaceShift", messages152.conditionalWhiteSpaceShift, cssLayoutConditionalWhiteSpaceWrapShift.severity, { whiteSpace })) continue;
40920
40895
  }
40921
40896
  }
40922
40897
  });
@@ -40938,13 +40913,13 @@ function hasWrapShiftDelta(delta) {
40938
40913
  }
40939
40914
 
40940
40915
  // src/cross-file/rules/css-layout-overflow-mode-toggle-instability.ts
40941
- var messages154 = {
40916
+ var messages153 = {
40942
40917
  overflowModeToggle: "Conditional overflow mode changes scrolling ('{{overflow}}') on '{{tag}}' without `scrollbar-gutter: stable`, which can trigger CLS."
40943
40918
  };
40944
40919
  var cssLayoutOverflowModeToggleInstability = defineCrossRule({
40945
40920
  id: "css-layout-overflow-mode-toggle-instability",
40946
40921
  severity: "warn",
40947
- messages: messages154,
40922
+ messages: messages153,
40948
40923
  meta: {
40949
40924
  description: "Disallow conditional overflow mode switches that can introduce scrollbar-induced layout shifts.",
40950
40925
  fixable: false,
@@ -40969,7 +40944,7 @@ var cssLayoutOverflowModeToggleInstability = defineCrossRule({
40969
40944
  const gutter = readKnownNormalizedWithGuard(snapshot, "scrollbar-gutter");
40970
40945
  if (gutter !== null && gutter.startsWith("stable")) continue;
40971
40946
  const overflowValue = scrollFact.overflowY ?? scrollFact.overflow ?? "auto";
40972
- if (!emitLayoutDiagnostic(context.layout, node, emit, cssLayoutOverflowModeToggleInstability.id, "overflowModeToggle", messages154.overflowModeToggle, cssLayoutOverflowModeToggleInstability.severity, { overflow: overflowValue })) continue;
40947
+ if (!emitLayoutDiagnostic(context.layout, node, emit, cssLayoutOverflowModeToggleInstability.id, "overflowModeToggle", messages153.overflowModeToggle, cssLayoutOverflowModeToggleInstability.severity, { overflow: overflowValue })) continue;
40973
40948
  }
40974
40949
  }
40975
40950
  });
@@ -40989,7 +40964,7 @@ function hasAnyScrollValue(delta) {
40989
40964
  }
40990
40965
 
40991
40966
  // src/cross-file/rules/css-layout-box-sizing-toggle-with-chrome.ts
40992
- var messages155 = {
40967
+ var messages154 = {
40993
40968
  boxSizingToggleWithChrome: "Conditional `box-sizing` toggle on '{{tag}}' combines with non-zero padding/border, which can shift layout and trigger CLS."
40994
40969
  };
40995
40970
  var BOX_SIZING_VALUES = /* @__PURE__ */ new Set(["content-box", "border-box"]);
@@ -41006,7 +40981,7 @@ var CHROME_PROPERTIES = [
41006
40981
  var cssLayoutBoxSizingToggleWithChrome = defineCrossRule({
41007
40982
  id: "css-layout-box-sizing-toggle-with-chrome",
41008
40983
  severity: "warn",
41009
- messages: messages155,
40984
+ messages: messages154,
41010
40985
  meta: {
41011
40986
  description: "Disallow conditional box-sizing mode toggles when box chrome contributes to geometry shifts.",
41012
40987
  fixable: false,
@@ -41024,7 +40999,7 @@ var cssLayoutBoxSizingToggleWithChrome = defineCrossRule({
41024
40999
  if (!boxSizingDelta.hasConditional) continue;
41025
41000
  if (!boxSizingDelta.hasDelta) continue;
41026
41001
  if (!hasNonZeroChrome(snapshot)) continue;
41027
- if (!emitLayoutDiagnostic(context.layout, node, emit, cssLayoutBoxSizingToggleWithChrome.id, "boxSizingToggleWithChrome", messages155.boxSizingToggleWithChrome, cssLayoutBoxSizingToggleWithChrome.severity)) continue;
41002
+ if (!emitLayoutDiagnostic(context.layout, node, emit, cssLayoutBoxSizingToggleWithChrome.id, "boxSizingToggleWithChrome", messages154.boxSizingToggleWithChrome, cssLayoutBoxSizingToggleWithChrome.severity)) continue;
41028
41003
  }
41029
41004
  }
41030
41005
  });
@@ -41033,13 +41008,13 @@ function hasNonZeroChrome(snapshot) {
41033
41008
  }
41034
41009
 
41035
41010
  // src/cross-file/rules/css-layout-content-visibility-no-intrinsic-size.ts
41036
- var messages156 = {
41011
+ var messages155 = {
41037
41012
  missingIntrinsicSize: "`content-visibility: auto` on '{{tag}}' lacks intrinsic size reservation (`contain-intrinsic-size`/min-height/height/aspect-ratio), which can cause CLS."
41038
41013
  };
41039
41014
  var cssLayoutContentVisibilityNoIntrinsicSize = defineCrossRule({
41040
41015
  id: "css-layout-content-visibility-no-intrinsic-size",
41041
41016
  severity: "warn",
41042
- messages: messages156,
41017
+ messages: messages155,
41043
41018
  meta: {
41044
41019
  description: "Require intrinsic size reservation when using content-visibility auto to avoid late layout shifts.",
41045
41020
  fixable: false,
@@ -41054,19 +41029,19 @@ var cssLayoutContentVisibilityNoIntrinsicSize = defineCrossRule({
41054
41029
  if (!isDeferredContainerLike(node, snapshot.node.textualContent)) continue;
41055
41030
  const reservedSpace = readReservedSpaceFact(context.layout, node);
41056
41031
  if (reservedSpace.hasReservedSpace) continue;
41057
- if (!emitLayoutDiagnostic(context.layout, node, emit, cssLayoutContentVisibilityNoIntrinsicSize.id, "missingIntrinsicSize", messages156.missingIntrinsicSize, cssLayoutContentVisibilityNoIntrinsicSize.severity)) continue;
41032
+ if (!emitLayoutDiagnostic(context.layout, node, emit, cssLayoutContentVisibilityNoIntrinsicSize.id, "missingIntrinsicSize", messages155.missingIntrinsicSize, cssLayoutContentVisibilityNoIntrinsicSize.severity)) continue;
41058
41033
  }
41059
41034
  }
41060
41035
  });
41061
41036
 
41062
41037
  // src/cross-file/rules/css-layout-conditional-offset-shift.ts
41063
- var messages157 = {
41038
+ var messages156 = {
41064
41039
  conditionalOffsetShift: "Conditional style applies non-zero '{{property}}' offset ({{value}}), which can cause layout shifts when conditions toggle."
41065
41040
  };
41066
41041
  var cssLayoutConditionalOffsetShift = defineCrossRule({
41067
41042
  id: "css-layout-conditional-offset-shift",
41068
41043
  severity: "warn",
41069
- messages: messages157,
41044
+ messages: messages156,
41070
41045
  meta: {
41071
41046
  description: "Disallow conditional non-zero block-axis offsets that can trigger layout shifts.",
41072
41047
  fixable: false,
@@ -41092,7 +41067,7 @@ var cssLayoutConditionalOffsetShift = defineCrossRule({
41092
41067
  ref.solid.sourceFile,
41093
41068
  cssLayoutConditionalOffsetShift.id,
41094
41069
  "conditionalOffsetShift",
41095
- resolveMessage(messages157.conditionalOffsetShift, {
41070
+ resolveMessage(messages156.conditionalOffsetShift, {
41096
41071
  property: match.property,
41097
41072
  value: `${formatFixed(match.value)}px`
41098
41073
  }),
@@ -41153,7 +41128,7 @@ function hasStableBaseline(baselineBySignal, property, expectedPx) {
41153
41128
 
41154
41129
  // src/cross-file/rules/jsx-layout-unstable-style-toggle.ts
41155
41130
  var import_typescript137 = __toESM(require("typescript"), 1);
41156
- var messages158 = {
41131
+ var messages157 = {
41157
41132
  unstableLayoutStyleToggle: "Dynamic style value for '{{property}}' can toggle layout geometry at runtime and cause CLS."
41158
41133
  };
41159
41134
  var PX_NUMBER_PROPERTIES = /* @__PURE__ */ new Set([
@@ -41176,7 +41151,7 @@ var POSITIONED_OFFSET_PROPERTIES = /* @__PURE__ */ new Set([
41176
41151
  var jsxLayoutUnstableStyleToggle = defineCrossRule({
41177
41152
  id: "jsx-layout-unstable-style-toggle",
41178
41153
  severity: "warn",
41179
- messages: messages158,
41154
+ messages: messages157,
41180
41155
  meta: {
41181
41156
  description: "Flag dynamic inline style values on layout-sensitive properties that can trigger CLS.",
41182
41157
  fixable: false,
@@ -41198,7 +41173,7 @@ var jsxLayoutUnstableStyleToggle = defineCrossRule({
41198
41173
  solid.sourceFile,
41199
41174
  jsxLayoutUnstableStyleToggle.id,
41200
41175
  "unstableLayoutStyleToggle",
41201
- resolveMessage(messages158.unstableLayoutStyleToggle, { property: normalized }),
41176
+ resolveMessage(messages157.unstableLayoutStyleToggle, { property: normalized }),
41202
41177
  "warn"
41203
41178
  )
41204
41179
  );
@@ -41315,14 +41290,14 @@ function unwrapTypeWrapper(node) {
41315
41290
 
41316
41291
  // src/cross-file/rules/jsx-layout-classlist-geometry-toggle.ts
41317
41292
  var import_typescript138 = __toESM(require("typescript"), 1);
41318
- var messages159 = {
41293
+ var messages158 = {
41319
41294
  classListGeometryToggle: "classList toggles '{{className}}', and matching CSS changes layout-affecting '{{property}}', which can cause CLS."
41320
41295
  };
41321
41296
  var OUT_OF_FLOW_POSITIONS2 = /* @__PURE__ */ new Set(["fixed", "absolute"]);
41322
41297
  var jsxLayoutClasslistGeometryToggle = defineCrossRule({
41323
41298
  id: "jsx-layout-classlist-geometry-toggle",
41324
41299
  severity: "warn",
41325
- messages: messages159,
41300
+ messages: messages158,
41326
41301
  meta: {
41327
41302
  description: "Flag classList-driven class toggles that map to layout-affecting CSS geometry changes.",
41328
41303
  fixable: false,
@@ -41348,7 +41323,7 @@ var jsxLayoutClasslistGeometryToggle = defineCrossRule({
41348
41323
  solid.sourceFile,
41349
41324
  jsxLayoutClasslistGeometryToggle.id,
41350
41325
  "classListGeometryToggle",
41351
- resolveMessage(messages159.classListGeometryToggle, {
41326
+ resolveMessage(messages158.classListGeometryToggle, {
41352
41327
  className,
41353
41328
  property
41354
41329
  }),
@@ -41397,14 +41372,14 @@ function isDynamicallyToggleable(node) {
41397
41372
  }
41398
41373
 
41399
41374
  // src/cross-file/rules/jsx-layout-picture-source-ratio-consistency.ts
41400
- var messages160 = {
41375
+ var messages159 = {
41401
41376
  inconsistentPictureRatio: "`<picture>` source ratio {{sourceRatio}} differs from fallback img ratio {{imgRatio}}, which can cause reserved-space mismatch and CLS."
41402
41377
  };
41403
41378
  var RATIO_DELTA_THRESHOLD = 0.02;
41404
41379
  var jsxLayoutPictureSourceRatioConsistency = defineCrossRule({
41405
41380
  id: "jsx-layout-picture-source-ratio-consistency",
41406
41381
  severity: "warn",
41407
- messages: messages160,
41382
+ messages: messages159,
41408
41383
  meta: {
41409
41384
  description: "Require consistent intrinsic aspect ratios across <picture> sources and fallback image.",
41410
41385
  fixable: false,
@@ -41427,7 +41402,7 @@ var jsxLayoutPictureSourceRatioConsistency = defineCrossRule({
41427
41402
  solid.sourceFile,
41428
41403
  jsxLayoutPictureSourceRatioConsistency.id,
41429
41404
  "inconsistentPictureRatio",
41430
- resolveMessage(messages160.inconsistentPictureRatio, {
41405
+ resolveMessage(messages159.inconsistentPictureRatio, {
41431
41406
  sourceRatio: mismatch.sourceRatio,
41432
41407
  imgRatio: mismatch.imgRatio
41433
41408
  }),
@@ -41497,13 +41472,13 @@ function formatRatio(value2) {
41497
41472
  }
41498
41473
 
41499
41474
  // src/cross-file/rules/jsx-layout-fill-image-parent-must-be-sized.ts
41500
- var messages161 = {
41475
+ var messages160 = {
41501
41476
  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."
41502
41477
  };
41503
41478
  var jsxLayoutFillImageParentMustBeSized = defineCrossRule({
41504
41479
  id: "jsx-layout-fill-image-parent-must-be-sized",
41505
41480
  severity: "warn",
41506
- messages: messages161,
41481
+ messages: messages160,
41507
41482
  meta: {
41508
41483
  description: "Require stable parent size and positioning for fill-image component usage.",
41509
41484
  fixable: false,
@@ -41533,7 +41508,7 @@ var jsxLayoutFillImageParentMustBeSized = defineCrossRule({
41533
41508
  solid.sourceFile,
41534
41509
  jsxLayoutFillImageParentMustBeSized.id,
41535
41510
  "unsizedFillParent",
41536
- resolveMessage(messages161.unsizedFillParent, {
41511
+ resolveMessage(messages160.unsizedFillParent, {
41537
41512
  component: element.tag ?? "Image"
41538
41513
  }),
41539
41514
  "warn"
@@ -41544,6 +41519,210 @@ var jsxLayoutFillImageParentMustBeSized = defineCrossRule({
41544
41519
  }
41545
41520
  });
41546
41521
 
41522
+ // src/cross-file/rules/jsx-layout-policy-touch-target.ts
41523
+ var messages161 = {
41524
+ heightTooSmall: "`{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.",
41525
+ widthTooSmall: "`{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.",
41526
+ paddingTooSmall: "Horizontal padding `{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.",
41527
+ 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.",
41528
+ 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."
41529
+ };
41530
+ var INTERACTIVE_HTML_TAGS2 = /* @__PURE__ */ new Set(["button", "a", "input", "select", "textarea", "label", "summary"]);
41531
+ var INTERACTIVE_ARIA_ROLES2 = /* @__PURE__ */ new Set([
41532
+ "button",
41533
+ "link",
41534
+ "checkbox",
41535
+ "radio",
41536
+ "combobox",
41537
+ "listbox",
41538
+ "menuitem",
41539
+ "menuitemcheckbox",
41540
+ "menuitemradio",
41541
+ "option",
41542
+ "switch",
41543
+ "tab"
41544
+ ]);
41545
+ function classifyInteractive(node, solid, element, layout) {
41546
+ const tag = node.tagName;
41547
+ if (tag !== null && INTERACTIVE_HTML_TAGS2.has(tag)) {
41548
+ if (tag === "input" || tag === "select" || tag === "textarea") return "input";
41549
+ return "button";
41550
+ }
41551
+ const roleAttr = getJSXAttributeEntity(solid, element, "role");
41552
+ if (roleAttr !== null && roleAttr.valueNode !== null) {
41553
+ const role = getStaticStringFromJSXValue(roleAttr.valueNode);
41554
+ if (role !== null && INTERACTIVE_ARIA_ROLES2.has(role)) return "button";
41555
+ }
41556
+ const hostRef = layout.hostElementRefsByNode.get(node) ?? null;
41557
+ if (hostRef !== null && hostRef.element.tagName !== null) {
41558
+ const hostTag = hostRef.element.tagName;
41559
+ if (INTERACTIVE_HTML_TAGS2.has(hostTag)) {
41560
+ if (hostTag === "input" || hostTag === "select" || hostTag === "textarea") return "input";
41561
+ return "button";
41562
+ }
41563
+ }
41564
+ return null;
41565
+ }
41566
+ function isVisuallyHidden(snapshot) {
41567
+ const position = readKnownNormalized(snapshot, "position");
41568
+ if (position !== "absolute" && position !== "fixed") return false;
41569
+ const node = snapshot.node;
41570
+ const opacityAttr = node.inlineStyleValues.get("opacity");
41571
+ if (opacityAttr === "0") return true;
41572
+ if (node.classTokenSet.has("opacity-0")) return true;
41573
+ return false;
41574
+ }
41575
+ var jsxLayoutPolicyTouchTarget = defineCrossRule({
41576
+ id: "jsx-layout-policy-touch-target",
41577
+ severity: "warn",
41578
+ messages: messages161,
41579
+ meta: {
41580
+ description: "Enforce minimum interactive element sizes per accessibility policy via resolved layout signals.",
41581
+ fixable: false,
41582
+ category: "css-a11y"
41583
+ },
41584
+ check(context, emit) {
41585
+ const policy = getActivePolicy();
41586
+ if (policy === null) return;
41587
+ const policyName = getActivePolicyName() ?? "";
41588
+ const { layout } = context;
41589
+ const elements = layout.elements;
41590
+ for (let i = 0; i < elements.length; i++) {
41591
+ const node = elements[i];
41592
+ if (!node) continue;
41593
+ const ref = readElementRef(layout, node);
41594
+ if (!ref) continue;
41595
+ const kind = classifyInteractive(node, ref.solid, ref.element, layout);
41596
+ if (kind === null) continue;
41597
+ const snapshot = collectSignalSnapshot(context, node);
41598
+ if (isVisuallyHidden(snapshot)) continue;
41599
+ const tag = node.tagName ?? node.tag ?? "element";
41600
+ checkDimension(
41601
+ snapshot,
41602
+ "height",
41603
+ kind === "button" ? policy.minButtonHeight : policy.minInputHeight,
41604
+ layout,
41605
+ node,
41606
+ emit,
41607
+ "heightTooSmall",
41608
+ messages161.heightTooSmall,
41609
+ tag,
41610
+ policyName
41611
+ );
41612
+ checkDimension(
41613
+ snapshot,
41614
+ "min-height",
41615
+ kind === "button" ? policy.minButtonHeight : policy.minInputHeight,
41616
+ layout,
41617
+ node,
41618
+ emit,
41619
+ "heightTooSmall",
41620
+ messages161.heightTooSmall,
41621
+ tag,
41622
+ policyName
41623
+ );
41624
+ checkDimension(
41625
+ snapshot,
41626
+ "width",
41627
+ kind === "button" ? policy.minButtonWidth : policy.minTouchTarget,
41628
+ layout,
41629
+ node,
41630
+ emit,
41631
+ "widthTooSmall",
41632
+ messages161.widthTooSmall,
41633
+ tag,
41634
+ policyName
41635
+ );
41636
+ checkDimension(
41637
+ snapshot,
41638
+ "min-width",
41639
+ kind === "button" ? policy.minButtonWidth : policy.minTouchTarget,
41640
+ layout,
41641
+ node,
41642
+ emit,
41643
+ "widthTooSmall",
41644
+ messages161.widthTooSmall,
41645
+ tag,
41646
+ policyName
41647
+ );
41648
+ if (kind === "button") {
41649
+ checkDimension(
41650
+ snapshot,
41651
+ "padding-left",
41652
+ policy.minButtonHorizontalPadding,
41653
+ layout,
41654
+ node,
41655
+ emit,
41656
+ "paddingTooSmall",
41657
+ messages161.paddingTooSmall,
41658
+ tag,
41659
+ policyName
41660
+ );
41661
+ checkDimension(
41662
+ snapshot,
41663
+ "padding-right",
41664
+ policy.minButtonHorizontalPadding,
41665
+ layout,
41666
+ node,
41667
+ emit,
41668
+ "paddingTooSmall",
41669
+ messages161.paddingTooSmall,
41670
+ tag,
41671
+ policyName
41672
+ );
41673
+ }
41674
+ const reservedSpace = readReservedSpaceFact(layout, node);
41675
+ const minBlock = kind === "button" ? policy.minButtonHeight : policy.minInputHeight;
41676
+ const minInline = kind === "button" ? policy.minButtonWidth : policy.minTouchTarget;
41677
+ if (!reservedSpace.hasUsableBlockDimension) {
41678
+ emitLayoutDiagnostic(
41679
+ layout,
41680
+ node,
41681
+ emit,
41682
+ jsxLayoutPolicyTouchTarget.id,
41683
+ "noReservedBlockSize",
41684
+ messages161.noReservedBlockSize,
41685
+ "warn",
41686
+ { tag, min: String(minBlock), policy: policyName }
41687
+ );
41688
+ }
41689
+ if (!reservedSpace.hasUsableInlineDimension) {
41690
+ emitLayoutDiagnostic(
41691
+ layout,
41692
+ node,
41693
+ emit,
41694
+ jsxLayoutPolicyTouchTarget.id,
41695
+ "noReservedInlineSize",
41696
+ messages161.noReservedInlineSize,
41697
+ "warn",
41698
+ { tag, min: String(minInline), policy: policyName }
41699
+ );
41700
+ }
41701
+ }
41702
+ }
41703
+ });
41704
+ function checkDimension(snapshot, signal, min, layout, node, emit, messageId, template, tag, policyName) {
41705
+ const px = readKnownPx(snapshot, signal);
41706
+ if (px === null) return;
41707
+ if (px >= min) return;
41708
+ emitLayoutDiagnostic(
41709
+ layout,
41710
+ node,
41711
+ emit,
41712
+ jsxLayoutPolicyTouchTarget.id,
41713
+ messageId,
41714
+ template,
41715
+ "warn",
41716
+ {
41717
+ signal,
41718
+ value: formatRounded(px),
41719
+ min: String(min),
41720
+ tag,
41721
+ policy: policyName
41722
+ }
41723
+ );
41724
+ }
41725
+
41547
41726
  // src/cross-file/rules/index.ts
41548
41727
  var rules3 = [
41549
41728
  jsxNoUndefinedCssClass,
@@ -41575,7 +41754,8 @@ var rules3 = [
41575
41754
  cssLayoutOverflowModeToggleInstability,
41576
41755
  cssLayoutBoxSizingToggleWithChrome,
41577
41756
  cssLayoutContentVisibilityNoIntrinsicSize,
41578
- cssLayoutConditionalOffsetShift
41757
+ cssLayoutConditionalOffsetShift,
41758
+ jsxLayoutPolicyTouchTarget
41579
41759
  ];
41580
41760
 
41581
41761
  // src/cross-file/plugin.ts
@@ -41877,19 +42057,6 @@ var RULES = [
41877
42057
  "paragraphSpacingTooSmall": "Paragraph spacing `{{value}}` ({{resolved}}em) is below the minimum `{{min}}em` ({{minMultiplier}}\xD7 font-size) for policy `{{policy}}`."
41878
42058
  }
41879
42059
  },
41880
- {
41881
- "id": "css-policy-touch-target",
41882
- "severity": "warn",
41883
- "description": "Enforce minimum interactive element sizes per accessibility policy.",
41884
- "fixable": false,
41885
- "category": "css-a11y",
41886
- "plugin": "css",
41887
- "messages": {
41888
- "heightTooSmall": "`{{property}}` of `{{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for `{{element}}` elements in policy `{{policy}}`.",
41889
- "widthTooSmall": "`{{property}}` of `{{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for `{{element}}` elements in policy `{{policy}}`.",
41890
- "paddingTooSmall": "Horizontal padding `{{value}}` ({{resolved}}px) is below the minimum `{{min}}px` for `{{element}}` elements in policy `{{policy}}`."
41891
- }
41892
- },
41893
42060
  {
41894
42061
  "id": "css-policy-typography",
41895
42062
  "severity": "warn",
@@ -41913,6 +42080,21 @@ var RULES = [
41913
42080
  "missingReducedMotion": "Animated selector `{{selector}}` lacks prefers-reduced-motion override."
41914
42081
  }
41915
42082
  },
42083
+ {
42084
+ "id": "jsx-layout-policy-touch-target",
42085
+ "severity": "warn",
42086
+ "description": "Enforce minimum interactive element sizes per accessibility policy via resolved layout signals.",
42087
+ "fixable": false,
42088
+ "category": "css-a11y",
42089
+ "plugin": "cross-file",
42090
+ "messages": {
42091
+ "heightTooSmall": "`{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.",
42092
+ "widthTooSmall": "`{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.",
42093
+ "paddingTooSmall": "Horizontal padding `{{signal}}` of `{{value}}px` is below the minimum `{{min}}px` for interactive element `<{{tag}}>` in policy `{{policy}}`.",
42094
+ "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.",
42095
+ "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."
42096
+ }
42097
+ },
41916
42098
  {
41917
42099
  "id": "css-no-discrete-transition",
41918
42100
  "severity": "error",
@@ -43571,7 +43753,7 @@ var RULES = [
43571
43753
  ];
43572
43754
  var RULES_BY_CATEGORY = {
43573
43755
  "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." } }],
43574
- "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." } }],
43756
+ "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." } }],
43575
43757
  "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." } }],
43576
43758
  "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." } }],
43577
43759
  "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}}`." } }],