@usefragments/core 1.6.0 → 1.7.1

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.
Files changed (52) hide show
  1. package/dist/chunk-RANPUC6C.js +72 -0
  2. package/dist/chunk-RANPUC6C.js.map +1 -0
  3. package/dist/{chunk-WVFNDPM4.js → chunk-WNMWKUYG.js} +26 -14
  4. package/dist/chunk-WNMWKUYG.js.map +1 -0
  5. package/dist/codes/index.d.ts +1 -1
  6. package/dist/codes/index.js +1 -1
  7. package/dist/compiled-types/index.d.ts +1 -1
  8. package/dist/compiled-types/index.js +8 -0
  9. package/dist/generate/index.d.ts +1 -1
  10. package/dist/{governance-DxFipN5V.d.ts → governance-D9KtH-vg.d.ts} +9 -3
  11. package/dist/index.d.ts +199 -139
  12. package/dist/index.js +706 -201
  13. package/dist/index.js.map +1 -1
  14. package/dist/react-types.d.ts +1 -1
  15. package/dist/registry.d.ts +36 -36
  16. package/dist/schemas/index.d.ts +1 -1
  17. package/dist/test-utils.d.ts +1 -1
  18. package/package.json +2 -1
  19. package/src/agent-format.test.ts +13 -0
  20. package/src/agent-format.ts +9 -3
  21. package/src/codes/__tests__/codes.test.ts +0 -1
  22. package/src/codes/codes.ts +1 -2
  23. package/src/compiled-types/index.ts +81 -0
  24. package/src/compiled-types/parse.test.ts +47 -0
  25. package/src/component-contract.ts +12 -0
  26. package/src/contract-parser.ts +2 -0
  27. package/src/facts/builders.ts +13 -0
  28. package/src/facts/compile.ts +13 -13
  29. package/src/facts/facts.test.ts +38 -1
  30. package/src/facts/index.ts +2 -0
  31. package/src/facts/types.ts +15 -0
  32. package/src/governance-integrity.test.ts +98 -1
  33. package/src/governance-integrity.ts +40 -20
  34. package/src/index.ts +8 -0
  35. package/src/rules/a11y-required-accessible-name.ts +175 -28
  36. package/src/rules/a11y-standard.ts +102 -0
  37. package/src/rules/a11y-utils.ts +7 -0
  38. package/src/rules/components-prefer-library.test.ts +75 -28
  39. package/src/rules/components-prefer-library.ts +35 -15
  40. package/src/rules/components-shadow-component.test.ts +21 -9
  41. package/src/rules/emit-gate.test.ts +74 -4
  42. package/src/rules/emit-gate.ts +24 -9
  43. package/src/rules/families.ts +1 -1
  44. package/src/rules/fix-availability.ts +1 -0
  45. package/src/rules/index.ts +12 -2
  46. package/src/rules/rules.test.ts +63 -7
  47. package/src/rules/tiers.ts +1 -0
  48. package/src/tokens/design-token-parser.test.ts +131 -0
  49. package/src/tokens/design-token-parser.ts +362 -49
  50. package/src/types.ts +2 -2
  51. package/dist/chunk-WVFNDPM4.js.map +0 -1
  52. package/dist/{index-DbkPE46t.d.ts → index-hZAlYCli.d.ts} +8 -8
