@usefragments/core 1.5.2 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-RANPUC6C.js +72 -0
- package/dist/chunk-RANPUC6C.js.map +1 -0
- package/dist/{chunk-BAHCOAVG.js → chunk-WNMWKUYG.js} +447 -201
- package/dist/chunk-WNMWKUYG.js.map +1 -0
- package/dist/codes/index.d.ts +2 -2
- package/dist/codes/index.js +1 -1
- package/dist/compiled-types/index.d.ts +1 -1
- package/dist/compiled-types/index.js +8 -0
- package/dist/generate/index.d.ts +1 -1
- package/dist/{governance-pKrfh517.d.ts → governance-D9KtH-vg.d.ts} +617 -22
- package/dist/index.d.ts +800 -179
- package/dist/index.js +1003 -237
- package/dist/index.js.map +1 -1
- package/dist/react-types.d.ts +1 -1
- package/dist/registry.d.ts +36 -36
- package/dist/schemas/index.d.ts +1 -1
- package/dist/test-utils.d.ts +1 -1
- package/package.json +2 -1
- package/src/__tests__/policy-exclude.test.ts +180 -0
- package/src/agent-format.test.ts +13 -0
- package/src/agent-format.ts +9 -3
- package/src/canonical-bridge.ts +69 -1
- package/src/canonical-direction.test.ts +118 -0
- package/src/canonical-direction.ts +43 -2
- package/src/codes/__tests__/codes.test.ts +14 -2
- package/src/codes/codes.ts +41 -2
- package/src/compiled-types/index.ts +81 -0
- package/src/compiled-types/parse.test.ts +47 -0
- package/src/facts/builders.ts +48 -0
- package/src/facts/compile.ts +148 -34
- package/src/facts/fact-index.ts +15 -2
- package/src/facts/facts.test.ts +44 -3
- package/src/facts/index.ts +11 -6
- package/src/facts/types.ts +60 -9
- package/src/governance-integrity.test.ts +272 -4
- package/src/governance-integrity.ts +345 -25
- package/src/governance.ts +87 -1
- package/src/index.ts +46 -1
- package/src/policy-exclude.ts +113 -0
- package/src/rules/a11y-required-accessible-name.ts +175 -28
- package/src/rules/a11y-standard.ts +102 -0
- package/src/rules/a11y-utils.ts +7 -0
- package/src/rules/components-prefer-library.test.ts +75 -28
- package/src/rules/components-prefer-library.ts +35 -15
- package/src/rules/components-shadow-component.test.ts +21 -9
- package/src/rules/emit-gate.test.ts +74 -4
- package/src/rules/emit-gate.ts +24 -9
- package/src/rules/families.test.ts +69 -0
- package/src/rules/families.ts +52 -0
- package/src/rules/fix-availability.ts +1 -0
- package/src/rules/index.ts +18 -2
- package/src/rules/jsx-preferred-import-path.ts +29 -11
- package/src/rules/rules.test.ts +188 -8
- package/src/rules/styles-no-raw-color.ts +13 -4
- package/src/rules/styles-no-raw-dimensions.ts +13 -4
- package/src/rules/styles-no-raw-spacing.ts +12 -4
- package/src/rules/styles-no-raw-typography.ts +13 -4
- package/src/rules/tiers.ts +1 -0
- package/src/rules/utils.ts +39 -0
- package/src/tokens/design-token-parser.test.ts +131 -0
- package/src/tokens/design-token-parser.ts +362 -49
- package/src/types.ts +2 -2
- package/dist/chunk-BAHCOAVG.js.map +0 -1
- package/dist/{index-DbkPE46t.d.ts → index-hZAlYCli.d.ts} +8 -8
package/dist/index.js
CHANGED
|
@@ -44,6 +44,8 @@ import {
|
|
|
44
44
|
CODES,
|
|
45
45
|
EXPLAIN_URL_BASE,
|
|
46
46
|
FactIndex,
|
|
47
|
+
RULE_FAMILY_IDS,
|
|
48
|
+
RULE_FAMILY_MEMBERS,
|
|
47
49
|
asComponentId,
|
|
48
50
|
bridgeSourceViolation,
|
|
49
51
|
bridgeSourceViolations,
|
|
@@ -56,6 +58,8 @@ import {
|
|
|
56
58
|
componentGovernanceRecordSchema,
|
|
57
59
|
componentGovernanceRecordsSchema,
|
|
58
60
|
componentId,
|
|
61
|
+
describePolicyExclude,
|
|
62
|
+
excludeGlobToRegExp,
|
|
59
63
|
explainUrlForCode,
|
|
60
64
|
factId,
|
|
61
65
|
g,
|
|
@@ -65,6 +69,8 @@ import {
|
|
|
65
69
|
governanceConfigSchema,
|
|
66
70
|
governanceSeveritySchema,
|
|
67
71
|
hash64Hex,
|
|
72
|
+
isPresetSourcedRule,
|
|
73
|
+
isRuleFamilyId,
|
|
68
74
|
makeA11yNameRequiredFact,
|
|
69
75
|
makeCanonicalCandidateFact,
|
|
70
76
|
makeCanonicalMappingFact,
|
|
@@ -99,20 +105,29 @@ import {
|
|
|
99
105
|
makeTailwindUnknownClassEnabledFact,
|
|
100
106
|
makeThemeDeclarationFact,
|
|
101
107
|
makeTokenDefinitionFact,
|
|
108
|
+
makeUsageChildContentFact,
|
|
102
109
|
makeUsageComponentFact,
|
|
103
110
|
makeUsageImportFact,
|
|
104
111
|
makeUsageInlineStyleFact,
|
|
105
112
|
makeUsageNodeFact,
|
|
106
113
|
makeUsagePropResolvedFact,
|
|
107
114
|
makeUsageTextChildFact,
|
|
115
|
+
markPresetSourcedRules,
|
|
116
|
+
matchPolicyExclude,
|
|
108
117
|
matchesGlob,
|
|
118
|
+
normalizeExcludePath,
|
|
109
119
|
normalizeGovernanceConfig,
|
|
120
|
+
normalizePolicyExcludes,
|
|
110
121
|
ownedComponentIdsEqual,
|
|
111
122
|
ownedImportMatchesRoot,
|
|
112
123
|
ownedImportsEqual,
|
|
124
|
+
policyExcludeMatchesPath,
|
|
125
|
+
policyExcludeSchema,
|
|
126
|
+
projectSupersededImportPathPreferences,
|
|
113
127
|
resolveComponentGovernance,
|
|
128
|
+
ruleFamilyMembers,
|
|
114
129
|
scaleGovernanceRecordSchema
|
|
115
|
-
} from "./chunk-
|
|
130
|
+
} from "./chunk-WNMWKUYG.js";
|
|
116
131
|
import {
|
|
117
132
|
AGENT_FORMAT_SCHEMA_VERSION,
|
|
118
133
|
agentErrorEnvelopeSchema,
|
|
@@ -167,6 +182,10 @@ import {
|
|
|
167
182
|
severitySchema,
|
|
168
183
|
sortBySeverity
|
|
169
184
|
} from "./chunk-V4VQB57N.js";
|
|
185
|
+
import {
|
|
186
|
+
CompiledFragmentsFileValidationError,
|
|
187
|
+
parseCompiledFragmentsFile
|
|
188
|
+
} from "./chunk-RANPUC6C.js";
|
|
170
189
|
|
|
171
190
|
// src/constants.ts
|
|
172
191
|
var BRAND = {
|
|
@@ -406,6 +425,7 @@ function buildAgentFormat(input) {
|
|
|
406
425
|
const zeroRulesLoaded = input.integrity.rulesLoaded === 0;
|
|
407
426
|
const integrityFailed = !input.integrity.healthy || input.integrity.inert;
|
|
408
427
|
const score = integrityFailed ? 0 : scoreFindings(input.findings);
|
|
428
|
+
const filesAffected = new Set(input.findings.map((finding) => finding.location.file)).size;
|
|
409
429
|
const visibleFindings = uniqueFindings(input.findings).slice(0, input.maxFindings ?? 50);
|
|
410
430
|
const agentFindings = visibleFindings.map((finding) => ({
|
|
411
431
|
code: finding.code,
|
|
@@ -423,7 +443,7 @@ function buildAgentFormat(input) {
|
|
|
423
443
|
const hasErrors = input.findings.some((finding) => severityLevel(finding.severity) === "error");
|
|
424
444
|
const shared = {
|
|
425
445
|
schemaVersion: AGENT_FORMAT_SCHEMA_VERSION,
|
|
426
|
-
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),
|
|
427
447
|
integrity: input.integrity,
|
|
428
448
|
fix_first: agentFindings.filter((finding) => finding.plan.confidence >= 0.8),
|
|
429
449
|
remaining: agentFindings.filter((finding) => finding.plan.confidence < 0.8),
|
|
@@ -484,9 +504,9 @@ function scoreFindings(findings) {
|
|
|
484
504
|
}
|
|
485
505
|
return Math.max(1, Math.round(100 / (1 + penalty / 40)));
|
|
486
506
|
}
|
|
487
|
-
function agentSummary(count, filesScanned, score) {
|
|
507
|
+
function agentSummary(count, filesAffected, filesScanned, score) {
|
|
488
508
|
if (count === 0) return `passed with score ${score}; no findings`;
|
|
489
|
-
return `${count} finding${count === 1 ? "" : "s"}
|
|
509
|
+
return `${count} finding${count === 1 ? "" : "s"} in ${filesAffected} file${filesAffected === 1 ? "" : "s"}; ${filesScanned} scanned; score ${score}`;
|
|
490
510
|
}
|
|
491
511
|
function planForFinding(finding) {
|
|
492
512
|
if (finding.fix?.deterministic === true) {
|
|
@@ -3288,7 +3308,7 @@ function buildToken(name, rawValue, lineNumber, source) {
|
|
|
3288
3308
|
}
|
|
3289
3309
|
|
|
3290
3310
|
// src/tokens/design-token-parser.ts
|
|
3291
|
-
var
|
|
3311
|
+
var CSS_CUSTOM_PROPERTY_NAME_CHARACTER = /[a-zA-Z0-9_-]/u;
|
|
3292
3312
|
var VAR_REFERENCE_PATTERN = /var\(\s*--([a-zA-Z0-9_-]+)(?:\s*,\s*([^)]+))?\s*\)/;
|
|
3293
3313
|
function parseDesignTokenContent(content, options = {}) {
|
|
3294
3314
|
const startTime = performance.now();
|
|
@@ -3340,19 +3360,48 @@ function parseDesignTokenContent(content, options = {}) {
|
|
|
3340
3360
|
errors.push({ message: err, file: filePath });
|
|
3341
3361
|
}
|
|
3342
3362
|
}
|
|
3343
|
-
const
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
const fullName =
|
|
3347
|
-
const lineNumber =
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
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
|
|
3351
3376
|
});
|
|
3352
3377
|
}
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
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;
|
|
3356
3405
|
const { resolvedValue, chain, hasCircular, unresolvedRef } = resolveDesignTokenValue(
|
|
3357
3406
|
rawValue,
|
|
3358
3407
|
tokensByName
|
|
@@ -3374,7 +3423,7 @@ function parseDesignTokenContent(content, options = {}) {
|
|
|
3374
3423
|
lineNumber: line,
|
|
3375
3424
|
theme,
|
|
3376
3425
|
selector,
|
|
3377
|
-
description: extractDescription(
|
|
3426
|
+
description: extractDescription(contentLines, line ?? 1)
|
|
3378
3427
|
});
|
|
3379
3428
|
}
|
|
3380
3429
|
if (tokens.length === 0 && trimmed.length > 0) {
|
|
@@ -3389,13 +3438,6 @@ function parseDesignTokenContent(content, options = {}) {
|
|
|
3389
3438
|
parseTimeMs: performance.now() - startTime
|
|
3390
3439
|
};
|
|
3391
3440
|
}
|
|
3392
|
-
function countNewlines(content, index) {
|
|
3393
|
-
let count = 0;
|
|
3394
|
-
for (let i = 0; i < index && i < content.length; i++) {
|
|
3395
|
-
if (content.charCodeAt(i) === 10) count++;
|
|
3396
|
-
}
|
|
3397
|
-
return count;
|
|
3398
|
-
}
|
|
3399
3441
|
function parsedOutputToDesignTokens(output, filePath) {
|
|
3400
3442
|
const tokens = [];
|
|
3401
3443
|
for (const cat of Object.values(output.categories)) {
|
|
@@ -3550,29 +3592,284 @@ function inferTokenLevel(name, rawValue, referenceChain) {
|
|
|
3550
3592
|
}
|
|
3551
3593
|
return 2;
|
|
3552
3594
|
}
|
|
3553
|
-
function
|
|
3554
|
-
const
|
|
3595
|
+
function scanCssCustomPropertyDeclarations(content) {
|
|
3596
|
+
const declarations = [];
|
|
3597
|
+
const parentSelectors = [];
|
|
3555
3598
|
let currentSelector = ":root";
|
|
3556
|
-
let
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
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++;
|
|
3564
3615
|
}
|
|
3616
|
+
continue;
|
|
3565
3617
|
}
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
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;
|
|
3570
3701
|
}
|
|
3571
3702
|
}
|
|
3572
|
-
return
|
|
3703
|
+
return declarations;
|
|
3573
3704
|
}
|
|
3574
|
-
function
|
|
3575
|
-
|
|
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++;
|
|
3809
|
+
}
|
|
3810
|
+
continue;
|
|
3811
|
+
}
|
|
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;
|
|
3827
|
+
}
|
|
3828
|
+
output += character;
|
|
3829
|
+
if (character === "'" || character === '"') quote = character;
|
|
3830
|
+
}
|
|
3831
|
+
return output;
|
|
3832
|
+
}
|
|
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) {
|
|
3576
3873
|
if (line <= 1) return void 0;
|
|
3577
3874
|
const prevLine = lines[line - 2]?.trim();
|
|
3578
3875
|
const singleLineMatch = prevLine?.match(/\/\/\s*(.+)$/);
|
|
@@ -4368,6 +4665,13 @@ function discoverComponents(moduleExports, options) {
|
|
|
4368
4665
|
return names.sort();
|
|
4369
4666
|
}
|
|
4370
4667
|
|
|
4668
|
+
// src/rules/a11y-utils.ts
|
|
4669
|
+
function isAriaHidden(props) {
|
|
4670
|
+
const prop = props.find((candidate) => candidate.prop === "aria-hidden");
|
|
4671
|
+
if (!prop || prop.resolution !== "static") return false;
|
|
4672
|
+
return prop.value === true || prop.value === "true";
|
|
4673
|
+
}
|
|
4674
|
+
|
|
4371
4675
|
// src/rules/finding.ts
|
|
4372
4676
|
function makeFinding(input) {
|
|
4373
4677
|
if (input.evidence.length === 0) {
|
|
@@ -4496,6 +4800,19 @@ function readUsageNode(ix, id) {
|
|
|
4496
4800
|
const fact = ix.get(id);
|
|
4497
4801
|
return fact && fact.kind === "usage_node" ? fact : void 0;
|
|
4498
4802
|
}
|
|
4803
|
+
function hoppedValue(fact) {
|
|
4804
|
+
const from = fact.valueFrom;
|
|
4805
|
+
if (!from) return void 0;
|
|
4806
|
+
const declaredAt = `${from.location.file}:${from.location.line}`;
|
|
4807
|
+
return {
|
|
4808
|
+
suffix: ` Value comes from \`${from.name}\`, declared at ${declaredAt}.`,
|
|
4809
|
+
attributes: {
|
|
4810
|
+
valueFrom: "const-binding",
|
|
4811
|
+
declaredAs: from.name,
|
|
4812
|
+
declaredAt
|
|
4813
|
+
}
|
|
4814
|
+
};
|
|
4815
|
+
}
|
|
4499
4816
|
function contractPrefixFamilies(names) {
|
|
4500
4817
|
const families = /* @__PURE__ */ new Set();
|
|
4501
4818
|
for (const name of names) {
|
|
@@ -4813,36 +5130,71 @@ function isApplicableTokenReference(referenceFormat) {
|
|
|
4813
5130
|
|
|
4814
5131
|
// src/rules/a11y-required-accessible-name.ts
|
|
4815
5132
|
var RULE_ID = "a11y/required-accessible-name";
|
|
4816
|
-
var RULE_VERSION = "
|
|
5133
|
+
var RULE_VERSION = "2";
|
|
4817
5134
|
var NAME_SOURCE_PROPS = /* @__PURE__ */ new Set(["aria-label", "aria-labelledby", "title"]);
|
|
5135
|
+
var COMPONENT_NAME_SOURCE_PROPS = /* @__PURE__ */ new Set([...NAME_SOURCE_PROPS, "label"]);
|
|
5136
|
+
var NAME_REQUIRED_TAGS = /* @__PURE__ */ new Set(["button", "input", "select", "textarea"]);
|
|
5137
|
+
var NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["button", "checkbox", "radio", "switch", "tab"]);
|
|
5138
|
+
var NAME_FROM_CONTENT_TAGS = /* @__PURE__ */ new Set(["button"]);
|
|
5139
|
+
var NAME_FROM_CONTENT_ROLES = /* @__PURE__ */ new Set(["button", "checkbox", "radio", "switch", "tab"]);
|
|
5140
|
+
var NAME_REQUIRED_COMPONENT_SUFFIXES = [
|
|
5141
|
+
"button",
|
|
5142
|
+
"input",
|
|
5143
|
+
"select",
|
|
5144
|
+
"textarea",
|
|
5145
|
+
"checkbox",
|
|
5146
|
+
"radio",
|
|
5147
|
+
"switch"
|
|
5148
|
+
];
|
|
4818
5149
|
function ruleA11yRequiredAccessibleName(ix) {
|
|
4819
5150
|
const findings = [];
|
|
4820
5151
|
const propsByNode = indexPropsByNodeId(ix);
|
|
4821
5152
|
const textByNode = indexTextChildrenByNodeId(ix);
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
5153
|
+
const childContentByNode = new Map(
|
|
5154
|
+
ix.byKind("usage_child_content").map((content) => [content.nodeId, content])
|
|
5155
|
+
);
|
|
5156
|
+
const globalPolicy = ix.policy.ruleConfig(RULE_ID);
|
|
5157
|
+
const usageByNode = new Map(ix.byKind("usage_component").map((usage) => [usage.nodeId, usage]));
|
|
5158
|
+
const definitions = new Map(
|
|
5159
|
+
ix.byKind("component_definition").map((definition) => [definition.componentKey, definition])
|
|
5160
|
+
);
|
|
5161
|
+
for (const node of ix.byKind("usage_node")) {
|
|
5162
|
+
const usageComponent = usageByNode.get(node.id);
|
|
5163
|
+
const componentPolicy = usageComponent ? ix.policy.a11yNameRequired(usageComponent.componentId) : void 0;
|
|
4827
5164
|
const props = propsByNode.get(node.id) ?? [];
|
|
4828
|
-
if (props
|
|
4829
|
-
|
|
5165
|
+
if (isAriaHidden(props)) continue;
|
|
5166
|
+
const genericControl = globalPolicy?.enabled === true && requiresAccessibleName(node, usageComponent, definitions, props);
|
|
5167
|
+
if (!componentPolicy && !genericControl) continue;
|
|
5168
|
+
if (hasAccessibleName(
|
|
5169
|
+
props,
|
|
5170
|
+
textByNode.get(node.id) ?? [],
|
|
5171
|
+
childContentByNode.get(node.id),
|
|
5172
|
+
usageComponent !== void 0,
|
|
5173
|
+
canNameFromContent(node, usageComponent, definitions)
|
|
5174
|
+
)) {
|
|
5175
|
+
continue;
|
|
5176
|
+
}
|
|
4830
5177
|
findings.push(
|
|
4831
5178
|
makeFinding({
|
|
4832
5179
|
ruleId: RULE_ID,
|
|
4833
5180
|
ruleVersion: RULE_VERSION,
|
|
4834
|
-
severity:
|
|
4835
|
-
message: `<${node.element}> has no accessible name. ${
|
|
5181
|
+
severity: componentPolicy?.severity ?? globalPolicy?.severity ?? "warn",
|
|
5182
|
+
message: `<${node.element}> has no accessible name. ${componentPolicy?.because ?? "Add visible text, aria-label, aria-labelledby, or a component label."}`.trim(),
|
|
4836
5183
|
location: node.location,
|
|
4837
|
-
evidence: ix.evidence([
|
|
5184
|
+
evidence: ix.evidence([
|
|
5185
|
+
node.id,
|
|
5186
|
+
...usageComponent ? [usageComponent.id] : [],
|
|
5187
|
+
...componentPolicy ? [componentPolicy.id] : [],
|
|
5188
|
+
...genericControl && globalPolicy ? [globalPolicy.id] : []
|
|
5189
|
+
]),
|
|
4838
5190
|
fingerprintIdentity: {
|
|
4839
5191
|
file: node.file,
|
|
4840
|
-
componentId: usageComponent
|
|
5192
|
+
componentId: usageComponent?.componentId,
|
|
4841
5193
|
element: node.element,
|
|
4842
5194
|
nodePath: node.nodePath
|
|
4843
5195
|
},
|
|
4844
5196
|
attributes: {
|
|
4845
|
-
componentId: usageComponent.componentId,
|
|
5197
|
+
...usageComponent ? { componentId: usageComponent.componentId } : {},
|
|
4846
5198
|
element: node.element
|
|
4847
5199
|
}
|
|
4848
5200
|
})
|
|
@@ -4850,12 +5202,18 @@ function ruleA11yRequiredAccessibleName(ix) {
|
|
|
4850
5202
|
}
|
|
4851
5203
|
return findings;
|
|
4852
5204
|
}
|
|
4853
|
-
function hasAccessibleName(props, textChildren) {
|
|
4854
|
-
|
|
4855
|
-
|
|
5205
|
+
function hasAccessibleName(props, textChildren, childContent, componentControl, nameFromContent) {
|
|
5206
|
+
const sourceProps = componentControl ? COMPONENT_NAME_SOURCE_PROPS : NAME_SOURCE_PROPS;
|
|
5207
|
+
if (nameFromContent) {
|
|
5208
|
+
for (const child of textChildren) {
|
|
5209
|
+
if (typeof child.text === "string" && child.text.trim().length > 0) return true;
|
|
5210
|
+
}
|
|
5211
|
+
if (childContent?.content === "static-text" || childContent?.content === "runtime") {
|
|
5212
|
+
return true;
|
|
5213
|
+
}
|
|
4856
5214
|
}
|
|
4857
5215
|
for (const prop of props) {
|
|
4858
|
-
if (!
|
|
5216
|
+
if (!sourceProps.has(prop.prop)) continue;
|
|
4859
5217
|
if (prop.resolution === "spread") continue;
|
|
4860
5218
|
if (prop.resolution === "dynamic") return true;
|
|
4861
5219
|
if (prop.resolution === "static") {
|
|
@@ -4865,10 +5223,137 @@ function hasAccessibleName(props, textChildren) {
|
|
|
4865
5223
|
}
|
|
4866
5224
|
return false;
|
|
4867
5225
|
}
|
|
4868
|
-
function
|
|
4869
|
-
|
|
4870
|
-
if (
|
|
4871
|
-
|
|
5226
|
+
function canNameFromContent(node, usage, definitions) {
|
|
5227
|
+
const element = node.element.toLowerCase();
|
|
5228
|
+
if (NAME_REQUIRED_TAGS.has(element)) return NAME_FROM_CONTENT_TAGS.has(element);
|
|
5229
|
+
if (node.role && NAME_FROM_CONTENT_ROLES.has(node.role.toLowerCase())) return true;
|
|
5230
|
+
if (!usage) return false;
|
|
5231
|
+
const definition = definitions.get(String(usage.componentId));
|
|
5232
|
+
if (definition) {
|
|
5233
|
+
const root = definition.renderRoot;
|
|
5234
|
+
if (root.resolution === "intrinsic") {
|
|
5235
|
+
return intrinsicCanNameFromContent(root.tag, root.role);
|
|
5236
|
+
}
|
|
5237
|
+
if (root.resolution === "canonical") return isContentNamedControl(root.canonical);
|
|
5238
|
+
if (root.resolution === "mixed") {
|
|
5239
|
+
return root.intrinsics.some((tag) => intrinsicCanNameFromContent(tag)) || root.canonicals.some(isContentNamedControl);
|
|
5240
|
+
}
|
|
5241
|
+
}
|
|
5242
|
+
return isContentNamedControl(String(usage.componentId));
|
|
5243
|
+
}
|
|
5244
|
+
function intrinsicCanNameFromContent(tag, role) {
|
|
5245
|
+
const normalizedTag = tag.toLowerCase();
|
|
5246
|
+
if (NAME_REQUIRED_TAGS.has(normalizedTag)) {
|
|
5247
|
+
return NAME_FROM_CONTENT_TAGS.has(normalizedTag);
|
|
5248
|
+
}
|
|
5249
|
+
return role !== void 0 && NAME_FROM_CONTENT_ROLES.has(role.toLowerCase());
|
|
5250
|
+
}
|
|
5251
|
+
function requiresAccessibleName(node, usage, definitions, props) {
|
|
5252
|
+
if (node.role && NAME_REQUIRED_ROLES.has(node.role.toLowerCase())) return true;
|
|
5253
|
+
if (NAME_REQUIRED_TAGS.has(node.element.toLowerCase())) {
|
|
5254
|
+
if (node.element.toLowerCase() === "input" && props.some(
|
|
5255
|
+
(prop) => prop.prop === "type" && prop.resolution === "static" && String(prop.value).toLowerCase() === "hidden"
|
|
5256
|
+
)) {
|
|
5257
|
+
return false;
|
|
5258
|
+
}
|
|
5259
|
+
return true;
|
|
5260
|
+
}
|
|
5261
|
+
if (!usage) return false;
|
|
5262
|
+
const definition = definitions.get(String(usage.componentId));
|
|
5263
|
+
if (definition) {
|
|
5264
|
+
const root = definition.renderRoot;
|
|
5265
|
+
if (root.resolution === "intrinsic") {
|
|
5266
|
+
return NAME_REQUIRED_TAGS.has(root.tag.toLowerCase()) || root.role !== void 0 && NAME_REQUIRED_ROLES.has(root.role.toLowerCase());
|
|
5267
|
+
}
|
|
5268
|
+
if (root.resolution === "canonical") return isNamedControl(root.canonical);
|
|
5269
|
+
if (root.resolution === "mixed") {
|
|
5270
|
+
return root.intrinsics.some((tag) => NAME_REQUIRED_TAGS.has(tag.toLowerCase())) || root.canonicals.some(isNamedControl);
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
5273
|
+
return isNamedControl(String(usage.componentId));
|
|
5274
|
+
}
|
|
5275
|
+
function isContentNamedControl(identity) {
|
|
5276
|
+
return normalizedControlName(identity).endsWith("button");
|
|
5277
|
+
}
|
|
5278
|
+
function isNamedControl(identity) {
|
|
5279
|
+
const normalized = normalizedControlName(identity);
|
|
5280
|
+
return NAME_REQUIRED_COMPONENT_SUFFIXES.some((suffix) => normalized.endsWith(suffix));
|
|
5281
|
+
}
|
|
5282
|
+
function normalizedControlName(identity) {
|
|
5283
|
+
const exported = identity.split("#").at(-1)?.split(".").at(-1) ?? identity;
|
|
5284
|
+
return exported.replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
5285
|
+
}
|
|
5286
|
+
|
|
5287
|
+
// src/rules/a11y-standard.ts
|
|
5288
|
+
var RULE_ID2 = "a11y/standard";
|
|
5289
|
+
var RULE_VERSION2 = "1";
|
|
5290
|
+
var KEY_HANDLERS = /* @__PURE__ */ new Set(["onKeyDown", "onKeyUp", "onKeyPress"]);
|
|
5291
|
+
function ruleA11yStandard(ix) {
|
|
5292
|
+
const policy = ix.policy.ruleConfig(RULE_ID2);
|
|
5293
|
+
if (!policy?.enabled) return [];
|
|
5294
|
+
const propsByNode = indexPropsByNodeId(ix);
|
|
5295
|
+
const findings = [];
|
|
5296
|
+
for (const node of ix.byKind("usage_node")) {
|
|
5297
|
+
if (node.role?.trim().toLowerCase() !== "button") continue;
|
|
5298
|
+
const props = propsByNode.get(node.id) ?? [];
|
|
5299
|
+
if (isAriaHidden(props) || isNativeButton(node.element, props)) continue;
|
|
5300
|
+
const missing = [
|
|
5301
|
+
...hasFocusableTabIndex(props) ? [] : ["tabIndex"],
|
|
5302
|
+
...hasKeyHandler(props) ? [] : ["keyboard handler"]
|
|
5303
|
+
];
|
|
5304
|
+
if (missing.length === 0) continue;
|
|
5305
|
+
findings.push(
|
|
5306
|
+
makeFinding({
|
|
5307
|
+
ruleId: RULE_ID2,
|
|
5308
|
+
ruleVersion: RULE_VERSION2,
|
|
5309
|
+
severity: policy.severity ?? "warn",
|
|
5310
|
+
message: `<${node.element} role="button"> is missing ${missing.join(
|
|
5311
|
+
" and "
|
|
5312
|
+
)}. Non-native buttons need tabIndex plus keyboard activation handling.`,
|
|
5313
|
+
location: node.location,
|
|
5314
|
+
evidence: ix.evidence([
|
|
5315
|
+
node.id,
|
|
5316
|
+
policy.id,
|
|
5317
|
+
...props.filter(
|
|
5318
|
+
(prop) => prop.prop === "tabIndex" || prop.prop === "tabindex" || KEY_HANDLERS.has(prop.prop)
|
|
5319
|
+
).map((prop) => prop.id)
|
|
5320
|
+
]),
|
|
5321
|
+
fingerprintIdentity: {
|
|
5322
|
+
file: node.file,
|
|
5323
|
+
element: node.element,
|
|
5324
|
+
nodePath: node.nodePath,
|
|
5325
|
+
standard: "keyboard-role-button"
|
|
5326
|
+
},
|
|
5327
|
+
attributes: {
|
|
5328
|
+
standard: "keyboard-role-button",
|
|
5329
|
+
role: "button",
|
|
5330
|
+
missing
|
|
5331
|
+
}
|
|
5332
|
+
})
|
|
5333
|
+
);
|
|
5334
|
+
}
|
|
5335
|
+
return findings;
|
|
5336
|
+
}
|
|
5337
|
+
function isNativeButton(element, props) {
|
|
5338
|
+
if (element.toLowerCase() === "button") return true;
|
|
5339
|
+
if (element.toLowerCase() !== "input") return false;
|
|
5340
|
+
return props.some(
|
|
5341
|
+
(prop) => prop.prop === "type" && prop.resolution === "static" && ["button", "submit", "reset"].includes(String(prop.value).toLowerCase())
|
|
5342
|
+
);
|
|
5343
|
+
}
|
|
5344
|
+
function hasFocusableTabIndex(props) {
|
|
5345
|
+
return props.some((prop) => {
|
|
5346
|
+
if (prop.prop !== "tabIndex" && prop.prop !== "tabindex") return false;
|
|
5347
|
+
if (prop.resolution === "dynamic") return true;
|
|
5348
|
+
if (prop.resolution !== "static") return false;
|
|
5349
|
+
const value = typeof prop.value === "number" ? prop.value : Number(prop.value);
|
|
5350
|
+
return Number.isFinite(value) && value >= 0;
|
|
5351
|
+
});
|
|
5352
|
+
}
|
|
5353
|
+
function hasKeyHandler(props) {
|
|
5354
|
+
return props.some(
|
|
5355
|
+
(prop) => KEY_HANDLERS.has(prop.prop) && prop.resolution !== "spread" && prop.value !== null && prop.value !== false
|
|
5356
|
+
);
|
|
4872
5357
|
}
|
|
4873
5358
|
|
|
4874
5359
|
// src/raw-html-canonical.ts
|
|
@@ -4977,7 +5462,7 @@ function projectCanonicalDirectionConflicts(ix, canonicalSources) {
|
|
|
4977
5462
|
const conflicts = [];
|
|
4978
5463
|
for (const definition of ix.byKind("component_definition")) {
|
|
4979
5464
|
if (!definition.exported || definition.renderRoot.resolution !== "canonical") continue;
|
|
4980
|
-
const target = npmTarget(definition.renderRoot
|
|
5465
|
+
const target = npmTarget(definition.renderRoot, npmSources);
|
|
4981
5466
|
if (!target) continue;
|
|
4982
5467
|
const identity = identityByComponent.get(definition.componentKey);
|
|
4983
5468
|
if (identity?.decisionId && decisionTargetsExport(identity.canonicalTarget, target.exportName)) {
|
|
@@ -5011,7 +5496,10 @@ function canonicalDirectionConflictsTarget(conflicts, importPath, exportName) {
|
|
|
5011
5496
|
(conflict) => ownedImportMatchesRoot(importPath, conflict.packageName) && conflict.exportName === exportName
|
|
5012
5497
|
);
|
|
5013
5498
|
}
|
|
5014
|
-
function npmTarget(
|
|
5499
|
+
function npmTarget(renderRoot, sources) {
|
|
5500
|
+
return npmTargetFromCanonicalKey(renderRoot.canonical, sources) ?? npmTargetFromImportSource(renderRoot, sources);
|
|
5501
|
+
}
|
|
5502
|
+
function npmTargetFromCanonicalKey(canonicalTarget, sources) {
|
|
5015
5503
|
const separator = canonicalTarget.lastIndexOf("#");
|
|
5016
5504
|
if (separator <= 0 || separator === canonicalTarget.length - 1) return null;
|
|
5017
5505
|
const importPath = canonicalTarget.slice(0, separator);
|
|
@@ -5028,6 +5516,19 @@ function npmTarget(canonicalTarget, sources) {
|
|
|
5028
5516
|
}
|
|
5029
5517
|
return null;
|
|
5030
5518
|
}
|
|
5519
|
+
function npmTargetFromImportSource(renderRoot, sources) {
|
|
5520
|
+
const separator = renderRoot.canonical.lastIndexOf("#");
|
|
5521
|
+
if (separator <= 0 || separator === renderRoot.canonical.length - 1) return null;
|
|
5522
|
+
const exportName = renderRoot.canonical.slice(separator + 1).split(".").at(-1);
|
|
5523
|
+
if (!exportName || exportName === "default") return null;
|
|
5524
|
+
for (const source of sources) {
|
|
5525
|
+
if (!ownedImportMatchesRoot(renderRoot.importSource, source.specifier)) continue;
|
|
5526
|
+
if (source.exclude?.includes(exportName)) continue;
|
|
5527
|
+
if (source.include && !source.include.includes(exportName)) continue;
|
|
5528
|
+
return { packageName: source.specifier, exportName };
|
|
5529
|
+
}
|
|
5530
|
+
return null;
|
|
5531
|
+
}
|
|
5031
5532
|
function packageSubpath(importPath, packageName) {
|
|
5032
5533
|
return importPath === packageName ? "" : importPath.slice(packageName.length + 1);
|
|
5033
5534
|
}
|
|
@@ -5037,8 +5538,8 @@ function decisionTargetsExport(target, exportName) {
|
|
|
5037
5538
|
}
|
|
5038
5539
|
|
|
5039
5540
|
// src/rules/components-prefer-library.ts
|
|
5040
|
-
var
|
|
5041
|
-
var
|
|
5541
|
+
var RULE_ID3 = "components/prefer-library";
|
|
5542
|
+
var RULE_VERSION3 = "1";
|
|
5042
5543
|
function isExplicitTagDeclaration(mapping) {
|
|
5043
5544
|
return mapping.declaredHtmlEquivalent === true;
|
|
5044
5545
|
}
|
|
@@ -5177,7 +5678,7 @@ function findClassNameReimpl(node, classTokensByNode, targets) {
|
|
|
5177
5678
|
return null;
|
|
5178
5679
|
}
|
|
5179
5680
|
function ruleComponentsPreferLibrary(ix) {
|
|
5180
|
-
const policy = ix.policy.ruleConfig(
|
|
5681
|
+
const policy = ix.policy.ruleConfig(RULE_ID3);
|
|
5181
5682
|
if (!policy?.enabled) return [];
|
|
5182
5683
|
const configuredSources = canonicalSourcesFromPolicy(policy.options?.canonicalSources);
|
|
5183
5684
|
const conflicts = projectCanonicalDirectionConflicts(ix, configuredSources);
|
|
@@ -5192,7 +5693,7 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5192
5693
|
const classTokensByNode = indexClassTokensByNode(ix);
|
|
5193
5694
|
const reimplTargets = canonicalReimplTargets(sources, mappings);
|
|
5194
5695
|
const canonicalDirectoryResolvedNodes = indexCanonicalDirectoryResolvedNodes(ix, sources);
|
|
5195
|
-
const shadowRenderRootNodeIds = indexShadowRenderRootNodeIds(ix);
|
|
5696
|
+
const shadowRenderRootNodeIds = ix.policy.ruleConfig("components/shadow-component")?.enabled === true ? indexShadowRenderRootNodeIds(ix) : /* @__PURE__ */ new Set();
|
|
5196
5697
|
const findings = [];
|
|
5197
5698
|
const seenImportFixes = /* @__PURE__ */ new Set();
|
|
5198
5699
|
for (const node of ix.byKind("usage_node")) {
|
|
@@ -5224,8 +5725,8 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5224
5725
|
seenImportFixes.add(key);
|
|
5225
5726
|
findings.push(
|
|
5226
5727
|
makeFinding({
|
|
5227
|
-
ruleId:
|
|
5228
|
-
ruleVersion:
|
|
5728
|
+
ruleId: RULE_ID3,
|
|
5729
|
+
ruleVersion: RULE_VERSION3,
|
|
5229
5730
|
severity: capAdvisorySeverity(policy.severity),
|
|
5230
5731
|
message: `<${node.element}> may need to import from ${importPath}; verify that the local component contract is compatible first.`,
|
|
5231
5732
|
location: imported.location,
|
|
@@ -5259,8 +5760,8 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5259
5760
|
if (node.element === suggestedComponent2) continue;
|
|
5260
5761
|
findings.push(
|
|
5261
5762
|
makeFinding({
|
|
5262
|
-
ruleId:
|
|
5263
|
-
ruleVersion:
|
|
5763
|
+
ruleId: RULE_ID3,
|
|
5764
|
+
ruleVersion: RULE_VERSION3,
|
|
5264
5765
|
severity: severityForTier(policy.severity, precisionTier2),
|
|
5265
5766
|
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}.`,
|
|
5266
5767
|
location: node.location,
|
|
@@ -5311,8 +5812,8 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5311
5812
|
const suggestedImport2 = reimpl.import ?? "the canonical library";
|
|
5312
5813
|
findings.push(
|
|
5313
5814
|
makeFinding({
|
|
5314
|
-
ruleId:
|
|
5315
|
-
ruleVersion:
|
|
5815
|
+
ruleId: RULE_ID3,
|
|
5816
|
+
ruleVersion: RULE_VERSION3,
|
|
5316
5817
|
severity: capAdvisorySeverity(policy.severity),
|
|
5317
5818
|
message: `<${node.element} class="${reimpl.matchedClass}"> looks like a hand-rolled <${reimpl.canonical}>. Use <${reimpl.canonical}> from ${suggestedImport2} instead of restyling a <${node.element}>.`,
|
|
5318
5819
|
location: node.location,
|
|
@@ -5352,8 +5853,8 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5352
5853
|
seenImportFixes.add(key);
|
|
5353
5854
|
findings.push(
|
|
5354
5855
|
makeFinding({
|
|
5355
|
-
ruleId:
|
|
5356
|
-
ruleVersion:
|
|
5856
|
+
ruleId: RULE_ID3,
|
|
5857
|
+
ruleVersion: RULE_VERSION3,
|
|
5357
5858
|
severity: capAdvisorySeverity(policy.severity),
|
|
5358
5859
|
message: `<${node.element}> may need to import from ${importPath}; verify that the local component contract is compatible first.`,
|
|
5359
5860
|
location: imported.location,
|
|
@@ -5395,14 +5896,15 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5395
5896
|
continue;
|
|
5396
5897
|
}
|
|
5397
5898
|
}
|
|
5398
|
-
const
|
|
5899
|
+
const importReady = suggestion.kind !== "directory";
|
|
5900
|
+
const suggestedImport = importReady ? sourceLabel(suggestion) : "the canonical component directory (resolve its configured import alias)";
|
|
5399
5901
|
const precisionTier = builtIn?.tier ?? "exact-html";
|
|
5400
5902
|
const rawElement = formatRawElement(node, props);
|
|
5401
5903
|
findings.push(
|
|
5402
5904
|
makeFinding({
|
|
5403
|
-
ruleId:
|
|
5404
|
-
ruleVersion:
|
|
5405
|
-
severity: severityForTier(policy.severity, precisionTier),
|
|
5905
|
+
ruleId: RULE_ID3,
|
|
5906
|
+
ruleVersion: RULE_VERSION3,
|
|
5907
|
+
severity: importReady ? severityForTier(policy.severity, precisionTier) : capAdvisorySeverity(policy.severity),
|
|
5406
5908
|
message: messageForBuiltInMatch({
|
|
5407
5909
|
node,
|
|
5408
5910
|
rawElement,
|
|
@@ -5419,7 +5921,7 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5419
5921
|
suggestedComponent,
|
|
5420
5922
|
suggestedImport
|
|
5421
5923
|
},
|
|
5422
|
-
|
|
5924
|
+
...importReady && !isRawHtmlAdvisoryTier(precisionTier) && isIdentifier(node.element) && isIdentifier(suggestedComponent) ? {
|
|
5423
5925
|
fix: {
|
|
5424
5926
|
kind: "replaceComponent",
|
|
5425
5927
|
title: `Replace ${rawElement} with <${suggestedComponent}>`,
|
|
@@ -5434,13 +5936,13 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5434
5936
|
attributes: {
|
|
5435
5937
|
rawValue: node.element,
|
|
5436
5938
|
suggestedComponent,
|
|
5437
|
-
suggestedImport,
|
|
5438
5939
|
suggestedImportSourceKind: suggestion.kind,
|
|
5940
|
+
...importReady ? { suggestedImport } : { canonicalDirectory: suggestion.path, importReady: false },
|
|
5439
5941
|
precisionTier,
|
|
5440
5942
|
propCompatibility: "unknown",
|
|
5441
5943
|
...builtIn?.matchedRole ? { matchedRole: builtIn.matchedRole } : {},
|
|
5442
5944
|
...builtIn?.inputType ? { matchedInputType: builtIn.inputType } : {},
|
|
5443
|
-
...isRawHtmlAdvisoryTier(precisionTier) ? { advisory: true } : {}
|
|
5945
|
+
...isRawHtmlAdvisoryTier(precisionTier) || !importReady ? { advisory: true } : {}
|
|
5444
5946
|
}
|
|
5445
5947
|
})
|
|
5446
5948
|
);
|
|
@@ -5460,11 +5962,19 @@ function suppressConflictedExports(source, conflicts) {
|
|
|
5460
5962
|
function indexShadowRenderRootNodeIds(ix) {
|
|
5461
5963
|
const identities = ix.byKind("component_identity");
|
|
5462
5964
|
if (identities.length === 0) return /* @__PURE__ */ new Set();
|
|
5965
|
+
const definitions = new Map(
|
|
5966
|
+
ix.byKind("component_definition").map((definition) => [definition.componentKey, definition])
|
|
5967
|
+
);
|
|
5463
5968
|
const nodeIds = /* @__PURE__ */ new Set();
|
|
5464
5969
|
for (const identity of identities) {
|
|
5465
5970
|
if (identity.state !== "shadow") continue;
|
|
5971
|
+
const definition = definitions.get(identity.componentKey);
|
|
5972
|
+
if (!definition) continue;
|
|
5466
5973
|
for (const evidenceId of identity.evidence) {
|
|
5467
|
-
|
|
5974
|
+
const evidence = ix.get(evidenceId);
|
|
5975
|
+
if (evidence?.kind === "usage_node" && evidence.file === definition.file) {
|
|
5976
|
+
nodeIds.add(evidenceId);
|
|
5977
|
+
}
|
|
5468
5978
|
}
|
|
5469
5979
|
}
|
|
5470
5980
|
return nodeIds;
|
|
@@ -5747,7 +6257,7 @@ function sourceLabel(source) {
|
|
|
5747
6257
|
function canonicalImportPath(source) {
|
|
5748
6258
|
if (source.kind === "npm") return source.specifier;
|
|
5749
6259
|
if (source.kind === "registry") return source.importPath;
|
|
5750
|
-
return
|
|
6260
|
+
return void 0;
|
|
5751
6261
|
}
|
|
5752
6262
|
function indexCanonicalDirectoryResolvedNodes(ix, sources) {
|
|
5753
6263
|
const out = /* @__PURE__ */ new Set();
|
|
@@ -5823,10 +6333,10 @@ function indexComponentIdentityUsage(ix) {
|
|
|
5823
6333
|
}
|
|
5824
6334
|
|
|
5825
6335
|
// src/rules/components-shadow-component.ts
|
|
5826
|
-
var
|
|
5827
|
-
var
|
|
6336
|
+
var RULE_ID4 = "components/shadow-component";
|
|
6337
|
+
var RULE_VERSION4 = "1";
|
|
5828
6338
|
function ruleComponentsShadowComponent(ix) {
|
|
5829
|
-
const policy = ix.policy.ruleConfig(
|
|
6339
|
+
const policy = ix.policy.ruleConfig(RULE_ID4);
|
|
5830
6340
|
if (!policy?.enabled) return [];
|
|
5831
6341
|
const definitions = new Map(
|
|
5832
6342
|
ix.byKind("component_definition").map((definition) => [definition.componentKey, definition])
|
|
@@ -5843,8 +6353,8 @@ function ruleComponentsShadowComponent(ix) {
|
|
|
5843
6353
|
const blastRadius = usage?.blastRadius ?? 0;
|
|
5844
6354
|
findings.push(
|
|
5845
6355
|
makeFinding({
|
|
5846
|
-
ruleId:
|
|
5847
|
-
ruleVersion:
|
|
6356
|
+
ruleId: RULE_ID4,
|
|
6357
|
+
ruleVersion: RULE_VERSION4,
|
|
5848
6358
|
severity: reviewVariant ? "info" : policy.severity ?? "warn",
|
|
5849
6359
|
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"}.`,
|
|
5850
6360
|
location: definitionLocation(ix, definition, identity),
|
|
@@ -5882,8 +6392,8 @@ function definitionLocation(ix, definition, identity) {
|
|
|
5882
6392
|
}
|
|
5883
6393
|
|
|
5884
6394
|
// src/rules/components-forbidden-prop-value.ts
|
|
5885
|
-
var
|
|
5886
|
-
var
|
|
6395
|
+
var RULE_ID5 = "components/forbidden-prop-value";
|
|
6396
|
+
var RULE_VERSION5 = "1";
|
|
5887
6397
|
function ruleComponentsForbiddenPropValue(ix) {
|
|
5888
6398
|
const findings = [];
|
|
5889
6399
|
const componentByNode = indexComponentByNodeId(ix);
|
|
@@ -5913,8 +6423,8 @@ function ruleComponentsForbiddenPropValue(ix) {
|
|
|
5913
6423
|
} : void 0;
|
|
5914
6424
|
findings.push(
|
|
5915
6425
|
makeFinding({
|
|
5916
|
-
ruleId:
|
|
5917
|
-
ruleVersion:
|
|
6426
|
+
ruleId: RULE_ID5,
|
|
6427
|
+
ruleVersion: RULE_VERSION5,
|
|
5918
6428
|
severity: policy.severity,
|
|
5919
6429
|
message: buildMessage(
|
|
5920
6430
|
node.element,
|
|
@@ -5958,8 +6468,8 @@ function buildMessage(element, prop, value, because, replacement) {
|
|
|
5958
6468
|
}
|
|
5959
6469
|
|
|
5960
6470
|
// src/rules/components-unknown-prop.ts
|
|
5961
|
-
var
|
|
5962
|
-
var
|
|
6471
|
+
var RULE_ID6 = "components/unknown-prop";
|
|
6472
|
+
var RULE_VERSION6 = "1";
|
|
5963
6473
|
function ruleComponentsUnknownProp(ix) {
|
|
5964
6474
|
const policy = ix.policy.jsxUnknownPropsForbidden();
|
|
5965
6475
|
if (!policy) return [];
|
|
@@ -5980,8 +6490,8 @@ function ruleComponentsUnknownProp(ix) {
|
|
|
5980
6490
|
if (knownNames.has(propUsage.prop)) continue;
|
|
5981
6491
|
findings.push(
|
|
5982
6492
|
makeFinding({
|
|
5983
|
-
ruleId:
|
|
5984
|
-
ruleVersion:
|
|
6493
|
+
ruleId: RULE_ID6,
|
|
6494
|
+
ruleVersion: RULE_VERSION6,
|
|
5985
6495
|
severity: policy.severity,
|
|
5986
6496
|
message: `Unknown prop "${propUsage.prop}" on <${node.element}>. Allowed props: ${formatPropList(knownNames)}.`,
|
|
5987
6497
|
location: node.location,
|
|
@@ -6013,7 +6523,7 @@ function formatPropList(names) {
|
|
|
6013
6523
|
// src/rules/composition-pattern.ts
|
|
6014
6524
|
var CARDINALITY_RULE_ID = "composition/cardinality";
|
|
6015
6525
|
var CO_OCCURRENCE_RULE_ID = "composition/co-occurrence";
|
|
6016
|
-
var
|
|
6526
|
+
var RULE_VERSION7 = "1";
|
|
6017
6527
|
function ruleCompositionCardinality(ix) {
|
|
6018
6528
|
return evaluate(ix, CARDINALITY_RULE_ID, "cardinality");
|
|
6019
6529
|
}
|
|
@@ -6081,7 +6591,7 @@ function checkCardinality(ctx, region, pattern, constraint, selected) {
|
|
|
6081
6591
|
const extras = selected.slice(constraint.max);
|
|
6082
6592
|
return makeFinding({
|
|
6083
6593
|
ruleId: ctx.ruleId,
|
|
6084
|
-
ruleVersion:
|
|
6594
|
+
ruleVersion: RULE_VERSION7,
|
|
6085
6595
|
severity: ctx.severity,
|
|
6086
6596
|
message: cardinalityMaxMessage(pattern, constraint.max, count),
|
|
6087
6597
|
location: extras[0].location,
|
|
@@ -6092,7 +6602,7 @@ function checkCardinality(ctx, region, pattern, constraint, selected) {
|
|
|
6092
6602
|
if (constraint.min !== void 0 && count < constraint.min) {
|
|
6093
6603
|
return makeFinding({
|
|
6094
6604
|
ruleId: ctx.ruleId,
|
|
6095
|
-
ruleVersion:
|
|
6605
|
+
ruleVersion: RULE_VERSION7,
|
|
6096
6606
|
severity: ctx.severity,
|
|
6097
6607
|
message: cardinalityMinMessage(pattern, constraint.min, count),
|
|
6098
6608
|
location: region.container.location,
|
|
@@ -6113,7 +6623,7 @@ function checkCoOccurrence(ctx, region, pattern, constraint, selected) {
|
|
|
6113
6623
|
if (satisfied) return null;
|
|
6114
6624
|
return makeFinding({
|
|
6115
6625
|
ruleId: ctx.ruleId,
|
|
6116
|
-
ruleVersion:
|
|
6626
|
+
ruleVersion: RULE_VERSION7,
|
|
6117
6627
|
severity: ctx.severity,
|
|
6118
6628
|
message: coOccurrenceMessage(pattern, constraint),
|
|
6119
6629
|
location: region.container.location,
|
|
@@ -6165,8 +6675,8 @@ function coOccurrenceMessage(pattern, constraint) {
|
|
|
6165
6675
|
}
|
|
6166
6676
|
|
|
6167
6677
|
// src/rules/jsx-preferred-component.ts
|
|
6168
|
-
var
|
|
6169
|
-
var
|
|
6678
|
+
var RULE_ID7 = "components/preferred-component";
|
|
6679
|
+
var RULE_VERSION8 = "1";
|
|
6170
6680
|
function ruleJsxPreferredComponent(ix) {
|
|
6171
6681
|
const policies = ix.policy.jsxComponentPreferred();
|
|
6172
6682
|
if (policies.length === 0) return [];
|
|
@@ -6182,8 +6692,8 @@ function ruleJsxPreferredComponent(ix) {
|
|
|
6182
6692
|
if (!replacement) continue;
|
|
6183
6693
|
findings.push(
|
|
6184
6694
|
makeFinding({
|
|
6185
|
-
ruleId:
|
|
6186
|
-
ruleVersion:
|
|
6695
|
+
ruleId: RULE_ID7,
|
|
6696
|
+
ruleVersion: RULE_VERSION8,
|
|
6187
6697
|
severity: policy.severity,
|
|
6188
6698
|
message: `<${node.element}> should use <${replacement}>.`,
|
|
6189
6699
|
location: node.location,
|
|
@@ -6220,27 +6730,23 @@ function componentNameFromId(componentId2) {
|
|
|
6220
6730
|
}
|
|
6221
6731
|
|
|
6222
6732
|
// src/rules/jsx-preferred-import-path.ts
|
|
6223
|
-
var
|
|
6224
|
-
var
|
|
6733
|
+
var RULE_ID8 = "imports/preferred-path";
|
|
6734
|
+
var RULE_VERSION9 = "1";
|
|
6225
6735
|
function ruleJsxPreferredImportPath(ix) {
|
|
6226
6736
|
const policies = ix.policy.jsxImportPathPreferred();
|
|
6227
6737
|
if (policies.length === 0) return [];
|
|
6228
6738
|
const findings = [];
|
|
6229
6739
|
const seen = /* @__PURE__ */ new Set();
|
|
6230
6740
|
for (const usage of ix.byKind("usage_import")) {
|
|
6741
|
+
const bridgeGoverned = policies.some((policy) => bridgeGovernsUnderlyingImport(policy, usage));
|
|
6231
6742
|
const matches = policies.filter((policy) => {
|
|
6232
6743
|
if (policy.bridge) {
|
|
6233
6744
|
if (policy.bridge.implementationFiles.some((file) => sameRepoPath(file, usage.file))) {
|
|
6234
6745
|
return false;
|
|
6235
6746
|
}
|
|
6236
|
-
|
|
6237
|
-
if (ownedImportsEqual(usage.source, policy.from)) {
|
|
6238
|
-
return usage.imported === policy.bridge.underlyingExportName;
|
|
6239
|
-
}
|
|
6240
|
-
const subpath = usage.source.slice(policy.from.length + 1);
|
|
6241
|
-
const leaf = subpath.split("/").at(-1);
|
|
6242
|
-
return leaf === policy.bridge.underlyingExportName && (usage.imported === "default" || usage.imported === policy.bridge.underlyingExportName);
|
|
6747
|
+
return bridgeGovernsUnderlyingImport(policy, usage);
|
|
6243
6748
|
}
|
|
6749
|
+
if (bridgeGoverned) return false;
|
|
6244
6750
|
if (!ownedImportsEqual(policy.from, usage.source)) return false;
|
|
6245
6751
|
if (policy.imported !== void 0 && policy.imported !== usage.imported) return false;
|
|
6246
6752
|
return true;
|
|
@@ -6253,8 +6759,8 @@ function ruleJsxPreferredImportPath(ix) {
|
|
|
6253
6759
|
seen.add(key);
|
|
6254
6760
|
findings.push(
|
|
6255
6761
|
makeFinding({
|
|
6256
|
-
ruleId:
|
|
6257
|
-
ruleVersion:
|
|
6762
|
+
ruleId: RULE_ID8,
|
|
6763
|
+
ruleVersion: RULE_VERSION9,
|
|
6258
6764
|
severity: policy.severity,
|
|
6259
6765
|
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}".`,
|
|
6260
6766
|
location: usage.location,
|
|
@@ -6294,6 +6800,17 @@ function ruleJsxPreferredImportPath(ix) {
|
|
|
6294
6800
|
}
|
|
6295
6801
|
return findings;
|
|
6296
6802
|
}
|
|
6803
|
+
function bridgeGovernsUnderlyingImport(policy, usage) {
|
|
6804
|
+
const bridge = policy.bridge;
|
|
6805
|
+
if (!bridge) return false;
|
|
6806
|
+
if (!ownedImportMatchesRoot(usage.source, policy.from)) return false;
|
|
6807
|
+
if (ownedImportsEqual(usage.source, policy.from)) {
|
|
6808
|
+
return usage.imported === bridge.underlyingExportName;
|
|
6809
|
+
}
|
|
6810
|
+
const subpath = usage.source.slice(policy.from.length + 1);
|
|
6811
|
+
const leaf = subpath.split("/").at(-1);
|
|
6812
|
+
return leaf === bridge.underlyingExportName && (usage.imported === "default" || usage.imported === bridge.underlyingExportName);
|
|
6813
|
+
}
|
|
6297
6814
|
function sameRepoPath(left, right) {
|
|
6298
6815
|
return normalizeRepoPath(left) === normalizeRepoPath(right);
|
|
6299
6816
|
}
|
|
@@ -6302,10 +6819,10 @@ function normalizeRepoPath(path) {
|
|
|
6302
6819
|
}
|
|
6303
6820
|
|
|
6304
6821
|
// src/rules/props-invalid-value.ts
|
|
6305
|
-
var
|
|
6306
|
-
var
|
|
6822
|
+
var RULE_ID9 = "props/invalid-value";
|
|
6823
|
+
var RULE_VERSION10 = "1";
|
|
6307
6824
|
function rulePropsInvalidValue(ix) {
|
|
6308
|
-
const policy = ix.policy.ruleConfig(
|
|
6825
|
+
const policy = ix.policy.ruleConfig(RULE_ID9);
|
|
6309
6826
|
if (!policy?.enabled) return [];
|
|
6310
6827
|
const findings = [];
|
|
6311
6828
|
const componentByNode = indexComponentByNodeId(ix);
|
|
@@ -6334,8 +6851,8 @@ function rulePropsInvalidValue(ix) {
|
|
|
6334
6851
|
} : void 0;
|
|
6335
6852
|
findings.push(
|
|
6336
6853
|
makeFinding({
|
|
6337
|
-
ruleId:
|
|
6338
|
-
ruleVersion:
|
|
6854
|
+
ruleId: RULE_ID9,
|
|
6855
|
+
ruleVersion: RULE_VERSION10,
|
|
6339
6856
|
severity: policy.severity ?? "warn",
|
|
6340
6857
|
message: buildMessage2(node.element, propUsage.prop, propUsage.value, allowedValues),
|
|
6341
6858
|
location: propUsage.location ?? node.location,
|
|
@@ -6534,8 +7051,8 @@ function localTokenCandidates(ix, category) {
|
|
|
6534
7051
|
}
|
|
6535
7052
|
|
|
6536
7053
|
// src/rules/styles-no-raw-color.ts
|
|
6537
|
-
var
|
|
6538
|
-
var
|
|
7054
|
+
var RULE_ID10 = "styles/no-raw-color";
|
|
7055
|
+
var RULE_VERSION11 = "1";
|
|
6539
7056
|
function ruleStylesNoRawColor(ix) {
|
|
6540
7057
|
const policy = ix.policy.rawColorPolicy();
|
|
6541
7058
|
if (!policy) return [];
|
|
@@ -6549,14 +7066,15 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6549
7066
|
if (!color) continue;
|
|
6550
7067
|
if (isExemptColor(color, policy.except)) continue;
|
|
6551
7068
|
const resolution = resolveColorToken(decl.property, color, colorTokens);
|
|
6552
|
-
const
|
|
7069
|
+
const hopped = hoppedValue(decl);
|
|
7070
|
+
const fixEmission = resolution?.kind === "exact" && !hopped ? buildTokenFix(decl.property, decl.value, color, resolution, ix) : void 0;
|
|
6553
7071
|
const evidenceIds = resolution ? [decl.id, policy.id, resolution.token.id] : [decl.id, policy.id];
|
|
6554
7072
|
findings.push(
|
|
6555
7073
|
makeFinding({
|
|
6556
|
-
ruleId:
|
|
6557
|
-
ruleVersion:
|
|
7074
|
+
ruleId: RULE_ID10,
|
|
7075
|
+
ruleVersion: RULE_VERSION11,
|
|
6558
7076
|
severity: policy.severity,
|
|
6559
|
-
message: colorMessage(color, `\`${decl.property}\``, resolution, preferLabel),
|
|
7077
|
+
message: colorMessage(color, `\`${decl.property}\``, resolution, preferLabel) + (hopped?.suffix ?? ""),
|
|
6560
7078
|
location: decl.location,
|
|
6561
7079
|
evidence: ix.evidence(evidenceIds),
|
|
6562
7080
|
fingerprintIdentity: {
|
|
@@ -6575,7 +7093,8 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6575
7093
|
source: "css",
|
|
6576
7094
|
suggestedToken: resolution?.token.name,
|
|
6577
7095
|
...resolution ? { tokenMatch: resolution.kind } : {},
|
|
6578
|
-
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
7096
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
7097
|
+
...hopped?.attributes ?? {}
|
|
6579
7098
|
}
|
|
6580
7099
|
})
|
|
6581
7100
|
);
|
|
@@ -6589,8 +7108,8 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6589
7108
|
const evidenceIds2 = componentEvidenceId2 ? [node2.id, componentEvidenceId2, inline.id, policy.id] : [node2.id, inline.id, policy.id];
|
|
6590
7109
|
findings.push(
|
|
6591
7110
|
makeFinding({
|
|
6592
|
-
ruleId:
|
|
6593
|
-
ruleVersion:
|
|
7111
|
+
ruleId: RULE_ID10,
|
|
7112
|
+
ruleVersion: RULE_VERSION11,
|
|
6594
7113
|
severity: advisorySeverity,
|
|
6595
7114
|
message: `Runtime-constructed raw color \`${inline.value}\` on inline \`${inline.property}\`. Use a ${preferLabel} instead.`,
|
|
6596
7115
|
location: node2.location,
|
|
@@ -6621,15 +7140,16 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6621
7140
|
if (!node) continue;
|
|
6622
7141
|
const componentEvidenceId = componentByNode.get(node.id)?.id;
|
|
6623
7142
|
const resolution = resolveColorToken(inline.property, color, colorTokens);
|
|
6624
|
-
const
|
|
7143
|
+
const hopped = hoppedValue(inline);
|
|
7144
|
+
const fixEmission = resolution?.kind === "exact" && !hopped ? buildTokenFix(inline.property, inline.value, color, resolution, ix) : void 0;
|
|
6625
7145
|
const baseEvidence = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id] : [node.id, inline.id, policy.id];
|
|
6626
7146
|
const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
|
|
6627
7147
|
findings.push(
|
|
6628
7148
|
makeFinding({
|
|
6629
|
-
ruleId:
|
|
6630
|
-
ruleVersion:
|
|
7149
|
+
ruleId: RULE_ID10,
|
|
7150
|
+
ruleVersion: RULE_VERSION11,
|
|
6631
7151
|
severity: policy.severity,
|
|
6632
|
-
message: colorMessage(color, `inline \`${inline.property}\``, resolution, preferLabel),
|
|
7152
|
+
message: colorMessage(color, `inline \`${inline.property}\``, resolution, preferLabel) + (hopped?.suffix ?? ""),
|
|
6633
7153
|
location: node.location,
|
|
6634
7154
|
evidence: ix.evidence(evidenceIds),
|
|
6635
7155
|
fingerprintIdentity: {
|
|
@@ -6648,7 +7168,8 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6648
7168
|
source: "jsx",
|
|
6649
7169
|
suggestedToken: resolution?.token.name,
|
|
6650
7170
|
...resolution ? { tokenMatch: resolution.kind } : {},
|
|
6651
|
-
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
7171
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
7172
|
+
...hopped?.attributes ?? {}
|
|
6652
7173
|
}
|
|
6653
7174
|
})
|
|
6654
7175
|
);
|
|
@@ -6670,8 +7191,8 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6670
7191
|
const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
|
|
6671
7192
|
findings.push(
|
|
6672
7193
|
makeFinding({
|
|
6673
|
-
ruleId:
|
|
6674
|
-
ruleVersion:
|
|
7194
|
+
ruleId: RULE_ID10,
|
|
7195
|
+
ruleVersion: RULE_VERSION11,
|
|
6675
7196
|
severity: advisorySeverity,
|
|
6676
7197
|
message: colorMessage(color, `\`${prop.prop}\``, resolution, preferLabel),
|
|
6677
7198
|
location: prop.location ?? node.location,
|
|
@@ -6822,8 +7343,8 @@ function escapeRegExp(value) {
|
|
|
6822
7343
|
}
|
|
6823
7344
|
|
|
6824
7345
|
// src/rules/styles-no-raw-dimensions.ts
|
|
6825
|
-
var
|
|
6826
|
-
var
|
|
7346
|
+
var RULE_ID11 = "styles/no-raw-dimensions";
|
|
7347
|
+
var RULE_VERSION12 = "1";
|
|
6827
7348
|
function ruleStylesNoRawDimensions(ix) {
|
|
6828
7349
|
const policy = ix.policy.rawDimensionPolicy();
|
|
6829
7350
|
if (!policy) return [];
|
|
@@ -6840,13 +7361,14 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6840
7361
|
if (!resolution) continue;
|
|
6841
7362
|
const tokenIds = resolutionTokenIds(resolution);
|
|
6842
7363
|
const evidenceIds = tokenIds.length ? [decl.id, policy.id, ...tokenIds] : [decl.id, policy.id];
|
|
6843
|
-
const
|
|
7364
|
+
const hopped = hoppedValue(decl);
|
|
7365
|
+
const fixEmission = hopped ? void 0 : dimensionFix(decl.property, resolution, ix);
|
|
6844
7366
|
findings.push(
|
|
6845
7367
|
makeFinding({
|
|
6846
|
-
ruleId:
|
|
6847
|
-
ruleVersion:
|
|
7368
|
+
ruleId: RULE_ID11,
|
|
7369
|
+
ruleVersion: RULE_VERSION12,
|
|
6848
7370
|
severity: policy.severity,
|
|
6849
|
-
message: dimensionMessage(decl.value, decl.property, preferLabel, resolution, false),
|
|
7371
|
+
message: dimensionMessage(decl.value, decl.property, preferLabel, resolution, false) + (hopped?.suffix ?? ""),
|
|
6850
7372
|
location: decl.location,
|
|
6851
7373
|
evidence: ix.evidence(evidenceIds),
|
|
6852
7374
|
fingerprintIdentity: {
|
|
@@ -6863,7 +7385,8 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6863
7385
|
rawValue: decl.value,
|
|
6864
7386
|
source: "css",
|
|
6865
7387
|
suggestedToken: resolutionSuggestedToken(resolution),
|
|
6866
|
-
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
7388
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
7389
|
+
...hopped?.attributes ?? {}
|
|
6867
7390
|
}
|
|
6868
7391
|
})
|
|
6869
7392
|
);
|
|
@@ -6882,13 +7405,14 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6882
7405
|
const baseEvidence = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id] : [node.id, inline.id, policy.id];
|
|
6883
7406
|
const tokenIds = resolutionTokenIds(resolution);
|
|
6884
7407
|
const evidenceIds = tokenIds.length ? [...baseEvidence, ...tokenIds] : baseEvidence;
|
|
6885
|
-
const
|
|
7408
|
+
const hopped = hoppedValue(inline);
|
|
7409
|
+
const fixEmission = hopped ? void 0 : dimensionFix(inline.property, resolution, ix);
|
|
6886
7410
|
findings.push(
|
|
6887
7411
|
makeFinding({
|
|
6888
|
-
ruleId:
|
|
6889
|
-
ruleVersion:
|
|
7412
|
+
ruleId: RULE_ID11,
|
|
7413
|
+
ruleVersion: RULE_VERSION12,
|
|
6890
7414
|
severity: policy.severity,
|
|
6891
|
-
message: dimensionMessage(inline.value, inline.property, preferLabel, resolution, true),
|
|
7415
|
+
message: dimensionMessage(inline.value, inline.property, preferLabel, resolution, true) + (hopped?.suffix ?? ""),
|
|
6892
7416
|
location: node.location,
|
|
6893
7417
|
evidence: ix.evidence(evidenceIds),
|
|
6894
7418
|
fingerprintIdentity: {
|
|
@@ -6905,7 +7429,8 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6905
7429
|
rawValue: inline.value,
|
|
6906
7430
|
source: "jsx",
|
|
6907
7431
|
suggestedToken: resolutionSuggestedToken(resolution),
|
|
6908
|
-
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
7432
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
7433
|
+
...hopped?.attributes ?? {}
|
|
6909
7434
|
}
|
|
6910
7435
|
})
|
|
6911
7436
|
);
|
|
@@ -7154,8 +7679,8 @@ function parseLengthParts(raw) {
|
|
|
7154
7679
|
}
|
|
7155
7680
|
|
|
7156
7681
|
// src/rules/styles-no-raw-spacing.ts
|
|
7157
|
-
var
|
|
7158
|
-
var
|
|
7682
|
+
var RULE_ID12 = "styles/no-raw-spacing";
|
|
7683
|
+
var RULE_VERSION13 = "1";
|
|
7159
7684
|
function ruleStylesNoRawSpacing(ix) {
|
|
7160
7685
|
const findings = [];
|
|
7161
7686
|
const lookupCache = /* @__PURE__ */ new Map();
|
|
@@ -7195,12 +7720,13 @@ function checkDeclaration(ix, decl, lookupFor) {
|
|
|
7195
7720
|
tokens: lookupFor(scale)
|
|
7196
7721
|
});
|
|
7197
7722
|
if (!checked) return null;
|
|
7198
|
-
const
|
|
7723
|
+
const hopped = hoppedValue(decl);
|
|
7724
|
+
const fixEmission = hopped ? void 0 : buildSpacingFix(decl.property, checked, ix);
|
|
7199
7725
|
return makeFinding({
|
|
7200
|
-
ruleId:
|
|
7201
|
-
ruleVersion:
|
|
7726
|
+
ruleId: RULE_ID12,
|
|
7727
|
+
ruleVersion: RULE_VERSION13,
|
|
7202
7728
|
severity: policy.severity,
|
|
7203
|
-
message: spacingMessage(decl.property, decl.value, allowed, scale, checked),
|
|
7729
|
+
message: spacingMessage(decl.property, decl.value, allowed, scale, checked) + (hopped?.suffix ?? ""),
|
|
7204
7730
|
location: decl.location,
|
|
7205
7731
|
evidence: ix.evidence([decl.id, policy.id, scale.id]),
|
|
7206
7732
|
fingerprintIdentity: {
|
|
@@ -7226,7 +7752,8 @@ function checkDeclaration(ix, decl, lookupFor) {
|
|
|
7226
7752
|
assumedRootFontSizePx: checked.assumedRootFontSizePx,
|
|
7227
7753
|
assumedEmBasePx: checked.assumedEmBasePx,
|
|
7228
7754
|
source: "css",
|
|
7229
|
-
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
7755
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
7756
|
+
...hopped?.attributes ?? {}
|
|
7230
7757
|
}
|
|
7231
7758
|
});
|
|
7232
7759
|
}
|
|
@@ -7246,16 +7773,17 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
|
|
|
7246
7773
|
bareNumberFix: inline.valueKind === "number"
|
|
7247
7774
|
});
|
|
7248
7775
|
if (!checked) return null;
|
|
7249
|
-
const
|
|
7776
|
+
const hopped = hoppedValue(inline);
|
|
7777
|
+
const fixEmission = hopped ? void 0 : buildSpacingFix(inline.property, checked, ix);
|
|
7250
7778
|
const node = readUsageNode(ix, inline.nodeId);
|
|
7251
7779
|
if (!node) return null;
|
|
7252
7780
|
const componentEvidenceId = componentByNode.get(node.id)?.id;
|
|
7253
7781
|
const evidenceIds = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id, scale.id] : [node.id, inline.id, policy.id, scale.id];
|
|
7254
7782
|
return makeFinding({
|
|
7255
|
-
ruleId:
|
|
7256
|
-
ruleVersion:
|
|
7783
|
+
ruleId: RULE_ID12,
|
|
7784
|
+
ruleVersion: RULE_VERSION13,
|
|
7257
7785
|
severity: policy.severity,
|
|
7258
|
-
message: spacingMessage(inline.property, inline.value, allowed, scale, checked),
|
|
7786
|
+
message: spacingMessage(inline.property, inline.value, allowed, scale, checked) + (hopped?.suffix ?? ""),
|
|
7259
7787
|
location: node.location,
|
|
7260
7788
|
evidence: ix.evidence(evidenceIds),
|
|
7261
7789
|
fingerprintIdentity: {
|
|
@@ -7281,7 +7809,8 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
|
|
|
7281
7809
|
assumedRootFontSizePx: checked.assumedRootFontSizePx,
|
|
7282
7810
|
assumedEmBasePx: checked.assumedEmBasePx,
|
|
7283
7811
|
source: "jsx",
|
|
7284
|
-
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
7812
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
7813
|
+
...hopped?.attributes ?? {}
|
|
7285
7814
|
}
|
|
7286
7815
|
});
|
|
7287
7816
|
}
|
|
@@ -7314,8 +7843,8 @@ function spacingMessage(property, value, allowed, scale, checked) {
|
|
|
7314
7843
|
}
|
|
7315
7844
|
|
|
7316
7845
|
// src/rules/styles-no-raw-typography.ts
|
|
7317
|
-
var
|
|
7318
|
-
var
|
|
7846
|
+
var RULE_ID13 = "styles/no-raw-typography";
|
|
7847
|
+
var RULE_VERSION14 = "1";
|
|
7319
7848
|
var FONT_SIZE_PROPERTIES = /* @__PURE__ */ new Set(["font-size", "fontsize"]);
|
|
7320
7849
|
function ruleStylesNoRawTypography(ix) {
|
|
7321
7850
|
const policy = ix.policy.fontSizeScale();
|
|
@@ -7330,13 +7859,14 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
7330
7859
|
if (decl.property.toLowerCase() !== "font-size") continue;
|
|
7331
7860
|
const checked = checkFontSize(decl.value, allowed, scale, tokens);
|
|
7332
7861
|
if (!checked) continue;
|
|
7333
|
-
const
|
|
7862
|
+
const hopped = hoppedValue(decl);
|
|
7863
|
+
const fixEmission = hopped ? void 0 : buildFix(decl.property, checked, ix);
|
|
7334
7864
|
findings.push(
|
|
7335
7865
|
makeFinding({
|
|
7336
|
-
ruleId:
|
|
7337
|
-
ruleVersion:
|
|
7866
|
+
ruleId: RULE_ID13,
|
|
7867
|
+
ruleVersion: RULE_VERSION14,
|
|
7338
7868
|
severity: policy.severity,
|
|
7339
|
-
message: typographyMessage(decl.property, decl.value, allowed, scale.unit, checked),
|
|
7869
|
+
message: typographyMessage(decl.property, decl.value, allowed, scale.unit, checked) + (hopped?.suffix ?? ""),
|
|
7340
7870
|
location: decl.location,
|
|
7341
7871
|
evidence: ix.evidence([decl.id, policy.id, scale.id]),
|
|
7342
7872
|
fingerprintIdentity: {
|
|
@@ -7356,7 +7886,8 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
7356
7886
|
suggestedValue: checked.suggestedValue,
|
|
7357
7887
|
matchedToken: checked.matchedToken,
|
|
7358
7888
|
source: "css",
|
|
7359
|
-
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
7889
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
7890
|
+
...hopped?.attributes ?? {}
|
|
7360
7891
|
}
|
|
7361
7892
|
})
|
|
7362
7893
|
);
|
|
@@ -7373,17 +7904,18 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
7373
7904
|
inline.valueKind === "number"
|
|
7374
7905
|
);
|
|
7375
7906
|
if (!checked) continue;
|
|
7376
|
-
const
|
|
7907
|
+
const hopped = hoppedValue(inline);
|
|
7908
|
+
const fixEmission = hopped ? void 0 : buildFix(inline.property, checked, ix);
|
|
7377
7909
|
const node = readUsageNode(ix, inline.nodeId);
|
|
7378
7910
|
if (!node) continue;
|
|
7379
7911
|
const componentEvidenceId = componentByNode.get(node.id)?.id;
|
|
7380
7912
|
const evidenceIds = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id, scale.id] : [node.id, inline.id, policy.id, scale.id];
|
|
7381
7913
|
findings.push(
|
|
7382
7914
|
makeFinding({
|
|
7383
|
-
ruleId:
|
|
7384
|
-
ruleVersion:
|
|
7915
|
+
ruleId: RULE_ID13,
|
|
7916
|
+
ruleVersion: RULE_VERSION14,
|
|
7385
7917
|
severity: policy.severity,
|
|
7386
|
-
message: typographyMessage(inline.property, inline.value, allowed, scale.unit, checked),
|
|
7918
|
+
message: typographyMessage(inline.property, inline.value, allowed, scale.unit, checked) + (hopped?.suffix ?? ""),
|
|
7387
7919
|
location: node.location,
|
|
7388
7920
|
evidence: ix.evidence(evidenceIds),
|
|
7389
7921
|
fingerprintIdentity: {
|
|
@@ -7403,7 +7935,8 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
7403
7935
|
suggestedValue: checked.suggestedValue,
|
|
7404
7936
|
matchedToken: checked.matchedToken,
|
|
7405
7937
|
source: "jsx",
|
|
7406
|
-
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
7938
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
7939
|
+
...hopped?.attributes ?? {}
|
|
7407
7940
|
}
|
|
7408
7941
|
})
|
|
7409
7942
|
);
|
|
@@ -7483,15 +8016,15 @@ function typographyMessage(property, value, allowed, unit, checked) {
|
|
|
7483
8016
|
}
|
|
7484
8017
|
|
|
7485
8018
|
// src/rules/theme-no-theme-coupled-literal.ts
|
|
7486
|
-
var
|
|
7487
|
-
var
|
|
8019
|
+
var RULE_ID14 = "theme/no-theme-coupled-literal";
|
|
8020
|
+
var RULE_VERSION15 = "1";
|
|
7488
8021
|
var COLOR_LITERAL_GLOBAL = /#[0-9a-fA-F]{3,8}\b|\b(?:rgba?|hsla?|oklab|oklch|lab|lch)\(\s*[\d.%\s,\/]+\s*\)/g;
|
|
7489
8022
|
var WHITE_RGBA = /^rgba?\(\s*255\s*,\s*255\s*,\s*255/i;
|
|
7490
8023
|
var BLACK_RGBA = /^rgba?\(\s*0\s*,\s*0\s*,\s*0/i;
|
|
7491
8024
|
var BOX_SHADOW_PROP = /^(?:-webkit-|-moz-)?box-shadow$/i;
|
|
7492
8025
|
var NEUTRAL_SHADOW = /^(?:none|inherit|initial|unset|revert|revert-layer)\s*$/i;
|
|
7493
8026
|
function ruleThemeNoThemeCoupledLiteral(ix) {
|
|
7494
|
-
const policy = ix.policy.ruleConfig(
|
|
8027
|
+
const policy = ix.policy.ruleConfig(RULE_ID14);
|
|
7495
8028
|
if (!policy?.enabled) return [];
|
|
7496
8029
|
const findings = [];
|
|
7497
8030
|
for (const decl of ix.byKind("style_declaration")) {
|
|
@@ -7500,8 +8033,8 @@ function ruleThemeNoThemeCoupledLiteral(ix) {
|
|
|
7500
8033
|
if (!hit) continue;
|
|
7501
8034
|
findings.push(
|
|
7502
8035
|
makeFinding({
|
|
7503
|
-
ruleId:
|
|
7504
|
-
ruleVersion:
|
|
8036
|
+
ruleId: RULE_ID14,
|
|
8037
|
+
ruleVersion: RULE_VERSION15,
|
|
7505
8038
|
severity: policy.severity ?? "warn",
|
|
7506
8039
|
message: hit.message,
|
|
7507
8040
|
location: decl.location,
|
|
@@ -7591,8 +8124,8 @@ function isTokenFile(filePath) {
|
|
|
7591
8124
|
}
|
|
7592
8125
|
|
|
7593
8126
|
// src/rules/tailwind-arbitrary-color.ts
|
|
7594
|
-
var
|
|
7595
|
-
var
|
|
8127
|
+
var RULE_ID15 = "tailwind/arbitrary-color";
|
|
8128
|
+
var RULE_VERSION16 = "1";
|
|
7596
8129
|
var COLOR_UTILITIES = /* @__PURE__ */ new Set([
|
|
7597
8130
|
"accent",
|
|
7598
8131
|
"bg",
|
|
@@ -7634,8 +8167,8 @@ function ruleTailwindArbitraryColor(ix) {
|
|
|
7634
8167
|
const evidenceIds = node ? [node.id, klass.id, policy.id] : [klass.id, policy.id];
|
|
7635
8168
|
findings.push(
|
|
7636
8169
|
makeFinding({
|
|
7637
|
-
ruleId:
|
|
7638
|
-
ruleVersion:
|
|
8170
|
+
ruleId: RULE_ID15,
|
|
8171
|
+
ruleVersion: RULE_VERSION16,
|
|
7639
8172
|
severity: policy.severity,
|
|
7640
8173
|
message: `\`${klass.raw}\` uses arbitrary raw color ${color}. Use a ${policy.prefer === "token" ? "design token" : "CSS variable"} instead.`,
|
|
7641
8174
|
location: klass.location,
|
|
@@ -7666,8 +8199,8 @@ function fingerprintIdentity(klass, color) {
|
|
|
7666
8199
|
}
|
|
7667
8200
|
|
|
7668
8201
|
// src/rules/tailwind-arbitrary-spacing.ts
|
|
7669
|
-
var
|
|
7670
|
-
var
|
|
8202
|
+
var RULE_ID16 = "tailwind/arbitrary-spacing";
|
|
8203
|
+
var RULE_VERSION17 = "1";
|
|
7671
8204
|
var SPACING_UTILITY_TO_PROPERTY = /* @__PURE__ */ new Map([
|
|
7672
8205
|
["m", "margin"],
|
|
7673
8206
|
["mt", "margin"],
|
|
@@ -7713,8 +8246,8 @@ function ruleTailwindArbitrarySpacing(ix) {
|
|
|
7713
8246
|
const fix = buildFix2(klass);
|
|
7714
8247
|
findings.push(
|
|
7715
8248
|
makeFinding({
|
|
7716
|
-
ruleId:
|
|
7717
|
-
ruleVersion:
|
|
8249
|
+
ruleId: RULE_ID16,
|
|
8250
|
+
ruleVersion: RULE_VERSION17,
|
|
7718
8251
|
severity: policy.severity,
|
|
7719
8252
|
message: `\`${klass.raw}\` is an arbitrary spacing value not on the project's spacing scale.`,
|
|
7720
8253
|
location: klass.location,
|
|
@@ -7845,8 +8378,8 @@ function matchesTokenGlob(value, pattern) {
|
|
|
7845
8378
|
}
|
|
7846
8379
|
|
|
7847
8380
|
// src/rules/tailwind-forbidden-palette.ts
|
|
7848
|
-
var
|
|
7849
|
-
var
|
|
8381
|
+
var RULE_ID17 = "tailwind/forbidden-palette";
|
|
8382
|
+
var RULE_VERSION18 = "1";
|
|
7850
8383
|
function ruleTailwindForbiddenPalette(ix) {
|
|
7851
8384
|
const deny = ix.policy.tailwindPaletteDeny();
|
|
7852
8385
|
const allow = ix.policy.tailwindPaletteAllow();
|
|
@@ -7871,8 +8404,8 @@ function ruleTailwindForbiddenPalette(ix) {
|
|
|
7871
8404
|
const evidenceIds = node ? [node.id, klass.id, resolved.id, policy.id] : [klass.id, resolved.id, policy.id];
|
|
7872
8405
|
findings.push(
|
|
7873
8406
|
makeFinding({
|
|
7874
|
-
ruleId:
|
|
7875
|
-
ruleVersion:
|
|
8407
|
+
ruleId: RULE_ID17,
|
|
8408
|
+
ruleVersion: RULE_VERSION18,
|
|
7876
8409
|
severity: policy.severity,
|
|
7877
8410
|
message: messageFor(klass, token, deniedPattern, allow?.patterns ?? []),
|
|
7878
8411
|
location: klass.location,
|
|
@@ -7919,8 +8452,8 @@ function buildFix3(klass) {
|
|
|
7919
8452
|
}
|
|
7920
8453
|
|
|
7921
8454
|
// src/rules/tailwind-off-scale-spacing-token.ts
|
|
7922
|
-
var
|
|
7923
|
-
var
|
|
8455
|
+
var RULE_ID18 = "tailwind/off-scale-spacing-token";
|
|
8456
|
+
var RULE_VERSION19 = "1";
|
|
7924
8457
|
function ruleTailwindOffScaleSpacingToken(ix) {
|
|
7925
8458
|
const resolvedByKey = indexResolvedTailwindTokens(ix);
|
|
7926
8459
|
const findings = [];
|
|
@@ -7946,8 +8479,8 @@ function ruleTailwindOffScaleSpacingToken(ix) {
|
|
|
7946
8479
|
const evidenceIds = node ? [node.id, klass.id, resolved.id, policy.id, scale.id] : [klass.id, resolved.id, policy.id, scale.id];
|
|
7947
8480
|
findings.push(
|
|
7948
8481
|
makeFinding({
|
|
7949
|
-
ruleId:
|
|
7950
|
-
ruleVersion:
|
|
8482
|
+
ruleId: RULE_ID18,
|
|
8483
|
+
ruleVersion: RULE_VERSION19,
|
|
7951
8484
|
severity: policy.severity,
|
|
7952
8485
|
message: `\`${klass.raw}\` resolves to ${formatResolvedValue(signedValue, scale.unit)}, which is not on the project's spacing scale [${allowed.join(", ")}].`,
|
|
7953
8486
|
location: klass.location,
|
|
@@ -7996,8 +8529,8 @@ function buildFix4(raw, utility) {
|
|
|
7996
8529
|
}
|
|
7997
8530
|
|
|
7998
8531
|
// src/rules/tailwind-raw-color-via-token.ts
|
|
7999
|
-
var
|
|
8000
|
-
var
|
|
8532
|
+
var RULE_ID19 = "tailwind/raw-color-via-token";
|
|
8533
|
+
var RULE_VERSION20 = "1";
|
|
8001
8534
|
function ruleTailwindRawColorViaToken(ix) {
|
|
8002
8535
|
const allow = ix.policy.tailwindPaletteAllow();
|
|
8003
8536
|
if (!allow || allow.patterns.length === 0) return [];
|
|
@@ -8017,8 +8550,8 @@ function ruleTailwindRawColorViaToken(ix) {
|
|
|
8017
8550
|
const evidenceIds = node ? [node.id, klass.id, resolved.id, allow.id] : [klass.id, resolved.id, allow.id];
|
|
8018
8551
|
findings.push(
|
|
8019
8552
|
makeFinding({
|
|
8020
|
-
ruleId:
|
|
8021
|
-
ruleVersion:
|
|
8553
|
+
ruleId: RULE_ID19,
|
|
8554
|
+
ruleVersion: RULE_VERSION20,
|
|
8022
8555
|
severity: allow.severity,
|
|
8023
8556
|
message: `\`${klass.raw}\` resolves to raw color \`${color}\` (${sourceLabel2(resolved.resolved.source)}). Project policy requires \`${allow.patterns.join("`, `")}\` palette tokens.`,
|
|
8024
8557
|
location: klass.location,
|
|
@@ -8051,8 +8584,8 @@ function sourceLabel2(source) {
|
|
|
8051
8584
|
}
|
|
8052
8585
|
|
|
8053
8586
|
// src/rules/tailwind-unknown-class.ts
|
|
8054
|
-
var
|
|
8055
|
-
var
|
|
8587
|
+
var RULE_ID20 = "tailwind/unknown-class";
|
|
8588
|
+
var RULE_VERSION21 = "1";
|
|
8056
8589
|
var UNIVERSAL_COLOR_KEYWORDS = /* @__PURE__ */ new Set([
|
|
8057
8590
|
"white",
|
|
8058
8591
|
"black",
|
|
@@ -8073,8 +8606,8 @@ function ruleTailwindUnknownClass(ix) {
|
|
|
8073
8606
|
const evidenceIds2 = node2 ? [node2.id, klass.id, policy.id] : [klass.id, policy.id];
|
|
8074
8607
|
findings.push(
|
|
8075
8608
|
makeFinding({
|
|
8076
|
-
ruleId:
|
|
8077
|
-
ruleVersion:
|
|
8609
|
+
ruleId: RULE_ID20,
|
|
8610
|
+
ruleVersion: RULE_VERSION21,
|
|
8078
8611
|
severity: policy.severity,
|
|
8079
8612
|
message: `\`${klass.raw}\` could not be parsed as a known Tailwind utility.`,
|
|
8080
8613
|
location: klass.location,
|
|
@@ -8108,8 +8641,8 @@ function ruleTailwindUnknownClass(ix) {
|
|
|
8108
8641
|
const evidenceIds = node ? [node.id, klass.id, resolved.id, policy.id] : [klass.id, resolved.id, policy.id];
|
|
8109
8642
|
findings.push(
|
|
8110
8643
|
makeFinding({
|
|
8111
|
-
ruleId:
|
|
8112
|
-
ruleVersion:
|
|
8644
|
+
ruleId: RULE_ID20,
|
|
8645
|
+
ruleVersion: RULE_VERSION21,
|
|
8113
8646
|
severity: policy.severity,
|
|
8114
8647
|
message: `\`${klass.raw}\` did not resolve to any known palette token.`,
|
|
8115
8648
|
location: klass.location,
|
|
@@ -8137,12 +8670,12 @@ function ruleTailwindUnknownClass(ix) {
|
|
|
8137
8670
|
}
|
|
8138
8671
|
|
|
8139
8672
|
// src/rules/tokens-require-dual-fallback.ts
|
|
8140
|
-
var
|
|
8141
|
-
var
|
|
8673
|
+
var RULE_ID21 = "tokens/require-dual-fallback";
|
|
8674
|
+
var RULE_VERSION22 = "1";
|
|
8142
8675
|
var CSS_VAR_WITH_OPTIONAL_FALLBACK = /var\(\s*(--[A-Za-z0-9_-]+)(\s*,\s*[^)]*)?\)/g;
|
|
8143
8676
|
var SASS_FILE = /\.(scss|sass)$/i;
|
|
8144
8677
|
function ruleTokensRequireDualFallback(ix) {
|
|
8145
|
-
const policy = ix.policy.ruleConfig(
|
|
8678
|
+
const policy = ix.policy.ruleConfig(RULE_ID21);
|
|
8146
8679
|
if (!policy?.enabled) return [];
|
|
8147
8680
|
const tokensByCssVariable = indexTokensByCssVariable(localTokenCandidates(ix));
|
|
8148
8681
|
const findings = [];
|
|
@@ -8177,8 +8710,8 @@ function ruleTokensRequireDualFallback(ix) {
|
|
|
8177
8710
|
) : void 0;
|
|
8178
8711
|
findings.push(
|
|
8179
8712
|
makeFinding({
|
|
8180
|
-
ruleId:
|
|
8181
|
-
ruleVersion:
|
|
8713
|
+
ruleId: RULE_ID21,
|
|
8714
|
+
ruleVersion: RULE_VERSION22,
|
|
8182
8715
|
severity: policy.severity ?? "warn",
|
|
8183
8716
|
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}.`,
|
|
8184
8717
|
location: decl.location,
|
|
@@ -8218,8 +8751,8 @@ function indexTokensByCssVariable(tokens) {
|
|
|
8218
8751
|
}
|
|
8219
8752
|
|
|
8220
8753
|
// src/rules/tokens-css-vars-must-be-defined.ts
|
|
8221
|
-
var
|
|
8222
|
-
var
|
|
8754
|
+
var RULE_ID22 = "tokens/css-vars-must-be-defined";
|
|
8755
|
+
var RULE_VERSION23 = "1";
|
|
8223
8756
|
var CSS_VAR_REFERENCE = /var\(\s*(--[A-Za-z0-9_-]+)/gi;
|
|
8224
8757
|
function ruleTokensCssVarsMustBeDefined(ix) {
|
|
8225
8758
|
const policy = ix.policy.cssVarsMustBeDefined();
|
|
@@ -8246,8 +8779,8 @@ function ruleTokensCssVarsMustBeDefined(ix) {
|
|
|
8246
8779
|
seen.add(tokenName);
|
|
8247
8780
|
findings.push(
|
|
8248
8781
|
makeFinding({
|
|
8249
|
-
ruleId:
|
|
8250
|
-
ruleVersion:
|
|
8782
|
+
ruleId: RULE_ID22,
|
|
8783
|
+
ruleVersion: RULE_VERSION23,
|
|
8251
8784
|
severity: policy.severity,
|
|
8252
8785
|
message: `var(${tokenName}) is not in the contract token vocabulary. Use a token defined in your contract's token source files.`,
|
|
8253
8786
|
location: decl.location,
|
|
@@ -8294,8 +8827,8 @@ function skipToCloseParen(value, openParenIndex) {
|
|
|
8294
8827
|
}
|
|
8295
8828
|
|
|
8296
8829
|
// src/rules/tokens-upstream-drift.ts
|
|
8297
|
-
var
|
|
8298
|
-
var
|
|
8830
|
+
var RULE_ID23 = "tokens/upstream-drift";
|
|
8831
|
+
var RULE_VERSION24 = "1";
|
|
8299
8832
|
function ruleTokensUpstreamDrift(ix) {
|
|
8300
8833
|
const local = ix.tokens.list().filter((token) => token.role !== "upstream");
|
|
8301
8834
|
const upstream = ix.tokens.list().filter((token) => token.role === "upstream");
|
|
@@ -8321,8 +8854,8 @@ function ruleTokensUpstreamDrift(ix) {
|
|
|
8321
8854
|
};
|
|
8322
8855
|
findings.push(
|
|
8323
8856
|
makeFinding({
|
|
8324
|
-
ruleId:
|
|
8325
|
-
ruleVersion:
|
|
8857
|
+
ruleId: RULE_ID23,
|
|
8858
|
+
ruleVersion: RULE_VERSION24,
|
|
8326
8859
|
severity: "warn",
|
|
8327
8860
|
message: `Local token ${token.name} is ${token.value}, but upstream ${upstreamName} is ${expected.value}.`,
|
|
8328
8861
|
location,
|
|
@@ -8354,8 +8887,12 @@ var BLOCKING_RULE_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
|
8354
8887
|
// off-scale Tailwind spacing utility
|
|
8355
8888
|
"components/prefer-library",
|
|
8356
8889
|
// raw element bypasses a canonical component
|
|
8357
|
-
"components/
|
|
8890
|
+
"components/shadow-component",
|
|
8891
|
+
// confirmed local semantic control shadows a canonical
|
|
8892
|
+
"components/preferred-component",
|
|
8358
8893
|
// imported component bypasses its canonical
|
|
8894
|
+
"tokens/css-vars-must-be-defined"
|
|
8895
|
+
// off-contract custom property reference
|
|
8359
8896
|
]);
|
|
8360
8897
|
function gatesCi(finding, failOnWarnings) {
|
|
8361
8898
|
if (!canBlock(finding.evidenceGrade ?? "source_backed")) return false;
|
|
@@ -8366,9 +8903,13 @@ function gatesCi(finding, failOnWarnings) {
|
|
|
8366
8903
|
function isDenyEligible(finding, failOnWarnings) {
|
|
8367
8904
|
if (!BLOCKING_RULE_ALLOWLIST.has(finding.ruleId)) return false;
|
|
8368
8905
|
if (finding.attributes?.advisory === true) return false;
|
|
8906
|
+
if (finding.ruleId === "components/shadow-component" && (finding.attributes?.state !== "shadow" || finding.attributes?.confidence !== "confirmed" || typeof finding.attributes?.canonicalTarget !== "string")) {
|
|
8907
|
+
return false;
|
|
8908
|
+
}
|
|
8369
8909
|
if (!gatesCi(finding, failOnWarnings)) return false;
|
|
8370
8910
|
if (finding.fix?.deterministic === false) {
|
|
8371
|
-
|
|
8911
|
+
const exactCanonicalBypass = finding.ruleId === "components/prefer-library" && finding.attributes?.precisionTier === "exact-html" && typeof finding.attributes?.suggestedComponent === "string";
|
|
8912
|
+
if (!exactCanonicalBypass) {
|
|
8372
8913
|
return false;
|
|
8373
8914
|
}
|
|
8374
8915
|
}
|
|
@@ -8405,6 +8946,7 @@ var RULE_TIER = {
|
|
|
8405
8946
|
"tokens/require-dual-fallback": "hygiene",
|
|
8406
8947
|
"theme/no-theme-coupled-literal": "hygiene",
|
|
8407
8948
|
"a11y/required-accessible-name": "hygiene",
|
|
8949
|
+
"a11y/standard": "hygiene",
|
|
8408
8950
|
// Composition constraints are authored contracts, but not part of the
|
|
8409
8951
|
// two-choice headline contract; they self-activate when patterns are declared
|
|
8410
8952
|
// (the compiler injects their configs), so off-by-default here is harmless.
|
|
@@ -8566,9 +9108,14 @@ var RULES = [
|
|
|
8566
9108
|
},
|
|
8567
9109
|
{
|
|
8568
9110
|
id: "a11y/required-accessible-name",
|
|
8569
|
-
version:
|
|
9111
|
+
version: RULE_VERSION,
|
|
8570
9112
|
run: ruleA11yRequiredAccessibleName
|
|
8571
9113
|
},
|
|
9114
|
+
{
|
|
9115
|
+
id: "a11y/standard",
|
|
9116
|
+
version: "1",
|
|
9117
|
+
run: ruleA11yStandard
|
|
9118
|
+
},
|
|
8572
9119
|
{
|
|
8573
9120
|
id: "composition/cardinality",
|
|
8574
9121
|
version: "1",
|
|
@@ -8607,12 +9154,21 @@ function hasEffectiveComponentVocabulary(options) {
|
|
|
8607
9154
|
}
|
|
8608
9155
|
function effectiveRuleConfigs(policy) {
|
|
8609
9156
|
const configs = /* @__PURE__ */ new Map();
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
9157
|
+
const facts = compileGlobalGovernanceFacts(policy);
|
|
9158
|
+
for (const fact of facts) {
|
|
9159
|
+
if (fact.kind === "governance_rule_config") {
|
|
9160
|
+
configs.set(fact.ruleId, {
|
|
9161
|
+
enabled: fact.enabled,
|
|
9162
|
+
severity: fact.severity,
|
|
9163
|
+
options: fact.options
|
|
9164
|
+
});
|
|
9165
|
+
}
|
|
9166
|
+
}
|
|
9167
|
+
const cssVarsPolicy = facts.find((fact) => fact.kind === "style_css_vars_must_be_defined");
|
|
9168
|
+
if (cssVarsPolicy?.kind === "style_css_vars_must_be_defined") {
|
|
9169
|
+
configs.set("tokens/css-vars-must-be-defined", {
|
|
9170
|
+
enabled: true,
|
|
9171
|
+
severity: cssVarsPolicy.severity
|
|
8616
9172
|
});
|
|
8617
9173
|
}
|
|
8618
9174
|
return configs;
|
|
@@ -8623,10 +9179,9 @@ function policyDeclaresCssVars(policy) {
|
|
|
8623
9179
|
function dedupe(values) {
|
|
8624
9180
|
return [...new Set(values)];
|
|
8625
9181
|
}
|
|
8626
|
-
var RULE_FAMILY_IDS = /* @__PURE__ */ new Set(["tokens/hardcoded-values", "components/usage", "a11y/wcag"]);
|
|
8627
9182
|
var CONSUMED_RULE_IDS = new Set(Object.keys(RULE_TIER));
|
|
8628
9183
|
var RECOGNIZED_RULE_IDS = /* @__PURE__ */ new Set([...CONSUMED_RULE_IDS, ...RULE_FAMILY_IDS]);
|
|
8629
|
-
var RULE_CONFIG_KEYS = /* @__PURE__ */ new Set(["enabled", "severity", "options"]);
|
|
9184
|
+
var RULE_CONFIG_KEYS = /* @__PURE__ */ new Set(["enabled", "severity", "options", "exclude"]);
|
|
8630
9185
|
var PASSTHROUGH_KEYS = [
|
|
8631
9186
|
{
|
|
8632
9187
|
path: ["tokens"],
|
|
@@ -8665,7 +9220,7 @@ var PASSTHROUGH_KEYS = [
|
|
|
8665
9220
|
},
|
|
8666
9221
|
{
|
|
8667
9222
|
path: ["govern", "ci"],
|
|
8668
|
-
keys: ["failOnWarnings"]
|
|
9223
|
+
keys: ["failOnWarnings", "failOnInert"]
|
|
8669
9224
|
}
|
|
8670
9225
|
];
|
|
8671
9226
|
function objectRecord(value) {
|
|
@@ -8697,6 +9252,139 @@ function unconsumedKeyDiagnostic(path) {
|
|
|
8697
9252
|
message: `${renderedPath} is not consumed by Fragments and has no effect. Remove it or use a supported config key.`
|
|
8698
9253
|
};
|
|
8699
9254
|
}
|
|
9255
|
+
var CONFIG_RECORD_FACT_KINDS = {
|
|
9256
|
+
scale: { section: "govern.scales", record: "scale", keys: ["name"] },
|
|
9257
|
+
scale_value: { section: "govern.scales", record: "scale.values", keys: ["scale", "value"] },
|
|
9258
|
+
style_raw_color_forbidden: {
|
|
9259
|
+
section: "govern.styles",
|
|
9260
|
+
record: "style.rawColors.forbid",
|
|
9261
|
+
keys: []
|
|
9262
|
+
},
|
|
9263
|
+
style_raw_dimension_forbidden: {
|
|
9264
|
+
section: "govern.styles",
|
|
9265
|
+
record: "style.rawDimensions.forbid",
|
|
9266
|
+
keys: []
|
|
9267
|
+
},
|
|
9268
|
+
style_property_scale: {
|
|
9269
|
+
section: "govern.styles",
|
|
9270
|
+
record: "style.rawSpacing.mustMatchScale",
|
|
9271
|
+
keys: ["property"]
|
|
9272
|
+
},
|
|
9273
|
+
style_font_size_scale: {
|
|
9274
|
+
section: "govern.styles",
|
|
9275
|
+
record: "style.fontSize.mustMatchScale",
|
|
9276
|
+
keys: []
|
|
9277
|
+
},
|
|
9278
|
+
style_css_vars_must_be_defined: {
|
|
9279
|
+
section: "govern.styles",
|
|
9280
|
+
record: "style.cssVars.mustBeDefined",
|
|
9281
|
+
keys: []
|
|
9282
|
+
},
|
|
9283
|
+
jsx_unknown_props_forbidden: {
|
|
9284
|
+
section: "govern.jsx",
|
|
9285
|
+
record: "jsx.unknownProps.forbid",
|
|
9286
|
+
keys: []
|
|
9287
|
+
},
|
|
9288
|
+
jsx_inline_style_forbidden_raw: {
|
|
9289
|
+
section: "govern.jsx",
|
|
9290
|
+
record: "jsx.inlineStyle.forbidRaw",
|
|
9291
|
+
keys: ["property"]
|
|
9292
|
+
},
|
|
9293
|
+
jsx_import_path_preferred: {
|
|
9294
|
+
section: "govern.jsx",
|
|
9295
|
+
record: "jsx.importPath.prefer",
|
|
9296
|
+
keys: ["from", "to", "imported"]
|
|
9297
|
+
},
|
|
9298
|
+
jsx_component_preferred: {
|
|
9299
|
+
section: "govern.jsx",
|
|
9300
|
+
record: "jsx.component.prefer",
|
|
9301
|
+
keys: ["from", "to"]
|
|
9302
|
+
},
|
|
9303
|
+
prop_value_avoided: {
|
|
9304
|
+
section: "govern.components",
|
|
9305
|
+
record: "prop.value.avoid",
|
|
9306
|
+
keys: ["componentId", "prop", "value"]
|
|
9307
|
+
},
|
|
9308
|
+
prop_value_forbidden: {
|
|
9309
|
+
section: "govern.components",
|
|
9310
|
+
record: "prop.value.forbid",
|
|
9311
|
+
keys: ["componentId", "prop", "value", "pathPattern"]
|
|
9312
|
+
},
|
|
9313
|
+
a11y_name_required: {
|
|
9314
|
+
section: "govern.components",
|
|
9315
|
+
record: "a11y.requireName",
|
|
9316
|
+
keys: ["componentId"]
|
|
9317
|
+
},
|
|
9318
|
+
tailwind_palette_allow: {
|
|
9319
|
+
section: "govern.tailwind",
|
|
9320
|
+
record: "palette.allow",
|
|
9321
|
+
keys: []
|
|
9322
|
+
},
|
|
9323
|
+
tailwind_palette_deny: { section: "govern.tailwind", record: "palette.deny", keys: [] },
|
|
9324
|
+
tailwind_unknown_class_enabled: {
|
|
9325
|
+
section: "govern.tailwind",
|
|
9326
|
+
record: "unknownClass",
|
|
9327
|
+
keys: []
|
|
9328
|
+
},
|
|
9329
|
+
governance_rule_config: { section: "govern.rules", record: "rule", keys: ["ruleId"] }
|
|
9330
|
+
};
|
|
9331
|
+
var COLLISION_DETAIL_KEYS = [
|
|
9332
|
+
"severity",
|
|
9333
|
+
"scale",
|
|
9334
|
+
"prefer",
|
|
9335
|
+
"enabled",
|
|
9336
|
+
"appliesTo",
|
|
9337
|
+
"except",
|
|
9338
|
+
"properties",
|
|
9339
|
+
"because"
|
|
9340
|
+
];
|
|
9341
|
+
function renderFactFields(fact, keys) {
|
|
9342
|
+
return keys.filter((key) => fact[key] !== void 0).map((key) => `${key}=${Array.isArray(fact[key]) ? JSON.stringify(fact[key]) : fact[key]}`).join(" ");
|
|
9343
|
+
}
|
|
9344
|
+
function collidingRecordDiagnostic(conflict) {
|
|
9345
|
+
return collidingRecordDiagnostics([conflict])[0] ?? null;
|
|
9346
|
+
}
|
|
9347
|
+
function collidingRecordDiagnostics(conflicts) {
|
|
9348
|
+
const groups = /* @__PURE__ */ new Map();
|
|
9349
|
+
for (const conflict of conflicts) {
|
|
9350
|
+
const shape = CONFIG_RECORD_FACT_KINDS[conflict.kept.kind];
|
|
9351
|
+
if (!shape || conflict.kept.kind !== conflict.skipped.kind) continue;
|
|
9352
|
+
const keptFact = conflict.kept;
|
|
9353
|
+
const skippedFact = conflict.skipped;
|
|
9354
|
+
const identity = renderFactFields(keptFact, shape.keys);
|
|
9355
|
+
const path = `${shape.section}[${identity ? `${shape.record} ${identity}` : shape.record}]`;
|
|
9356
|
+
const group = groups.get(path) ?? {
|
|
9357
|
+
record: shape.record,
|
|
9358
|
+
kept: renderFactFields(keptFact, COLLISION_DETAIL_KEYS),
|
|
9359
|
+
dropped: [],
|
|
9360
|
+
keptFallback: "the first record"
|
|
9361
|
+
};
|
|
9362
|
+
const dropped = renderFactFields(skippedFact, COLLISION_DETAIL_KEYS);
|
|
9363
|
+
group.dropped.push(dropped || `record ${group.dropped.length + 2}`);
|
|
9364
|
+
groups.set(path, group);
|
|
9365
|
+
}
|
|
9366
|
+
return [...groups].map(([path, group]) => ({
|
|
9367
|
+
code: "FUI9007",
|
|
9368
|
+
kind: "colliding-record",
|
|
9369
|
+
severity: "warn",
|
|
9370
|
+
path,
|
|
9371
|
+
message: `${path} has ${group.dropped.length + 1} \`${group.record}\` records that compile to the same policy fact \u2014 enforcing ${group.kept || group.keptFallback} and dropping ${group.dropped.join("; ")}. Author one record for this policy, or scope them apart, so the enforced setting is the one you can read.`
|
|
9372
|
+
}));
|
|
9373
|
+
}
|
|
9374
|
+
function configRecordShape(kind) {
|
|
9375
|
+
const shape = CONFIG_RECORD_FACT_KINDS[kind];
|
|
9376
|
+
return shape ? { section: shape.section, record: shape.record } : null;
|
|
9377
|
+
}
|
|
9378
|
+
function overriddenRecordSeverityDiagnostic(input) {
|
|
9379
|
+
const path = `${input.section}[${input.record}]`;
|
|
9380
|
+
return {
|
|
9381
|
+
code: "FUI9008",
|
|
9382
|
+
kind: "overridden-record-severity",
|
|
9383
|
+
severity: "warn",
|
|
9384
|
+
path,
|
|
9385
|
+
message: `${path} authored severity=${input.authored}, but ${input.source} enforces severity=${input.enforced} on \`${input.ruleId}\`. The record's severity is not what gates CI \u2014 drop the rule override, or author the record at the severity you want enforced.`
|
|
9386
|
+
};
|
|
9387
|
+
}
|
|
8700
9388
|
function collectStrippedKeys(authored, parsed, path, diagnostics) {
|
|
8701
9389
|
if (Array.isArray(authored)) {
|
|
8702
9390
|
if (!Array.isArray(parsed)) return;
|
|
@@ -8786,6 +9474,41 @@ function detectUnconsumedConfigKeys(authoredConfig, parsedConfig) {
|
|
|
8786
9474
|
collectPassthroughRecordValues(authoredConfig, ["govern", "runners"], /* @__PURE__ */ new Set(), diagnostics);
|
|
8787
9475
|
return stableConfigDiagnostics(diagnostics);
|
|
8788
9476
|
}
|
|
9477
|
+
function authoredPolicyExcludes(policy) {
|
|
9478
|
+
const out = [];
|
|
9479
|
+
const collect = (path, scope, raw) => {
|
|
9480
|
+
for (const exclude of normalizePolicyExcludes(raw) ?? []) {
|
|
9481
|
+
out.push({ path, scope, exclude });
|
|
9482
|
+
}
|
|
9483
|
+
};
|
|
9484
|
+
(policy?.styles ?? []).forEach((record, index) => {
|
|
9485
|
+
collect(`govern.styles[${index}].exclude`, record.kind, record.exclude);
|
|
9486
|
+
});
|
|
9487
|
+
(policy?.jsx ?? []).forEach((record, index) => {
|
|
9488
|
+
collect(`govern.jsx[${index}].exclude`, record.kind, record.exclude);
|
|
9489
|
+
});
|
|
9490
|
+
for (const ruleId of Object.keys(policy?.rules ?? {}).sort()) {
|
|
9491
|
+
const record = objectRecord(policy?.rules?.[ruleId]);
|
|
9492
|
+
if (!record) continue;
|
|
9493
|
+
collect(`govern.rules.${ruleId}.exclude`, ruleId, record.exclude);
|
|
9494
|
+
}
|
|
9495
|
+
return out;
|
|
9496
|
+
}
|
|
9497
|
+
function detectUnmatchedPolicyExcludes(policy, scannedFiles) {
|
|
9498
|
+
if (scannedFiles.length === 0) return [];
|
|
9499
|
+
const diagnostics = [];
|
|
9500
|
+
for (const { path, scope, exclude } of authoredPolicyExcludes(policy)) {
|
|
9501
|
+
if (scannedFiles.some((file) => policyExcludeMatchesPath(exclude, file))) continue;
|
|
9502
|
+
diagnostics.push({
|
|
9503
|
+
code: "FUI9006",
|
|
9504
|
+
kind: "unmatched-exclude",
|
|
9505
|
+
severity: "warn",
|
|
9506
|
+
path,
|
|
9507
|
+
message: `${path} pattern "${exclude.glob}" matched no scanned file, so ${scope} is not actually scoped by it. Correct the glob to a path this scan covers, or remove it.`
|
|
9508
|
+
});
|
|
9509
|
+
}
|
|
9510
|
+
return stableConfigDiagnostics(diagnostics);
|
|
9511
|
+
}
|
|
8789
9512
|
function effectiveScaleBindings(policy) {
|
|
8790
9513
|
const bindings = [];
|
|
8791
9514
|
for (const style of policy?.styles ?? []) {
|
|
@@ -8849,23 +9572,28 @@ function evaluateGovernanceIntegrity(input) {
|
|
|
8849
9572
|
if (!policyArmed) {
|
|
8850
9573
|
policyFamily.reason = "no governance policy resolved";
|
|
8851
9574
|
}
|
|
8852
|
-
const
|
|
9575
|
+
const preferLibraryConfig = configs.get("components/prefer-library");
|
|
9576
|
+
const shadowComponentConfig = configs.get("components/shadow-component");
|
|
8853
9577
|
const confirmedBridges = input.policy?.canonicalBridges ?? [];
|
|
8854
|
-
const
|
|
9578
|
+
const componentRuleEnabled = preferLibraryConfig?.enabled === true || shadowComponentConfig?.enabled === true;
|
|
9579
|
+
const preferLibraryArmed = preferLibraryConfig?.enabled === true && hasEffectiveComponentVocabulary(preferLibraryConfig.options);
|
|
9580
|
+
const shadowComponentArmed = shadowComponentConfig?.enabled === true && hasEffectiveComponentVocabulary(shadowComponentConfig.options);
|
|
9581
|
+
const componentsArmed = confirmedBridges.length > 0 || preferLibraryArmed || shadowComponentArmed;
|
|
8855
9582
|
const componentsFamily = {
|
|
8856
9583
|
id: "components",
|
|
8857
9584
|
armed: componentsArmed,
|
|
8858
9585
|
rules: [
|
|
8859
9586
|
...confirmedBridges.length > 0 ? ["imports/preferred-path"] : [],
|
|
8860
|
-
...
|
|
9587
|
+
...preferLibraryConfig?.enabled === true ? ["components/prefer-library"] : [],
|
|
9588
|
+
...shadowComponentConfig?.enabled === true ? ["components/shadow-component"] : []
|
|
8861
9589
|
]
|
|
8862
9590
|
};
|
|
8863
9591
|
if (!componentsArmed) {
|
|
8864
|
-
if (
|
|
8865
|
-
componentsFamily.reason = "
|
|
9592
|
+
if (componentRuleEnabled) {
|
|
9593
|
+
componentsFamily.reason = "component governance enabled but no effective canonical source";
|
|
8866
9594
|
componentsFamily.remediation = "add govern.canonicalBridges for local wrappers, govern.canonicalSources, or designSystem.path/packageName";
|
|
8867
9595
|
} else {
|
|
8868
|
-
componentsFamily.reason = "
|
|
9596
|
+
componentsFamily.reason = "canonical-component rules not enabled";
|
|
8869
9597
|
componentsFamily.remediation = "add govern.canonicalBridges for local wrappers, govern.canonicalSources, or designSystem.path/packageName";
|
|
8870
9598
|
}
|
|
8871
9599
|
}
|
|
@@ -8908,7 +9636,7 @@ function evaluateGovernanceIntegrity(input) {
|
|
|
8908
9636
|
for (const ruleId of BLOCKING_RULE_ALLOWLIST) {
|
|
8909
9637
|
const config = configs.get(ruleId);
|
|
8910
9638
|
if (config?.enabled !== true) continue;
|
|
8911
|
-
const familyArmed = ruleId === "components/prefer-library" ? componentsArmed : hygieneArmed;
|
|
9639
|
+
const familyArmed = ruleId === "components/prefer-library" || ruleId === "components/shadow-component" ? componentsArmed : ruleId === "tokens/css-vars-must-be-defined" ? tokensArmed : hygieneArmed;
|
|
8912
9640
|
if (!familyArmed) continue;
|
|
8913
9641
|
if (config.severity !== "error" && !failOnWarnings) continue;
|
|
8914
9642
|
blockingRules.push(ruleId);
|
|
@@ -8982,6 +9710,19 @@ function canonicalBridgeContractMappings(bridges) {
|
|
|
8982
9710
|
}
|
|
8983
9711
|
}));
|
|
8984
9712
|
}
|
|
9713
|
+
function canonicalBridgeIdentityBindings(bridges) {
|
|
9714
|
+
return (bridges ?? []).map((bridge) => ({
|
|
9715
|
+
source: {
|
|
9716
|
+
kind: "npm",
|
|
9717
|
+
specifier: bridge.underlying.packageName,
|
|
9718
|
+
include: [bridge.underlying.exportName]
|
|
9719
|
+
},
|
|
9720
|
+
localComponentKey: bridge.local.componentKey
|
|
9721
|
+
}));
|
|
9722
|
+
}
|
|
9723
|
+
function canonicalBridgeIdentitySources(bridges) {
|
|
9724
|
+
return canonicalBridgeIdentityBindings(bridges).map((binding) => binding.source);
|
|
9725
|
+
}
|
|
8985
9726
|
|
|
8986
9727
|
// src/effective-governance-inputs.ts
|
|
8987
9728
|
var EFFECTIVE_GOVERNANCE_INPUTS_SCHEMA = "effective-governance-inputs:v1";
|
|
@@ -9526,6 +10267,7 @@ export {
|
|
|
9526
10267
|
CONTRACT_DOMAINS,
|
|
9527
10268
|
CONTRACT_PREIMAGE_CAPABILITY_HEADER,
|
|
9528
10269
|
CONTRACT_PREIMAGE_SCHEMA,
|
|
10270
|
+
CompiledFragmentsFileValidationError,
|
|
9529
10271
|
DEFAULTS,
|
|
9530
10272
|
DEFAULT_ENHANCED_STYLE_PROPERTIES,
|
|
9531
10273
|
DEFAULT_STYLE_PROPERTIES,
|
|
@@ -9548,6 +10290,8 @@ export {
|
|
|
9548
10290
|
REGISTRY_INSTALL_RECEIPT_SCHEMA_VERSION,
|
|
9549
10291
|
REGISTRY_MANIFEST_SCHEMA_VERSION,
|
|
9550
10292
|
RULES,
|
|
10293
|
+
RULE_FAMILY_IDS,
|
|
10294
|
+
RULE_FAMILY_MEMBERS,
|
|
9551
10295
|
RULE_TIER,
|
|
9552
10296
|
SEVERITIES,
|
|
9553
10297
|
SEVERITY_RANK,
|
|
@@ -9585,6 +10329,8 @@ export {
|
|
|
9585
10329
|
calculateDeltaE,
|
|
9586
10330
|
canBlock,
|
|
9587
10331
|
canonicalBridgeContractMappings,
|
|
10332
|
+
canonicalBridgeIdentityBindings,
|
|
10333
|
+
canonicalBridgeIdentitySources,
|
|
9588
10334
|
canonicalBridgeV1Schema,
|
|
9589
10335
|
canonicalDirectionConflictsTarget,
|
|
9590
10336
|
canonicalJson,
|
|
@@ -9599,6 +10345,8 @@ export {
|
|
|
9599
10345
|
classifyComplexity,
|
|
9600
10346
|
classifyIdentity,
|
|
9601
10347
|
collectRegistryInstallDependencies,
|
|
10348
|
+
collidingRecordDiagnostic,
|
|
10349
|
+
collidingRecordDiagnostics,
|
|
9602
10350
|
colorSimilarity,
|
|
9603
10351
|
compareByVocabularyRank,
|
|
9604
10352
|
compareColors,
|
|
@@ -9623,6 +10371,7 @@ export {
|
|
|
9623
10371
|
compositionPatternSchema,
|
|
9624
10372
|
computeRegistryHash,
|
|
9625
10373
|
configDeclarationForDiagnostics,
|
|
10374
|
+
configRecordShape,
|
|
9626
10375
|
containsTailwindV4Theme,
|
|
9627
10376
|
contractComponentsFromFragments,
|
|
9628
10377
|
contractHash,
|
|
@@ -9633,14 +10382,17 @@ export {
|
|
|
9633
10382
|
defineConfig,
|
|
9634
10383
|
defineFragment,
|
|
9635
10384
|
defineRecipe,
|
|
10385
|
+
describePolicyExclude,
|
|
9636
10386
|
detectOrphanGovernanceScales,
|
|
9637
10387
|
detectSubComponentPaths,
|
|
9638
10388
|
detectUnconsumedConfigKeys,
|
|
10389
|
+
detectUnmatchedPolicyExcludes,
|
|
9639
10390
|
diffContractDomains,
|
|
9640
10391
|
discoverComponents,
|
|
9641
10392
|
dtcgTokenFileSchema,
|
|
9642
10393
|
emptyConformResult,
|
|
9643
10394
|
evaluateGovernanceIntegrity,
|
|
10395
|
+
excludeGlobToRegExp,
|
|
9644
10396
|
executeVariantLoaders,
|
|
9645
10397
|
explainUrlForCode,
|
|
9646
10398
|
factEvidenceSchema,
|
|
@@ -9713,8 +10465,10 @@ export {
|
|
|
9713
10465
|
isFigmaPropMapping,
|
|
9714
10466
|
isForceIncluded,
|
|
9715
10467
|
isPortableRepoPath,
|
|
10468
|
+
isPresetSourcedRule,
|
|
9716
10469
|
isRawHtmlAdvisoryTier,
|
|
9717
10470
|
isReactComponent,
|
|
10471
|
+
isRuleFamilyId,
|
|
9718
10472
|
localCanonicalContractMappings,
|
|
9719
10473
|
lowerCompositionContract,
|
|
9720
10474
|
makeA11yNameRequiredFact,
|
|
@@ -9752,12 +10506,15 @@ export {
|
|
|
9752
10506
|
makeTailwindUnknownClassEnabledFact,
|
|
9753
10507
|
makeThemeDeclarationFact,
|
|
9754
10508
|
makeTokenDefinitionFact,
|
|
10509
|
+
makeUsageChildContentFact,
|
|
9755
10510
|
makeUsageComponentFact,
|
|
9756
10511
|
makeUsageImportFact,
|
|
9757
10512
|
makeUsageInlineStyleFact,
|
|
9758
10513
|
makeUsageNodeFact,
|
|
9759
10514
|
makeUsagePropResolvedFact,
|
|
9760
10515
|
makeUsageTextChildFact,
|
|
10516
|
+
markPresetSourcedRules,
|
|
10517
|
+
matchPolicyExclude,
|
|
9761
10518
|
matchesGlob,
|
|
9762
10519
|
maxSeverity,
|
|
9763
10520
|
mcpBlockSchema,
|
|
@@ -9783,8 +10540,10 @@ export {
|
|
|
9783
10540
|
nearestSignedScaleValue,
|
|
9784
10541
|
normalizeColor,
|
|
9785
10542
|
normalizeConfigPath,
|
|
10543
|
+
normalizeExcludePath,
|
|
9786
10544
|
normalizeFinding,
|
|
9787
10545
|
normalizeGovernanceConfig,
|
|
10546
|
+
normalizePolicyExcludes,
|
|
9788
10547
|
normalizeRegistryRepoPath,
|
|
9789
10548
|
normalizeSeverity,
|
|
9790
10549
|
normalizeStyleValue,
|
|
@@ -9792,9 +10551,11 @@ export {
|
|
|
9792
10551
|
normalizeTokenGroupComment,
|
|
9793
10552
|
normalizeTokenValue,
|
|
9794
10553
|
normalizeViolation,
|
|
10554
|
+
overriddenRecordSeverityDiagnostic,
|
|
9795
10555
|
ownedImportEquivalents,
|
|
9796
10556
|
parseColor,
|
|
9797
10557
|
parseColorToRgb,
|
|
10558
|
+
parseCompiledFragmentsFile,
|
|
9798
10559
|
parseComponentContract,
|
|
9799
10560
|
parseContractStamp,
|
|
9800
10561
|
parseCssTokens,
|
|
@@ -9807,9 +10568,12 @@ export {
|
|
|
9807
10568
|
parseTailwindV4Theme,
|
|
9808
10569
|
parseTokenFile,
|
|
9809
10570
|
parseTokens,
|
|
10571
|
+
policyExcludeMatchesPath,
|
|
10572
|
+
policyExcludeSchema,
|
|
9810
10573
|
portableRepoPathError,
|
|
9811
10574
|
projectCanonicalDirectionConflicts,
|
|
9812
10575
|
projectContractPreimage,
|
|
10576
|
+
projectSupersededImportPathPreferences,
|
|
9813
10577
|
projectV1OwnedComponentId,
|
|
9814
10578
|
projectV1OwnedImportIdentity,
|
|
9815
10579
|
propDefinitionSchema,
|
|
@@ -9849,10 +10613,12 @@ export {
|
|
|
9849
10613
|
resolveTokenValue,
|
|
9850
10614
|
rgbToHex,
|
|
9851
10615
|
ruleA11yRequiredAccessibleName,
|
|
10616
|
+
ruleA11yStandard,
|
|
9852
10617
|
ruleComponentsForbiddenPropValue,
|
|
9853
10618
|
ruleComponentsPreferLibrary,
|
|
9854
10619
|
ruleComponentsShadowComponent,
|
|
9855
10620
|
ruleComponentsUnknownProp,
|
|
10621
|
+
ruleFamilyMembers,
|
|
9856
10622
|
ruleJsxPreferredComponent,
|
|
9857
10623
|
ruleJsxPreferredImportPath,
|
|
9858
10624
|
rulePropsInvalidValue,
|