@usefragments/core 1.0.0 → 1.3.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-R3X7UBHU.js → chunk-57QDBEHQ.js} +101 -6
- package/dist/chunk-57QDBEHQ.js.map +1 -0
- package/dist/{chunk-243QYRUF.js → chunk-MLRDNSSA.js} +88 -47
- package/dist/chunk-MLRDNSSA.js.map +1 -0
- package/dist/codes/index.d.ts +2 -2
- package/dist/codes/index.js +2 -2
- package/dist/compiled-types/index.d.ts +1 -1
- package/dist/generate/index.d.ts +1 -1
- package/dist/{governance-BOa3KyiJ.d.ts → governance-DYSirwJu.d.ts} +22 -1
- package/dist/index-h_yWj15D.d.ts +6981 -0
- package/dist/index.d.ts +113 -97
- package/dist/index.js +387 -113
- 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 +3 -2584
- package/dist/schemas/index.js +9 -1
- package/dist/test-utils.d.ts +1 -1
- package/package.json +1 -1
- package/src/agent-format.test.ts +144 -2
- package/src/agent-format.ts +34 -7
- package/src/codes/__tests__/codes.test.ts +57 -7
- package/src/codes/codes.ts +44 -42
- package/src/conform-prove.ts +4 -8
- package/src/contract/index.ts +3 -0
- package/src/contract/preimage.test.ts +50 -0
- package/src/contract/preimage.ts +56 -0
- package/src/evidence.ts +27 -0
- package/src/facts/builders.ts +4 -0
- package/src/facts/fact-index.ts +25 -1
- package/src/facts/facts.test.ts +49 -0
- package/src/facts/types.ts +3 -0
- package/src/governance-integrity.test.ts +2 -0
- package/src/governance-integrity.ts +7 -1
- package/src/index.ts +13 -0
- package/src/rules/__tests__/fix-emission-invariant.test.ts +174 -0
- package/src/rules/__tests__/tokens-require-dual-fallback.test.ts +90 -0
- package/src/rules/components-prefer-library.ts +165 -7
- package/src/rules/emit-gate.test.ts +40 -2
- package/src/rules/emit-gate.ts +21 -10
- package/src/rules/finding.ts +3 -0
- package/src/rules/fix-availability.ts +33 -0
- package/src/rules/rules.test.ts +180 -24
- package/src/rules/spacing-resolution.ts +5 -8
- package/src/rules/styles-no-raw-color.ts +32 -23
- package/src/rules/styles-no-raw-dimensions.ts +31 -12
- package/src/rules/styles-no-raw-spacing.ts +33 -11
- package/src/rules/styles-no-raw-typography.ts +37 -15
- package/src/rules/taxonomy.test.ts +19 -3
- package/src/rules/tiers.ts +1 -1
- package/src/rules/tokens-require-dual-fallback.ts +54 -17
- package/src/rules/utils.ts +51 -0
- package/src/schemas/index.ts +106 -4
- package/src/token-types.ts +6 -0
- package/src/tokens/__tests__/source-names.test.ts +42 -0
- package/src/tokens/design-token-parser.test.ts +24 -5
- package/src/tokens/design-token-parser.ts +40 -8
- package/dist/chunk-243QYRUF.js.map +0 -1
- package/dist/chunk-R3X7UBHU.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ComponentGraphEngine
|
|
3
|
-
} from "./chunk-SH4KPIYH.js";
|
|
4
1
|
import {
|
|
5
2
|
generateContext
|
|
6
3
|
} from "./chunk-X34IA4LR.js";
|
|
4
|
+
import {
|
|
5
|
+
ComponentGraphEngine
|
|
6
|
+
} from "./chunk-SH4KPIYH.js";
|
|
7
7
|
import {
|
|
8
8
|
resolveArea
|
|
9
9
|
} from "./chunk-3LLRNCPX.js";
|
|
@@ -109,9 +109,13 @@ import {
|
|
|
109
109
|
ownedImportsEqual,
|
|
110
110
|
resolveComponentGovernance,
|
|
111
111
|
scaleGovernanceRecordSchema
|
|
112
|
-
} from "./chunk-
|
|
112
|
+
} from "./chunk-MLRDNSSA.js";
|
|
113
113
|
import {
|
|
114
|
+
AGENT_FORMAT_SCHEMA_VERSION,
|
|
115
|
+
agentErrorEnvelopeSchema,
|
|
114
116
|
agentFormatSchema,
|
|
117
|
+
agentIntegritySchema,
|
|
118
|
+
agentOutputSchema,
|
|
115
119
|
factEvidenceSchema,
|
|
116
120
|
factLocationSchema,
|
|
117
121
|
findingFixSchema,
|
|
@@ -130,7 +134,7 @@ import {
|
|
|
130
134
|
suppressionDirectiveSchema,
|
|
131
135
|
validatorResultSchema,
|
|
132
136
|
violationSchema
|
|
133
|
-
} from "./chunk-
|
|
137
|
+
} from "./chunk-57QDBEHQ.js";
|
|
134
138
|
import {
|
|
135
139
|
OWNED_PACKAGE_IDENTITY_EPOCH,
|
|
136
140
|
OWNED_PACKAGE_IDENTITY_SOURCE_SHA256,
|
|
@@ -234,6 +238,18 @@ var DEFAULTS = {
|
|
|
234
238
|
port: 6006
|
|
235
239
|
};
|
|
236
240
|
|
|
241
|
+
// src/evidence.ts
|
|
242
|
+
var EVIDENCE_ORDER = [
|
|
243
|
+
"none",
|
|
244
|
+
"runtime_advisory",
|
|
245
|
+
"provenance_bound",
|
|
246
|
+
"source_backed",
|
|
247
|
+
"receipt_backed"
|
|
248
|
+
];
|
|
249
|
+
function canBlock(grade) {
|
|
250
|
+
return EVIDENCE_ORDER.indexOf(grade) >= EVIDENCE_ORDER.indexOf("source_backed");
|
|
251
|
+
}
|
|
252
|
+
|
|
237
253
|
// src/conform-prove.ts
|
|
238
254
|
var PROVE_MAX_PASSES = 8;
|
|
239
255
|
var PROVE_DEFAULT_MAX_PASSES = 4;
|
|
@@ -382,7 +398,9 @@ async function proveCompliant(input, callbacks, options = {}) {
|
|
|
382
398
|
|
|
383
399
|
// src/agent-format.ts
|
|
384
400
|
function buildAgentFormat(input) {
|
|
385
|
-
const
|
|
401
|
+
const zeroRulesLoaded = input.integrity.rulesLoaded === 0;
|
|
402
|
+
const integrityFailed = !input.integrity.healthy || input.integrity.inert;
|
|
403
|
+
const score = integrityFailed ? 0 : scoreFindings(input.findings);
|
|
386
404
|
const visibleFindings = uniqueFindings(input.findings).slice(0, input.maxFindings ?? 50);
|
|
387
405
|
const agentFindings = visibleFindings.map((finding) => ({
|
|
388
406
|
code: finding.code,
|
|
@@ -393,15 +411,15 @@ function buildAgentFormat(input) {
|
|
|
393
411
|
severity: finding.severity,
|
|
394
412
|
level: severityLevel(finding.severity),
|
|
395
413
|
message: finding.message,
|
|
414
|
+
evidenceGrade: finding.evidenceGrade,
|
|
396
415
|
evidence: input.includeEvidence ? finding.evidence : [],
|
|
397
416
|
plan: planForFinding(finding)
|
|
398
417
|
}));
|
|
399
418
|
const hasErrors = input.findings.some((finding) => severityLevel(finding.severity) === "error");
|
|
400
|
-
|
|
401
|
-
schemaVersion:
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
summary: agentSummary(input.findings.length, input.filesScanned, score),
|
|
419
|
+
const shared = {
|
|
420
|
+
schemaVersion: AGENT_FORMAT_SCHEMA_VERSION,
|
|
421
|
+
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),
|
|
422
|
+
integrity: input.integrity,
|
|
405
423
|
fix_first: agentFindings.filter((finding) => finding.plan.confidence >= 0.8),
|
|
406
424
|
remaining: agentFindings.filter((finding) => finding.plan.confidence < 0.8),
|
|
407
425
|
suppression_template: {
|
|
@@ -422,7 +440,23 @@ function buildAgentFormat(input) {
|
|
|
422
440
|
...suppression.expiresOn ? { expiresOn: suppression.expiresOn } : {},
|
|
423
441
|
scope: "line"
|
|
424
442
|
}))
|
|
425
|
-
} : {}
|
|
443
|
+
} : {},
|
|
444
|
+
...input.provenance ? { provenance: input.provenance } : {},
|
|
445
|
+
...input.verdict ? { verdict: input.verdict } : {}
|
|
446
|
+
};
|
|
447
|
+
if (zeroRulesLoaded) {
|
|
448
|
+
return {
|
|
449
|
+
...shared,
|
|
450
|
+
integrity: { ...input.integrity, rulesLoaded: 0 },
|
|
451
|
+
passed: false,
|
|
452
|
+
status: "indeterminate",
|
|
453
|
+
reasons: ["zero_rules_loaded"]
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
...shared,
|
|
458
|
+
passed: !integrityFailed && (input.passed ?? true) && !hasErrors,
|
|
459
|
+
score
|
|
426
460
|
};
|
|
427
461
|
}
|
|
428
462
|
function uniqueFindings(findings) {
|
|
@@ -3320,19 +3354,35 @@ function parsedOutputToDesignTokens(output, filePath) {
|
|
|
3320
3354
|
return tokens;
|
|
3321
3355
|
}
|
|
3322
3356
|
function scssOutputToDesignTokens(output, filePath) {
|
|
3323
|
-
const
|
|
3324
|
-
const tokens = [];
|
|
3357
|
+
const tokensByName = /* @__PURE__ */ new Map();
|
|
3325
3358
|
for (const cat of Object.values(output.categories)) {
|
|
3326
3359
|
for (const t of cat) {
|
|
3327
|
-
const
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3360
|
+
const isScssVariable = t.name.startsWith("$");
|
|
3361
|
+
const cssName = isScssVariable ? `--${t.name.slice(1)}` : t.name;
|
|
3362
|
+
if (!cssName.startsWith("--")) continue;
|
|
3363
|
+
const existing = tokensByName.get(cssName);
|
|
3364
|
+
if (!existing) {
|
|
3365
|
+
tokensByName.set(cssName, {
|
|
3366
|
+
parsed: t,
|
|
3367
|
+
referenceFormat: isScssVariable ? "scss-var" : "css-var",
|
|
3368
|
+
sourceNames: /* @__PURE__ */ new Set([t.name])
|
|
3369
|
+
});
|
|
3370
|
+
} else {
|
|
3371
|
+
existing.sourceNames.add(t.name);
|
|
3372
|
+
if (isScssVariable) {
|
|
3373
|
+
existing.parsed = t;
|
|
3374
|
+
existing.referenceFormat = "scss-var";
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3331
3377
|
}
|
|
3332
3378
|
}
|
|
3333
|
-
return
|
|
3379
|
+
return [...tokensByName].map(
|
|
3380
|
+
([name, token]) => designTokenFromParsed(name, token.parsed, filePath, token.referenceFormat, [
|
|
3381
|
+
...token.sourceNames
|
|
3382
|
+
])
|
|
3383
|
+
);
|
|
3334
3384
|
}
|
|
3335
|
-
function designTokenFromParsed(name, parsed, filePath) {
|
|
3385
|
+
function designTokenFromParsed(name, parsed, filePath, referenceFormat, sourceNames) {
|
|
3336
3386
|
return {
|
|
3337
3387
|
name,
|
|
3338
3388
|
rawValue: parsed.value ?? "",
|
|
@@ -3345,6 +3395,8 @@ function designTokenFromParsed(name, parsed, filePath) {
|
|
|
3345
3395
|
level: 1,
|
|
3346
3396
|
referenceChain: [],
|
|
3347
3397
|
sourceFile: filePath,
|
|
3398
|
+
referenceFormat,
|
|
3399
|
+
...sourceNames?.length ? { sourceNames } : {},
|
|
3348
3400
|
theme: "default",
|
|
3349
3401
|
selector: ":root",
|
|
3350
3402
|
description: parsed.description
|
|
@@ -4283,12 +4335,37 @@ function makeFinding(input) {
|
|
|
4283
4335
|
fingerprint,
|
|
4284
4336
|
location: input.location,
|
|
4285
4337
|
evidence: input.evidence,
|
|
4338
|
+
evidenceGrade: input.evidenceGrade ?? "source_backed",
|
|
4286
4339
|
fix: input.fix,
|
|
4287
4340
|
attributes: input.attributes
|
|
4288
4341
|
});
|
|
4289
4342
|
}
|
|
4290
4343
|
|
|
4291
4344
|
// src/rules/utils.ts
|
|
4345
|
+
function emitFix(fix, proof, ix) {
|
|
4346
|
+
const reasons = [];
|
|
4347
|
+
if (proof.editKind !== fix.kind) reasons.push("edit kind does not match proof");
|
|
4348
|
+
if (!proof.valuePreserving) reasons.push("value-preservation proof missing");
|
|
4349
|
+
if (proof.symbols.length === 0) reasons.push("token-symbol proof missing");
|
|
4350
|
+
const missingSymbols = proof.symbols.filter((symbol) => !ix.tokens.symbolExists(symbol));
|
|
4351
|
+
if (missingSymbols.length > 0) {
|
|
4352
|
+
reasons.push(`absent token symbol: ${missingSymbols.join(", ")}`);
|
|
4353
|
+
}
|
|
4354
|
+
const inapplicableSymbols = proof.symbols.filter((symbol) => {
|
|
4355
|
+
const definition = ix.tokens.definitionForSymbol(symbol);
|
|
4356
|
+
return definition !== void 0 && !isApplicableTokenReference(definition.referenceFormat);
|
|
4357
|
+
});
|
|
4358
|
+
if (inapplicableSymbols.length > 0) {
|
|
4359
|
+
reasons.push(`token reference is not directly applicable: ${inapplicableSymbols.join(", ")}`);
|
|
4360
|
+
}
|
|
4361
|
+
return {
|
|
4362
|
+
fix: { ...fix, deterministic: reasons.length === 0 },
|
|
4363
|
+
...reasons.length > 0 ? { downgradeReason: reasons.join("; ") } : {}
|
|
4364
|
+
};
|
|
4365
|
+
}
|
|
4366
|
+
function tokenSymbolsInText(value) {
|
|
4367
|
+
return [...new Set(value.match(/\$[A-Za-z0-9_-]+|--[A-Za-z0-9_-]+/g) ?? [])];
|
|
4368
|
+
}
|
|
4292
4369
|
function indexPropsByNodeId(ix) {
|
|
4293
4370
|
const out = /* @__PURE__ */ new Map();
|
|
4294
4371
|
for (const fact of ix.byKind("usage_prop_resolved")) {
|
|
@@ -4367,13 +4444,6 @@ function readUsageNode(ix, id) {
|
|
|
4367
4444
|
const fact = ix.get(id);
|
|
4368
4445
|
return fact && fact.kind === "usage_node" ? fact : void 0;
|
|
4369
4446
|
}
|
|
4370
|
-
function cssVariableNames(tokens) {
|
|
4371
|
-
const names = /* @__PURE__ */ new Set();
|
|
4372
|
-
for (const token of tokens) {
|
|
4373
|
-
names.add(token.name.startsWith("--") ? token.name : `--${token.name}`);
|
|
4374
|
-
}
|
|
4375
|
-
return names;
|
|
4376
|
-
}
|
|
4377
4447
|
function contractPrefixFamilies(names) {
|
|
4378
4448
|
const families = /* @__PURE__ */ new Set();
|
|
4379
4449
|
for (const name of names) {
|
|
@@ -4994,7 +5064,8 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
4994
5064
|
if (node.element.includes(".")) continue;
|
|
4995
5065
|
const props = propsByNode.get(node.id) ?? [];
|
|
4996
5066
|
const textChildren = textByNode.get(node.id) ?? [];
|
|
4997
|
-
const
|
|
5067
|
+
const importsForFile = importsByFileAndLocal.get(node.file);
|
|
5068
|
+
const imported = importsForFile?.get(node.element);
|
|
4998
5069
|
const nodeInputType = node.element === "input" ? readStaticStringProp(props, "type") : void 0;
|
|
4999
5070
|
const mapped = findMapping(node, nodeInputType, mappings);
|
|
5000
5071
|
if (mapped && imported && isMappedCanonicalImport(imported, mapped)) continue;
|
|
@@ -5005,9 +5076,11 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5005
5076
|
const suggestedComponent2 = mapped.name;
|
|
5006
5077
|
const suggestedImport2 = importPath ?? "the canonical library";
|
|
5007
5078
|
const propMapping = mapped.propMapping ?? [];
|
|
5079
|
+
const propCompatibility = assessMappedPropCompatibility(props, propMapping);
|
|
5008
5080
|
const roleMatch = isRoleMatch(node, mapped);
|
|
5009
5081
|
const precisionTier2 = roleMatch ? "role-reimpl" : "exact-html";
|
|
5010
5082
|
if (imported && importPath && !ownedImportsEqual(imported.source, importPath) && node.element === suggestedComponent2) {
|
|
5083
|
+
if (!isLocalExactComponentImportRedirectCandidate(imported, suggestedComponent2)) continue;
|
|
5011
5084
|
const key = `${imported.file}\0${imported.local}\0${imported.source}\0${importPath}`;
|
|
5012
5085
|
if (seenImportFixes.has(key)) continue;
|
|
5013
5086
|
seenImportFixes.add(key);
|
|
@@ -5015,8 +5088,8 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5015
5088
|
makeFinding({
|
|
5016
5089
|
ruleId: RULE_ID2,
|
|
5017
5090
|
ruleVersion: RULE_VERSION2,
|
|
5018
|
-
severity: policy.severity
|
|
5019
|
-
message: `<${node.element}>
|
|
5091
|
+
severity: capAdvisorySeverity(policy.severity),
|
|
5092
|
+
message: `<${node.element}> may need to import from ${importPath}; verify that the local component contract is compatible first.`,
|
|
5020
5093
|
location: imported.location,
|
|
5021
5094
|
evidence: ix.evidence([node.id, imported.id, policy.id]),
|
|
5022
5095
|
fingerprintIdentity: {
|
|
@@ -5030,14 +5103,16 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5030
5103
|
title: `Replace import path with "${importPath}"`,
|
|
5031
5104
|
from: imported.source,
|
|
5032
5105
|
to: importPath,
|
|
5033
|
-
deterministic:
|
|
5106
|
+
deterministic: false
|
|
5034
5107
|
},
|
|
5035
5108
|
attributes: {
|
|
5036
5109
|
rawValue: imported.source,
|
|
5037
5110
|
suggestedComponent: suggestedComponent2,
|
|
5038
5111
|
suggestedImport: importPath,
|
|
5039
5112
|
canonical: mapped.canonical,
|
|
5040
|
-
confidence: mapped.confidence
|
|
5113
|
+
confidence: mapped.confidence,
|
|
5114
|
+
advisory: true,
|
|
5115
|
+
contractProof: "missing"
|
|
5041
5116
|
}
|
|
5042
5117
|
})
|
|
5043
5118
|
);
|
|
@@ -5067,7 +5142,11 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5067
5142
|
title: `Replace <${node.element}> with <${suggestedComponent2}>`,
|
|
5068
5143
|
from: node.element,
|
|
5069
5144
|
to: suggestedComponent2,
|
|
5070
|
-
|
|
5145
|
+
// The fact IR cannot prove that it observed every JSX
|
|
5146
|
+
// attribute shape (notably dynamic inline styles). The
|
|
5147
|
+
// conform AST preflight may promote this guided fix only
|
|
5148
|
+
// after it proves the complete prop surface directly.
|
|
5149
|
+
deterministic: false
|
|
5071
5150
|
}
|
|
5072
5151
|
} : {},
|
|
5073
5152
|
attributes: {
|
|
@@ -5077,8 +5156,12 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5077
5156
|
canonical: mapped.canonical,
|
|
5078
5157
|
confidence: mapped.confidence,
|
|
5079
5158
|
propMapping,
|
|
5159
|
+
propCompatibility: propCompatibility.complete ? "observed-complete" : "incomplete",
|
|
5160
|
+
...mapped.conformStatus === "confirmed" ? { canonicalMappingConfirmed: true } : {},
|
|
5161
|
+
...propCompatibility.unprovenProps.length > 0 ? { unprovenProps: propCompatibility.unprovenProps } : {},
|
|
5080
5162
|
precisionTier: precisionTier2,
|
|
5081
|
-
...roleMatch ? { matchedRole: node.role } : {}
|
|
5163
|
+
...roleMatch ? { matchedRole: node.role } : {},
|
|
5164
|
+
...isRawHtmlAdvisoryTier(precisionTier2) ? { advisory: true } : {}
|
|
5082
5165
|
}
|
|
5083
5166
|
})
|
|
5084
5167
|
);
|
|
@@ -5121,6 +5204,47 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5121
5204
|
const suggestion = findSuggestionSource(suggestedComponent, sources);
|
|
5122
5205
|
if (!suggestion) continue;
|
|
5123
5206
|
if (!builtIn && !shouldSuggest(node, imported)) continue;
|
|
5207
|
+
if (node.element === suggestedComponent) {
|
|
5208
|
+
const importPath = canonicalImportPath(suggestion);
|
|
5209
|
+
if (!imported || !importPath || !isLocalExactComponentImportRedirectCandidate(imported, suggestedComponent)) {
|
|
5210
|
+
continue;
|
|
5211
|
+
}
|
|
5212
|
+
const key = `${imported.file}\0${imported.local}\0${imported.source}\0${importPath}`;
|
|
5213
|
+
if (seenImportFixes.has(key)) continue;
|
|
5214
|
+
seenImportFixes.add(key);
|
|
5215
|
+
findings.push(
|
|
5216
|
+
makeFinding({
|
|
5217
|
+
ruleId: RULE_ID2,
|
|
5218
|
+
ruleVersion: RULE_VERSION2,
|
|
5219
|
+
severity: capAdvisorySeverity(policy.severity),
|
|
5220
|
+
message: `<${node.element}> may need to import from ${importPath}; verify that the local component contract is compatible first.`,
|
|
5221
|
+
location: imported.location,
|
|
5222
|
+
evidence: ix.evidence([node.id, imported.id, policy.id]),
|
|
5223
|
+
fingerprintIdentity: {
|
|
5224
|
+
file: imported.file,
|
|
5225
|
+
local: imported.local,
|
|
5226
|
+
from: imported.source,
|
|
5227
|
+
to: importPath
|
|
5228
|
+
},
|
|
5229
|
+
fix: {
|
|
5230
|
+
kind: "replaceImport",
|
|
5231
|
+
title: `Replace import path with "${importPath}"`,
|
|
5232
|
+
from: imported.source,
|
|
5233
|
+
to: importPath,
|
|
5234
|
+
deterministic: false
|
|
5235
|
+
},
|
|
5236
|
+
attributes: {
|
|
5237
|
+
rawValue: imported.source,
|
|
5238
|
+
suggestedComponent,
|
|
5239
|
+
suggestedImport: importPath,
|
|
5240
|
+
suggestedImportSourceKind: suggestion.kind,
|
|
5241
|
+
advisory: true,
|
|
5242
|
+
contractProof: "missing"
|
|
5243
|
+
}
|
|
5244
|
+
})
|
|
5245
|
+
);
|
|
5246
|
+
continue;
|
|
5247
|
+
}
|
|
5124
5248
|
if (suggestion.kind === "directory") {
|
|
5125
5249
|
if (node.element === suggestedComponent) continue;
|
|
5126
5250
|
if (!builtIn) continue;
|
|
@@ -5163,7 +5287,10 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5163
5287
|
title: `Replace ${rawElement} with <${suggestedComponent}>`,
|
|
5164
5288
|
from: node.element,
|
|
5165
5289
|
to: suggestedComponent,
|
|
5166
|
-
|
|
5290
|
+
// A canonical source proves that the target exists, not that
|
|
5291
|
+
// this native element's complete prop contract is compatible.
|
|
5292
|
+
// Only explicit canonical mappings carry that proof.
|
|
5293
|
+
deterministic: false
|
|
5167
5294
|
}
|
|
5168
5295
|
} : {},
|
|
5169
5296
|
attributes: {
|
|
@@ -5172,6 +5299,7 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
5172
5299
|
suggestedImport,
|
|
5173
5300
|
suggestedImportSourceKind: suggestion.kind,
|
|
5174
5301
|
precisionTier,
|
|
5302
|
+
propCompatibility: "unknown",
|
|
5175
5303
|
...builtIn?.matchedRole ? { matchedRole: builtIn.matchedRole } : {},
|
|
5176
5304
|
...builtIn?.inputType ? { matchedInputType: builtIn.inputType } : {},
|
|
5177
5305
|
...isRawHtmlAdvisoryTier(precisionTier) ? { advisory: true } : {}
|
|
@@ -5288,6 +5416,39 @@ function shouldSuggest(node, imported) {
|
|
|
5288
5416
|
if (!/^[A-Z]/.test(node.element)) return false;
|
|
5289
5417
|
return imported !== void 0;
|
|
5290
5418
|
}
|
|
5419
|
+
function assessMappedPropCompatibility(props, mappings) {
|
|
5420
|
+
const byRawProp = new Map(mappings.map((mapping) => [mapping.rawProp, mapping]));
|
|
5421
|
+
const unprovenProps = props.flatMap((prop) => {
|
|
5422
|
+
if (prop.resolution === "spread") return [prop.prop];
|
|
5423
|
+
const mapping = byRawProp.get(prop.prop);
|
|
5424
|
+
if (!mapping || !mappingCanPreserveProp(mapping, prop)) return [prop.prop];
|
|
5425
|
+
return [];
|
|
5426
|
+
});
|
|
5427
|
+
return { complete: unprovenProps.length === 0, unprovenProps };
|
|
5428
|
+
}
|
|
5429
|
+
function mappingCanPreserveProp(mapping, prop) {
|
|
5430
|
+
const renames = mapping.rawProp !== mapping.canonicalProp;
|
|
5431
|
+
const valueMap = mapping.valueMap;
|
|
5432
|
+
const translatesValue = valueMap !== void 0 && Object.keys(valueMap).length > 0;
|
|
5433
|
+
if (renames && (!isIdentifier(mapping.rawProp) || !isIdentifier(mapping.canonicalProp))) {
|
|
5434
|
+
return false;
|
|
5435
|
+
}
|
|
5436
|
+
if (!translatesValue) return true;
|
|
5437
|
+
if (!isIdentifier(mapping.rawProp)) return false;
|
|
5438
|
+
return prop.resolution === "static" && typeof prop.value === "string" && Object.prototype.hasOwnProperty.call(valueMap, prop.value);
|
|
5439
|
+
}
|
|
5440
|
+
function isLocalExactComponentImportRedirectCandidate(usage, componentName) {
|
|
5441
|
+
return isUnaliasedNamedImport(usage, componentName) && isProjectLocalImportSource(usage.source);
|
|
5442
|
+
}
|
|
5443
|
+
function isUnaliasedNamedImport(usage, componentName) {
|
|
5444
|
+
return isSimpleComponentBinding(usage, componentName) && usage.imported === componentName;
|
|
5445
|
+
}
|
|
5446
|
+
function isSimpleComponentBinding(usage, componentName) {
|
|
5447
|
+
return usage.namespace !== true && usage.local === componentName;
|
|
5448
|
+
}
|
|
5449
|
+
function isProjectLocalImportSource(source) {
|
|
5450
|
+
return source.startsWith(".") || source.startsWith("/") || source.startsWith("@/") || source.startsWith("~/");
|
|
5451
|
+
}
|
|
5291
5452
|
function sameHtmlTag(htmlEquivalent, element) {
|
|
5292
5453
|
return htmlEquivalent !== void 0 && htmlEquivalent.toLowerCase() === element.toLowerCase();
|
|
5293
5454
|
}
|
|
@@ -5320,6 +5481,7 @@ function isCanonicalMappingOption(value) {
|
|
|
5320
5481
|
if (record.htmlType !== void 0 && typeof record.htmlType !== "string") return false;
|
|
5321
5482
|
if (record.importPath !== void 0 && typeof record.importPath !== "string") return false;
|
|
5322
5483
|
if (record.confidence !== void 0 && typeof record.confidence !== "number") return false;
|
|
5484
|
+
if (record.conformStatus !== void 0 && record.conformStatus !== "confirmed") return false;
|
|
5323
5485
|
if (record.declaredHtmlEquivalent !== void 0 && typeof record.declaredHtmlEquivalent !== "boolean") {
|
|
5324
5486
|
return false;
|
|
5325
5487
|
}
|
|
@@ -5422,6 +5584,11 @@ function sourceLabel(source) {
|
|
|
5422
5584
|
if (source.kind === "registry") return source.importPath ?? source.installPath;
|
|
5423
5585
|
return source.path;
|
|
5424
5586
|
}
|
|
5587
|
+
function canonicalImportPath(source) {
|
|
5588
|
+
if (source.kind === "npm") return source.specifier;
|
|
5589
|
+
if (source.kind === "registry") return source.importPath;
|
|
5590
|
+
return void 0;
|
|
5591
|
+
}
|
|
5425
5592
|
function isCanonicalSourceImplementationFile(file, sources) {
|
|
5426
5593
|
const normalizedFile = normalizePath(file);
|
|
5427
5594
|
return sources.some((source) => {
|
|
@@ -6067,6 +6234,7 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6067
6234
|
if (!color) continue;
|
|
6068
6235
|
if (isExemptColor(color, policy.except)) continue;
|
|
6069
6236
|
const resolution = resolveColorToken(decl.property, color, colorTokens);
|
|
6237
|
+
const fixEmission = resolution?.kind === "exact" ? buildTokenFix(decl.property, decl.value, color, resolution, ix) : void 0;
|
|
6070
6238
|
const evidenceIds = resolution ? [decl.id, policy.id, resolution.token.id] : [decl.id, policy.id];
|
|
6071
6239
|
findings.push(
|
|
6072
6240
|
makeFinding({
|
|
@@ -6084,14 +6252,15 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6084
6252
|
property: decl.property,
|
|
6085
6253
|
value: color
|
|
6086
6254
|
},
|
|
6087
|
-
fix:
|
|
6255
|
+
fix: fixEmission?.fix,
|
|
6088
6256
|
attributes: {
|
|
6089
6257
|
property: decl.property,
|
|
6090
6258
|
rawValue: decl.value,
|
|
6091
6259
|
color,
|
|
6092
6260
|
source: "css",
|
|
6093
6261
|
suggestedToken: resolution?.token.name,
|
|
6094
|
-
...resolution ? { tokenMatch: resolution.kind } : {}
|
|
6262
|
+
...resolution ? { tokenMatch: resolution.kind } : {},
|
|
6263
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6095
6264
|
}
|
|
6096
6265
|
})
|
|
6097
6266
|
);
|
|
@@ -6106,6 +6275,7 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6106
6275
|
if (!node) continue;
|
|
6107
6276
|
const componentEvidenceId = componentByNode.get(node.id)?.id;
|
|
6108
6277
|
const resolution = resolveColorToken(inline.property, color, colorTokens);
|
|
6278
|
+
const fixEmission = resolution?.kind === "exact" ? buildTokenFix(inline.property, inline.value, color, resolution, ix) : void 0;
|
|
6109
6279
|
const baseEvidence = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id] : [node.id, inline.id, policy.id];
|
|
6110
6280
|
const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
|
|
6111
6281
|
findings.push(
|
|
@@ -6124,14 +6294,15 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6124
6294
|
property: inline.property,
|
|
6125
6295
|
value: color
|
|
6126
6296
|
},
|
|
6127
|
-
fix:
|
|
6297
|
+
fix: fixEmission?.fix,
|
|
6128
6298
|
attributes: {
|
|
6129
6299
|
property: inline.property,
|
|
6130
6300
|
rawValue: inline.value,
|
|
6131
6301
|
color,
|
|
6132
6302
|
source: "jsx",
|
|
6133
6303
|
suggestedToken: resolution?.token.name,
|
|
6134
|
-
...resolution ? { tokenMatch: resolution.kind } : {}
|
|
6304
|
+
...resolution ? { tokenMatch: resolution.kind } : {},
|
|
6305
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6135
6306
|
}
|
|
6136
6307
|
})
|
|
6137
6308
|
);
|
|
@@ -6215,21 +6386,24 @@ function normalizeColor2(value) {
|
|
|
6215
6386
|
}
|
|
6216
6387
|
return lower;
|
|
6217
6388
|
}
|
|
6218
|
-
function buildTokenFix(property, rawValue, rawColor, match) {
|
|
6389
|
+
function buildTokenFix(property, rawValue, rawColor, match, ix) {
|
|
6219
6390
|
const reference = tokenReference(match.token.name);
|
|
6220
6391
|
const value = replaceRawColor(rawValue, rawColor, reference);
|
|
6221
|
-
return
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6392
|
+
return emitFix(
|
|
6393
|
+
{
|
|
6394
|
+
kind: "replaceStyleValue",
|
|
6395
|
+
title: `Replace ${property} with ${value}`,
|
|
6396
|
+
property,
|
|
6397
|
+
value,
|
|
6398
|
+
deterministic: true
|
|
6399
|
+
},
|
|
6400
|
+
{
|
|
6401
|
+
symbols: [match.token.name],
|
|
6402
|
+
editKind: "replaceStyleValue",
|
|
6403
|
+
valuePreserving: !match.ambiguous
|
|
6404
|
+
},
|
|
6405
|
+
ix
|
|
6406
|
+
);
|
|
6233
6407
|
}
|
|
6234
6408
|
function replaceRawColor(rawValue, rawColor, replacement) {
|
|
6235
6409
|
if (rawValue === rawColor) return replacement;
|
|
@@ -6260,6 +6434,7 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6260
6434
|
if (!resolution) continue;
|
|
6261
6435
|
const tokenIds = resolutionTokenIds(resolution);
|
|
6262
6436
|
const evidenceIds = tokenIds.length ? [decl.id, policy.id, ...tokenIds] : [decl.id, policy.id];
|
|
6437
|
+
const fixEmission = dimensionFix(decl.property, resolution, ix);
|
|
6263
6438
|
findings.push(
|
|
6264
6439
|
makeFinding({
|
|
6265
6440
|
ruleId: RULE_ID9,
|
|
@@ -6276,12 +6451,13 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6276
6451
|
property: decl.property,
|
|
6277
6452
|
value: decl.value
|
|
6278
6453
|
},
|
|
6279
|
-
fix:
|
|
6454
|
+
fix: fixEmission?.fix,
|
|
6280
6455
|
attributes: {
|
|
6281
6456
|
property: decl.property,
|
|
6282
6457
|
rawValue: decl.value,
|
|
6283
6458
|
source: "css",
|
|
6284
|
-
suggestedToken: resolutionSuggestedToken(resolution)
|
|
6459
|
+
suggestedToken: resolutionSuggestedToken(resolution),
|
|
6460
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6285
6461
|
}
|
|
6286
6462
|
})
|
|
6287
6463
|
);
|
|
@@ -6300,6 +6476,7 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6300
6476
|
const baseEvidence = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id] : [node.id, inline.id, policy.id];
|
|
6301
6477
|
const tokenIds = resolutionTokenIds(resolution);
|
|
6302
6478
|
const evidenceIds = tokenIds.length ? [...baseEvidence, ...tokenIds] : baseEvidence;
|
|
6479
|
+
const fixEmission = dimensionFix(inline.property, resolution, ix);
|
|
6303
6480
|
findings.push(
|
|
6304
6481
|
makeFinding({
|
|
6305
6482
|
ruleId: RULE_ID9,
|
|
@@ -6316,12 +6493,13 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6316
6493
|
property: inline.property,
|
|
6317
6494
|
value: inline.value
|
|
6318
6495
|
},
|
|
6319
|
-
fix:
|
|
6496
|
+
fix: fixEmission?.fix,
|
|
6320
6497
|
attributes: {
|
|
6321
6498
|
property: inline.property,
|
|
6322
6499
|
rawValue: inline.value,
|
|
6323
6500
|
source: "jsx",
|
|
6324
|
-
suggestedToken: resolutionSuggestedToken(resolution)
|
|
6501
|
+
suggestedToken: resolutionSuggestedToken(resolution),
|
|
6502
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6325
6503
|
}
|
|
6326
6504
|
})
|
|
6327
6505
|
);
|
|
@@ -6366,15 +6544,23 @@ function dimensionMessage(rawValue, property, preferLabel, resolution, inline) {
|
|
|
6366
6544
|
}
|
|
6367
6545
|
return `Raw dimension ${rawValue} on ${where}. Use a ${preferLabel} instead.`;
|
|
6368
6546
|
}
|
|
6369
|
-
function dimensionFix(property, resolution) {
|
|
6547
|
+
function dimensionFix(property, resolution, ix) {
|
|
6370
6548
|
if (resolution.kind === "exact") {
|
|
6371
|
-
return
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6549
|
+
return emitFix(
|
|
6550
|
+
{
|
|
6551
|
+
kind: "replaceStyleValue",
|
|
6552
|
+
title: `Replace ${property} with ${resolution.fixValue}`,
|
|
6553
|
+
property,
|
|
6554
|
+
value: resolution.fixValue,
|
|
6555
|
+
deterministic: true
|
|
6556
|
+
},
|
|
6557
|
+
{
|
|
6558
|
+
symbols: resolution.tokens.map((token) => token.name),
|
|
6559
|
+
editKind: "replaceStyleValue",
|
|
6560
|
+
valuePreserving: true
|
|
6561
|
+
},
|
|
6562
|
+
ix
|
|
6563
|
+
);
|
|
6378
6564
|
}
|
|
6379
6565
|
return void 0;
|
|
6380
6566
|
}
|
|
@@ -6475,7 +6661,7 @@ function resolveSpacingValue(input) {
|
|
|
6475
6661
|
let anyOffScale = false;
|
|
6476
6662
|
let canFixAll = true;
|
|
6477
6663
|
let reliableSuggestion = true;
|
|
6478
|
-
let
|
|
6664
|
+
let allValuePreserving = true;
|
|
6479
6665
|
for (const part of parts) {
|
|
6480
6666
|
const normalized = normalizeLengthForScale(part.parsed, input.scale);
|
|
6481
6667
|
if (normalized === null) return null;
|
|
@@ -6489,8 +6675,7 @@ function resolveSpacingValue(input) {
|
|
|
6489
6675
|
}
|
|
6490
6676
|
anyViolation = true;
|
|
6491
6677
|
const reference = tokenReference(exactToken.name);
|
|
6492
|
-
|
|
6493
|
-
if (!applicable) allApplicableValuePreserving = false;
|
|
6678
|
+
if (!normalized.deterministicFix) allValuePreserving = false;
|
|
6494
6679
|
suggestedParts.push(reference);
|
|
6495
6680
|
if (!firstViolation) {
|
|
6496
6681
|
firstViolation = {
|
|
@@ -6498,7 +6683,7 @@ function resolveSpacingValue(input) {
|
|
|
6498
6683
|
suggestedValue: reference,
|
|
6499
6684
|
suggestedToken: exactToken.name,
|
|
6500
6685
|
fixEmittable: true,
|
|
6501
|
-
deterministicFix:
|
|
6686
|
+
deterministicFix: normalized.deterministicFix,
|
|
6502
6687
|
reason: "token-equivalent",
|
|
6503
6688
|
matchedToken: exactToken.name,
|
|
6504
6689
|
assumedRootFontSizePx: normalized.assumedRootFontSizePx,
|
|
@@ -6509,7 +6694,7 @@ function resolveSpacingValue(input) {
|
|
|
6509
6694
|
}
|
|
6510
6695
|
anyViolation = true;
|
|
6511
6696
|
anyOffScale = true;
|
|
6512
|
-
|
|
6697
|
+
allValuePreserving = false;
|
|
6513
6698
|
const nearest = nearestSignedScaleValue(normalized.value, input.allowed);
|
|
6514
6699
|
const snapsToZero = nearest === 0 && normalized.value !== 0;
|
|
6515
6700
|
let suggestedPart;
|
|
@@ -6542,7 +6727,7 @@ function resolveSpacingValue(input) {
|
|
|
6542
6727
|
const joined = suggestedParts.join(" ");
|
|
6543
6728
|
const changes = joined !== input.raw.trim().replace(/\s+/g, " ");
|
|
6544
6729
|
const fixEmittable = canFixAll && reliableSuggestion && changes;
|
|
6545
|
-
const deterministic = fixEmittable && !anyOffScale &&
|
|
6730
|
+
const deterministic = fixEmittable && !anyOffScale && allValuePreserving;
|
|
6546
6731
|
return {
|
|
6547
6732
|
...firstViolation,
|
|
6548
6733
|
reason: anyOffScale ? "off-scale" : "token-equivalent",
|
|
@@ -6606,6 +6791,7 @@ function checkDeclaration(ix, decl, lookupFor) {
|
|
|
6606
6791
|
tokens: lookupFor(scale)
|
|
6607
6792
|
});
|
|
6608
6793
|
if (!checked) return null;
|
|
6794
|
+
const fixEmission = buildSpacingFix(decl.property, checked, ix);
|
|
6609
6795
|
return makeFinding({
|
|
6610
6796
|
ruleId: RULE_ID10,
|
|
6611
6797
|
ruleVersion: RULE_VERSION11,
|
|
@@ -6621,7 +6807,7 @@ function checkDeclaration(ix, decl, lookupFor) {
|
|
|
6621
6807
|
property: decl.property,
|
|
6622
6808
|
value: decl.value
|
|
6623
6809
|
},
|
|
6624
|
-
fix:
|
|
6810
|
+
fix: fixEmission?.fix,
|
|
6625
6811
|
attributes: {
|
|
6626
6812
|
property: decl.property,
|
|
6627
6813
|
rawValue: decl.value,
|
|
@@ -6635,7 +6821,8 @@ function checkDeclaration(ix, decl, lookupFor) {
|
|
|
6635
6821
|
normalizedUnit: scale.unit,
|
|
6636
6822
|
assumedRootFontSizePx: checked.assumedRootFontSizePx,
|
|
6637
6823
|
assumedEmBasePx: checked.assumedEmBasePx,
|
|
6638
|
-
source: "css"
|
|
6824
|
+
source: "css",
|
|
6825
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6639
6826
|
}
|
|
6640
6827
|
});
|
|
6641
6828
|
}
|
|
@@ -6655,6 +6842,7 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
|
|
|
6655
6842
|
bareNumberFix: inline.valueKind === "number"
|
|
6656
6843
|
});
|
|
6657
6844
|
if (!checked) return null;
|
|
6845
|
+
const fixEmission = buildSpacingFix(inline.property, checked, ix);
|
|
6658
6846
|
const node = readUsageNode(ix, inline.nodeId);
|
|
6659
6847
|
if (!node) return null;
|
|
6660
6848
|
const componentEvidenceId = componentByNode.get(node.id)?.id;
|
|
@@ -6674,7 +6862,7 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
|
|
|
6674
6862
|
property: inline.property,
|
|
6675
6863
|
value: inline.value
|
|
6676
6864
|
},
|
|
6677
|
-
fix:
|
|
6865
|
+
fix: fixEmission?.fix,
|
|
6678
6866
|
attributes: {
|
|
6679
6867
|
property: inline.property,
|
|
6680
6868
|
rawValue: inline.value,
|
|
@@ -6688,19 +6876,28 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
|
|
|
6688
6876
|
normalizedUnit: scale.unit,
|
|
6689
6877
|
assumedRootFontSizePx: checked.assumedRootFontSizePx,
|
|
6690
6878
|
assumedEmBasePx: checked.assumedEmBasePx,
|
|
6691
|
-
source: "jsx"
|
|
6879
|
+
source: "jsx",
|
|
6880
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6692
6881
|
}
|
|
6693
6882
|
});
|
|
6694
6883
|
}
|
|
6695
|
-
function buildSpacingFix(property, checked) {
|
|
6884
|
+
function buildSpacingFix(property, checked, ix) {
|
|
6696
6885
|
if (!checked.fixEmittable || checked.suggestedValue === void 0) return void 0;
|
|
6697
|
-
return
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6886
|
+
return emitFix(
|
|
6887
|
+
{
|
|
6888
|
+
kind: "replaceStyleValue",
|
|
6889
|
+
title: `Replace ${property} with ${checked.suggestedValue}`,
|
|
6890
|
+
property,
|
|
6891
|
+
value: checked.suggestedValue,
|
|
6892
|
+
deterministic: true
|
|
6893
|
+
},
|
|
6894
|
+
{
|
|
6895
|
+
symbols: tokenSymbolsInText(checked.suggestedValue),
|
|
6896
|
+
editKind: "replaceStyleValue",
|
|
6897
|
+
valuePreserving: checked.deterministicFix
|
|
6898
|
+
},
|
|
6899
|
+
ix
|
|
6900
|
+
);
|
|
6704
6901
|
}
|
|
6705
6902
|
function spacingMessage(property, value, allowed, unit, checked) {
|
|
6706
6903
|
if (checked.reason === "token-equivalent" && checked.matchedToken) {
|
|
@@ -6729,6 +6926,7 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6729
6926
|
if (decl.property.toLowerCase() !== "font-size") continue;
|
|
6730
6927
|
const checked = checkFontSize(decl.value, allowed, scale, tokens);
|
|
6731
6928
|
if (!checked) continue;
|
|
6929
|
+
const fixEmission = buildFix(decl.property, checked, ix);
|
|
6732
6930
|
findings.push(
|
|
6733
6931
|
makeFinding({
|
|
6734
6932
|
ruleId: RULE_ID11,
|
|
@@ -6745,7 +6943,7 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6745
6943
|
property: decl.property,
|
|
6746
6944
|
value: decl.value
|
|
6747
6945
|
},
|
|
6748
|
-
fix:
|
|
6946
|
+
fix: fixEmission?.fix,
|
|
6749
6947
|
attributes: {
|
|
6750
6948
|
property: decl.property,
|
|
6751
6949
|
rawValue: decl.value,
|
|
@@ -6753,7 +6951,8 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6753
6951
|
allowed,
|
|
6754
6952
|
suggestedValue: checked.suggestedValue,
|
|
6755
6953
|
matchedToken: checked.matchedToken,
|
|
6756
|
-
source: "css"
|
|
6954
|
+
source: "css",
|
|
6955
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6757
6956
|
}
|
|
6758
6957
|
})
|
|
6759
6958
|
);
|
|
@@ -6762,8 +6961,15 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6762
6961
|
for (const inline of ix.byKind("usage_inline_style")) {
|
|
6763
6962
|
if (inline.valueKind === "css-variable") continue;
|
|
6764
6963
|
if (!FONT_SIZE_PROPERTIES.has(inline.property.toLowerCase())) continue;
|
|
6765
|
-
const checked = checkFontSize(
|
|
6964
|
+
const checked = checkFontSize(
|
|
6965
|
+
inline.value,
|
|
6966
|
+
allowed,
|
|
6967
|
+
scale,
|
|
6968
|
+
tokens,
|
|
6969
|
+
inline.valueKind === "number"
|
|
6970
|
+
);
|
|
6766
6971
|
if (!checked) continue;
|
|
6972
|
+
const fixEmission = buildFix(inline.property, checked, ix);
|
|
6767
6973
|
const node = readUsageNode(ix, inline.nodeId);
|
|
6768
6974
|
if (!node) continue;
|
|
6769
6975
|
const componentEvidenceId = componentByNode.get(node.id)?.id;
|
|
@@ -6784,7 +6990,7 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6784
6990
|
property: inline.property,
|
|
6785
6991
|
value: inline.value
|
|
6786
6992
|
},
|
|
6787
|
-
fix:
|
|
6993
|
+
fix: fixEmission?.fix,
|
|
6788
6994
|
attributes: {
|
|
6789
6995
|
property: inline.property,
|
|
6790
6996
|
rawValue: inline.value,
|
|
@@ -6792,7 +6998,8 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6792
6998
|
allowed,
|
|
6793
6999
|
suggestedValue: checked.suggestedValue,
|
|
6794
7000
|
matchedToken: checked.matchedToken,
|
|
6795
|
-
source: "jsx"
|
|
7001
|
+
source: "jsx",
|
|
7002
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6796
7003
|
}
|
|
6797
7004
|
})
|
|
6798
7005
|
);
|
|
@@ -6822,11 +7029,10 @@ function checkFontSize(raw, allowed, scale, tokens, bareNumberFix = false) {
|
|
|
6822
7029
|
const exactToken = tokens.get(magnitude);
|
|
6823
7030
|
if (matchesScale(normalized.value, allowed)) {
|
|
6824
7031
|
if (!exactToken) return null;
|
|
6825
|
-
const applicable = isApplicableTokenReference(exactToken.referenceFormat) && normalized.deterministicFix;
|
|
6826
7032
|
return {
|
|
6827
7033
|
suggestedValue: tokenReference(exactToken.name),
|
|
6828
7034
|
fixEmittable: true,
|
|
6829
|
-
deterministicFix:
|
|
7035
|
+
deterministicFix: normalized.deterministicFix,
|
|
6830
7036
|
reason: "token-equivalent",
|
|
6831
7037
|
matchedToken: exactToken.name
|
|
6832
7038
|
};
|
|
@@ -6844,15 +7050,23 @@ function checkFontSize(raw, allowed, scale, tokens, bareNumberFix = false) {
|
|
|
6844
7050
|
reason: "off-scale"
|
|
6845
7051
|
};
|
|
6846
7052
|
}
|
|
6847
|
-
function buildFix(property, checked) {
|
|
7053
|
+
function buildFix(property, checked, ix) {
|
|
6848
7054
|
if (!checked.fixEmittable || checked.suggestedValue === void 0) return void 0;
|
|
6849
|
-
return
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
7055
|
+
return emitFix(
|
|
7056
|
+
{
|
|
7057
|
+
kind: "replaceStyleValue",
|
|
7058
|
+
title: `Replace ${property} with ${checked.suggestedValue}`,
|
|
7059
|
+
property,
|
|
7060
|
+
value: checked.suggestedValue,
|
|
7061
|
+
deterministic: true
|
|
7062
|
+
},
|
|
7063
|
+
{
|
|
7064
|
+
symbols: tokenSymbolsInText(checked.suggestedValue),
|
|
7065
|
+
editKind: "replaceStyleValue",
|
|
7066
|
+
valuePreserving: checked.deterministicFix
|
|
7067
|
+
},
|
|
7068
|
+
ix
|
|
7069
|
+
);
|
|
6856
7070
|
}
|
|
6857
7071
|
function typographyMessage(property, value, allowed, unit, checked) {
|
|
6858
7072
|
if (checked.reason === "token-equivalent" && checked.matchedToken) {
|
|
@@ -7526,7 +7740,7 @@ var SASS_FILE = /\.(scss|sass)$/i;
|
|
|
7526
7740
|
function ruleTokensRequireDualFallback(ix) {
|
|
7527
7741
|
const policy = ix.policy.ruleConfig(RULE_ID19);
|
|
7528
7742
|
if (!policy?.enabled) return [];
|
|
7529
|
-
const
|
|
7743
|
+
const tokensByCssVariable = indexTokensByCssVariable(ix.tokens.list());
|
|
7530
7744
|
const findings = [];
|
|
7531
7745
|
for (const decl of ix.byKind("style_declaration")) {
|
|
7532
7746
|
if (!SASS_FILE.test(decl.file)) continue;
|
|
@@ -7536,19 +7750,35 @@ function ruleTokensRequireDualFallback(ix) {
|
|
|
7536
7750
|
const tokenName = match[1];
|
|
7537
7751
|
const hasFallback = match[2] !== void 0;
|
|
7538
7752
|
if (hasFallback) continue;
|
|
7539
|
-
|
|
7753
|
+
const token = tokensByCssVariable.get(tokenName);
|
|
7754
|
+
if (!token) continue;
|
|
7540
7755
|
const rawValue = match[0];
|
|
7541
7756
|
const scssVar = tokenName.replace(/^--/, "$");
|
|
7542
|
-
const
|
|
7543
|
-
const nextValue = decl.value.replace(rawValue,
|
|
7757
|
+
const hasScssVariable = token.referenceFormat === "scss-var";
|
|
7758
|
+
const nextValue = hasScssVariable ? decl.value.replace(rawValue, `var(${tokenName}, ${scssVar})`) : void 0;
|
|
7759
|
+
const fixEmission = nextValue ? emitFix(
|
|
7760
|
+
{
|
|
7761
|
+
kind: "replaceStyleValue",
|
|
7762
|
+
title: `Add fallback for ${tokenName}`,
|
|
7763
|
+
property: decl.property,
|
|
7764
|
+
value: nextValue,
|
|
7765
|
+
deterministic: true
|
|
7766
|
+
},
|
|
7767
|
+
{
|
|
7768
|
+
symbols: [tokenName, scssVar],
|
|
7769
|
+
editKind: "replaceStyleValue",
|
|
7770
|
+
valuePreserving: true
|
|
7771
|
+
},
|
|
7772
|
+
ix
|
|
7773
|
+
) : void 0;
|
|
7544
7774
|
findings.push(
|
|
7545
7775
|
makeFinding({
|
|
7546
7776
|
ruleId: RULE_ID19,
|
|
7547
7777
|
ruleVersion: RULE_VERSION20,
|
|
7548
7778
|
severity: policy.severity ?? "warn",
|
|
7549
|
-
message: `var(${tokenName}) is missing an SCSS fallback. Use var(${tokenName}, ${scssVar}).`,
|
|
7779
|
+
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}.`,
|
|
7550
7780
|
location: decl.location,
|
|
7551
|
-
evidence: ix.evidence([decl.id, policy.id]),
|
|
7781
|
+
evidence: ix.evidence([decl.id, token.id, policy.id]),
|
|
7552
7782
|
fingerprintIdentity: {
|
|
7553
7783
|
file: decl.file,
|
|
7554
7784
|
selector: decl.selector,
|
|
@@ -7556,18 +7786,13 @@ function ruleTokensRequireDualFallback(ix) {
|
|
|
7556
7786
|
property: decl.property,
|
|
7557
7787
|
tokenName
|
|
7558
7788
|
},
|
|
7559
|
-
fix:
|
|
7560
|
-
kind: "replaceStyleValue",
|
|
7561
|
-
title: `Add fallback for ${tokenName}`,
|
|
7562
|
-
property: decl.property,
|
|
7563
|
-
value: nextValue,
|
|
7564
|
-
deterministic: true
|
|
7565
|
-
},
|
|
7789
|
+
fix: fixEmission?.fix,
|
|
7566
7790
|
attributes: {
|
|
7567
7791
|
property: decl.property,
|
|
7568
7792
|
rawValue: decl.value,
|
|
7569
7793
|
tokenName,
|
|
7570
|
-
suggestedValue: nextValue,
|
|
7794
|
+
...nextValue ? { suggestedValue: nextValue } : {},
|
|
7795
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
7571
7796
|
source: "css"
|
|
7572
7797
|
}
|
|
7573
7798
|
})
|
|
@@ -7576,6 +7801,17 @@ function ruleTokensRequireDualFallback(ix) {
|
|
|
7576
7801
|
}
|
|
7577
7802
|
return findings;
|
|
7578
7803
|
}
|
|
7804
|
+
function indexTokensByCssVariable(tokens) {
|
|
7805
|
+
const indexed = /* @__PURE__ */ new Map();
|
|
7806
|
+
for (const token of tokens) {
|
|
7807
|
+
const cssVariable = token.name.startsWith("--") ? token.name : token.name.startsWith("$") ? `--${token.name.slice(1)}` : `--${token.name}`;
|
|
7808
|
+
const existing = indexed.get(cssVariable);
|
|
7809
|
+
if (!existing || token.referenceFormat === "scss-var") {
|
|
7810
|
+
indexed.set(cssVariable, token);
|
|
7811
|
+
}
|
|
7812
|
+
}
|
|
7813
|
+
return indexed;
|
|
7814
|
+
}
|
|
7579
7815
|
|
|
7580
7816
|
// src/rules/tokens-css-vars-must-be-defined.ts
|
|
7581
7817
|
var RULE_ID20 = "tokens/css-vars-must-be-defined";
|
|
@@ -7667,6 +7903,8 @@ var BLOCKING_RULE_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
|
7667
7903
|
// imported component bypasses its canonical
|
|
7668
7904
|
]);
|
|
7669
7905
|
function gatesCi(finding, failOnWarnings) {
|
|
7906
|
+
if (!canBlock(finding.evidenceGrade ?? "source_backed")) return false;
|
|
7907
|
+
if (finding.attributes?.advisory === true) return false;
|
|
7670
7908
|
const level = severityLevel(finding.severity);
|
|
7671
7909
|
return level === "error" || failOnWarnings && level === "warn";
|
|
7672
7910
|
}
|
|
@@ -7674,7 +7912,11 @@ function isDenyEligible(finding, failOnWarnings) {
|
|
|
7674
7912
|
if (!BLOCKING_RULE_ALLOWLIST.has(finding.ruleId)) return false;
|
|
7675
7913
|
if (finding.attributes?.advisory === true) return false;
|
|
7676
7914
|
if (!gatesCi(finding, failOnWarnings)) return false;
|
|
7677
|
-
if (finding.fix?.deterministic === false)
|
|
7915
|
+
if (finding.fix?.deterministic === false) {
|
|
7916
|
+
if (finding.ruleId !== "components/prefer-library" || finding.attributes?.propCompatibility !== "observed-complete") {
|
|
7917
|
+
return false;
|
|
7918
|
+
}
|
|
7919
|
+
}
|
|
7678
7920
|
return true;
|
|
7679
7921
|
}
|
|
7680
7922
|
|
|
@@ -7682,7 +7924,7 @@ function isDenyEligible(finding, failOnWarnings) {
|
|
|
7682
7924
|
var RULE_TIER = {
|
|
7683
7925
|
// ---- Tier A — contract vocabulary -------------------------------------
|
|
7684
7926
|
"components/prefer-library": "contract",
|
|
7685
|
-
// canonical-component bypass (
|
|
7927
|
+
// canonical-component bypass (FUI1004)
|
|
7686
7928
|
"tokens/css-vars-must-be-defined": "contract",
|
|
7687
7929
|
// token drift (FUI2015)
|
|
7688
7930
|
// ---- Tier B — generic hygiene -----------------------------------------
|
|
@@ -7947,6 +8189,7 @@ function evaluateGovernanceIntegrity(input) {
|
|
|
7947
8189
|
componentsFamily.remediation = "add govern.canonicalSources (a directory source, or npm/registry with a non-empty include) or designSystem.path/packageName";
|
|
7948
8190
|
} else {
|
|
7949
8191
|
componentsFamily.reason = "components/prefer-library not enabled";
|
|
8192
|
+
componentsFamily.remediation = "add govern.canonicalSources (a directory source, or npm/registry with a non-empty include) or designSystem.path/packageName";
|
|
7950
8193
|
}
|
|
7951
8194
|
}
|
|
7952
8195
|
const cssVarsActive = input.cssVarsActive ?? policyDeclaresCssVars(input.policy);
|
|
@@ -8035,6 +8278,29 @@ function evaluateGovernanceIntegrity(input) {
|
|
|
8035
8278
|
// src/contract/preimage.ts
|
|
8036
8279
|
var CONTRACT_DOMAINS = ["components", "tokens", "canonicalMap", "policy"];
|
|
8037
8280
|
var CONTRACT_PREIMAGE_SCHEMA = "fcid-preimage:v1";
|
|
8281
|
+
var CONTRACT_PREIMAGE_CAPABILITY_HEADER = "X-Fragments-Contract-Preimage";
|
|
8282
|
+
var SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/u;
|
|
8283
|
+
function verifiedContractPreimageFromPin(pin) {
|
|
8284
|
+
if (pin.preimageSchema !== CONTRACT_PREIMAGE_SCHEMA || !SHA256_HEX_PATTERN.test(pin.contractHash) || !pin.domainHashes || typeof pin.domainHashes !== "object") {
|
|
8285
|
+
return null;
|
|
8286
|
+
}
|
|
8287
|
+
const keys = Object.keys(pin.domainHashes).sort();
|
|
8288
|
+
const expectedKeys = [...CONTRACT_DOMAINS].sort();
|
|
8289
|
+
if (keys.length !== expectedKeys.length || keys.some((key, index) => key !== expectedKeys[index])) {
|
|
8290
|
+
return null;
|
|
8291
|
+
}
|
|
8292
|
+
const domains = Object.fromEntries(
|
|
8293
|
+
CONTRACT_DOMAINS.map((domain) => [domain, pin.domainHashes?.[domain]])
|
|
8294
|
+
);
|
|
8295
|
+
if (CONTRACT_DOMAINS.some((domain) => !SHA256_HEX_PATTERN.test(domains[domain] ?? ""))) {
|
|
8296
|
+
return null;
|
|
8297
|
+
}
|
|
8298
|
+
const preimage = {
|
|
8299
|
+
schema: CONTRACT_PREIMAGE_SCHEMA,
|
|
8300
|
+
domains
|
|
8301
|
+
};
|
|
8302
|
+
return contractHash(preimage) === pin.contractHash ? preimage : null;
|
|
8303
|
+
}
|
|
8038
8304
|
function sortCanonical(items) {
|
|
8039
8305
|
return items.map((item) => ({ item, key: canonicalPreimage(item) })).sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0).map(({ item }) => item);
|
|
8040
8306
|
}
|
|
@@ -8358,15 +8624,18 @@ function readPreferLibraryRule(config) {
|
|
|
8358
8624
|
}
|
|
8359
8625
|
export {
|
|
8360
8626
|
AGENT_CONTEXT_RELATIVE_PATH,
|
|
8627
|
+
AGENT_FORMAT_SCHEMA_VERSION,
|
|
8361
8628
|
BLOCKING_RULE_ALLOWLIST,
|
|
8362
8629
|
BRAND,
|
|
8363
8630
|
CATALOG_FIXTURE_A_FCID,
|
|
8364
8631
|
CODES,
|
|
8365
8632
|
CONTRACT_DOMAINS,
|
|
8633
|
+
CONTRACT_PREIMAGE_CAPABILITY_HEADER,
|
|
8366
8634
|
CONTRACT_PREIMAGE_SCHEMA,
|
|
8367
8635
|
DEFAULTS,
|
|
8368
8636
|
DEFAULT_ENHANCED_STYLE_PROPERTIES,
|
|
8369
8637
|
DEFAULT_STYLE_PROPERTIES,
|
|
8638
|
+
EVIDENCE_ORDER,
|
|
8370
8639
|
EXPLAIN_URL_BASE,
|
|
8371
8640
|
FRAGMENTS_INTERNAL_RULE_IDS,
|
|
8372
8641
|
FactIndex,
|
|
@@ -8388,7 +8657,10 @@ export {
|
|
|
8388
8657
|
SEVERITIES,
|
|
8389
8658
|
SEVERITY_RANK,
|
|
8390
8659
|
SEVERITY_WEIGHTS,
|
|
8660
|
+
agentErrorEnvelopeSchema,
|
|
8391
8661
|
agentFormatSchema,
|
|
8662
|
+
agentIntegritySchema,
|
|
8663
|
+
agentOutputSchema,
|
|
8392
8664
|
aiMetadataSchema,
|
|
8393
8665
|
analyzeComposition,
|
|
8394
8666
|
asComponentId,
|
|
@@ -8415,6 +8687,7 @@ export {
|
|
|
8415
8687
|
byCode,
|
|
8416
8688
|
byRuleId,
|
|
8417
8689
|
calculateDeltaE,
|
|
8690
|
+
canBlock,
|
|
8418
8691
|
canonicalJson,
|
|
8419
8692
|
canonicalPreimage,
|
|
8420
8693
|
canonicalizeOwnedComponentId,
|
|
@@ -8705,6 +8978,7 @@ export {
|
|
|
8705
8978
|
topologyBase,
|
|
8706
8979
|
usePreviewVariantRuntime,
|
|
8707
8980
|
validatorResultSchema,
|
|
8981
|
+
verifiedContractPreimageFromPin,
|
|
8708
8982
|
violationSchema,
|
|
8709
8983
|
vocabularyRank
|
|
8710
8984
|
};
|