package/dist/index.js CHANGED
@@ -105,6 +105,7 @@ import {
105
105
  makeTailwindUnknownClassEnabledFact,
106
106
  makeThemeDeclarationFact,
107
107
  makeTokenDefinitionFact,
108
+ makeUsageChildContentFact,
108
109
  makeUsageComponentFact,
109
110
  makeUsageImportFact,
110
111
  makeUsageInlineStyleFact,
@@ -126,7 +127,7 @@ import {
126
127
  resolveComponentGovernance,
127
128
  ruleFamilyMembers,
128
129
  scaleGovernanceRecordSchema
129
- } from "./chunk-WVFNDPM4.js";
130
+ } from "./chunk-WNMWKUYG.js";
130
131
  import {
131
132
  AGENT_FORMAT_SCHEMA_VERSION,
132
133
  agentErrorEnvelopeSchema,
@@ -181,6 +182,10 @@ import {
181
182
  severitySchema,
182
183
  sortBySeverity
183
184
  } from "./chunk-V4VQB57N.js";
185
+ import {
186
+ CompiledFragmentsFileValidationError,
187
+ parseCompiledFragmentsFile
188
+ } from "./chunk-RANPUC6C.js";
184
189
 
185
190
  // src/constants.ts
186
191
  var BRAND = {
@@ -420,6 +425,7 @@ function buildAgentFormat(input) {
420
425
  const zeroRulesLoaded = input.integrity.rulesLoaded === 0;
421
426
  const integrityFailed = !input.integrity.healthy || input.integrity.inert;
422
427
  const score = integrityFailed ? 0 : scoreFindings(input.findings);
428
+ const filesAffected = new Set(input.findings.map((finding) => finding.location.file)).size;
423
429
  const visibleFindings = uniqueFindings(input.findings).slice(0, input.maxFindings ?? 50);
424
430
  const agentFindings = visibleFindings.map((finding) => ({
425
431
  code: finding.code,
@@ -437,7 +443,7 @@ function buildAgentFormat(input) {
437
443
  const hasErrors = input.findings.some((finding) => severityLevel(finding.severity) === "error");
438
444
  const shared = {
439
445
  schemaVersion: AGENT_FORMAT_SCHEMA_VERSION,
440
- summary: zeroRulesLoaded ? "indeterminate; zero_rules_loaded" : integrityFailed ? `failed with score ${score}; ${input.integrity.reasons.join("; ") || "governance integrity is unhealthy"}` : agentSummary(input.findings.length, input.filesScanned, score),
446
+ summary: zeroRulesLoaded ? "indeterminate; zero_rules_loaded" : integrityFailed ? `failed with score ${score}; ${input.integrity.reasons.join("; ") || "governance integrity is unhealthy"}` : agentSummary(input.findings.length, filesAffected, input.filesScanned, score),
441
447
  integrity: input.integrity,
442
448
  fix_first: agentFindings.filter((finding) => finding.plan.confidence >= 0.8),
443
449
  remaining: agentFindings.filter((finding) => finding.plan.confidence < 0.8),
@@ -498,9 +504,9 @@ function scoreFindings(findings) {
498
504
  }
499
505
  return Math.max(1, Math.round(100 / (1 + penalty / 40)));
500
506
  }
501
- function agentSummary(count, filesScanned, score) {
507
+ function agentSummary(count, filesAffected, filesScanned, score) {
502
508
  if (count === 0) return `passed with score ${score}; no findings`;
503
- return `${count} finding${count === 1 ? "" : "s"} across ${filesScanned} file${filesScanned === 1 ? "" : "s"}; score ${score}`;
509
+ return `${count} finding${count === 1 ? "" : "s"} in ${filesAffected} file${filesAffected === 1 ? "" : "s"}; ${filesScanned} scanned; score ${score}`;
504
510
  }
505
511
  function planForFinding(finding) {
506
512
  if (finding.fix?.deterministic === true) {
@@ -3302,7 +3308,7 @@ function buildToken(name, rawValue, lineNumber, source) {
3302
3308
  }
3303
3309
 
3304
3310
  // src/tokens/design-token-parser.ts
3305
- var TOKEN_DECLARATION_PATTERN = /--([a-zA-Z0-9_-]+)\s*:\s*([^;{}]+?)\s*(?:;|(?=\})|$)/g;
3311
+ var CSS_CUSTOM_PROPERTY_NAME_CHARACTER = /[a-zA-Z0-9_-]/u;
3306
3312
  var VAR_REFERENCE_PATTERN = /var\(\s*--([a-zA-Z0-9_-]+)(?:\s*,\s*([^)]+))?\s*\)/;
3307
3313
  function parseDesignTokenContent(content, options = {}) {
3308
3314
  const startTime = performance.now();
@@ -3354,19 +3360,48 @@ function parseDesignTokenContent(content, options = {}) {
3354
3360
  errors.push({ message: err, file: filePath });
3355
3361
  }
3356
3362
  }
3357
- const tokensByName = /* @__PURE__ */ new Map();
3358
- for (const match of content.matchAll(TOKEN_DECLARATION_PATTERN)) {
3359
- const [, name, rawValue] = match;
3360
- const fullName = `--${name}`;
3361
- const lineNumber = countNewlines(content, match.index ?? 0) + 1;
3362
- tokensByName.set(fullName, {
3363
- rawValue: rawValue.trim().replace(/\s+/g, " "),
3364
- line: lineNumber
3363
+ const declarationsByThemeAndName = /* @__PURE__ */ new Map();
3364
+ const declarationMatches = scanCssCustomPropertyDeclarations(content);
3365
+ for (const match of declarationMatches) {
3366
+ const fullName = match.name;
3367
+ const lineNumber = match.line;
3368
+ const selector = match.selector;
3369
+ const theme = themeSelectors[selector] || "default";
3370
+ declarationsByThemeAndName.set(`${theme}\0${fullName}`, {
3371
+ name: fullName,
3372
+ rawValue: normalizeCssCustomPropertyValue(match.rawValue),
3373
+ line: lineNumber,
3374
+ selector,
3375
+ theme
3365
3376
  });
3366
3377
  }
3367
- for (const [name, { rawValue, line }] of tokensByName) {
3368
- const selector = findSelectorForLine(content, line ?? 1);
3369
- const theme = themeSelectors[selector] || "default";
3378
+ const declarations = [...declarationsByThemeAndName.values()];
3379
+ const defaults = /* @__PURE__ */ new Map();
3380
+ const declarationsByTheme = /* @__PURE__ */ new Map();
3381
+ for (const declaration of declarations) {
3382
+ if (declaration.theme === "default") defaults.set(declaration.name, declaration);
3383
+ let themeDeclarations = declarationsByTheme.get(declaration.theme);
3384
+ if (!themeDeclarations) {
3385
+ themeDeclarations = /* @__PURE__ */ new Map();
3386
+ declarationsByTheme.set(declaration.theme, themeDeclarations);
3387
+ }
3388
+ themeDeclarations.set(declaration.name, declaration);
3389
+ }
3390
+ const tokensByTheme = /* @__PURE__ */ new Map();
3391
+ for (const [theme, themeDeclarations] of declarationsByTheme) {
3392
+ if (theme === "default") {
3393
+ tokensByTheme.set(theme, defaults);
3394
+ continue;
3395
+ }
3396
+ const tokensByName = new Map(defaults);
3397
+ for (const [name, declaration] of themeDeclarations) {
3398
+ tokensByName.set(name, declaration);
3399
+ }
3400
+ tokensByTheme.set(theme, tokensByName);
3401
+ }
3402
+ const contentLines = content.split("\n");
3403
+ for (const { name, rawValue, line, selector, theme } of declarations) {
3404
+ const tokensByName = tokensByTheme.get(theme) ?? defaults;
3370
3405
  const { resolvedValue, chain, hasCircular, unresolvedRef } = resolveDesignTokenValue(
3371
3406
  rawValue,
3372
3407
  tokensByName
@@ -3388,7 +3423,7 @@ function parseDesignTokenContent(content, options = {}) {
3388
3423
  lineNumber: line,
3389
3424
  theme,
3390
3425
  selector,
3391
- description: extractDescription(content, line ?? 1)
3426
+ description: extractDescription(contentLines, line ?? 1)
3392
3427
  });
3393
3428
  }
3394
3429
  if (tokens.length === 0 && trimmed.length > 0) {
@@ -3403,13 +3438,6 @@ function parseDesignTokenContent(content, options = {}) {
3403
3438
  parseTimeMs: performance.now() - startTime
3404
3439
  };
3405
3440
  }
3406
- function countNewlines(content, index) {
3407
- let count = 0;
3408
- for (let i = 0; i < index && i < content.length; i++) {
3409
- if (content.charCodeAt(i) === 10) count++;
3410
- }
3411
- return count;
3412
- }
3413
3441
  function parsedOutputToDesignTokens(output, filePath) {
3414
3442
  const tokens = [];
3415
3443
  for (const cat of Object.values(output.categories)) {
@@ -3564,29 +3592,284 @@ function inferTokenLevel(name, rawValue, referenceChain) {
3564
3592
  }
3565
3593
  return 2;
3566
3594
  }
3567
- function findSelectorForLine(content, targetLine) {
3568
- const lines = content.split("\n");
3595
+ function scanCssCustomPropertyDeclarations(content) {
3596
+ const declarations = [];
3597
+ const parentSelectors = [];
3569
3598
  let currentSelector = ":root";
3570
- let braceDepth = 0;
3571
- for (let i = 0; i < Math.min(targetLine, lines.length); i++) {
3572
- const line = lines[i];
3573
- const selectorMatch = line.match(/^\s*([^{]+)\s*\{/);
3574
- if (selectorMatch) {
3575
- const selector = selectorMatch[1].trim();
3576
- if ((line.match(/\{/g) || []).length > (line.match(/\}/g) || []).length) {
3577
- currentSelector = selector;
3599
+ let boundary = 0;
3600
+ let statementHasContent = false;
3601
+ let line = 1;
3602
+ let quote = null;
3603
+ let escaped = false;
3604
+ let inComment = false;
3605
+ let parentheses = 0;
3606
+ let brackets = 0;
3607
+ for (let index = 0; index < content.length; index++) {
3608
+ const character = content[index];
3609
+ const next = content[index + 1];
3610
+ if (character === "\n") line++;
3611
+ if (inComment) {
3612
+ if (character === "*" && next === "/") {
3613
+ inComment = false;
3614
+ index++;
3615
+ }
3616
+ continue;
3617
+ }
3618
+ if (quote) {
3619
+ if (escaped) {
3620
+ escaped = false;
3621
+ } else if (character === "\\") {
3622
+ escaped = true;
3623
+ } else if (character === quote) {
3624
+ quote = null;
3625
+ }
3626
+ continue;
3627
+ }
3628
+ if (character === "/" && next === "*") {
3629
+ inComment = true;
3630
+ index++;
3631
+ continue;
3632
+ }
3633
+ if (character === "'" || character === '"') {
3634
+ quote = character;
3635
+ statementHasContent = true;
3636
+ continue;
3637
+ }
3638
+ if (character === "\\") {
3639
+ statementHasContent = true;
3640
+ if (next === "\n") line++;
3641
+ index++;
3642
+ continue;
3643
+ }
3644
+ if (character === "(") {
3645
+ parentheses++;
3646
+ statementHasContent = true;
3647
+ continue;
3648
+ }
3649
+ if (character === ")") {
3650
+ parentheses = Math.max(0, parentheses - 1);
3651
+ statementHasContent = true;
3652
+ continue;
3653
+ }
3654
+ if (character === "[") {
3655
+ brackets++;
3656
+ statementHasContent = true;
3657
+ continue;
3658
+ }
3659
+ if (character === "]") {
3660
+ brackets = Math.max(0, brackets - 1);
3661
+ statementHasContent = true;
3662
+ continue;
3663
+ }
3664
+ if (parentheses > 0 || brackets > 0) {
3665
+ if (!/\s/u.test(character)) statementHasContent = true;
3666
+ continue;
3667
+ }
3668
+ if (character === "{") {
3669
+ const header = stripCssComments(content.slice(boundary, index)).trim();
3670
+ parentSelectors.push(currentSelector);
3671
+ if (header && !header.startsWith("@")) currentSelector = header;
3672
+ boundary = index + 1;
3673
+ statementHasContent = false;
3674
+ } else if (character === "}") {
3675
+ currentSelector = parentSelectors.pop() ?? ":root";
3676
+ boundary = index + 1;
3677
+ statementHasContent = false;
3678
+ } else if (character === ";") {
3679
+ boundary = index + 1;
3680
+ statementHasContent = false;
3681
+ } else if (character === "-" && next === "-" && !statementHasContent) {
3682
+ const declaration = scanCssCustomPropertyAt(content, index);
3683
+ if (!declaration) {
3684
+ statementHasContent = true;
3685
+ continue;
3686
+ }
3687
+ const rawValue = content.slice(declaration.valueStart, declaration.end).trim();
3688
+ if (rawValue) {
3689
+ declarations.push({
3690
+ name: declaration.name,
3691
+ rawValue,
3692
+ line,
3693
+ selector: currentSelector
3694
+ });
3695
+ }
3696
+ line += countNewlinesInRange(content, index + 1, declaration.end);
3697
+ statementHasContent = true;
3698
+ index = declaration.end - 1;
3699
+ } else if (!/\s/u.test(character)) {
3700
+ statementHasContent = true;
3701
+ }
3702
+ }
3703
+ return declarations;
3704
+ }
3705
+ function scanCssCustomPropertyAt(content, start) {
3706
+ let cursor = start + 2;
3707
+ while (cursor < content.length && CSS_CUSTOM_PROPERTY_NAME_CHARACTER.test(content[cursor] ?? "")) {
3708
+ cursor++;
3709
+ }
3710
+ if (cursor === start + 2) return null;
3711
+ const nameEnd = cursor;
3712
+ cursor = skipCssWhitespaceAndComments(content, cursor);
3713
+ if (content[cursor] !== ":") return null;
3714
+ const valueStart = cursor + 1;
3715
+ return {
3716
+ name: content.slice(start, nameEnd),
3717
+ valueStart,
3718
+ end: findCssCustomPropertyValueEnd(content, valueStart)
3719
+ };
3720
+ }
3721
+ function findCssCustomPropertyValueEnd(content, start) {
3722
+ let quote = null;
3723
+ let escaped = false;
3724
+ let inComment = false;
3725
+ let parentheses = 0;
3726
+ let brackets = 0;
3727
+ let braces = 0;
3728
+ for (let index = start; index < content.length; index++) {
3729
+ const character = content[index];
3730
+ const next = content[index + 1];
3731
+ if (inComment) {
3732
+ if (character === "*" && next === "/") {
3733
+ inComment = false;
3734
+ index++;
3735
+ }
3736
+ continue;
3737
+ }
3738
+ if (quote) {
3739
+ if (escaped) {
3740
+ escaped = false;
3741
+ } else if (character === "\\") {
3742
+ escaped = true;
3743
+ } else if (character === quote) {
3744
+ quote = null;
3745
+ }
3746
+ continue;
3747
+ }
3748
+ if (character === "/" && next === "*") {
3749
+ inComment = true;
3750
+ index++;
3751
+ continue;
3752
+ }
3753
+ if (character === "'" || character === '"') {
3754
+ quote = character;
3755
+ continue;
3756
+ }
3757
+ if (character === "\\") {
3758
+ index++;
3759
+ continue;
3760
+ }
3761
+ if (character === "(") {
3762
+ parentheses++;
3763
+ } else if (character === ")") {
3764
+ parentheses = Math.max(0, parentheses - 1);
3765
+ } else if (character === "[") {
3766
+ brackets++;
3767
+ } else if (character === "]") {
3768
+ brackets = Math.max(0, brackets - 1);
3769
+ } else if (character === "{") {
3770
+ braces++;
3771
+ } else if (character === "}") {
3772
+ if (braces > 0) {
3773
+ braces--;
3774
+ } else if (parentheses === 0 && brackets === 0) {
3775
+ return index;
3776
+ }
3777
+ } else if (character === ";" && parentheses === 0 && brackets === 0 && braces === 0) {
3778
+ return index;
3779
+ }
3780
+ }
3781
+ return content.length;
3782
+ }
3783
+ function skipCssWhitespaceAndComments(content, start) {
3784
+ let cursor = start;
3785
+ while (cursor < content.length) {
3786
+ if (/\s/u.test(content[cursor] ?? "")) {
3787
+ cursor++;
3788
+ continue;
3789
+ }
3790
+ if (content[cursor] !== "/" || content[cursor + 1] !== "*") break;
3791
+ const commentEnd = content.indexOf("*/", cursor + 2);
3792
+ if (commentEnd < 0) return content.length;
3793
+ cursor = commentEnd + 2;
3794
+ }
3795
+ return cursor;
3796
+ }
3797
+ function stripCssComments(content) {
3798
+ let output = "";
3799
+ let quote = null;
3800
+ let escaped = false;
3801
+ let inComment = false;
3802
+ for (let index = 0; index < content.length; index++) {
3803
+ const character = content[index];
3804
+ const next = content[index + 1];
3805
+ if (inComment) {
3806
+ if (character === "*" && next === "/") {
3807
+ inComment = false;
3808
+ index++;
3578
3809
  }
3810
+ continue;
3579
3811
  }
3580
- braceDepth += (line.match(/\{/g) || []).length;
3581
- braceDepth -= (line.match(/\}/g) || []).length;
3582
- if (braceDepth === 0) {
3583
- currentSelector = ":root";
3812
+ if (quote) {
3813
+ output += character;
3814
+ if (escaped) {
3815
+ escaped = false;
3816
+ } else if (character === "\\") {
3817
+ escaped = true;
3818
+ } else if (character === quote) {
3819
+ quote = null;
3820
+ }
3821
+ continue;
3822
+ }
3823
+ if (character === "/" && next === "*") {
3824
+ inComment = true;
3825
+ index++;
3826
+ continue;
3584
3827
  }
3828
+ output += character;
3829
+ if (character === "'" || character === '"') quote = character;
3585
3830
  }
3586
- return currentSelector;
3831
+ return output;
3587
3832
  }
3588
- function extractDescription(content, line) {
3589
- const lines = content.split("\n");
3833
+ function countNewlinesInRange(content, start, end) {
3834
+ let count = 0;
3835
+ for (let index = start; index < end; index++) {
3836
+ if (content.charCodeAt(index) === 10) count++;
3837
+ }
3838
+ return count;
3839
+ }
3840
+ function normalizeCssCustomPropertyValue(value) {
3841
+ let output = "";
3842
+ let quote = null;
3843
+ let escaped = false;
3844
+ let pendingWhitespace = false;
3845
+ for (const character of value.trim()) {
3846
+ if (quote) {
3847
+ output += character;
3848
+ if (escaped) {
3849
+ escaped = false;
3850
+ } else if (character === "\\") {
3851
+ escaped = true;
3852
+ } else if (character === quote) {
3853
+ quote = null;
3854
+ }
3855
+ continue;
3856
+ }
3857
+ if (character === "'" || character === '"') {
3858
+ if (pendingWhitespace && output) output += " ";
3859
+ pendingWhitespace = false;
3860
+ quote = character;
3861
+ output += character;
3862
+ } else if (/\s/u.test(character)) {
3863
+ pendingWhitespace = true;
3864
+ } else {
3865
+ if (pendingWhitespace && output) output += " ";
3866
+ pendingWhitespace = false;
3867
+ output += character;
3868
+ }
3869
+ }
3870
+ return output;
3871
+ }
3872
+ function extractDescription(lines, line) {
3590
3873
  if (line <= 1) return void 0;
3591
3874
  const prevLine = lines[line - 2]?.trim();
3592
3875
  const singleLineMatch = prevLine?.match(/\/\/\s*(.+)$/);
@@ -3815,6 +4098,11 @@ var componentContractSchema = z6.object({
3815
4098
  tags: z6.array(z6.string()).optional(),
3816
4099
  status: z6.enum(["stable", "beta", "deprecated", "experimental"]).optional(),
3817
4100
  framework: z6.enum(["react", "vue", "svelte", "web-components", "angular"]).optional(),
4101
+ dependencies: z6.array(z6.object({
4102
+ name: z6.string(),
4103
+ version: z6.string(),
4104
+ reason: z6.string().optional()
4105
+ })).optional(),
3818
4106
  sourcePath: z6.string(),
3819
4107
  exportName: z6.string(),
3820
4108
  propsSummary: z6.array(z6.string()),
@@ -3845,6 +4133,7 @@ function parseComponentContract(content, filePath) {
3845
4133
  category: validated.category,
3846
4134
  tags: validated.tags,
3847
4135
  status: validated.status,
4136
+ dependencies: validated.dependencies,
3848
4137
  figma: validated.figma?.nodeUrl,
3849
4138
  figmaProps: validated.figma?.propMappings
3850
4139
  },
@@ -4382,6 +4671,13 @@ function discoverComponents(moduleExports, options) {
4382
4671
  return names.sort();
4383
4672
  }
4384
4673
 
4674
+ // src/rules/a11y-utils.ts
4675
+ function isAriaHidden(props) {
4676
+ const prop = props.find((candidate) => candidate.prop === "aria-hidden");
4677
+ if (!prop || prop.resolution !== "static") return false;
4678
+ return prop.value === true || prop.value === "true";
4679
+ }
4680
+
4385
4681
  // src/rules/finding.ts
4386
4682
  function makeFinding(input) {
4387
4683
  if (input.evidence.length === 0) {
@@ -4840,36 +5136,71 @@ function isApplicableTokenReference(referenceFormat) {
4840
5136
 
4841
5137
  // src/rules/a11y-required-accessible-name.ts
4842
5138
  var RULE_ID = "a11y/required-accessible-name";
4843
- var RULE_VERSION = "1";
5139
+ var RULE_VERSION = "2";
4844
5140
  var NAME_SOURCE_PROPS = /* @__PURE__ */ new Set(["aria-label", "aria-labelledby", "title"]);
5141
+ var COMPONENT_NAME_SOURCE_PROPS = /* @__PURE__ */ new Set([...NAME_SOURCE_PROPS, "label"]);
5142
+ var NAME_REQUIRED_TAGS = /* @__PURE__ */ new Set(["button", "input", "select", "textarea"]);
5143
+ var NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["button", "checkbox", "radio", "switch", "tab"]);
5144
+ var NAME_FROM_CONTENT_TAGS = /* @__PURE__ */ new Set(["button"]);
5145
+ var NAME_FROM_CONTENT_ROLES = /* @__PURE__ */ new Set(["button", "checkbox", "radio", "switch", "tab"]);
5146
+ var NAME_REQUIRED_COMPONENT_SUFFIXES = [
5147
+ "button",
5148
+ "input",
5149
+ "select",
5150
+ "textarea",
5151
+ "checkbox",
5152
+ "radio",
5153
+ "switch"
5154
+ ];
4845
5155
  function ruleA11yRequiredAccessibleName(ix) {
4846
5156
  const findings = [];
4847
5157
  const propsByNode = indexPropsByNodeId(ix);
4848
5158
  const textByNode = indexTextChildrenByNodeId(ix);
4849
- for (const usageComponent of ix.byKind("usage_component")) {
4850
- const policy = ix.policy.a11yNameRequired(usageComponent.componentId);
4851
- if (!policy) continue;
4852
- const node = readUsageNode(ix, usageComponent.nodeId);
4853
- if (!node) continue;
5159
+ const childContentByNode = new Map(
5160
+ ix.byKind("usage_child_content").map((content) => [content.nodeId, content])
5161
+ );
5162
+ const globalPolicy = ix.policy.ruleConfig(RULE_ID);
5163
+ const usageByNode = new Map(ix.byKind("usage_component").map((usage) => [usage.nodeId, usage]));
5164
+ const definitions = new Map(
5165
+ ix.byKind("component_definition").map((definition) => [definition.componentKey, definition])
5166
+ );
5167
+ for (const node of ix.byKind("usage_node")) {
5168
+ const usageComponent = usageByNode.get(node.id);
5169
+ const componentPolicy = usageComponent ? ix.policy.a11yNameRequired(usageComponent.componentId) : void 0;
4854
5170
  const props = propsByNode.get(node.id) ?? [];
4855
- if (props.some(isAriaHidden)) continue;
4856
- if (hasAccessibleName(props, textByNode.get(node.id) ?? [])) continue;
5171
+ if (isAriaHidden(props)) continue;
5172
+ const genericControl = globalPolicy?.enabled === true && requiresAccessibleName(node, usageComponent, definitions, props);
5173
+ if (!componentPolicy && !genericControl) continue;
5174
+ if (hasAccessibleName(
5175
+ props,
5176
+ textByNode.get(node.id) ?? [],
5177
+ childContentByNode.get(node.id),
5178
+ usageComponent !== void 0,
5179
+ canNameFromContent(node, usageComponent, definitions)
5180
+ )) {
5181
+ continue;
5182
+ }
4857
5183
  findings.push(
4858
5184
  makeFinding({
4859
5185
  ruleId: RULE_ID,
4860
5186
  ruleVersion: RULE_VERSION,
4861
- severity: policy.severity,
4862
- message: `<${node.element}> has no accessible name. ${policy.because}`.trim(),
5187
+ severity: componentPolicy?.severity ?? globalPolicy?.severity ?? "warn",
5188
+ message: `<${node.element}> has no accessible name. ${componentPolicy?.because ?? "Add visible text, aria-label, aria-labelledby, or a component label."}`.trim(),
4863
5189
  location: node.location,
4864
- evidence: ix.evidence([node.id, usageComponent.id, policy.id]),
5190
+ evidence: ix.evidence([
5191
+ node.id,
5192
+ ...usageComponent ? [usageComponent.id] : [],
5193
+ ...componentPolicy ? [componentPolicy.id] : [],
5194
+ ...genericControl && globalPolicy ? [globalPolicy.id] : []
5195
+ ]),
4865
5196
  fingerprintIdentity: {
4866
5197
  file: node.file,
4867
- componentId: usageComponent.componentId,
5198
+ componentId: usageComponent?.componentId,
4868
5199
  element: node.element,
4869
5200
  nodePath: node.nodePath
4870
5201
  },
4871
5202
  attributes: {
4872
- componentId: usageComponent.componentId,
5203
+ ...usageComponent ? { componentId: usageComponent.componentId } : {},
4873
5204
  element: node.element
4874
5205
  }
4875
5206
  })
@@ -4877,12 +5208,18 @@ function ruleA11yRequiredAccessibleName(ix) {
4877
5208
  }
4878
5209
  return findings;
4879
5210
  }
4880
- function hasAccessibleName(props, textChildren) {
4881
- for (const child of textChildren) {
4882
- if (typeof child.text === "string" && child.text.trim().length > 0) return true;
5211
+ function hasAccessibleName(props, textChildren, childContent, componentControl, nameFromContent) {
5212
+ const sourceProps = componentControl ? COMPONENT_NAME_SOURCE_PROPS : NAME_SOURCE_PROPS;
5213
+ if (nameFromContent) {
5214
+ for (const child of textChildren) {
5215
+ if (typeof child.text === "string" && child.text.trim().length > 0) return true;
5216
+ }
5217
+ if (childContent?.content === "static-text" || childContent?.content === "runtime") {
5218
+ return true;
5219
+ }
4883
5220
  }
4884
5221
  for (const prop of props) {
4885
- if (!NAME_SOURCE_PROPS.has(prop.prop)) continue;
5222
+ if (!sourceProps.has(prop.prop)) continue;
4886
5223
  if (prop.resolution === "spread") continue;
4887
5224
  if (prop.resolution === "dynamic") return true;
4888
5225
  if (prop.resolution === "static") {
@@ -4892,10 +5229,137 @@ function hasAccessibleName(props, textChildren) {
4892
5229
  }
4893
5230
  return false;
4894
5231
  }
4895
- function isAriaHidden(prop) {
4896
- if (prop.prop !== "aria-hidden") return false;
4897
- if (prop.resolution !== "static") return false;
4898
- return prop.value === true || prop.value === "true";
5232
+ function canNameFromContent(node, usage, definitions) {
5233
+ const element = node.element.toLowerCase();
5234
+ if (NAME_REQUIRED_TAGS.has(element)) return NAME_FROM_CONTENT_TAGS.has(element);
5235
+ if (node.role && NAME_FROM_CONTENT_ROLES.has(node.role.toLowerCase())) return true;
5236
+ if (!usage) return false;
5237
+ const definition = definitions.get(String(usage.componentId));
5238
+ if (definition) {
5239
+ const root = definition.renderRoot;
5240
+ if (root.resolution === "intrinsic") {
5241
+ return intrinsicCanNameFromContent(root.tag, root.role);
5242
+ }
5243
+ if (root.resolution === "canonical") return isContentNamedControl(root.canonical);
5244
+ if (root.resolution === "mixed") {
5245
+ return root.intrinsics.some((tag) => intrinsicCanNameFromContent(tag)) || root.canonicals.some(isContentNamedControl);
5246
+ }
5247
+ }
5248
+ return isContentNamedControl(String(usage.componentId));
5249
+ }
5250
+ function intrinsicCanNameFromContent(tag, role) {
5251
+ const normalizedTag = tag.toLowerCase();
5252
+ if (NAME_REQUIRED_TAGS.has(normalizedTag)) {
5253
+ return NAME_FROM_CONTENT_TAGS.has(normalizedTag);
5254
+ }
5255
+ return role !== void 0 && NAME_FROM_CONTENT_ROLES.has(role.toLowerCase());
5256
+ }
5257
+ function requiresAccessibleName(node, usage, definitions, props) {
5258
+ if (node.role && NAME_REQUIRED_ROLES.has(node.role.toLowerCase())) return true;
5259
+ if (NAME_REQUIRED_TAGS.has(node.element.toLowerCase())) {
5260
+ if (node.element.toLowerCase() === "input" && props.some(
5261
+ (prop) => prop.prop === "type" && prop.resolution === "static" && String(prop.value).toLowerCase() === "hidden"
5262
+ )) {
5263
+ return false;
5264
+ }
5265
+ return true;
5266
+ }
5267
+ if (!usage) return false;
5268
+ const definition = definitions.get(String(usage.componentId));
5269
+ if (definition) {
5270
+ const root = definition.renderRoot;
5271
+ if (root.resolution === "intrinsic") {
5272
+ return NAME_REQUIRED_TAGS.has(root.tag.toLowerCase()) || root.role !== void 0 && NAME_REQUIRED_ROLES.has(root.role.toLowerCase());
5273
+ }
5274
+ if (root.resolution === "canonical") return isNamedControl(root.canonical);
5275
+ if (root.resolution === "mixed") {
5276
+ return root.intrinsics.some((tag) => NAME_REQUIRED_TAGS.has(tag.toLowerCase())) || root.canonicals.some(isNamedControl);
5277
+ }
5278
+ }
5279
+ return isNamedControl(String(usage.componentId));
5280
+ }
5281
+ function isContentNamedControl(identity) {
5282
+ return normalizedControlName(identity).endsWith("button");
5283
+ }
5284
+ function isNamedControl(identity) {
5285
+ const normalized = normalizedControlName(identity);
5286
+ return NAME_REQUIRED_COMPONENT_SUFFIXES.some((suffix) => normalized.endsWith(suffix));
5287
+ }
5288
+ function normalizedControlName(identity) {
5289
+ const exported = identity.split("#").at(-1)?.split(".").at(-1) ?? identity;
5290
+ return exported.replace(/[^a-z0-9]/gi, "").toLowerCase();
5291
+ }
5292
+
5293
+ // src/rules/a11y-standard.ts
5294
+ var RULE_ID2 = "a11y/standard";
5295
+ var RULE_VERSION2 = "1";
5296
+ var KEY_HANDLERS = /* @__PURE__ */ new Set(["onKeyDown", "onKeyUp", "onKeyPress"]);
5297
+ function ruleA11yStandard(ix) {
5298
+ const policy = ix.policy.ruleConfig(RULE_ID2);
5299
+ if (!policy?.enabled) return [];
5300
+ const propsByNode = indexPropsByNodeId(ix);
5301
+ const findings = [];
5302
+ for (const node of ix.byKind("usage_node")) {
5303
+ if (node.role?.trim().toLowerCase() !== "button") continue;
5304
+ const props = propsByNode.get(node.id) ?? [];
5305
+ if (isAriaHidden(props) || isNativeButton(node.element, props)) continue;
5306
+ const missing = [
5307
+ ...hasFocusableTabIndex(props) ? [] : ["tabIndex"],
5308
+ ...hasKeyHandler(props) ? [] : ["keyboard handler"]
5309
+ ];
5310
+ if (missing.length === 0) continue;
5311
+ findings.push(
5312
+ makeFinding({
5313
+ ruleId: RULE_ID2,
5314
+ ruleVersion: RULE_VERSION2,
5315
+ severity: policy.severity ?? "warn",
5316
+ message: `<${node.element} role="button"> is missing ${missing.join(
5317
+ " and "
5318
+ )}. Non-native buttons need tabIndex plus keyboard activation handling.`,
5319
+ location: node.location,
5320
+ evidence: ix.evidence([
5321
+ node.id,
5322
+ policy.id,
5323
+ ...props.filter(
5324
+ (prop) => prop.prop === "tabIndex" || prop.prop === "tabindex" || KEY_HANDLERS.has(prop.prop)
5325
+ ).map((prop) => prop.id)
5326
+ ]),
5327
+ fingerprintIdentity: {
5328
+ file: node.file,
5329
+ element: node.element,
5330
+ nodePath: node.nodePath,
5331
+ standard: "keyboard-role-button"
5332
+ },
5333
+ attributes: {
5334
+ standard: "keyboard-role-button",
5335
+ role: "button",
5336
+ missing
5337
+ }
5338
+ })
5339
+ );
5340
+ }
5341
+ return findings;
5342
+ }
5343
+ function isNativeButton(element, props) {
5344
+ if (element.toLowerCase() === "button") return true;
5345
+ if (element.toLowerCase() !== "input") return false;
5346
+ return props.some(
5347
+ (prop) => prop.prop === "type" && prop.resolution === "static" && ["button", "submit", "reset"].includes(String(prop.value).toLowerCase())
5348
+ );
5349
+ }
5350
+ function hasFocusableTabIndex(props) {
5351
+ return props.some((prop) => {
5352
+ if (prop.prop !== "tabIndex" && prop.prop !== "tabindex") return false;
5353
+ if (prop.resolution === "dynamic") return true;
5354
+ if (prop.resolution !== "static") return false;
5355
+ const value = typeof prop.value === "number" ? prop.value : Number(prop.value);
5356
+ return Number.isFinite(value) && value >= 0;
5357
+ });
5358
+ }
5359
+ function hasKeyHandler(props) {
5360
+ return props.some(
5361
+ (prop) => KEY_HANDLERS.has(prop.prop) && prop.resolution !== "spread" && prop.value !== null && prop.value !== false
5362
+ );
4899
5363
  }
4900
5364
 
4901
5365
  // src/raw-html-canonical.ts
@@ -5080,8 +5544,8 @@ function decisionTargetsExport(target, exportName) {
5080
5544
  }
5081
5545
 
5082
5546
  // src/rules/components-prefer-library.ts
5083
- var RULE_ID2 = "components/prefer-library";
5084
- var RULE_VERSION2 = "1";
5547
+ var RULE_ID3 = "components/prefer-library";
5548
+ var RULE_VERSION3 = "1";
5085
5549
  function isExplicitTagDeclaration(mapping) {
5086
5550
  return mapping.declaredHtmlEquivalent === true;
5087
5551
  }
@@ -5220,7 +5684,7 @@ function findClassNameReimpl(node, classTokensByNode, targets) {
5220
5684
  return null;
5221
5685
  }
5222
5686
  function ruleComponentsPreferLibrary(ix) {
5223
- const policy = ix.policy.ruleConfig(RULE_ID2);
5687
+ const policy = ix.policy.ruleConfig(RULE_ID3);
5224
5688
  if (!policy?.enabled) return [];
5225
5689
  const configuredSources = canonicalSourcesFromPolicy(policy.options?.canonicalSources);
5226
5690
  const conflicts = projectCanonicalDirectionConflicts(ix, configuredSources);
@@ -5235,7 +5699,7 @@ function ruleComponentsPreferLibrary(ix) {
5235
5699
  const classTokensByNode = indexClassTokensByNode(ix);
5236
5700
  const reimplTargets = canonicalReimplTargets(sources, mappings);
5237
5701
  const canonicalDirectoryResolvedNodes = indexCanonicalDirectoryResolvedNodes(ix, sources);
5238
- const shadowRenderRootNodeIds = indexShadowRenderRootNodeIds(ix);
5702
+ const shadowRenderRootNodeIds = ix.policy.ruleConfig("components/shadow-component")?.enabled === true ? indexShadowRenderRootNodeIds(ix) : /* @__PURE__ */ new Set();
5239
5703
  const findings = [];
5240
5704
  const seenImportFixes = /* @__PURE__ */ new Set();
5241
5705
  for (const node of ix.byKind("usage_node")) {
@@ -5267,8 +5731,8 @@ function ruleComponentsPreferLibrary(ix) {
5267
5731
  seenImportFixes.add(key);
5268
5732
  findings.push(
5269
5733
  makeFinding({
5270
- ruleId: RULE_ID2,
5271
- ruleVersion: RULE_VERSION2,
5734
+ ruleId: RULE_ID3,
5735
+ ruleVersion: RULE_VERSION3,
5272
5736
  severity: capAdvisorySeverity(policy.severity),
5273
5737
  message: `<${node.element}> may need to import from ${importPath}; verify that the local component contract is compatible first.`,
5274
5738
  location: imported.location,
@@ -5302,8 +5766,8 @@ function ruleComponentsPreferLibrary(ix) {
5302
5766
  if (node.element === suggestedComponent2) continue;
5303
5767
  findings.push(
5304
5768
  makeFinding({
5305
- ruleId: RULE_ID2,
5306
- ruleVersion: RULE_VERSION2,
5769
+ ruleId: RULE_ID3,
5770
+ ruleVersion: RULE_VERSION3,
5307
5771
  severity: severityForTier(policy.severity, precisionTier2),
5308
5772
  message: node.element === "button" ? `Bespoke <button> has a library equivalent. Swap to <${suggestedComponent2}> from ${suggestedImport2}.` : roleMatch ? `Custom <${node.element} role="${node.role}"> reimplements a primitive. Use <${suggestedComponent2}> from ${suggestedImport2}.` : `Bespoke <${node.element}> should use <${suggestedComponent2}> from ${suggestedImport2}.`,
5309
5773
  location: node.location,
@@ -5354,8 +5818,8 @@ function ruleComponentsPreferLibrary(ix) {
5354
5818
  const suggestedImport2 = reimpl.import ?? "the canonical library";
5355
5819
  findings.push(
5356
5820
  makeFinding({
5357
- ruleId: RULE_ID2,
5358
- ruleVersion: RULE_VERSION2,
5821
+ ruleId: RULE_ID3,
5822
+ ruleVersion: RULE_VERSION3,
5359
5823
  severity: capAdvisorySeverity(policy.severity),
5360
5824
  message: `<${node.element} class="${reimpl.matchedClass}"> looks like a hand-rolled <${reimpl.canonical}>. Use <${reimpl.canonical}> from ${suggestedImport2} instead of restyling a <${node.element}>.`,
5361
5825
  location: node.location,
@@ -5395,8 +5859,8 @@ function ruleComponentsPreferLibrary(ix) {
5395
5859
  seenImportFixes.add(key);
5396
5860
  findings.push(
5397
5861
  makeFinding({
5398
- ruleId: RULE_ID2,
5399
- ruleVersion: RULE_VERSION2,
5862
+ ruleId: RULE_ID3,
5863
+ ruleVersion: RULE_VERSION3,
5400
5864
  severity: capAdvisorySeverity(policy.severity),
5401
5865
  message: `<${node.element}> may need to import from ${importPath}; verify that the local component contract is compatible first.`,
5402
5866
  location: imported.location,
@@ -5438,14 +5902,15 @@ function ruleComponentsPreferLibrary(ix) {
5438
5902
  continue;
5439
5903
  }
5440
5904
  }
5441
- const suggestedImport = sourceLabel(suggestion);
5905
+ const importReady = suggestion.kind !== "directory";
5906
+ const suggestedImport = importReady ? sourceLabel(suggestion) : "the canonical component directory (resolve its configured import alias)";
5442
5907
  const precisionTier = builtIn?.tier ?? "exact-html";
5443
5908
  const rawElement = formatRawElement(node, props);
5444
5909
  findings.push(
5445
5910
  makeFinding({
5446
- ruleId: RULE_ID2,
5447
- ruleVersion: RULE_VERSION2,
5448
- severity: severityForTier(policy.severity, precisionTier),
5911
+ ruleId: RULE_ID3,
5912
+ ruleVersion: RULE_VERSION3,
5913
+ severity: importReady ? severityForTier(policy.severity, precisionTier) : capAdvisorySeverity(policy.severity),
5449
5914
  message: messageForBuiltInMatch({
5450
5915
  node,
5451
5916
  rawElement,
@@ -5462,7 +5927,7 @@ function ruleComponentsPreferLibrary(ix) {
5462
5927
  suggestedComponent,
5463
5928
  suggestedImport
5464
5929
  },
5465
- ...!isRawHtmlAdvisoryTier(precisionTier) && isIdentifier(node.element) && isIdentifier(suggestedComponent) ? {
5930
+ ...importReady && !isRawHtmlAdvisoryTier(precisionTier) && isIdentifier(node.element) && isIdentifier(suggestedComponent) ? {
5466
5931
  fix: {
5467
5932
  kind: "replaceComponent",
5468
5933
  title: `Replace ${rawElement} with <${suggestedComponent}>`,
@@ -5477,13 +5942,13 @@ function ruleComponentsPreferLibrary(ix) {
5477
5942
  attributes: {
5478
5943
  rawValue: node.element,
5479
5944
  suggestedComponent,
5480
- suggestedImport,
5481
5945
  suggestedImportSourceKind: suggestion.kind,
5946
+ ...importReady ? { suggestedImport } : { canonicalDirectory: suggestion.path, importReady: false },
5482
5947
  precisionTier,
5483
5948
  propCompatibility: "unknown",
5484
5949
  ...builtIn?.matchedRole ? { matchedRole: builtIn.matchedRole } : {},
5485
5950
  ...builtIn?.inputType ? { matchedInputType: builtIn.inputType } : {},
5486
- ...isRawHtmlAdvisoryTier(precisionTier) ? { advisory: true } : {}
5951
+ ...isRawHtmlAdvisoryTier(precisionTier) || !importReady ? { advisory: true } : {}
5487
5952
  }
5488
5953
  })
5489
5954
  );
@@ -5503,11 +5968,19 @@ function suppressConflictedExports(source, conflicts) {
5503
5968
  function indexShadowRenderRootNodeIds(ix) {
5504
5969
  const identities = ix.byKind("component_identity");
5505
5970
  if (identities.length === 0) return /* @__PURE__ */ new Set();
5971
+ const definitions = new Map(
5972
+ ix.byKind("component_definition").map((definition) => [definition.componentKey, definition])
5973
+ );
5506
5974
  const nodeIds = /* @__PURE__ */ new Set();
5507
5975
  for (const identity of identities) {
5508
5976
  if (identity.state !== "shadow") continue;
5977
+ const definition = definitions.get(identity.componentKey);
5978
+ if (!definition) continue;
5509
5979
  for (const evidenceId of identity.evidence) {
5510
- if (ix.get(evidenceId)?.kind === "usage_node") nodeIds.add(evidenceId);
5980
+ const evidence = ix.get(evidenceId);
5981
+ if (evidence?.kind === "usage_node" && evidence.file === definition.file) {
5982
+ nodeIds.add(evidenceId);
5983
+ }
5511
5984
  }
5512
5985
  }
5513
5986
  return nodeIds;
@@ -5790,7 +6263,7 @@ function sourceLabel(source) {
5790
6263
  function canonicalImportPath(source) {
5791
6264
  if (source.kind === "npm") return source.specifier;
5792
6265
  if (source.kind === "registry") return source.importPath;
5793
- return source.include?.length ? source.path : void 0;
6266
+ return void 0;
5794
6267
  }
5795
6268
  function indexCanonicalDirectoryResolvedNodes(ix, sources) {
5796
6269
  const out = /* @__PURE__ */ new Set();
@@ -5866,10 +6339,10 @@ function indexComponentIdentityUsage(ix) {
5866
6339
  }
5867
6340
 
5868
6341
  // src/rules/components-shadow-component.ts
5869
- var RULE_ID3 = "components/shadow-component";
5870
- var RULE_VERSION3 = "1";
6342
+ var RULE_ID4 = "components/shadow-component";
6343
+ var RULE_VERSION4 = "1";
5871
6344
  function ruleComponentsShadowComponent(ix) {
5872
- const policy = ix.policy.ruleConfig(RULE_ID3);
6345
+ const policy = ix.policy.ruleConfig(RULE_ID4);
5873
6346
  if (!policy?.enabled) return [];
5874
6347
  const definitions = new Map(
5875
6348
  ix.byKind("component_definition").map((definition) => [definition.componentKey, definition])
@@ -5886,8 +6359,8 @@ function ruleComponentsShadowComponent(ix) {
5886
6359
  const blastRadius = usage?.blastRadius ?? 0;
5887
6360
  findings.push(
5888
6361
  makeFinding({
5889
- ruleId: RULE_ID3,
5890
- ruleVersion: RULE_VERSION3,
6362
+ ruleId: RULE_ID4,
6363
+ ruleVersion: RULE_VERSION4,
5891
6364
  severity: reviewVariant ? "info" : policy.severity ?? "warn",
5892
6365
  message: reviewVariant ? `${definition.exportName} wraps ${identity.canonicalTarget ?? "a canonical component"}; sanction it as a variant or consolidate its usages.` : `${definition.exportName} shadows ${identity.canonicalTarget ?? "a canonical component"} across ${usageCount} usage${usageCount === 1 ? "" : "s"}.`,
5893
6366
  location: definitionLocation(ix, definition, identity),
@@ -5925,8 +6398,8 @@ function definitionLocation(ix, definition, identity) {
5925
6398
  }
5926
6399
 
5927
6400
  // src/rules/components-forbidden-prop-value.ts
5928
- var RULE_ID4 = "components/forbidden-prop-value";
5929
- var RULE_VERSION4 = "1";
6401
+ var RULE_ID5 = "components/forbidden-prop-value";
6402
+ var RULE_VERSION5 = "1";
5930
6403
  function ruleComponentsForbiddenPropValue(ix) {
5931
6404
  const findings = [];
5932
6405
  const componentByNode = indexComponentByNodeId(ix);
@@ -5956,8 +6429,8 @@ function ruleComponentsForbiddenPropValue(ix) {
5956
6429
  } : void 0;
5957
6430
  findings.push(
5958
6431
  makeFinding({
5959
- ruleId: RULE_ID4,
5960
- ruleVersion: RULE_VERSION4,
6432
+ ruleId: RULE_ID5,
6433
+ ruleVersion: RULE_VERSION5,
5961
6434
  severity: policy.severity,
5962
6435
  message: buildMessage(
5963
6436
  node.element,
@@ -6001,8 +6474,8 @@ function buildMessage(element, prop, value, because, replacement) {
6001
6474
  }
6002
6475
 
6003
6476
  // src/rules/components-unknown-prop.ts
6004
- var RULE_ID5 = "components/unknown-prop";
6005
- var RULE_VERSION5 = "1";
6477
+ var RULE_ID6 = "components/unknown-prop";
6478
+ var RULE_VERSION6 = "1";
6006
6479
  function ruleComponentsUnknownProp(ix) {
6007
6480
  const policy = ix.policy.jsxUnknownPropsForbidden();
6008
6481
  if (!policy) return [];
@@ -6023,8 +6496,8 @@ function ruleComponentsUnknownProp(ix) {
6023
6496
  if (knownNames.has(propUsage.prop)) continue;
6024
6497
  findings.push(
6025
6498
  makeFinding({
6026
- ruleId: RULE_ID5,
6027
- ruleVersion: RULE_VERSION5,
6499
+ ruleId: RULE_ID6,
6500
+ ruleVersion: RULE_VERSION6,
6028
6501
  severity: policy.severity,
6029
6502
  message: `Unknown prop "${propUsage.prop}" on <${node.element}>. Allowed props: ${formatPropList(knownNames)}.`,
6030
6503
  location: node.location,
@@ -6056,7 +6529,7 @@ function formatPropList(names) {
6056
6529
  // src/rules/composition-pattern.ts
6057
6530
  var CARDINALITY_RULE_ID = "composition/cardinality";
6058
6531
  var CO_OCCURRENCE_RULE_ID = "composition/co-occurrence";
6059
- var RULE_VERSION6 = "1";
6532
+ var RULE_VERSION7 = "1";
6060
6533
  function ruleCompositionCardinality(ix) {
6061
6534
  return evaluate(ix, CARDINALITY_RULE_ID, "cardinality");
6062
6535
  }
@@ -6124,7 +6597,7 @@ function checkCardinality(ctx, region, pattern, constraint, selected) {
6124
6597
  const extras = selected.slice(constraint.max);
6125
6598
  return makeFinding({
6126
6599
  ruleId: ctx.ruleId,
6127
- ruleVersion: RULE_VERSION6,
6600
+ ruleVersion: RULE_VERSION7,
6128
6601
  severity: ctx.severity,
6129
6602
  message: cardinalityMaxMessage(pattern, constraint.max, count),
6130
6603
  location: extras[0].location,
@@ -6135,7 +6608,7 @@ function checkCardinality(ctx, region, pattern, constraint, selected) {
6135
6608
  if (constraint.min !== void 0 && count < constraint.min) {
6136
6609
  return makeFinding({
6137
6610
  ruleId: ctx.ruleId,
6138
- ruleVersion: RULE_VERSION6,
6611
+ ruleVersion: RULE_VERSION7,
6139
6612
  severity: ctx.severity,
6140
6613
  message: cardinalityMinMessage(pattern, constraint.min, count),
6141
6614
  location: region.container.location,
@@ -6156,7 +6629,7 @@ function checkCoOccurrence(ctx, region, pattern, constraint, selected) {
6156
6629
  if (satisfied) return null;
6157
6630
  return makeFinding({
6158
6631
  ruleId: ctx.ruleId,
6159
- ruleVersion: RULE_VERSION6,
6632
+ ruleVersion: RULE_VERSION7,
6160
6633
  severity: ctx.severity,
6161
6634
  message: coOccurrenceMessage(pattern, constraint),
6162
6635
  location: region.container.location,
@@ -6208,8 +6681,8 @@ function coOccurrenceMessage(pattern, constraint) {
6208
6681
  }
6209
6682
 
6210
6683
  // src/rules/jsx-preferred-component.ts
6211
- var RULE_ID6 = "components/preferred-component";
6212
- var RULE_VERSION7 = "1";
6684
+ var RULE_ID7 = "components/preferred-component";
6685
+ var RULE_VERSION8 = "1";
6213
6686
  function ruleJsxPreferredComponent(ix) {
6214
6687
  const policies = ix.policy.jsxComponentPreferred();
6215
6688
  if (policies.length === 0) return [];
@@ -6225,8 +6698,8 @@ function ruleJsxPreferredComponent(ix) {
6225
6698
  if (!replacement) continue;
6226
6699
  findings.push(
6227
6700
  makeFinding({
6228
- ruleId: RULE_ID6,
6229
- ruleVersion: RULE_VERSION7,
6701
+ ruleId: RULE_ID7,
6702
+ ruleVersion: RULE_VERSION8,
6230
6703
  severity: policy.severity,
6231
6704
  message: `<${node.element}> should use <${replacement}>.`,
6232
6705
  location: node.location,
@@ -6263,8 +6736,8 @@ function componentNameFromId(componentId2) {
6263
6736
  }
6264
6737
 
6265
6738
  // src/rules/jsx-preferred-import-path.ts
6266
- var RULE_ID7 = "imports/preferred-path";
6267
- var RULE_VERSION8 = "1";
6739
+ var RULE_ID8 = "imports/preferred-path";
6740
+ var RULE_VERSION9 = "1";
6268
6741
  function ruleJsxPreferredImportPath(ix) {
6269
6742
  const policies = ix.policy.jsxImportPathPreferred();
6270
6743
  if (policies.length === 0) return [];
@@ -6292,8 +6765,8 @@ function ruleJsxPreferredImportPath(ix) {
6292
6765
  seen.add(key);
6293
6766
  findings.push(
6294
6767
  makeFinding({
6295
- ruleId: RULE_ID7,
6296
- ruleVersion: RULE_VERSION8,
6768
+ ruleId: RULE_ID8,
6769
+ ruleVersion: RULE_VERSION9,
6297
6770
  severity: policy.severity,
6298
6771
  message: policy.bridge ? `Import ${policy.bridge.underlyingExportName} through the confirmed local ${policy.bridge.localExportName} wrapper from "${policy.to}".` : `Import from "${usage.source}" should use "${policy.to}".`,
6299
6772
  location: usage.location,
@@ -6352,10 +6825,10 @@ function normalizeRepoPath(path) {
6352
6825
  }
6353
6826
 
6354
6827
  // src/rules/props-invalid-value.ts
6355
- var RULE_ID8 = "props/invalid-value";
6356
- var RULE_VERSION9 = "1";
6828
+ var RULE_ID9 = "props/invalid-value";
6829
+ var RULE_VERSION10 = "1";
6357
6830
  function rulePropsInvalidValue(ix) {
6358
- const policy = ix.policy.ruleConfig(RULE_ID8);
6831
+ const policy = ix.policy.ruleConfig(RULE_ID9);
6359
6832
  if (!policy?.enabled) return [];
6360
6833
  const findings = [];
6361
6834
  const componentByNode = indexComponentByNodeId(ix);
@@ -6384,8 +6857,8 @@ function rulePropsInvalidValue(ix) {
6384
6857
  } : void 0;
6385
6858
  findings.push(
6386
6859
  makeFinding({
6387
- ruleId: RULE_ID8,
6388
- ruleVersion: RULE_VERSION9,
6860
+ ruleId: RULE_ID9,
6861
+ ruleVersion: RULE_VERSION10,
6389
6862
  severity: policy.severity ?? "warn",
6390
6863
  message: buildMessage2(node.element, propUsage.prop, propUsage.value, allowedValues),
6391
6864
  location: propUsage.location ?? node.location,
@@ -6584,8 +7057,8 @@ function localTokenCandidates(ix, category) {
6584
7057
  }
6585
7058
 
6586
7059
  // src/rules/styles-no-raw-color.ts
6587
- var RULE_ID9 = "styles/no-raw-color";
6588
- var RULE_VERSION10 = "1";
7060
+ var RULE_ID10 = "styles/no-raw-color";
7061
+ var RULE_VERSION11 = "1";
6589
7062
  function ruleStylesNoRawColor(ix) {
6590
7063
  const policy = ix.policy.rawColorPolicy();
6591
7064
  if (!policy) return [];
@@ -6604,8 +7077,8 @@ function ruleStylesNoRawColor(ix) {
6604
7077
  const evidenceIds = resolution ? [decl.id, policy.id, resolution.token.id] : [decl.id, policy.id];
6605
7078
  findings.push(
6606
7079
  makeFinding({
6607
- ruleId: RULE_ID9,
6608
- ruleVersion: RULE_VERSION10,
7080
+ ruleId: RULE_ID10,
7081
+ ruleVersion: RULE_VERSION11,
6609
7082
  severity: policy.severity,
6610
7083
  message: colorMessage(color, `\`${decl.property}\``, resolution, preferLabel) + (hopped?.suffix ?? ""),
6611
7084
  location: decl.location,
@@ -6641,8 +7114,8 @@ function ruleStylesNoRawColor(ix) {
6641
7114
  const evidenceIds2 = componentEvidenceId2 ? [node2.id, componentEvidenceId2, inline.id, policy.id] : [node2.id, inline.id, policy.id];
6642
7115
  findings.push(
6643
7116
  makeFinding({
6644
- ruleId: RULE_ID9,
6645
- ruleVersion: RULE_VERSION10,
7117
+ ruleId: RULE_ID10,
7118
+ ruleVersion: RULE_VERSION11,
6646
7119
  severity: advisorySeverity,
6647
7120
  message: `Runtime-constructed raw color \`${inline.value}\` on inline \`${inline.property}\`. Use a ${preferLabel} instead.`,
6648
7121
  location: node2.location,
@@ -6679,8 +7152,8 @@ function ruleStylesNoRawColor(ix) {
6679
7152
  const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
6680
7153
  findings.push(
6681
7154
  makeFinding({
6682
- ruleId: RULE_ID9,
6683
- ruleVersion: RULE_VERSION10,
7155
+ ruleId: RULE_ID10,
7156
+ ruleVersion: RULE_VERSION11,
6684
7157
  severity: policy.severity,
6685
7158
  message: colorMessage(color, `inline \`${inline.property}\``, resolution, preferLabel) + (hopped?.suffix ?? ""),
6686
7159
  location: node.location,
@@ -6724,8 +7197,8 @@ function ruleStylesNoRawColor(ix) {
6724
7197
  const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
6725
7198
  findings.push(
6726
7199
  makeFinding({
6727
- ruleId: RULE_ID9,
6728
- ruleVersion: RULE_VERSION10,
7200
+ ruleId: RULE_ID10,
7201
+ ruleVersion: RULE_VERSION11,
6729
7202
  severity: advisorySeverity,
6730
7203
  message: colorMessage(color, `\`${prop.prop}\``, resolution, preferLabel),
6731
7204
  location: prop.location ?? node.location,
@@ -6876,8 +7349,8 @@ function escapeRegExp(value) {
6876
7349
  }
6877
7350
 
6878
7351
  // src/rules/styles-no-raw-dimensions.ts
6879
- var RULE_ID10 = "styles/no-raw-dimensions";
6880
- var RULE_VERSION11 = "1";
7352
+ var RULE_ID11 = "styles/no-raw-dimensions";
7353
+ var RULE_VERSION12 = "1";
6881
7354
  function ruleStylesNoRawDimensions(ix) {
6882
7355
  const policy = ix.policy.rawDimensionPolicy();
6883
7356
  if (!policy) return [];
@@ -6898,8 +7371,8 @@ function ruleStylesNoRawDimensions(ix) {
6898
7371
  const fixEmission = hopped ? void 0 : dimensionFix(decl.property, resolution, ix);
6899
7372
  findings.push(
6900
7373
  makeFinding({
6901
- ruleId: RULE_ID10,
6902
- ruleVersion: RULE_VERSION11,
7374
+ ruleId: RULE_ID11,
7375
+ ruleVersion: RULE_VERSION12,
6903
7376
  severity: policy.severity,
6904
7377
  message: dimensionMessage(decl.value, decl.property, preferLabel, resolution, false) + (hopped?.suffix ?? ""),
6905
7378
  location: decl.location,
@@ -6942,8 +7415,8 @@ function ruleStylesNoRawDimensions(ix) {
6942
7415
  const fixEmission = hopped ? void 0 : dimensionFix(inline.property, resolution, ix);
6943
7416
  findings.push(
6944
7417
  makeFinding({
6945
- ruleId: RULE_ID10,
6946
- ruleVersion: RULE_VERSION11,
7418
+ ruleId: RULE_ID11,
7419
+ ruleVersion: RULE_VERSION12,
6947
7420
  severity: policy.severity,
6948
7421
  message: dimensionMessage(inline.value, inline.property, preferLabel, resolution, true) + (hopped?.suffix ?? ""),
6949
7422
  location: node.location,
@@ -7212,8 +7685,8 @@ function parseLengthParts(raw) {
7212
7685
  }
7213
7686
 
7214
7687
  // src/rules/styles-no-raw-spacing.ts
7215
- var RULE_ID11 = "styles/no-raw-spacing";
7216
- var RULE_VERSION12 = "1";
7688
+ var RULE_ID12 = "styles/no-raw-spacing";
7689
+ var RULE_VERSION13 = "1";
7217
7690
  function ruleStylesNoRawSpacing(ix) {
7218
7691
  const findings = [];
7219
7692
  const lookupCache = /* @__PURE__ */ new Map();
@@ -7256,8 +7729,8 @@ function checkDeclaration(ix, decl, lookupFor) {
7256
7729
  const hopped = hoppedValue(decl);
7257
7730
  const fixEmission = hopped ? void 0 : buildSpacingFix(decl.property, checked, ix);
7258
7731
  return makeFinding({
7259
- ruleId: RULE_ID11,
7260
- ruleVersion: RULE_VERSION12,
7732
+ ruleId: RULE_ID12,
7733
+ ruleVersion: RULE_VERSION13,
7261
7734
  severity: policy.severity,
7262
7735
  message: spacingMessage(decl.property, decl.value, allowed, scale, checked) + (hopped?.suffix ?? ""),
7263
7736
  location: decl.location,
@@ -7313,8 +7786,8 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
7313
7786
  const componentEvidenceId = componentByNode.get(node.id)?.id;
7314
7787
  const evidenceIds = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id, scale.id] : [node.id, inline.id, policy.id, scale.id];
7315
7788
  return makeFinding({
7316
- ruleId: RULE_ID11,
7317
- ruleVersion: RULE_VERSION12,
7789
+ ruleId: RULE_ID12,
7790
+ ruleVersion: RULE_VERSION13,
7318
7791
  severity: policy.severity,
7319
7792
  message: spacingMessage(inline.property, inline.value, allowed, scale, checked) + (hopped?.suffix ?? ""),
7320
7793
  location: node.location,
@@ -7376,8 +7849,8 @@ function spacingMessage(property, value, allowed, scale, checked) {
7376
7849
  }
7377
7850
 
7378
7851
  // src/rules/styles-no-raw-typography.ts
7379
- var RULE_ID12 = "styles/no-raw-typography";
7380
- var RULE_VERSION13 = "1";
7852
+ var RULE_ID13 = "styles/no-raw-typography";
7853
+ var RULE_VERSION14 = "1";
7381
7854
  var FONT_SIZE_PROPERTIES = /* @__PURE__ */ new Set(["font-size", "fontsize"]);
7382
7855
  function ruleStylesNoRawTypography(ix) {
7383
7856
  const policy = ix.policy.fontSizeScale();
@@ -7396,8 +7869,8 @@ function ruleStylesNoRawTypography(ix) {
7396
7869
  const fixEmission = hopped ? void 0 : buildFix(decl.property, checked, ix);
7397
7870
  findings.push(
7398
7871
  makeFinding({
7399
- ruleId: RULE_ID12,
7400
- ruleVersion: RULE_VERSION13,
7872
+ ruleId: RULE_ID13,
7873
+ ruleVersion: RULE_VERSION14,
7401
7874
  severity: policy.severity,
7402
7875
  message: typographyMessage(decl.property, decl.value, allowed, scale.unit, checked) + (hopped?.suffix ?? ""),
7403
7876
  location: decl.location,
@@ -7445,8 +7918,8 @@ function ruleStylesNoRawTypography(ix) {
7445
7918
  const evidenceIds = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id, scale.id] : [node.id, inline.id, policy.id, scale.id];
7446
7919
  findings.push(
7447
7920
  makeFinding({
7448
- ruleId: RULE_ID12,
7449
- ruleVersion: RULE_VERSION13,
7921
+ ruleId: RULE_ID13,
7922
+ ruleVersion: RULE_VERSION14,
7450
7923
  severity: policy.severity,
7451
7924
  message: typographyMessage(inline.property, inline.value, allowed, scale.unit, checked) + (hopped?.suffix ?? ""),
7452
7925
  location: node.location,
@@ -7549,15 +8022,15 @@ function typographyMessage(property, value, allowed, unit, checked) {
7549
8022
  }
7550
8023
 
7551
8024
  // src/rules/theme-no-theme-coupled-literal.ts
7552
- var RULE_ID13 = "theme/no-theme-coupled-literal";
7553
- var RULE_VERSION14 = "1";
8025
+ var RULE_ID14 = "theme/no-theme-coupled-literal";
8026
+ var RULE_VERSION15 = "1";
7554
8027
  var COLOR_LITERAL_GLOBAL = /#[0-9a-fA-F]{3,8}\b|\b(?:rgba?|hsla?|oklab|oklch|lab|lch)\(\s*[\d.%\s,\/]+\s*\)/g;
7555
8028
  var WHITE_RGBA = /^rgba?\(\s*255\s*,\s*255\s*,\s*255/i;
7556
8029
  var BLACK_RGBA = /^rgba?\(\s*0\s*,\s*0\s*,\s*0/i;
7557
8030
  var BOX_SHADOW_PROP = /^(?:-webkit-|-moz-)?box-shadow$/i;
7558
8031
  var NEUTRAL_SHADOW = /^(?:none|inherit|initial|unset|revert|revert-layer)\s*$/i;
7559
8032
  function ruleThemeNoThemeCoupledLiteral(ix) {
7560
- const policy = ix.policy.ruleConfig(RULE_ID13);
8033
+ const policy = ix.policy.ruleConfig(RULE_ID14);
7561
8034
  if (!policy?.enabled) return [];
7562
8035
  const findings = [];
7563
8036
  for (const decl of ix.byKind("style_declaration")) {
@@ -7566,8 +8039,8 @@ function ruleThemeNoThemeCoupledLiteral(ix) {
7566
8039
  if (!hit) continue;
7567
8040
  findings.push(
7568
8041
  makeFinding({
7569
- ruleId: RULE_ID13,
7570
- ruleVersion: RULE_VERSION14,
8042
+ ruleId: RULE_ID14,
8043
+ ruleVersion: RULE_VERSION15,
7571
8044
  severity: policy.severity ?? "warn",
7572
8045
  message: hit.message,
7573
8046
  location: decl.location,
@@ -7657,8 +8130,8 @@ function isTokenFile(filePath) {
7657
8130
  }
7658
8131
 
7659
8132
  // src/rules/tailwind-arbitrary-color.ts
7660
- var RULE_ID14 = "tailwind/arbitrary-color";
7661
- var RULE_VERSION15 = "1";
8133
+ var RULE_ID15 = "tailwind/arbitrary-color";
8134
+ var RULE_VERSION16 = "1";
7662
8135
  var COLOR_UTILITIES = /* @__PURE__ */ new Set([
7663
8136
  "accent",
7664
8137
  "bg",
@@ -7700,8 +8173,8 @@ function ruleTailwindArbitraryColor(ix) {
7700
8173
  const evidenceIds = node ? [node.id, klass.id, policy.id] : [klass.id, policy.id];
7701
8174
  findings.push(
7702
8175
  makeFinding({
7703
- ruleId: RULE_ID14,
7704
- ruleVersion: RULE_VERSION15,
8176
+ ruleId: RULE_ID15,
8177
+ ruleVersion: RULE_VERSION16,
7705
8178
  severity: policy.severity,
7706
8179
  message: `\`${klass.raw}\` uses arbitrary raw color ${color}. Use a ${policy.prefer === "token" ? "design token" : "CSS variable"} instead.`,
7707
8180
  location: klass.location,
@@ -7732,8 +8205,8 @@ function fingerprintIdentity(klass, color) {
7732
8205
  }
7733
8206
 
7734
8207
  // src/rules/tailwind-arbitrary-spacing.ts
7735
- var RULE_ID15 = "tailwind/arbitrary-spacing";
7736
- var RULE_VERSION16 = "1";
8208
+ var RULE_ID16 = "tailwind/arbitrary-spacing";
8209
+ var RULE_VERSION17 = "1";
7737
8210
  var SPACING_UTILITY_TO_PROPERTY = /* @__PURE__ */ new Map([
7738
8211
  ["m", "margin"],
7739
8212
  ["mt", "margin"],
@@ -7779,8 +8252,8 @@ function ruleTailwindArbitrarySpacing(ix) {
7779
8252
  const fix = buildFix2(klass);
7780
8253
  findings.push(
7781
8254
  makeFinding({
7782
- ruleId: RULE_ID15,
7783
- ruleVersion: RULE_VERSION16,
8255
+ ruleId: RULE_ID16,
8256
+ ruleVersion: RULE_VERSION17,
7784
8257
  severity: policy.severity,
7785
8258
  message: `\`${klass.raw}\` is an arbitrary spacing value not on the project's spacing scale.`,
7786
8259
  location: klass.location,
@@ -7911,8 +8384,8 @@ function matchesTokenGlob(value, pattern) {
7911
8384
  }
7912
8385
 
7913
8386
  // src/rules/tailwind-forbidden-palette.ts
7914
- var RULE_ID16 = "tailwind/forbidden-palette";
7915
- var RULE_VERSION17 = "1";
8387
+ var RULE_ID17 = "tailwind/forbidden-palette";
8388
+ var RULE_VERSION18 = "1";
7916
8389
  function ruleTailwindForbiddenPalette(ix) {
7917
8390
  const deny = ix.policy.tailwindPaletteDeny();
7918
8391
  const allow = ix.policy.tailwindPaletteAllow();
@@ -7937,8 +8410,8 @@ function ruleTailwindForbiddenPalette(ix) {
7937
8410
  const evidenceIds = node ? [node.id, klass.id, resolved.id, policy.id] : [klass.id, resolved.id, policy.id];
7938
8411
  findings.push(
7939
8412
  makeFinding({
7940
- ruleId: RULE_ID16,
7941
- ruleVersion: RULE_VERSION17,
8413
+ ruleId: RULE_ID17,
8414
+ ruleVersion: RULE_VERSION18,
7942
8415
  severity: policy.severity,
7943
8416
  message: messageFor(klass, token, deniedPattern, allow?.patterns ?? []),
7944
8417
  location: klass.location,
@@ -7985,8 +8458,8 @@ function buildFix3(klass) {
7985
8458
  }
7986
8459
 
7987
8460
  // src/rules/tailwind-off-scale-spacing-token.ts
7988
- var RULE_ID17 = "tailwind/off-scale-spacing-token";
7989
- var RULE_VERSION18 = "1";
8461
+ var RULE_ID18 = "tailwind/off-scale-spacing-token";
8462
+ var RULE_VERSION19 = "1";
7990
8463
  function ruleTailwindOffScaleSpacingToken(ix) {
7991
8464
  const resolvedByKey = indexResolvedTailwindTokens(ix);
7992
8465
  const findings = [];
@@ -8012,8 +8485,8 @@ function ruleTailwindOffScaleSpacingToken(ix) {
8012
8485
  const evidenceIds = node ? [node.id, klass.id, resolved.id, policy.id, scale.id] : [klass.id, resolved.id, policy.id, scale.id];
8013
8486
  findings.push(
8014
8487
  makeFinding({
8015
- ruleId: RULE_ID17,
8016
- ruleVersion: RULE_VERSION18,
8488
+ ruleId: RULE_ID18,
8489
+ ruleVersion: RULE_VERSION19,
8017
8490
  severity: policy.severity,
8018
8491
  message: `\`${klass.raw}\` resolves to ${formatResolvedValue(signedValue, scale.unit)}, which is not on the project's spacing scale [${allowed.join(", ")}].`,
8019
8492
  location: klass.location,
@@ -8062,8 +8535,8 @@ function buildFix4(raw, utility) {
8062
8535
  }
8063
8536
 
8064
8537
  // src/rules/tailwind-raw-color-via-token.ts
8065
- var RULE_ID18 = "tailwind/raw-color-via-token";
8066
- var RULE_VERSION19 = "1";
8538
+ var RULE_ID19 = "tailwind/raw-color-via-token";
8539
+ var RULE_VERSION20 = "1";
8067
8540
  function ruleTailwindRawColorViaToken(ix) {
8068
8541
  const allow = ix.policy.tailwindPaletteAllow();
8069
8542
  if (!allow || allow.patterns.length === 0) return [];
@@ -8083,8 +8556,8 @@ function ruleTailwindRawColorViaToken(ix) {
8083
8556
  const evidenceIds = node ? [node.id, klass.id, resolved.id, allow.id] : [klass.id, resolved.id, allow.id];
8084
8557
  findings.push(
8085
8558
  makeFinding({
8086
- ruleId: RULE_ID18,
8087
- ruleVersion: RULE_VERSION19,
8559
+ ruleId: RULE_ID19,
8560
+ ruleVersion: RULE_VERSION20,
8088
8561
  severity: allow.severity,
8089
8562
  message: `\`${klass.raw}\` resolves to raw color \`${color}\` (${sourceLabel2(resolved.resolved.source)}). Project policy requires \`${allow.patterns.join("`, `")}\` palette tokens.`,
8090
8563
  location: klass.location,
@@ -8117,8 +8590,8 @@ function sourceLabel2(source) {
8117
8590
  }
8118
8591
 
8119
8592
  // src/rules/tailwind-unknown-class.ts
8120
- var RULE_ID19 = "tailwind/unknown-class";
8121
- var RULE_VERSION20 = "1";
8593
+ var RULE_ID20 = "tailwind/unknown-class";
8594
+ var RULE_VERSION21 = "1";
8122
8595
  var UNIVERSAL_COLOR_KEYWORDS = /* @__PURE__ */ new Set([
8123
8596
  "white",
8124
8597
  "black",
@@ -8139,8 +8612,8 @@ function ruleTailwindUnknownClass(ix) {
8139
8612
  const evidenceIds2 = node2 ? [node2.id, klass.id, policy.id] : [klass.id, policy.id];
8140
8613
  findings.push(
8141
8614
  makeFinding({
8142
- ruleId: RULE_ID19,
8143
- ruleVersion: RULE_VERSION20,
8615
+ ruleId: RULE_ID20,
8616
+ ruleVersion: RULE_VERSION21,
8144
8617
  severity: policy.severity,
8145
8618
  message: `\`${klass.raw}\` could not be parsed as a known Tailwind utility.`,
8146
8619
  location: klass.location,
@@ -8174,8 +8647,8 @@ function ruleTailwindUnknownClass(ix) {
8174
8647
  const evidenceIds = node ? [node.id, klass.id, resolved.id, policy.id] : [klass.id, resolved.id, policy.id];
8175
8648
  findings.push(
8176
8649
  makeFinding({
8177
- ruleId: RULE_ID19,
8178
- ruleVersion: RULE_VERSION20,
8650
+ ruleId: RULE_ID20,
8651
+ ruleVersion: RULE_VERSION21,
8179
8652
  severity: policy.severity,
8180
8653
  message: `\`${klass.raw}\` did not resolve to any known palette token.`,
8181
8654
  location: klass.location,
@@ -8203,12 +8676,12 @@ function ruleTailwindUnknownClass(ix) {
8203
8676
  }
8204
8677
 
8205
8678
  // src/rules/tokens-require-dual-fallback.ts
8206
- var RULE_ID20 = "tokens/require-dual-fallback";
8207
- var RULE_VERSION21 = "1";
8679
+ var RULE_ID21 = "tokens/require-dual-fallback";
8680
+ var RULE_VERSION22 = "1";
8208
8681
  var CSS_VAR_WITH_OPTIONAL_FALLBACK = /var\(\s*(--[A-Za-z0-9_-]+)(\s*,\s*[^)]*)?\)/g;
8209
8682
  var SASS_FILE = /\.(scss|sass)$/i;
8210
8683
  function ruleTokensRequireDualFallback(ix) {
8211
- const policy = ix.policy.ruleConfig(RULE_ID20);
8684
+ const policy = ix.policy.ruleConfig(RULE_ID21);
8212
8685
  if (!policy?.enabled) return [];
8213
8686
  const tokensByCssVariable = indexTokensByCssVariable(localTokenCandidates(ix));
8214
8687
  const findings = [];
@@ -8243,8 +8716,8 @@ function ruleTokensRequireDualFallback(ix) {
8243
8716
  ) : void 0;
8244
8717
  findings.push(
8245
8718
  makeFinding({
8246
- ruleId: RULE_ID20,
8247
- ruleVersion: RULE_VERSION21,
8719
+ ruleId: RULE_ID21,
8720
+ ruleVersion: RULE_VERSION22,
8248
8721
  severity: policy.severity ?? "warn",
8249
8722
  message: hasScssVariable ? `var(${tokenName}) is missing an SCSS fallback. Use var(${tokenName}, ${scssVar}).` : `var(${tokenName}) is missing a fallback. No matching SCSS variable was found in the token source; add a fallback manually or define ${scssVar}.`,
8250
8723
  location: decl.location,
@@ -8284,8 +8757,8 @@ function indexTokensByCssVariable(tokens) {
8284
8757
  }
8285
8758
 
8286
8759
  // src/rules/tokens-css-vars-must-be-defined.ts
8287
- var RULE_ID21 = "tokens/css-vars-must-be-defined";
8288
- var RULE_VERSION22 = "1";
8760
+ var RULE_ID22 = "tokens/css-vars-must-be-defined";
8761
+ var RULE_VERSION23 = "1";
8289
8762
  var CSS_VAR_REFERENCE = /var\(\s*(--[A-Za-z0-9_-]+)/gi;
8290
8763
  function ruleTokensCssVarsMustBeDefined(ix) {
8291
8764
  const policy = ix.policy.cssVarsMustBeDefined();
@@ -8312,8 +8785,8 @@ function ruleTokensCssVarsMustBeDefined(ix) {
8312
8785
  seen.add(tokenName);
8313
8786
  findings.push(
8314
8787
  makeFinding({
8315
- ruleId: RULE_ID21,
8316
- ruleVersion: RULE_VERSION22,
8788
+ ruleId: RULE_ID22,
8789
+ ruleVersion: RULE_VERSION23,
8317
8790
  severity: policy.severity,
8318
8791
  message: `var(${tokenName}) is not in the contract token vocabulary. Use a token defined in your contract's token source files.`,
8319
8792
  location: decl.location,
@@ -8360,8 +8833,8 @@ function skipToCloseParen(value, openParenIndex) {
8360
8833
  }
8361
8834
 
8362
8835
  // src/rules/tokens-upstream-drift.ts
8363
- var RULE_ID22 = "tokens/upstream-drift";
8364
- var RULE_VERSION23 = "1";
8836
+ var RULE_ID23 = "tokens/upstream-drift";
8837
+ var RULE_VERSION24 = "1";
8365
8838
  function ruleTokensUpstreamDrift(ix) {
8366
8839
  const local = ix.tokens.list().filter((token) => token.role !== "upstream");
8367
8840
  const upstream = ix.tokens.list().filter((token) => token.role === "upstream");
@@ -8387,8 +8860,8 @@ function ruleTokensUpstreamDrift(ix) {
8387
8860
  };
8388
8861
  findings.push(
8389
8862
  makeFinding({
8390
- ruleId: RULE_ID22,
8391
- ruleVersion: RULE_VERSION23,
8863
+ ruleId: RULE_ID23,
8864
+ ruleVersion: RULE_VERSION24,
8392
8865
  severity: "warn",
8393
8866
  message: `Local token ${token.name} is ${token.value}, but upstream ${upstreamName} is ${expected.value}.`,
8394
8867
  location,
@@ -8420,8 +8893,12 @@ var BLOCKING_RULE_ALLOWLIST = /* @__PURE__ */ new Set([
8420
8893
  // off-scale Tailwind spacing utility
8421
8894
  "components/prefer-library",
8422
8895
  // raw element bypasses a canonical component
8423
- "components/preferred-component"
8896
+ "components/shadow-component",
8897
+ // confirmed local semantic control shadows a canonical
8898
+ "components/preferred-component",
8424
8899
  // imported component bypasses its canonical
8900
+ "tokens/css-vars-must-be-defined"
8901
+ // off-contract custom property reference
8425
8902
  ]);
8426
8903
  function gatesCi(finding, failOnWarnings) {
8427
8904
  if (!canBlock(finding.evidenceGrade ?? "source_backed")) return false;
@@ -8432,9 +8909,13 @@ function gatesCi(finding, failOnWarnings) {
8432
8909
  function isDenyEligible(finding, failOnWarnings) {
8433
8910
  if (!BLOCKING_RULE_ALLOWLIST.has(finding.ruleId)) return false;
8434
8911
  if (finding.attributes?.advisory === true) return false;
8912
+ if (finding.ruleId === "components/shadow-component" && (finding.attributes?.state !== "shadow" || finding.attributes?.confidence !== "confirmed" || typeof finding.attributes?.canonicalTarget !== "string")) {
8913
+ return false;
8914
+ }
8435
8915
  if (!gatesCi(finding, failOnWarnings)) return false;
8436
8916
  if (finding.fix?.deterministic === false) {
8437
- if (finding.ruleId !== "components/prefer-library" || finding.attributes?.propCompatibility !== "observed-complete") {
8917
+ const exactCanonicalBypass = finding.ruleId === "components/prefer-library" && finding.attributes?.precisionTier === "exact-html" && typeof finding.attributes?.suggestedComponent === "string";
8918
+ if (!exactCanonicalBypass) {
8438
8919
  return false;
8439
8920
  }
8440
8921
  }
@@ -8471,6 +8952,7 @@ var RULE_TIER = {
8471
8952
  "tokens/require-dual-fallback": "hygiene",
8472
8953
  "theme/no-theme-coupled-literal": "hygiene",
8473
8954
  "a11y/required-accessible-name": "hygiene",
8955
+ "a11y/standard": "hygiene",
8474
8956
  // Composition constraints are authored contracts, but not part of the
8475
8957
  // two-choice headline contract; they self-activate when patterns are declared
8476
8958
  // (the compiler injects their configs), so off-by-default here is harmless.
@@ -8632,9 +9114,14 @@ var RULES = [
8632
9114
  },
8633
9115
  {
8634
9116
  id: "a11y/required-accessible-name",
8635
- version: "1",
9117
+ version: RULE_VERSION,
8636
9118
  run: ruleA11yRequiredAccessibleName
8637
9119
  },
9120
+ {
9121
+ id: "a11y/standard",
9122
+ version: "1",
9123
+ run: ruleA11yStandard
9124
+ },
8638
9125
  {
8639
9126
  id: "composition/cardinality",
8640
9127
  version: "1",
@@ -8673,12 +9160,21 @@ function hasEffectiveComponentVocabulary(options) {
8673
9160
  }
8674
9161
  function effectiveRuleConfigs(policy) {
8675
9162
  const configs = /* @__PURE__ */ new Map();
8676
- for (const fact of compileGlobalGovernanceFacts(policy)) {
8677
- if (fact.kind !== "governance_rule_config") continue;
8678
- configs.set(fact.ruleId, {
8679
- enabled: fact.enabled,
8680
- severity: fact.severity,
8681
- options: fact.options
9163
+ const facts = compileGlobalGovernanceFacts(policy);
9164
+ for (const fact of facts) {
9165
+ if (fact.kind === "governance_rule_config") {
9166
+ configs.set(fact.ruleId, {
9167
+ enabled: fact.enabled,
9168
+ severity: fact.severity,
9169
+ options: fact.options
9170
+ });
9171
+ }
9172
+ }
9173
+ const cssVarsPolicy = facts.find((fact) => fact.kind === "style_css_vars_must_be_defined");
9174
+ if (cssVarsPolicy?.kind === "style_css_vars_must_be_defined") {
9175
+ configs.set("tokens/css-vars-must-be-defined", {
9176
+ enabled: true,
9177
+ severity: cssVarsPolicy.severity
8682
9178
  });
8683
9179
  }
8684
9180
  return configs;
@@ -9082,23 +9578,28 @@ function evaluateGovernanceIntegrity(input) {
9082
9578
  if (!policyArmed) {
9083
9579
  policyFamily.reason = "no governance policy resolved";
9084
9580
  }
9085
- const componentsConfig = configs.get("components/prefer-library");
9581
+ const preferLibraryConfig = configs.get("components/prefer-library");
9582
+ const shadowComponentConfig = configs.get("components/shadow-component");
9086
9583
  const confirmedBridges = input.policy?.canonicalBridges ?? [];
9087
- const componentsArmed = confirmedBridges.length > 0 || componentsConfig?.enabled === true && hasEffectiveComponentVocabulary(componentsConfig.options);
9584
+ const componentRuleEnabled = preferLibraryConfig?.enabled === true || shadowComponentConfig?.enabled === true;
9585
+ const preferLibraryArmed = preferLibraryConfig?.enabled === true && hasEffectiveComponentVocabulary(preferLibraryConfig.options);
9586
+ const shadowComponentArmed = shadowComponentConfig?.enabled === true && hasEffectiveComponentVocabulary(shadowComponentConfig.options);
9587
+ const componentsArmed = confirmedBridges.length > 0 || preferLibraryArmed || shadowComponentArmed;
9088
9588
  const componentsFamily = {
9089
9589
  id: "components",
9090
9590
  armed: componentsArmed,
9091
9591
  rules: [
9092
9592
  ...confirmedBridges.length > 0 ? ["imports/preferred-path"] : [],
9093
- ...componentsConfig?.enabled === true ? ["components/prefer-library"] : []
9593
+ ...preferLibraryConfig?.enabled === true ? ["components/prefer-library"] : [],
9594
+ ...shadowComponentConfig?.enabled === true ? ["components/shadow-component"] : []
9094
9595
  ]
9095
9596
  };
9096
9597
  if (!componentsArmed) {
9097
- if (componentsConfig?.enabled === true) {
9098
- componentsFamily.reason = "components/prefer-library enabled but no effective canonical source";
9598
+ if (componentRuleEnabled) {
9599
+ componentsFamily.reason = "component governance enabled but no effective canonical source";
9099
9600
  componentsFamily.remediation = "add govern.canonicalBridges for local wrappers, govern.canonicalSources, or designSystem.path/packageName";
9100
9601
  } else {
9101
- componentsFamily.reason = "components/prefer-library not enabled";
9602
+ componentsFamily.reason = "canonical-component rules not enabled";
9102
9603
  componentsFamily.remediation = "add govern.canonicalBridges for local wrappers, govern.canonicalSources, or designSystem.path/packageName";
9103
9604
  }
9104
9605
  }
@@ -9141,7 +9642,7 @@ function evaluateGovernanceIntegrity(input) {
9141
9642
  for (const ruleId of BLOCKING_RULE_ALLOWLIST) {
9142
9643
  const config = configs.get(ruleId);
9143
9644
  if (config?.enabled !== true) continue;
9144
- const familyArmed = ruleId === "components/prefer-library" ? componentsArmed : hygieneArmed;
9645
+ const familyArmed = ruleId === "components/prefer-library" || ruleId === "components/shadow-component" ? componentsArmed : ruleId === "tokens/css-vars-must-be-defined" ? tokensArmed : hygieneArmed;
9145
9646
  if (!familyArmed) continue;
9146
9647
  if (config.severity !== "error" && !failOnWarnings) continue;
9147
9648
  blockingRules.push(ruleId);
@@ -9772,6 +10273,7 @@ export {
9772
10273
  CONTRACT_DOMAINS,
9773
10274
  CONTRACT_PREIMAGE_CAPABILITY_HEADER,
9774
10275
  CONTRACT_PREIMAGE_SCHEMA,
10276
+ CompiledFragmentsFileValidationError,
9775
10277
  DEFAULTS,
9776
10278
  DEFAULT_ENHANCED_STYLE_PROPERTIES,
9777
10279
  DEFAULT_STYLE_PROPERTIES,
@@ -10010,6 +10512,7 @@ export {
10010
10512
  makeTailwindUnknownClassEnabledFact,
10011
10513
  makeThemeDeclarationFact,
10012
10514
  makeTokenDefinitionFact,
10515
+ makeUsageChildContentFact,
10013
10516
  makeUsageComponentFact,
10014
10517
  makeUsageImportFact,
10015
10518
  makeUsageInlineStyleFact,
@@ -10058,6 +10561,7 @@ export {
10058
10561
  ownedImportEquivalents,
10059
10562
  parseColor,
10060
10563
  parseColorToRgb,
10564
+ parseCompiledFragmentsFile,
10061
10565
  parseComponentContract,
10062
10566
  parseContractStamp,
10063
10567
  parseCssTokens,
@@ -10115,6 +10619,7 @@ export {
10115
10619
  resolveTokenValue,
10116
10620
  rgbToHex,
10117
10621
  ruleA11yRequiredAccessibleName,
10622
+ ruleA11yStandard,
10118
10623
  ruleComponentsForbiddenPropValue,
10119
10624
  ruleComponentsPreferLibrary,
10120
10625
  ruleComponentsShadowComponent,