@usefragments/core 1.2.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-BQQTMGLA.js → chunk-57QDBEHQ.js} +79 -6
- package/dist/chunk-57QDBEHQ.js.map +1 -0
- package/dist/{chunk-DBVTSUMT.js → chunk-MLRDNSSA.js} +20 -6
- package/dist/chunk-MLRDNSSA.js.map +1 -0
- package/dist/codes/index.d.ts +1 -1
- package/dist/codes/index.js +2 -2
- package/dist/index-h_yWj15D.d.ts +6981 -0
- package/dist/index.d.ts +106 -92
- package/dist/index.js +235 -98
- package/dist/index.js.map +1 -1
- package/dist/registry.d.ts +36 -36
- package/dist/schemas/index.d.ts +3 -3377
- package/dist/schemas/index.js +1 -1
- package/package.json +1 -1
- package/src/agent-format.test.ts +75 -3
- package/src/agent-format.ts +28 -6
- package/src/evidence.ts +27 -0
- package/src/facts/builders.ts +2 -0
- package/src/facts/fact-index.ts +22 -2
- package/src/facts/facts.test.ts +1 -1
- package/src/facts/types.ts +2 -0
- package/src/index.ts +4 -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/emit-gate.test.ts +16 -0
- package/src/rules/emit-gate.ts +6 -3
- package/src/rules/finding.ts +3 -0
- package/src/rules/rules.test.ts +2 -1
- 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 +13 -0
- package/src/rules/tokens-require-dual-fallback.ts +54 -17
- package/src/rules/utils.ts +51 -0
- package/src/schemas/index.ts +81 -3
- 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-BQQTMGLA.js.map +0 -1
- package/dist/chunk-DBVTSUMT.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -109,7 +109,7 @@ import {
|
|
|
109
109
|
ownedImportsEqual,
|
|
110
110
|
resolveComponentGovernance,
|
|
111
111
|
scaleGovernanceRecordSchema
|
|
112
|
-
} from "./chunk-
|
|
112
|
+
} from "./chunk-MLRDNSSA.js";
|
|
113
113
|
import {
|
|
114
114
|
AGENT_FORMAT_SCHEMA_VERSION,
|
|
115
115
|
agentErrorEnvelopeSchema,
|
|
@@ -134,7 +134,7 @@ import {
|
|
|
134
134
|
suppressionDirectiveSchema,
|
|
135
135
|
validatorResultSchema,
|
|
136
136
|
violationSchema
|
|
137
|
-
} from "./chunk-
|
|
137
|
+
} from "./chunk-57QDBEHQ.js";
|
|
138
138
|
import {
|
|
139
139
|
OWNED_PACKAGE_IDENTITY_EPOCH,
|
|
140
140
|
OWNED_PACKAGE_IDENTITY_SOURCE_SHA256,
|
|
@@ -238,6 +238,18 @@ var DEFAULTS = {
|
|
|
238
238
|
port: 6006
|
|
239
239
|
};
|
|
240
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
|
+
|
|
241
253
|
// src/conform-prove.ts
|
|
242
254
|
var PROVE_MAX_PASSES = 8;
|
|
243
255
|
var PROVE_DEFAULT_MAX_PASSES = 4;
|
|
@@ -386,6 +398,7 @@ async function proveCompliant(input, callbacks, options = {}) {
|
|
|
386
398
|
|
|
387
399
|
// src/agent-format.ts
|
|
388
400
|
function buildAgentFormat(input) {
|
|
401
|
+
const zeroRulesLoaded = input.integrity.rulesLoaded === 0;
|
|
389
402
|
const integrityFailed = !input.integrity.healthy || input.integrity.inert;
|
|
390
403
|
const score = integrityFailed ? 0 : scoreFindings(input.findings);
|
|
391
404
|
const visibleFindings = uniqueFindings(input.findings).slice(0, input.maxFindings ?? 50);
|
|
@@ -398,15 +411,14 @@ function buildAgentFormat(input) {
|
|
|
398
411
|
severity: finding.severity,
|
|
399
412
|
level: severityLevel(finding.severity),
|
|
400
413
|
message: finding.message,
|
|
414
|
+
evidenceGrade: finding.evidenceGrade,
|
|
401
415
|
evidence: input.includeEvidence ? finding.evidence : [],
|
|
402
416
|
plan: planForFinding(finding)
|
|
403
417
|
}));
|
|
404
418
|
const hasErrors = input.findings.some((finding) => severityLevel(finding.severity) === "error");
|
|
405
|
-
|
|
419
|
+
const shared = {
|
|
406
420
|
schemaVersion: AGENT_FORMAT_SCHEMA_VERSION,
|
|
407
|
-
|
|
408
|
-
score,
|
|
409
|
-
summary: integrityFailed ? `failed with score ${score}; ${input.integrity.reasons.join("; ") || "governance integrity is unhealthy"}` : agentSummary(input.findings.length, input.filesScanned, score),
|
|
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),
|
|
410
422
|
integrity: input.integrity,
|
|
411
423
|
fix_first: agentFindings.filter((finding) => finding.plan.confidence >= 0.8),
|
|
412
424
|
remaining: agentFindings.filter((finding) => finding.plan.confidence < 0.8),
|
|
@@ -428,7 +440,23 @@ function buildAgentFormat(input) {
|
|
|
428
440
|
...suppression.expiresOn ? { expiresOn: suppression.expiresOn } : {},
|
|
429
441
|
scope: "line"
|
|
430
442
|
}))
|
|
431
|
-
} : {}
|
|
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
|
|
432
460
|
};
|
|
433
461
|
}
|
|
434
462
|
function uniqueFindings(findings) {
|
|
@@ -3326,19 +3354,35 @@ function parsedOutputToDesignTokens(output, filePath) {
|
|
|
3326
3354
|
return tokens;
|
|
3327
3355
|
}
|
|
3328
3356
|
function scssOutputToDesignTokens(output, filePath) {
|
|
3329
|
-
const
|
|
3330
|
-
const tokens = [];
|
|
3357
|
+
const tokensByName = /* @__PURE__ */ new Map();
|
|
3331
3358
|
for (const cat of Object.values(output.categories)) {
|
|
3332
3359
|
for (const t of cat) {
|
|
3333
|
-
const
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
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
|
+
}
|
|
3337
3377
|
}
|
|
3338
3378
|
}
|
|
3339
|
-
return
|
|
3379
|
+
return [...tokensByName].map(
|
|
3380
|
+
([name, token]) => designTokenFromParsed(name, token.parsed, filePath, token.referenceFormat, [
|
|
3381
|
+
...token.sourceNames
|
|
3382
|
+
])
|
|
3383
|
+
);
|
|
3340
3384
|
}
|
|
3341
|
-
function designTokenFromParsed(name, parsed, filePath) {
|
|
3385
|
+
function designTokenFromParsed(name, parsed, filePath, referenceFormat, sourceNames) {
|
|
3342
3386
|
return {
|
|
3343
3387
|
name,
|
|
3344
3388
|
rawValue: parsed.value ?? "",
|
|
@@ -3351,6 +3395,8 @@ function designTokenFromParsed(name, parsed, filePath) {
|
|
|
3351
3395
|
level: 1,
|
|
3352
3396
|
referenceChain: [],
|
|
3353
3397
|
sourceFile: filePath,
|
|
3398
|
+
referenceFormat,
|
|
3399
|
+
...sourceNames?.length ? { sourceNames } : {},
|
|
3354
3400
|
theme: "default",
|
|
3355
3401
|
selector: ":root",
|
|
3356
3402
|
description: parsed.description
|
|
@@ -4289,12 +4335,37 @@ function makeFinding(input) {
|
|
|
4289
4335
|
fingerprint,
|
|
4290
4336
|
location: input.location,
|
|
4291
4337
|
evidence: input.evidence,
|
|
4338
|
+
evidenceGrade: input.evidenceGrade ?? "source_backed",
|
|
4292
4339
|
fix: input.fix,
|
|
4293
4340
|
attributes: input.attributes
|
|
4294
4341
|
});
|
|
4295
4342
|
}
|
|
4296
4343
|
|
|
4297
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
|
+
}
|
|
4298
4369
|
function indexPropsByNodeId(ix) {
|
|
4299
4370
|
const out = /* @__PURE__ */ new Map();
|
|
4300
4371
|
for (const fact of ix.byKind("usage_prop_resolved")) {
|
|
@@ -4373,13 +4444,6 @@ function readUsageNode(ix, id) {
|
|
|
4373
4444
|
const fact = ix.get(id);
|
|
4374
4445
|
return fact && fact.kind === "usage_node" ? fact : void 0;
|
|
4375
4446
|
}
|
|
4376
|
-
function cssVariableNames(tokens) {
|
|
4377
|
-
const names = /* @__PURE__ */ new Set();
|
|
4378
|
-
for (const token of tokens) {
|
|
4379
|
-
names.add(token.name.startsWith("--") ? token.name : `--${token.name}`);
|
|
4380
|
-
}
|
|
4381
|
-
return names;
|
|
4382
|
-
}
|
|
4383
4447
|
function contractPrefixFamilies(names) {
|
|
4384
4448
|
const families = /* @__PURE__ */ new Set();
|
|
4385
4449
|
for (const name of names) {
|
|
@@ -6170,6 +6234,7 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6170
6234
|
if (!color) continue;
|
|
6171
6235
|
if (isExemptColor(color, policy.except)) continue;
|
|
6172
6236
|
const resolution = resolveColorToken(decl.property, color, colorTokens);
|
|
6237
|
+
const fixEmission = resolution?.kind === "exact" ? buildTokenFix(decl.property, decl.value, color, resolution, ix) : void 0;
|
|
6173
6238
|
const evidenceIds = resolution ? [decl.id, policy.id, resolution.token.id] : [decl.id, policy.id];
|
|
6174
6239
|
findings.push(
|
|
6175
6240
|
makeFinding({
|
|
@@ -6187,14 +6252,15 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6187
6252
|
property: decl.property,
|
|
6188
6253
|
value: color
|
|
6189
6254
|
},
|
|
6190
|
-
fix:
|
|
6255
|
+
fix: fixEmission?.fix,
|
|
6191
6256
|
attributes: {
|
|
6192
6257
|
property: decl.property,
|
|
6193
6258
|
rawValue: decl.value,
|
|
6194
6259
|
color,
|
|
6195
6260
|
source: "css",
|
|
6196
6261
|
suggestedToken: resolution?.token.name,
|
|
6197
|
-
...resolution ? { tokenMatch: resolution.kind } : {}
|
|
6262
|
+
...resolution ? { tokenMatch: resolution.kind } : {},
|
|
6263
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6198
6264
|
}
|
|
6199
6265
|
})
|
|
6200
6266
|
);
|
|
@@ -6209,6 +6275,7 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6209
6275
|
if (!node) continue;
|
|
6210
6276
|
const componentEvidenceId = componentByNode.get(node.id)?.id;
|
|
6211
6277
|
const resolution = resolveColorToken(inline.property, color, colorTokens);
|
|
6278
|
+
const fixEmission = resolution?.kind === "exact" ? buildTokenFix(inline.property, inline.value, color, resolution, ix) : void 0;
|
|
6212
6279
|
const baseEvidence = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id] : [node.id, inline.id, policy.id];
|
|
6213
6280
|
const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
|
|
6214
6281
|
findings.push(
|
|
@@ -6227,14 +6294,15 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6227
6294
|
property: inline.property,
|
|
6228
6295
|
value: color
|
|
6229
6296
|
},
|
|
6230
|
-
fix:
|
|
6297
|
+
fix: fixEmission?.fix,
|
|
6231
6298
|
attributes: {
|
|
6232
6299
|
property: inline.property,
|
|
6233
6300
|
rawValue: inline.value,
|
|
6234
6301
|
color,
|
|
6235
6302
|
source: "jsx",
|
|
6236
6303
|
suggestedToken: resolution?.token.name,
|
|
6237
|
-
...resolution ? { tokenMatch: resolution.kind } : {}
|
|
6304
|
+
...resolution ? { tokenMatch: resolution.kind } : {},
|
|
6305
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6238
6306
|
}
|
|
6239
6307
|
})
|
|
6240
6308
|
);
|
|
@@ -6318,21 +6386,24 @@ function normalizeColor2(value) {
|
|
|
6318
6386
|
}
|
|
6319
6387
|
return lower;
|
|
6320
6388
|
}
|
|
6321
|
-
function buildTokenFix(property, rawValue, rawColor, match) {
|
|
6389
|
+
function buildTokenFix(property, rawValue, rawColor, match, ix) {
|
|
6322
6390
|
const reference = tokenReference(match.token.name);
|
|
6323
6391
|
const value = replaceRawColor(rawValue, rawColor, reference);
|
|
6324
|
-
return
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
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
|
+
);
|
|
6336
6407
|
}
|
|
6337
6408
|
function replaceRawColor(rawValue, rawColor, replacement) {
|
|
6338
6409
|
if (rawValue === rawColor) return replacement;
|
|
@@ -6363,6 +6434,7 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6363
6434
|
if (!resolution) continue;
|
|
6364
6435
|
const tokenIds = resolutionTokenIds(resolution);
|
|
6365
6436
|
const evidenceIds = tokenIds.length ? [decl.id, policy.id, ...tokenIds] : [decl.id, policy.id];
|
|
6437
|
+
const fixEmission = dimensionFix(decl.property, resolution, ix);
|
|
6366
6438
|
findings.push(
|
|
6367
6439
|
makeFinding({
|
|
6368
6440
|
ruleId: RULE_ID9,
|
|
@@ -6379,12 +6451,13 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6379
6451
|
property: decl.property,
|
|
6380
6452
|
value: decl.value
|
|
6381
6453
|
},
|
|
6382
|
-
fix:
|
|
6454
|
+
fix: fixEmission?.fix,
|
|
6383
6455
|
attributes: {
|
|
6384
6456
|
property: decl.property,
|
|
6385
6457
|
rawValue: decl.value,
|
|
6386
6458
|
source: "css",
|
|
6387
|
-
suggestedToken: resolutionSuggestedToken(resolution)
|
|
6459
|
+
suggestedToken: resolutionSuggestedToken(resolution),
|
|
6460
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6388
6461
|
}
|
|
6389
6462
|
})
|
|
6390
6463
|
);
|
|
@@ -6403,6 +6476,7 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6403
6476
|
const baseEvidence = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id] : [node.id, inline.id, policy.id];
|
|
6404
6477
|
const tokenIds = resolutionTokenIds(resolution);
|
|
6405
6478
|
const evidenceIds = tokenIds.length ? [...baseEvidence, ...tokenIds] : baseEvidence;
|
|
6479
|
+
const fixEmission = dimensionFix(inline.property, resolution, ix);
|
|
6406
6480
|
findings.push(
|
|
6407
6481
|
makeFinding({
|
|
6408
6482
|
ruleId: RULE_ID9,
|
|
@@ -6419,12 +6493,13 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6419
6493
|
property: inline.property,
|
|
6420
6494
|
value: inline.value
|
|
6421
6495
|
},
|
|
6422
|
-
fix:
|
|
6496
|
+
fix: fixEmission?.fix,
|
|
6423
6497
|
attributes: {
|
|
6424
6498
|
property: inline.property,
|
|
6425
6499
|
rawValue: inline.value,
|
|
6426
6500
|
source: "jsx",
|
|
6427
|
-
suggestedToken: resolutionSuggestedToken(resolution)
|
|
6501
|
+
suggestedToken: resolutionSuggestedToken(resolution),
|
|
6502
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6428
6503
|
}
|
|
6429
6504
|
})
|
|
6430
6505
|
);
|
|
@@ -6469,15 +6544,23 @@ function dimensionMessage(rawValue, property, preferLabel, resolution, inline) {
|
|
|
6469
6544
|
}
|
|
6470
6545
|
return `Raw dimension ${rawValue} on ${where}. Use a ${preferLabel} instead.`;
|
|
6471
6546
|
}
|
|
6472
|
-
function dimensionFix(property, resolution) {
|
|
6547
|
+
function dimensionFix(property, resolution, ix) {
|
|
6473
6548
|
if (resolution.kind === "exact") {
|
|
6474
|
-
return
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
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
|
+
);
|
|
6481
6564
|
}
|
|
6482
6565
|
return void 0;
|
|
6483
6566
|
}
|
|
@@ -6578,7 +6661,7 @@ function resolveSpacingValue(input) {
|
|
|
6578
6661
|
let anyOffScale = false;
|
|
6579
6662
|
let canFixAll = true;
|
|
6580
6663
|
let reliableSuggestion = true;
|
|
6581
|
-
let
|
|
6664
|
+
let allValuePreserving = true;
|
|
6582
6665
|
for (const part of parts) {
|
|
6583
6666
|
const normalized = normalizeLengthForScale(part.parsed, input.scale);
|
|
6584
6667
|
if (normalized === null) return null;
|
|
@@ -6592,8 +6675,7 @@ function resolveSpacingValue(input) {
|
|
|
6592
6675
|
}
|
|
6593
6676
|
anyViolation = true;
|
|
6594
6677
|
const reference = tokenReference(exactToken.name);
|
|
6595
|
-
|
|
6596
|
-
if (!applicable) allApplicableValuePreserving = false;
|
|
6678
|
+
if (!normalized.deterministicFix) allValuePreserving = false;
|
|
6597
6679
|
suggestedParts.push(reference);
|
|
6598
6680
|
if (!firstViolation) {
|
|
6599
6681
|
firstViolation = {
|
|
@@ -6601,7 +6683,7 @@ function resolveSpacingValue(input) {
|
|
|
6601
6683
|
suggestedValue: reference,
|
|
6602
6684
|
suggestedToken: exactToken.name,
|
|
6603
6685
|
fixEmittable: true,
|
|
6604
|
-
deterministicFix:
|
|
6686
|
+
deterministicFix: normalized.deterministicFix,
|
|
6605
6687
|
reason: "token-equivalent",
|
|
6606
6688
|
matchedToken: exactToken.name,
|
|
6607
6689
|
assumedRootFontSizePx: normalized.assumedRootFontSizePx,
|
|
@@ -6612,7 +6694,7 @@ function resolveSpacingValue(input) {
|
|
|
6612
6694
|
}
|
|
6613
6695
|
anyViolation = true;
|
|
6614
6696
|
anyOffScale = true;
|
|
6615
|
-
|
|
6697
|
+
allValuePreserving = false;
|
|
6616
6698
|
const nearest = nearestSignedScaleValue(normalized.value, input.allowed);
|
|
6617
6699
|
const snapsToZero = nearest === 0 && normalized.value !== 0;
|
|
6618
6700
|
let suggestedPart;
|
|
@@ -6645,7 +6727,7 @@ function resolveSpacingValue(input) {
|
|
|
6645
6727
|
const joined = suggestedParts.join(" ");
|
|
6646
6728
|
const changes = joined !== input.raw.trim().replace(/\s+/g, " ");
|
|
6647
6729
|
const fixEmittable = canFixAll && reliableSuggestion && changes;
|
|
6648
|
-
const deterministic = fixEmittable && !anyOffScale &&
|
|
6730
|
+
const deterministic = fixEmittable && !anyOffScale && allValuePreserving;
|
|
6649
6731
|
return {
|
|
6650
6732
|
...firstViolation,
|
|
6651
6733
|
reason: anyOffScale ? "off-scale" : "token-equivalent",
|
|
@@ -6709,6 +6791,7 @@ function checkDeclaration(ix, decl, lookupFor) {
|
|
|
6709
6791
|
tokens: lookupFor(scale)
|
|
6710
6792
|
});
|
|
6711
6793
|
if (!checked) return null;
|
|
6794
|
+
const fixEmission = buildSpacingFix(decl.property, checked, ix);
|
|
6712
6795
|
return makeFinding({
|
|
6713
6796
|
ruleId: RULE_ID10,
|
|
6714
6797
|
ruleVersion: RULE_VERSION11,
|
|
@@ -6724,7 +6807,7 @@ function checkDeclaration(ix, decl, lookupFor) {
|
|
|
6724
6807
|
property: decl.property,
|
|
6725
6808
|
value: decl.value
|
|
6726
6809
|
},
|
|
6727
|
-
fix:
|
|
6810
|
+
fix: fixEmission?.fix,
|
|
6728
6811
|
attributes: {
|
|
6729
6812
|
property: decl.property,
|
|
6730
6813
|
rawValue: decl.value,
|
|
@@ -6738,7 +6821,8 @@ function checkDeclaration(ix, decl, lookupFor) {
|
|
|
6738
6821
|
normalizedUnit: scale.unit,
|
|
6739
6822
|
assumedRootFontSizePx: checked.assumedRootFontSizePx,
|
|
6740
6823
|
assumedEmBasePx: checked.assumedEmBasePx,
|
|
6741
|
-
source: "css"
|
|
6824
|
+
source: "css",
|
|
6825
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6742
6826
|
}
|
|
6743
6827
|
});
|
|
6744
6828
|
}
|
|
@@ -6758,6 +6842,7 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
|
|
|
6758
6842
|
bareNumberFix: inline.valueKind === "number"
|
|
6759
6843
|
});
|
|
6760
6844
|
if (!checked) return null;
|
|
6845
|
+
const fixEmission = buildSpacingFix(inline.property, checked, ix);
|
|
6761
6846
|
const node = readUsageNode(ix, inline.nodeId);
|
|
6762
6847
|
if (!node) return null;
|
|
6763
6848
|
const componentEvidenceId = componentByNode.get(node.id)?.id;
|
|
@@ -6777,7 +6862,7 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
|
|
|
6777
6862
|
property: inline.property,
|
|
6778
6863
|
value: inline.value
|
|
6779
6864
|
},
|
|
6780
|
-
fix:
|
|
6865
|
+
fix: fixEmission?.fix,
|
|
6781
6866
|
attributes: {
|
|
6782
6867
|
property: inline.property,
|
|
6783
6868
|
rawValue: inline.value,
|
|
@@ -6791,19 +6876,28 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
|
|
|
6791
6876
|
normalizedUnit: scale.unit,
|
|
6792
6877
|
assumedRootFontSizePx: checked.assumedRootFontSizePx,
|
|
6793
6878
|
assumedEmBasePx: checked.assumedEmBasePx,
|
|
6794
|
-
source: "jsx"
|
|
6879
|
+
source: "jsx",
|
|
6880
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6795
6881
|
}
|
|
6796
6882
|
});
|
|
6797
6883
|
}
|
|
6798
|
-
function buildSpacingFix(property, checked) {
|
|
6884
|
+
function buildSpacingFix(property, checked, ix) {
|
|
6799
6885
|
if (!checked.fixEmittable || checked.suggestedValue === void 0) return void 0;
|
|
6800
|
-
return
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
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
|
+
);
|
|
6807
6901
|
}
|
|
6808
6902
|
function spacingMessage(property, value, allowed, unit, checked) {
|
|
6809
6903
|
if (checked.reason === "token-equivalent" && checked.matchedToken) {
|
|
@@ -6832,6 +6926,7 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6832
6926
|
if (decl.property.toLowerCase() !== "font-size") continue;
|
|
6833
6927
|
const checked = checkFontSize(decl.value, allowed, scale, tokens);
|
|
6834
6928
|
if (!checked) continue;
|
|
6929
|
+
const fixEmission = buildFix(decl.property, checked, ix);
|
|
6835
6930
|
findings.push(
|
|
6836
6931
|
makeFinding({
|
|
6837
6932
|
ruleId: RULE_ID11,
|
|
@@ -6848,7 +6943,7 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6848
6943
|
property: decl.property,
|
|
6849
6944
|
value: decl.value
|
|
6850
6945
|
},
|
|
6851
|
-
fix:
|
|
6946
|
+
fix: fixEmission?.fix,
|
|
6852
6947
|
attributes: {
|
|
6853
6948
|
property: decl.property,
|
|
6854
6949
|
rawValue: decl.value,
|
|
@@ -6856,7 +6951,8 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6856
6951
|
allowed,
|
|
6857
6952
|
suggestedValue: checked.suggestedValue,
|
|
6858
6953
|
matchedToken: checked.matchedToken,
|
|
6859
|
-
source: "css"
|
|
6954
|
+
source: "css",
|
|
6955
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6860
6956
|
}
|
|
6861
6957
|
})
|
|
6862
6958
|
);
|
|
@@ -6865,8 +6961,15 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6865
6961
|
for (const inline of ix.byKind("usage_inline_style")) {
|
|
6866
6962
|
if (inline.valueKind === "css-variable") continue;
|
|
6867
6963
|
if (!FONT_SIZE_PROPERTIES.has(inline.property.toLowerCase())) continue;
|
|
6868
|
-
const checked = checkFontSize(
|
|
6964
|
+
const checked = checkFontSize(
|
|
6965
|
+
inline.value,
|
|
6966
|
+
allowed,
|
|
6967
|
+
scale,
|
|
6968
|
+
tokens,
|
|
6969
|
+
inline.valueKind === "number"
|
|
6970
|
+
);
|
|
6869
6971
|
if (!checked) continue;
|
|
6972
|
+
const fixEmission = buildFix(inline.property, checked, ix);
|
|
6870
6973
|
const node = readUsageNode(ix, inline.nodeId);
|
|
6871
6974
|
if (!node) continue;
|
|
6872
6975
|
const componentEvidenceId = componentByNode.get(node.id)?.id;
|
|
@@ -6887,7 +6990,7 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6887
6990
|
property: inline.property,
|
|
6888
6991
|
value: inline.value
|
|
6889
6992
|
},
|
|
6890
|
-
fix:
|
|
6993
|
+
fix: fixEmission?.fix,
|
|
6891
6994
|
attributes: {
|
|
6892
6995
|
property: inline.property,
|
|
6893
6996
|
rawValue: inline.value,
|
|
@@ -6895,7 +6998,8 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6895
6998
|
allowed,
|
|
6896
6999
|
suggestedValue: checked.suggestedValue,
|
|
6897
7000
|
matchedToken: checked.matchedToken,
|
|
6898
|
-
source: "jsx"
|
|
7001
|
+
source: "jsx",
|
|
7002
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6899
7003
|
}
|
|
6900
7004
|
})
|
|
6901
7005
|
);
|
|
@@ -6925,11 +7029,10 @@ function checkFontSize(raw, allowed, scale, tokens, bareNumberFix = false) {
|
|
|
6925
7029
|
const exactToken = tokens.get(magnitude);
|
|
6926
7030
|
if (matchesScale(normalized.value, allowed)) {
|
|
6927
7031
|
if (!exactToken) return null;
|
|
6928
|
-
const applicable = isApplicableTokenReference(exactToken.referenceFormat) && normalized.deterministicFix;
|
|
6929
7032
|
return {
|
|
6930
7033
|
suggestedValue: tokenReference(exactToken.name),
|
|
6931
7034
|
fixEmittable: true,
|
|
6932
|
-
deterministicFix:
|
|
7035
|
+
deterministicFix: normalized.deterministicFix,
|
|
6933
7036
|
reason: "token-equivalent",
|
|
6934
7037
|
matchedToken: exactToken.name
|
|
6935
7038
|
};
|
|
@@ -6947,15 +7050,23 @@ function checkFontSize(raw, allowed, scale, tokens, bareNumberFix = false) {
|
|
|
6947
7050
|
reason: "off-scale"
|
|
6948
7051
|
};
|
|
6949
7052
|
}
|
|
6950
|
-
function buildFix(property, checked) {
|
|
7053
|
+
function buildFix(property, checked, ix) {
|
|
6951
7054
|
if (!checked.fixEmittable || checked.suggestedValue === void 0) return void 0;
|
|
6952
|
-
return
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
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
|
+
);
|
|
6959
7070
|
}
|
|
6960
7071
|
function typographyMessage(property, value, allowed, unit, checked) {
|
|
6961
7072
|
if (checked.reason === "token-equivalent" && checked.matchedToken) {
|
|
@@ -7629,7 +7740,7 @@ var SASS_FILE = /\.(scss|sass)$/i;
|
|
|
7629
7740
|
function ruleTokensRequireDualFallback(ix) {
|
|
7630
7741
|
const policy = ix.policy.ruleConfig(RULE_ID19);
|
|
7631
7742
|
if (!policy?.enabled) return [];
|
|
7632
|
-
const
|
|
7743
|
+
const tokensByCssVariable = indexTokensByCssVariable(ix.tokens.list());
|
|
7633
7744
|
const findings = [];
|
|
7634
7745
|
for (const decl of ix.byKind("style_declaration")) {
|
|
7635
7746
|
if (!SASS_FILE.test(decl.file)) continue;
|
|
@@ -7639,19 +7750,35 @@ function ruleTokensRequireDualFallback(ix) {
|
|
|
7639
7750
|
const tokenName = match[1];
|
|
7640
7751
|
const hasFallback = match[2] !== void 0;
|
|
7641
7752
|
if (hasFallback) continue;
|
|
7642
|
-
|
|
7753
|
+
const token = tokensByCssVariable.get(tokenName);
|
|
7754
|
+
if (!token) continue;
|
|
7643
7755
|
const rawValue = match[0];
|
|
7644
7756
|
const scssVar = tokenName.replace(/^--/, "$");
|
|
7645
|
-
const
|
|
7646
|
-
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;
|
|
7647
7774
|
findings.push(
|
|
7648
7775
|
makeFinding({
|
|
7649
7776
|
ruleId: RULE_ID19,
|
|
7650
7777
|
ruleVersion: RULE_VERSION20,
|
|
7651
7778
|
severity: policy.severity ?? "warn",
|
|
7652
|
-
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}.`,
|
|
7653
7780
|
location: decl.location,
|
|
7654
|
-
evidence: ix.evidence([decl.id, policy.id]),
|
|
7781
|
+
evidence: ix.evidence([decl.id, token.id, policy.id]),
|
|
7655
7782
|
fingerprintIdentity: {
|
|
7656
7783
|
file: decl.file,
|
|
7657
7784
|
selector: decl.selector,
|
|
@@ -7659,18 +7786,13 @@ function ruleTokensRequireDualFallback(ix) {
|
|
|
7659
7786
|
property: decl.property,
|
|
7660
7787
|
tokenName
|
|
7661
7788
|
},
|
|
7662
|
-
fix:
|
|
7663
|
-
kind: "replaceStyleValue",
|
|
7664
|
-
title: `Add fallback for ${tokenName}`,
|
|
7665
|
-
property: decl.property,
|
|
7666
|
-
value: nextValue,
|
|
7667
|
-
deterministic: true
|
|
7668
|
-
},
|
|
7789
|
+
fix: fixEmission?.fix,
|
|
7669
7790
|
attributes: {
|
|
7670
7791
|
property: decl.property,
|
|
7671
7792
|
rawValue: decl.value,
|
|
7672
7793
|
tokenName,
|
|
7673
|
-
suggestedValue: nextValue,
|
|
7794
|
+
...nextValue ? { suggestedValue: nextValue } : {},
|
|
7795
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
7674
7796
|
source: "css"
|
|
7675
7797
|
}
|
|
7676
7798
|
})
|
|
@@ -7679,6 +7801,17 @@ function ruleTokensRequireDualFallback(ix) {
|
|
|
7679
7801
|
}
|
|
7680
7802
|
return findings;
|
|
7681
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
|
+
}
|
|
7682
7815
|
|
|
7683
7816
|
// src/rules/tokens-css-vars-must-be-defined.ts
|
|
7684
7817
|
var RULE_ID20 = "tokens/css-vars-must-be-defined";
|
|
@@ -7770,6 +7903,8 @@ var BLOCKING_RULE_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
|
7770
7903
|
// imported component bypasses its canonical
|
|
7771
7904
|
]);
|
|
7772
7905
|
function gatesCi(finding, failOnWarnings) {
|
|
7906
|
+
if (!canBlock(finding.evidenceGrade ?? "source_backed")) return false;
|
|
7907
|
+
if (finding.attributes?.advisory === true) return false;
|
|
7773
7908
|
const level = severityLevel(finding.severity);
|
|
7774
7909
|
return level === "error" || failOnWarnings && level === "warn";
|
|
7775
7910
|
}
|
|
@@ -8500,6 +8635,7 @@ export {
|
|
|
8500
8635
|
DEFAULTS,
|
|
8501
8636
|
DEFAULT_ENHANCED_STYLE_PROPERTIES,
|
|
8502
8637
|
DEFAULT_STYLE_PROPERTIES,
|
|
8638
|
+
EVIDENCE_ORDER,
|
|
8503
8639
|
EXPLAIN_URL_BASE,
|
|
8504
8640
|
FRAGMENTS_INTERNAL_RULE_IDS,
|
|
8505
8641
|
FactIndex,
|
|
@@ -8551,6 +8687,7 @@ export {
|
|
|
8551
8687
|
byCode,
|
|
8552
8688
|
byRuleId,
|
|
8553
8689
|
calculateDeltaE,
|
|
8690
|
+
canBlock,
|
|
8554
8691
|
canonicalJson,
|
|
8555
8692
|
canonicalPreimage,
|
|
8556
8693
|
canonicalizeOwnedComponentId,
|