@usefragments/core 1.2.0 → 1.4.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-CBSNXFOD.js} +20 -6
- package/dist/chunk-CBSNXFOD.js.map +1 -0
- package/dist/codes/index.d.ts +1 -1
- 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-DYSirwJu.d.ts → governance-DxdJV6lx.d.ts} +10 -0
- package/dist/index-0lmh0Lbo.d.ts +6981 -0
- package/dist/index.d.ts +65 -29
- package/dist/index.js +397 -127
- package/dist/index.js.map +1 -1
- package/dist/react-types.d.ts +1 -1
- package/dist/registry.d.ts +18 -18
- package/dist/schemas/index.d.ts +3 -3377
- package/dist/schemas/index.js +1 -1
- package/dist/test-utils.d.ts +1 -1
- package/package.json +1 -1
- package/src/agent-format.test.ts +75 -3
- package/src/agent-format.ts +28 -6
- package/src/constants.ts +3 -0
- package/src/evidence.ts +27 -0
- package/src/facts/builders.ts +3 -1
- package/src/facts/fact-index.ts +22 -2
- package/src/facts/facts.test.ts +1 -1
- package/src/facts/types.ts +16 -1
- 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/components-prefer-library.test.ts +148 -0
- package/src/rules/components-prefer-library.ts +52 -1
- 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.test.ts +213 -0
- package/src/rules/styles-no-raw-color.ts +157 -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/schema.ts +6 -2
- package/src/schemas/index.ts +81 -3
- package/src/token-types.ts +18 -0
- package/src/tokens/__tests__/source-names.test.ts +42 -0
- package/src/tokens/categories.ts +54 -24
- package/src/tokens/design-token-parser.test.ts +24 -5
- package/src/tokens/design-token-parser.ts +40 -8
- package/src/tokens/scss.test.ts +94 -0
- package/src/tokens/scss.ts +11 -4
- package/src/types.ts +11 -0
- 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-CBSNXFOD.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,
|
|
@@ -185,6 +185,8 @@ var BRAND = {
|
|
|
185
185
|
legacyConfigFile: "segments.config.ts",
|
|
186
186
|
/** CLI command name (e.g., "fragments") */
|
|
187
187
|
cliCommand: "fragments",
|
|
188
|
+
/** Full user-facing CLI invocation for copy printed to end users (e.g., "npx @usefragments/cli") */
|
|
189
|
+
cliInvocation: "npx @usefragments/cli",
|
|
188
190
|
/** Directory for storing fragments, registry, and cache */
|
|
189
191
|
dataDir: ".fragments",
|
|
190
192
|
/** Components subdirectory within .fragments/ */
|
|
@@ -238,6 +240,18 @@ var DEFAULTS = {
|
|
|
238
240
|
port: 6006
|
|
239
241
|
};
|
|
240
242
|
|
|
243
|
+
// src/evidence.ts
|
|
244
|
+
var EVIDENCE_ORDER = [
|
|
245
|
+
"none",
|
|
246
|
+
"runtime_advisory",
|
|
247
|
+
"provenance_bound",
|
|
248
|
+
"source_backed",
|
|
249
|
+
"receipt_backed"
|
|
250
|
+
];
|
|
251
|
+
function canBlock(grade) {
|
|
252
|
+
return EVIDENCE_ORDER.indexOf(grade) >= EVIDENCE_ORDER.indexOf("source_backed");
|
|
253
|
+
}
|
|
254
|
+
|
|
241
255
|
// src/conform-prove.ts
|
|
242
256
|
var PROVE_MAX_PASSES = 8;
|
|
243
257
|
var PROVE_DEFAULT_MAX_PASSES = 4;
|
|
@@ -386,6 +400,7 @@ async function proveCompliant(input, callbacks, options = {}) {
|
|
|
386
400
|
|
|
387
401
|
// src/agent-format.ts
|
|
388
402
|
function buildAgentFormat(input) {
|
|
403
|
+
const zeroRulesLoaded = input.integrity.rulesLoaded === 0;
|
|
389
404
|
const integrityFailed = !input.integrity.healthy || input.integrity.inert;
|
|
390
405
|
const score = integrityFailed ? 0 : scoreFindings(input.findings);
|
|
391
406
|
const visibleFindings = uniqueFindings(input.findings).slice(0, input.maxFindings ?? 50);
|
|
@@ -398,15 +413,14 @@ function buildAgentFormat(input) {
|
|
|
398
413
|
severity: finding.severity,
|
|
399
414
|
level: severityLevel(finding.severity),
|
|
400
415
|
message: finding.message,
|
|
416
|
+
evidenceGrade: finding.evidenceGrade,
|
|
401
417
|
evidence: input.includeEvidence ? finding.evidence : [],
|
|
402
418
|
plan: planForFinding(finding)
|
|
403
419
|
}));
|
|
404
420
|
const hasErrors = input.findings.some((finding) => severityLevel(finding.severity) === "error");
|
|
405
|
-
|
|
421
|
+
const shared = {
|
|
406
422
|
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),
|
|
423
|
+
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
424
|
integrity: input.integrity,
|
|
411
425
|
fix_first: agentFindings.filter((finding) => finding.plan.confidence >= 0.8),
|
|
412
426
|
remaining: agentFindings.filter((finding) => finding.plan.confidence < 0.8),
|
|
@@ -428,7 +442,23 @@ function buildAgentFormat(input) {
|
|
|
428
442
|
...suppression.expiresOn ? { expiresOn: suppression.expiresOn } : {},
|
|
429
443
|
scope: "line"
|
|
430
444
|
}))
|
|
431
|
-
} : {}
|
|
445
|
+
} : {},
|
|
446
|
+
...input.provenance ? { provenance: input.provenance } : {},
|
|
447
|
+
...input.verdict ? { verdict: input.verdict } : {}
|
|
448
|
+
};
|
|
449
|
+
if (zeroRulesLoaded) {
|
|
450
|
+
return {
|
|
451
|
+
...shared,
|
|
452
|
+
integrity: { ...input.integrity, rulesLoaded: 0 },
|
|
453
|
+
passed: false,
|
|
454
|
+
status: "indeterminate",
|
|
455
|
+
reasons: ["zero_rules_loaded"]
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
...shared,
|
|
460
|
+
passed: !integrityFailed && (input.passed ?? true) && !hasErrors,
|
|
461
|
+
score
|
|
432
462
|
};
|
|
433
463
|
}
|
|
434
464
|
function uniqueFindings(findings) {
|
|
@@ -1651,16 +1681,20 @@ var tokenSourceSchema = z4.object({
|
|
|
1651
1681
|
var tokenConfigSchema = z4.object({
|
|
1652
1682
|
include: repoRelativePathsSchema.optional(),
|
|
1653
1683
|
sources: z4.array(tokenSourceSchema).min(1).optional(),
|
|
1684
|
+
// Declared design-system packages whose shipped fragments.json token
|
|
1685
|
+
// vocabulary seeds the known-token set (#7c). A packages-only tokens block
|
|
1686
|
+
// is valid — see the superRefine below.
|
|
1687
|
+
packages: z4.array(z4.string().min(1)).optional(),
|
|
1654
1688
|
exclude: z4.array(repoRelativePathSchema).optional(),
|
|
1655
1689
|
themeSelectors: z4.record(z4.string()).optional(),
|
|
1656
1690
|
enabled: z4.boolean().optional(),
|
|
1657
1691
|
format: z4.enum(["auto", "css", "scss", "dtcg", "tailwind"]).optional(),
|
|
1658
1692
|
namespace: z4.string().min(1).optional()
|
|
1659
1693
|
}).passthrough().superRefine((tokens, ctx) => {
|
|
1660
|
-
if (!tokens.include?.length && !tokens.sources?.length) {
|
|
1694
|
+
if (!tokens.include?.length && !tokens.sources?.length && !tokens.packages?.length) {
|
|
1661
1695
|
ctx.addIssue({
|
|
1662
1696
|
code: z4.ZodIssueCode.custom,
|
|
1663
|
-
message: "Add tokens.include or tokens.
|
|
1697
|
+
message: "Add tokens.include, tokens.sources, or tokens.packages",
|
|
1664
1698
|
path: ["sources"]
|
|
1665
1699
|
});
|
|
1666
1700
|
}
|
|
@@ -2903,30 +2937,41 @@ function inferTokenGroup(name) {
|
|
|
2903
2937
|
return category;
|
|
2904
2938
|
}
|
|
2905
2939
|
}
|
|
2940
|
+
var TOKEN_GROUP_COMMENT_MAPPINGS = {
|
|
2941
|
+
"base configuration": "base",
|
|
2942
|
+
typography: "typography",
|
|
2943
|
+
"spacing (micro)": "spacing",
|
|
2944
|
+
spacing: "spacing",
|
|
2945
|
+
"density padding": "spacing",
|
|
2946
|
+
"border radius": "radius",
|
|
2947
|
+
transitions: "transitions",
|
|
2948
|
+
colors: "colors",
|
|
2949
|
+
surfaces: "surfaces",
|
|
2950
|
+
text: "text",
|
|
2951
|
+
borders: "borders",
|
|
2952
|
+
shadows: "shadows",
|
|
2953
|
+
focus: "focus",
|
|
2954
|
+
scrollbar: "scrollbar",
|
|
2955
|
+
"component heights": "component-sizing",
|
|
2956
|
+
"appshell layout": "layout",
|
|
2957
|
+
codeblock: "code",
|
|
2958
|
+
tooltip: "tooltip",
|
|
2959
|
+
"hero/marketing gradient": "marketing"
|
|
2960
|
+
};
|
|
2961
|
+
function looksLikeCleanHeading(text) {
|
|
2962
|
+
if (!/^[a-z0-9][a-z0-9 -]*$/.test(text)) return false;
|
|
2963
|
+
const wordCount = text.split(/\s+/).filter(Boolean).length;
|
|
2964
|
+
return wordCount > 0 && wordCount <= 3;
|
|
2965
|
+
}
|
|
2906
2966
|
function normalizeTokenGroupComment(comment) {
|
|
2907
2967
|
const text = comment.trim().replace(/^\/\/\s*/, "").replace(/^\/\*+\s*/, "").replace(/\s*\*+\/$/, "").trim().toLowerCase();
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
transitions: "transitions",
|
|
2916
|
-
colors: "colors",
|
|
2917
|
-
surfaces: "surfaces",
|
|
2918
|
-
text: "text",
|
|
2919
|
-
borders: "borders",
|
|
2920
|
-
shadows: "shadows",
|
|
2921
|
-
focus: "focus",
|
|
2922
|
-
scrollbar: "scrollbar",
|
|
2923
|
-
"component heights": "component-sizing",
|
|
2924
|
-
"appshell layout": "layout",
|
|
2925
|
-
codeblock: "code",
|
|
2926
|
-
tooltip: "tooltip",
|
|
2927
|
-
"hero/marketing gradient": "marketing"
|
|
2928
|
-
};
|
|
2929
|
-
return mappings[text] ?? text.replace(/\s+/g, "-");
|
|
2968
|
+
if (text in TOKEN_GROUP_COMMENT_MAPPINGS) {
|
|
2969
|
+
return TOKEN_GROUP_COMMENT_MAPPINGS[text];
|
|
2970
|
+
}
|
|
2971
|
+
if (!looksLikeCleanHeading(text)) {
|
|
2972
|
+
return null;
|
|
2973
|
+
}
|
|
2974
|
+
return text.replace(/\s+/g, "-");
|
|
2930
2975
|
}
|
|
2931
2976
|
|
|
2932
2977
|
// src/tokens/scss.ts
|
|
@@ -2993,7 +3038,7 @@ function parseScssTokens(content, filePath = "tokens.scss") {
|
|
|
2993
3038
|
const tokens = [];
|
|
2994
3039
|
const seenNames = /* @__PURE__ */ new Set();
|
|
2995
3040
|
let currentCategory = "other";
|
|
2996
|
-
let
|
|
3041
|
+
let currentCategoryIsCurated = false;
|
|
2997
3042
|
const scssVars = parseScssVariables(content);
|
|
2998
3043
|
const varDeclRegex = /^\s*(--[\w-]+)\s*:\s*(.+?)\s*;/;
|
|
2999
3044
|
const sectionCommentRegex = /^\s*\/\/\s+([A-Z].+)$/;
|
|
@@ -3003,7 +3048,9 @@ function parseScssTokens(content, filePath = "tokens.scss") {
|
|
|
3003
3048
|
const normalized = normalizeTokenGroupComment(commentMatch[0]);
|
|
3004
3049
|
if (normalized) {
|
|
3005
3050
|
currentCategory = normalized;
|
|
3006
|
-
|
|
3051
|
+
currentCategoryIsCurated = true;
|
|
3052
|
+
} else {
|
|
3053
|
+
currentCategoryIsCurated = false;
|
|
3007
3054
|
}
|
|
3008
3055
|
continue;
|
|
3009
3056
|
}
|
|
@@ -3019,7 +3066,7 @@ function parseScssTokens(content, filePath = "tokens.scss") {
|
|
|
3019
3066
|
tokens.push({
|
|
3020
3067
|
name,
|
|
3021
3068
|
value: cleanValue || void 0,
|
|
3022
|
-
category:
|
|
3069
|
+
category: currentCategoryIsCurated ? currentCategory : inferTokenGroup(name),
|
|
3023
3070
|
description
|
|
3024
3071
|
});
|
|
3025
3072
|
}
|
|
@@ -3030,7 +3077,7 @@ function parseScssTokens(content, filePath = "tokens.scss") {
|
|
|
3030
3077
|
tokens.push({
|
|
3031
3078
|
name,
|
|
3032
3079
|
value,
|
|
3033
|
-
category:
|
|
3080
|
+
category: currentCategoryIsCurated ? currentCategory : inferTokenGroup(name)
|
|
3034
3081
|
});
|
|
3035
3082
|
}
|
|
3036
3083
|
const cssVarValues = /* @__PURE__ */ new Map();
|
|
@@ -3326,19 +3373,35 @@ function parsedOutputToDesignTokens(output, filePath) {
|
|
|
3326
3373
|
return tokens;
|
|
3327
3374
|
}
|
|
3328
3375
|
function scssOutputToDesignTokens(output, filePath) {
|
|
3329
|
-
const
|
|
3330
|
-
const tokens = [];
|
|
3376
|
+
const tokensByName = /* @__PURE__ */ new Map();
|
|
3331
3377
|
for (const cat of Object.values(output.categories)) {
|
|
3332
3378
|
for (const t of cat) {
|
|
3333
|
-
const
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3379
|
+
const isScssVariable = t.name.startsWith("$");
|
|
3380
|
+
const cssName = isScssVariable ? `--${t.name.slice(1)}` : t.name;
|
|
3381
|
+
if (!cssName.startsWith("--")) continue;
|
|
3382
|
+
const existing = tokensByName.get(cssName);
|
|
3383
|
+
if (!existing) {
|
|
3384
|
+
tokensByName.set(cssName, {
|
|
3385
|
+
parsed: t,
|
|
3386
|
+
referenceFormat: isScssVariable ? "scss-var" : "css-var",
|
|
3387
|
+
sourceNames: /* @__PURE__ */ new Set([t.name])
|
|
3388
|
+
});
|
|
3389
|
+
} else {
|
|
3390
|
+
existing.sourceNames.add(t.name);
|
|
3391
|
+
if (isScssVariable) {
|
|
3392
|
+
existing.parsed = t;
|
|
3393
|
+
existing.referenceFormat = "scss-var";
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3337
3396
|
}
|
|
3338
3397
|
}
|
|
3339
|
-
return
|
|
3398
|
+
return [...tokensByName].map(
|
|
3399
|
+
([name, token]) => designTokenFromParsed(name, token.parsed, filePath, token.referenceFormat, [
|
|
3400
|
+
...token.sourceNames
|
|
3401
|
+
])
|
|
3402
|
+
);
|
|
3340
3403
|
}
|
|
3341
|
-
function designTokenFromParsed(name, parsed, filePath) {
|
|
3404
|
+
function designTokenFromParsed(name, parsed, filePath, referenceFormat, sourceNames) {
|
|
3342
3405
|
return {
|
|
3343
3406
|
name,
|
|
3344
3407
|
rawValue: parsed.value ?? "",
|
|
@@ -3351,6 +3414,8 @@ function designTokenFromParsed(name, parsed, filePath) {
|
|
|
3351
3414
|
level: 1,
|
|
3352
3415
|
referenceChain: [],
|
|
3353
3416
|
sourceFile: filePath,
|
|
3417
|
+
referenceFormat,
|
|
3418
|
+
...sourceNames?.length ? { sourceNames } : {},
|
|
3354
3419
|
theme: "default",
|
|
3355
3420
|
selector: ":root",
|
|
3356
3421
|
description: parsed.description
|
|
@@ -4289,12 +4354,37 @@ function makeFinding(input) {
|
|
|
4289
4354
|
fingerprint,
|
|
4290
4355
|
location: input.location,
|
|
4291
4356
|
evidence: input.evidence,
|
|
4357
|
+
evidenceGrade: input.evidenceGrade ?? "source_backed",
|
|
4292
4358
|
fix: input.fix,
|
|
4293
4359
|
attributes: input.attributes
|
|
4294
4360
|
});
|
|
4295
4361
|
}
|
|
4296
4362
|
|
|
4297
4363
|
// src/rules/utils.ts
|
|
4364
|
+
function emitFix(fix, proof, ix) {
|
|
4365
|
+
const reasons = [];
|
|
4366
|
+
if (proof.editKind !== fix.kind) reasons.push("edit kind does not match proof");
|
|
4367
|
+
if (!proof.valuePreserving) reasons.push("value-preservation proof missing");
|
|
4368
|
+
if (proof.symbols.length === 0) reasons.push("token-symbol proof missing");
|
|
4369
|
+
const missingSymbols = proof.symbols.filter((symbol) => !ix.tokens.symbolExists(symbol));
|
|
4370
|
+
if (missingSymbols.length > 0) {
|
|
4371
|
+
reasons.push(`absent token symbol: ${missingSymbols.join(", ")}`);
|
|
4372
|
+
}
|
|
4373
|
+
const inapplicableSymbols = proof.symbols.filter((symbol) => {
|
|
4374
|
+
const definition = ix.tokens.definitionForSymbol(symbol);
|
|
4375
|
+
return definition !== void 0 && !isApplicableTokenReference(definition.referenceFormat);
|
|
4376
|
+
});
|
|
4377
|
+
if (inapplicableSymbols.length > 0) {
|
|
4378
|
+
reasons.push(`token reference is not directly applicable: ${inapplicableSymbols.join(", ")}`);
|
|
4379
|
+
}
|
|
4380
|
+
return {
|
|
4381
|
+
fix: { ...fix, deterministic: reasons.length === 0 },
|
|
4382
|
+
...reasons.length > 0 ? { downgradeReason: reasons.join("; ") } : {}
|
|
4383
|
+
};
|
|
4384
|
+
}
|
|
4385
|
+
function tokenSymbolsInText(value) {
|
|
4386
|
+
return [...new Set(value.match(/\$[A-Za-z0-9_-]+|--[A-Za-z0-9_-]+/g) ?? [])];
|
|
4387
|
+
}
|
|
4298
4388
|
function indexPropsByNodeId(ix) {
|
|
4299
4389
|
const out = /* @__PURE__ */ new Map();
|
|
4300
4390
|
for (const fact of ix.byKind("usage_prop_resolved")) {
|
|
@@ -4373,13 +4463,6 @@ function readUsageNode(ix, id) {
|
|
|
4373
4463
|
const fact = ix.get(id);
|
|
4374
4464
|
return fact && fact.kind === "usage_node" ? fact : void 0;
|
|
4375
4465
|
}
|
|
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
4466
|
function contractPrefixFamilies(names) {
|
|
4384
4467
|
const families = /* @__PURE__ */ new Set();
|
|
4385
4468
|
for (const name of names) {
|
|
@@ -4993,11 +5076,13 @@ function ruleComponentsPreferLibrary(ix) {
|
|
|
4993
5076
|
const textByNode = indexTextChildrenByNodeId(ix);
|
|
4994
5077
|
const classTokensByNode = indexClassTokensByNode(ix);
|
|
4995
5078
|
const reimplTargets = canonicalReimplTargets(sources, mappings);
|
|
5079
|
+
const canonicalDirectoryResolvedNodes = indexCanonicalDirectoryResolvedNodes(ix, sources);
|
|
4996
5080
|
const findings = [];
|
|
4997
5081
|
const seenImportFixes = /* @__PURE__ */ new Set();
|
|
4998
5082
|
for (const node of ix.byKind("usage_node")) {
|
|
4999
5083
|
if (isCanonicalSourceImplementationFile(node.file, sources)) continue;
|
|
5000
5084
|
if (node.element.includes(".")) continue;
|
|
5085
|
+
if (canonicalDirectoryResolvedNodes.has(node.id)) continue;
|
|
5001
5086
|
const props = propsByNode.get(node.id) ?? [];
|
|
5002
5087
|
const textChildren = textByNode.get(node.id) ?? [];
|
|
5003
5088
|
const importsForFile = importsByFileAndLocal.get(node.file);
|
|
@@ -5523,7 +5608,27 @@ function sourceLabel(source) {
|
|
|
5523
5608
|
function canonicalImportPath(source) {
|
|
5524
5609
|
if (source.kind === "npm") return source.specifier;
|
|
5525
5610
|
if (source.kind === "registry") return source.importPath;
|
|
5526
|
-
return void 0;
|
|
5611
|
+
return source.include?.length ? source.path : void 0;
|
|
5612
|
+
}
|
|
5613
|
+
function indexCanonicalDirectoryResolvedNodes(ix, sources) {
|
|
5614
|
+
const out = /* @__PURE__ */ new Set();
|
|
5615
|
+
const directoryPaths = sources.filter(
|
|
5616
|
+
(source) => source.kind === "directory"
|
|
5617
|
+
).map((source) => normalizePath(source.path));
|
|
5618
|
+
if (directoryPaths.length === 0) return out;
|
|
5619
|
+
for (const fact of ix.byKind("usage_component")) {
|
|
5620
|
+
const module = componentIdModule(fact.componentId);
|
|
5621
|
+
if (module === void 0) continue;
|
|
5622
|
+
const normalizedModule = normalizePath(module);
|
|
5623
|
+
if (directoryPaths.some((path) => isPathInSource(normalizedModule, path))) {
|
|
5624
|
+
out.add(fact.nodeId);
|
|
5625
|
+
}
|
|
5626
|
+
}
|
|
5627
|
+
return out;
|
|
5628
|
+
}
|
|
5629
|
+
function componentIdModule(componentId2) {
|
|
5630
|
+
const hash = componentId2.indexOf("#");
|
|
5631
|
+
return hash > 0 ? componentId2.slice(0, hash) : void 0;
|
|
5527
5632
|
}
|
|
5528
5633
|
function isCanonicalSourceImplementationFile(file, sources) {
|
|
5529
5634
|
const normalizedFile = normalizePath(file);
|
|
@@ -6164,12 +6269,14 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6164
6269
|
const colorTokens = ix.tokens.byCategory("color");
|
|
6165
6270
|
const preferLabel = policy.prefer === "token" ? "design token" : "CSS variable";
|
|
6166
6271
|
const findings = [];
|
|
6272
|
+
const advisorySeverity = policy.severity === "error" ? "warn" : policy.severity;
|
|
6167
6273
|
for (const decl of ix.byKind("style_declaration")) {
|
|
6168
6274
|
if (decl.property.startsWith("--") && decl.declaredTokenSource === true) continue;
|
|
6169
6275
|
const color = detectRawColor(decl.value);
|
|
6170
6276
|
if (!color) continue;
|
|
6171
6277
|
if (isExemptColor(color, policy.except)) continue;
|
|
6172
6278
|
const resolution = resolveColorToken(decl.property, color, colorTokens);
|
|
6279
|
+
const fixEmission = resolution?.kind === "exact" ? buildTokenFix(decl.property, decl.value, color, resolution, ix) : void 0;
|
|
6173
6280
|
const evidenceIds = resolution ? [decl.id, policy.id, resolution.token.id] : [decl.id, policy.id];
|
|
6174
6281
|
findings.push(
|
|
6175
6282
|
makeFinding({
|
|
@@ -6187,20 +6294,52 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6187
6294
|
property: decl.property,
|
|
6188
6295
|
value: color
|
|
6189
6296
|
},
|
|
6190
|
-
fix:
|
|
6297
|
+
fix: fixEmission?.fix,
|
|
6191
6298
|
attributes: {
|
|
6192
6299
|
property: decl.property,
|
|
6193
6300
|
rawValue: decl.value,
|
|
6194
6301
|
color,
|
|
6195
6302
|
source: "css",
|
|
6196
6303
|
suggestedToken: resolution?.token.name,
|
|
6197
|
-
...resolution ? { tokenMatch: resolution.kind } : {}
|
|
6304
|
+
...resolution ? { tokenMatch: resolution.kind } : {},
|
|
6305
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6198
6306
|
}
|
|
6199
6307
|
})
|
|
6200
6308
|
);
|
|
6201
6309
|
}
|
|
6202
6310
|
const componentByNode = indexComponentByNodeId(ix);
|
|
6203
6311
|
for (const inline of ix.byKind("usage_inline_style")) {
|
|
6312
|
+
if (inline.valueKind === "dynamic-raw") {
|
|
6313
|
+
const node2 = readUsageNode(ix, inline.nodeId);
|
|
6314
|
+
if (!node2) continue;
|
|
6315
|
+
const componentEvidenceId2 = componentByNode.get(node2.id)?.id;
|
|
6316
|
+
const evidenceIds2 = componentEvidenceId2 ? [node2.id, componentEvidenceId2, inline.id, policy.id] : [node2.id, inline.id, policy.id];
|
|
6317
|
+
findings.push(
|
|
6318
|
+
makeFinding({
|
|
6319
|
+
ruleId: RULE_ID8,
|
|
6320
|
+
ruleVersion: RULE_VERSION9,
|
|
6321
|
+
severity: advisorySeverity,
|
|
6322
|
+
message: `Runtime-constructed raw color \`${inline.value}\` on inline \`${inline.property}\`. Use a ${preferLabel} instead.`,
|
|
6323
|
+
location: node2.location,
|
|
6324
|
+
evidence: ix.evidence(evidenceIds2),
|
|
6325
|
+
fingerprintIdentity: {
|
|
6326
|
+
source: "usage_inline_style_dynamic",
|
|
6327
|
+
file: node2.file,
|
|
6328
|
+
nodePath: node2.nodePath,
|
|
6329
|
+
element: node2.element,
|
|
6330
|
+
property: inline.property,
|
|
6331
|
+
value: inline.value
|
|
6332
|
+
},
|
|
6333
|
+
attributes: {
|
|
6334
|
+
property: inline.property,
|
|
6335
|
+
rawValue: inline.value,
|
|
6336
|
+
source: "jsx",
|
|
6337
|
+
dynamic: true
|
|
6338
|
+
}
|
|
6339
|
+
})
|
|
6340
|
+
);
|
|
6341
|
+
continue;
|
|
6342
|
+
}
|
|
6204
6343
|
if (inline.valueKind === "css-variable") continue;
|
|
6205
6344
|
const color = detectRawColor(inline.value);
|
|
6206
6345
|
if (!color) continue;
|
|
@@ -6209,6 +6348,7 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6209
6348
|
if (!node) continue;
|
|
6210
6349
|
const componentEvidenceId = componentByNode.get(node.id)?.id;
|
|
6211
6350
|
const resolution = resolveColorToken(inline.property, color, colorTokens);
|
|
6351
|
+
const fixEmission = resolution?.kind === "exact" ? buildTokenFix(inline.property, inline.value, color, resolution, ix) : void 0;
|
|
6212
6352
|
const baseEvidence = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id] : [node.id, inline.id, policy.id];
|
|
6213
6353
|
const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
|
|
6214
6354
|
findings.push(
|
|
@@ -6227,20 +6367,81 @@ function ruleStylesNoRawColor(ix) {
|
|
|
6227
6367
|
property: inline.property,
|
|
6228
6368
|
value: color
|
|
6229
6369
|
},
|
|
6230
|
-
fix:
|
|
6370
|
+
fix: fixEmission?.fix,
|
|
6231
6371
|
attributes: {
|
|
6232
6372
|
property: inline.property,
|
|
6233
6373
|
rawValue: inline.value,
|
|
6234
6374
|
color,
|
|
6235
6375
|
source: "jsx",
|
|
6236
6376
|
suggestedToken: resolution?.token.name,
|
|
6237
|
-
...resolution ? { tokenMatch: resolution.kind } : {}
|
|
6377
|
+
...resolution ? { tokenMatch: resolution.kind } : {},
|
|
6378
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6379
|
+
}
|
|
6380
|
+
})
|
|
6381
|
+
);
|
|
6382
|
+
}
|
|
6383
|
+
for (const prop of ix.byKind("usage_prop_resolved")) {
|
|
6384
|
+
if (prop.resolution !== "static") continue;
|
|
6385
|
+
if (typeof prop.value !== "string") continue;
|
|
6386
|
+
if (!SVG_PAINT_ATTRS.has(prop.prop)) continue;
|
|
6387
|
+
const color = detectRawColor(prop.value);
|
|
6388
|
+
if (!color) continue;
|
|
6389
|
+
if (isExemptColor(color, policy.except)) continue;
|
|
6390
|
+
const node = readUsageNode(ix, prop.nodeId);
|
|
6391
|
+
if (!node) continue;
|
|
6392
|
+
const componentEvidenceId = componentByNode.get(node.id)?.id;
|
|
6393
|
+
const cssProperty = SVG_PAINT_CSS_PROPERTY[prop.prop] ?? prop.prop;
|
|
6394
|
+
const resolution = resolveColorToken(cssProperty, color, colorTokens);
|
|
6395
|
+
const fixEmission = resolution?.kind === "exact" ? buildTokenFix(cssProperty, prop.value, color, resolution, ix) : void 0;
|
|
6396
|
+
const baseEvidence = componentEvidenceId ? [node.id, componentEvidenceId, prop.id, policy.id] : [node.id, prop.id, policy.id];
|
|
6397
|
+
const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
|
|
6398
|
+
findings.push(
|
|
6399
|
+
makeFinding({
|
|
6400
|
+
ruleId: RULE_ID8,
|
|
6401
|
+
ruleVersion: RULE_VERSION9,
|
|
6402
|
+
severity: advisorySeverity,
|
|
6403
|
+
message: colorMessage(color, `\`${prop.prop}\``, resolution, preferLabel),
|
|
6404
|
+
location: prop.location ?? node.location,
|
|
6405
|
+
evidence: ix.evidence(evidenceIds),
|
|
6406
|
+
fingerprintIdentity: {
|
|
6407
|
+
source: "usage_prop_resolved",
|
|
6408
|
+
file: node.file,
|
|
6409
|
+
nodePath: node.nodePath,
|
|
6410
|
+
element: node.element,
|
|
6411
|
+
property: prop.prop,
|
|
6412
|
+
value: color
|
|
6413
|
+
},
|
|
6414
|
+
fix: fixEmission?.fix,
|
|
6415
|
+
attributes: {
|
|
6416
|
+
property: prop.prop,
|
|
6417
|
+
rawValue: prop.value,
|
|
6418
|
+
color,
|
|
6419
|
+
source: "jsx",
|
|
6420
|
+
suggestedToken: resolution?.token.name,
|
|
6421
|
+
...resolution ? { tokenMatch: resolution.kind } : {},
|
|
6422
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6238
6423
|
}
|
|
6239
6424
|
})
|
|
6240
6425
|
);
|
|
6241
6426
|
}
|
|
6242
6427
|
return findings;
|
|
6243
6428
|
}
|
|
6429
|
+
var SVG_PAINT_ATTRS = /* @__PURE__ */ new Set([
|
|
6430
|
+
"fill",
|
|
6431
|
+
"stroke",
|
|
6432
|
+
"color",
|
|
6433
|
+
"stop-color",
|
|
6434
|
+
"stopColor",
|
|
6435
|
+
"flood-color",
|
|
6436
|
+
"floodColor",
|
|
6437
|
+
"lighting-color",
|
|
6438
|
+
"lightingColor"
|
|
6439
|
+
]);
|
|
6440
|
+
var SVG_PAINT_CSS_PROPERTY = {
|
|
6441
|
+
stopColor: "stop-color",
|
|
6442
|
+
floodColor: "flood-color",
|
|
6443
|
+
lightingColor: "lighting-color"
|
|
6444
|
+
};
|
|
6244
6445
|
function resolveColorToken(property, raw, tokens) {
|
|
6245
6446
|
const exact = findTokenMatch(property, raw, tokens);
|
|
6246
6447
|
if (exact) return { kind: "exact", token: exact.token, ambiguous: exact.ambiguous };
|
|
@@ -6318,21 +6519,24 @@ function normalizeColor2(value) {
|
|
|
6318
6519
|
}
|
|
6319
6520
|
return lower;
|
|
6320
6521
|
}
|
|
6321
|
-
function buildTokenFix(property, rawValue, rawColor, match) {
|
|
6522
|
+
function buildTokenFix(property, rawValue, rawColor, match, ix) {
|
|
6322
6523
|
const reference = tokenReference(match.token.name);
|
|
6323
6524
|
const value = replaceRawColor(rawValue, rawColor, reference);
|
|
6324
|
-
return
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6525
|
+
return emitFix(
|
|
6526
|
+
{
|
|
6527
|
+
kind: "replaceStyleValue",
|
|
6528
|
+
title: `Replace ${property} with ${value}`,
|
|
6529
|
+
property,
|
|
6530
|
+
value,
|
|
6531
|
+
deterministic: true
|
|
6532
|
+
},
|
|
6533
|
+
{
|
|
6534
|
+
symbols: [match.token.name],
|
|
6535
|
+
editKind: "replaceStyleValue",
|
|
6536
|
+
valuePreserving: !match.ambiguous
|
|
6537
|
+
},
|
|
6538
|
+
ix
|
|
6539
|
+
);
|
|
6336
6540
|
}
|
|
6337
6541
|
function replaceRawColor(rawValue, rawColor, replacement) {
|
|
6338
6542
|
if (rawValue === rawColor) return replacement;
|
|
@@ -6363,6 +6567,7 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6363
6567
|
if (!resolution) continue;
|
|
6364
6568
|
const tokenIds = resolutionTokenIds(resolution);
|
|
6365
6569
|
const evidenceIds = tokenIds.length ? [decl.id, policy.id, ...tokenIds] : [decl.id, policy.id];
|
|
6570
|
+
const fixEmission = dimensionFix(decl.property, resolution, ix);
|
|
6366
6571
|
findings.push(
|
|
6367
6572
|
makeFinding({
|
|
6368
6573
|
ruleId: RULE_ID9,
|
|
@@ -6379,12 +6584,13 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6379
6584
|
property: decl.property,
|
|
6380
6585
|
value: decl.value
|
|
6381
6586
|
},
|
|
6382
|
-
fix:
|
|
6587
|
+
fix: fixEmission?.fix,
|
|
6383
6588
|
attributes: {
|
|
6384
6589
|
property: decl.property,
|
|
6385
6590
|
rawValue: decl.value,
|
|
6386
6591
|
source: "css",
|
|
6387
|
-
suggestedToken: resolutionSuggestedToken(resolution)
|
|
6592
|
+
suggestedToken: resolutionSuggestedToken(resolution),
|
|
6593
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6388
6594
|
}
|
|
6389
6595
|
})
|
|
6390
6596
|
);
|
|
@@ -6403,6 +6609,7 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6403
6609
|
const baseEvidence = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id] : [node.id, inline.id, policy.id];
|
|
6404
6610
|
const tokenIds = resolutionTokenIds(resolution);
|
|
6405
6611
|
const evidenceIds = tokenIds.length ? [...baseEvidence, ...tokenIds] : baseEvidence;
|
|
6612
|
+
const fixEmission = dimensionFix(inline.property, resolution, ix);
|
|
6406
6613
|
findings.push(
|
|
6407
6614
|
makeFinding({
|
|
6408
6615
|
ruleId: RULE_ID9,
|
|
@@ -6419,12 +6626,13 @@ function ruleStylesNoRawDimensions(ix) {
|
|
|
6419
6626
|
property: inline.property,
|
|
6420
6627
|
value: inline.value
|
|
6421
6628
|
},
|
|
6422
|
-
fix:
|
|
6629
|
+
fix: fixEmission?.fix,
|
|
6423
6630
|
attributes: {
|
|
6424
6631
|
property: inline.property,
|
|
6425
6632
|
rawValue: inline.value,
|
|
6426
6633
|
source: "jsx",
|
|
6427
|
-
suggestedToken: resolutionSuggestedToken(resolution)
|
|
6634
|
+
suggestedToken: resolutionSuggestedToken(resolution),
|
|
6635
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6428
6636
|
}
|
|
6429
6637
|
})
|
|
6430
6638
|
);
|
|
@@ -6469,15 +6677,23 @@ function dimensionMessage(rawValue, property, preferLabel, resolution, inline) {
|
|
|
6469
6677
|
}
|
|
6470
6678
|
return `Raw dimension ${rawValue} on ${where}. Use a ${preferLabel} instead.`;
|
|
6471
6679
|
}
|
|
6472
|
-
function dimensionFix(property, resolution) {
|
|
6680
|
+
function dimensionFix(property, resolution, ix) {
|
|
6473
6681
|
if (resolution.kind === "exact") {
|
|
6474
|
-
return
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6682
|
+
return emitFix(
|
|
6683
|
+
{
|
|
6684
|
+
kind: "replaceStyleValue",
|
|
6685
|
+
title: `Replace ${property} with ${resolution.fixValue}`,
|
|
6686
|
+
property,
|
|
6687
|
+
value: resolution.fixValue,
|
|
6688
|
+
deterministic: true
|
|
6689
|
+
},
|
|
6690
|
+
{
|
|
6691
|
+
symbols: resolution.tokens.map((token) => token.name),
|
|
6692
|
+
editKind: "replaceStyleValue",
|
|
6693
|
+
valuePreserving: true
|
|
6694
|
+
},
|
|
6695
|
+
ix
|
|
6696
|
+
);
|
|
6481
6697
|
}
|
|
6482
6698
|
return void 0;
|
|
6483
6699
|
}
|
|
@@ -6578,7 +6794,7 @@ function resolveSpacingValue(input) {
|
|
|
6578
6794
|
let anyOffScale = false;
|
|
6579
6795
|
let canFixAll = true;
|
|
6580
6796
|
let reliableSuggestion = true;
|
|
6581
|
-
let
|
|
6797
|
+
let allValuePreserving = true;
|
|
6582
6798
|
for (const part of parts) {
|
|
6583
6799
|
const normalized = normalizeLengthForScale(part.parsed, input.scale);
|
|
6584
6800
|
if (normalized === null) return null;
|
|
@@ -6592,8 +6808,7 @@ function resolveSpacingValue(input) {
|
|
|
6592
6808
|
}
|
|
6593
6809
|
anyViolation = true;
|
|
6594
6810
|
const reference = tokenReference(exactToken.name);
|
|
6595
|
-
|
|
6596
|
-
if (!applicable) allApplicableValuePreserving = false;
|
|
6811
|
+
if (!normalized.deterministicFix) allValuePreserving = false;
|
|
6597
6812
|
suggestedParts.push(reference);
|
|
6598
6813
|
if (!firstViolation) {
|
|
6599
6814
|
firstViolation = {
|
|
@@ -6601,7 +6816,7 @@ function resolveSpacingValue(input) {
|
|
|
6601
6816
|
suggestedValue: reference,
|
|
6602
6817
|
suggestedToken: exactToken.name,
|
|
6603
6818
|
fixEmittable: true,
|
|
6604
|
-
deterministicFix:
|
|
6819
|
+
deterministicFix: normalized.deterministicFix,
|
|
6605
6820
|
reason: "token-equivalent",
|
|
6606
6821
|
matchedToken: exactToken.name,
|
|
6607
6822
|
assumedRootFontSizePx: normalized.assumedRootFontSizePx,
|
|
@@ -6612,7 +6827,7 @@ function resolveSpacingValue(input) {
|
|
|
6612
6827
|
}
|
|
6613
6828
|
anyViolation = true;
|
|
6614
6829
|
anyOffScale = true;
|
|
6615
|
-
|
|
6830
|
+
allValuePreserving = false;
|
|
6616
6831
|
const nearest = nearestSignedScaleValue(normalized.value, input.allowed);
|
|
6617
6832
|
const snapsToZero = nearest === 0 && normalized.value !== 0;
|
|
6618
6833
|
let suggestedPart;
|
|
@@ -6645,7 +6860,7 @@ function resolveSpacingValue(input) {
|
|
|
6645
6860
|
const joined = suggestedParts.join(" ");
|
|
6646
6861
|
const changes = joined !== input.raw.trim().replace(/\s+/g, " ");
|
|
6647
6862
|
const fixEmittable = canFixAll && reliableSuggestion && changes;
|
|
6648
|
-
const deterministic = fixEmittable && !anyOffScale &&
|
|
6863
|
+
const deterministic = fixEmittable && !anyOffScale && allValuePreserving;
|
|
6649
6864
|
return {
|
|
6650
6865
|
...firstViolation,
|
|
6651
6866
|
reason: anyOffScale ? "off-scale" : "token-equivalent",
|
|
@@ -6709,6 +6924,7 @@ function checkDeclaration(ix, decl, lookupFor) {
|
|
|
6709
6924
|
tokens: lookupFor(scale)
|
|
6710
6925
|
});
|
|
6711
6926
|
if (!checked) return null;
|
|
6927
|
+
const fixEmission = buildSpacingFix(decl.property, checked, ix);
|
|
6712
6928
|
return makeFinding({
|
|
6713
6929
|
ruleId: RULE_ID10,
|
|
6714
6930
|
ruleVersion: RULE_VERSION11,
|
|
@@ -6724,7 +6940,7 @@ function checkDeclaration(ix, decl, lookupFor) {
|
|
|
6724
6940
|
property: decl.property,
|
|
6725
6941
|
value: decl.value
|
|
6726
6942
|
},
|
|
6727
|
-
fix:
|
|
6943
|
+
fix: fixEmission?.fix,
|
|
6728
6944
|
attributes: {
|
|
6729
6945
|
property: decl.property,
|
|
6730
6946
|
rawValue: decl.value,
|
|
@@ -6738,7 +6954,8 @@ function checkDeclaration(ix, decl, lookupFor) {
|
|
|
6738
6954
|
normalizedUnit: scale.unit,
|
|
6739
6955
|
assumedRootFontSizePx: checked.assumedRootFontSizePx,
|
|
6740
6956
|
assumedEmBasePx: checked.assumedEmBasePx,
|
|
6741
|
-
source: "css"
|
|
6957
|
+
source: "css",
|
|
6958
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6742
6959
|
}
|
|
6743
6960
|
});
|
|
6744
6961
|
}
|
|
@@ -6758,6 +6975,7 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
|
|
|
6758
6975
|
bareNumberFix: inline.valueKind === "number"
|
|
6759
6976
|
});
|
|
6760
6977
|
if (!checked) return null;
|
|
6978
|
+
const fixEmission = buildSpacingFix(inline.property, checked, ix);
|
|
6761
6979
|
const node = readUsageNode(ix, inline.nodeId);
|
|
6762
6980
|
if (!node) return null;
|
|
6763
6981
|
const componentEvidenceId = componentByNode.get(node.id)?.id;
|
|
@@ -6777,7 +6995,7 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
|
|
|
6777
6995
|
property: inline.property,
|
|
6778
6996
|
value: inline.value
|
|
6779
6997
|
},
|
|
6780
|
-
fix:
|
|
6998
|
+
fix: fixEmission?.fix,
|
|
6781
6999
|
attributes: {
|
|
6782
7000
|
property: inline.property,
|
|
6783
7001
|
rawValue: inline.value,
|
|
@@ -6791,19 +7009,28 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
|
|
|
6791
7009
|
normalizedUnit: scale.unit,
|
|
6792
7010
|
assumedRootFontSizePx: checked.assumedRootFontSizePx,
|
|
6793
7011
|
assumedEmBasePx: checked.assumedEmBasePx,
|
|
6794
|
-
source: "jsx"
|
|
7012
|
+
source: "jsx",
|
|
7013
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6795
7014
|
}
|
|
6796
7015
|
});
|
|
6797
7016
|
}
|
|
6798
|
-
function buildSpacingFix(property, checked) {
|
|
7017
|
+
function buildSpacingFix(property, checked, ix) {
|
|
6799
7018
|
if (!checked.fixEmittable || checked.suggestedValue === void 0) return void 0;
|
|
6800
|
-
return
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
7019
|
+
return emitFix(
|
|
7020
|
+
{
|
|
7021
|
+
kind: "replaceStyleValue",
|
|
7022
|
+
title: `Replace ${property} with ${checked.suggestedValue}`,
|
|
7023
|
+
property,
|
|
7024
|
+
value: checked.suggestedValue,
|
|
7025
|
+
deterministic: true
|
|
7026
|
+
},
|
|
7027
|
+
{
|
|
7028
|
+
symbols: tokenSymbolsInText(checked.suggestedValue),
|
|
7029
|
+
editKind: "replaceStyleValue",
|
|
7030
|
+
valuePreserving: checked.deterministicFix
|
|
7031
|
+
},
|
|
7032
|
+
ix
|
|
7033
|
+
);
|
|
6807
7034
|
}
|
|
6808
7035
|
function spacingMessage(property, value, allowed, unit, checked) {
|
|
6809
7036
|
if (checked.reason === "token-equivalent" && checked.matchedToken) {
|
|
@@ -6832,6 +7059,7 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6832
7059
|
if (decl.property.toLowerCase() !== "font-size") continue;
|
|
6833
7060
|
const checked = checkFontSize(decl.value, allowed, scale, tokens);
|
|
6834
7061
|
if (!checked) continue;
|
|
7062
|
+
const fixEmission = buildFix(decl.property, checked, ix);
|
|
6835
7063
|
findings.push(
|
|
6836
7064
|
makeFinding({
|
|
6837
7065
|
ruleId: RULE_ID11,
|
|
@@ -6848,7 +7076,7 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6848
7076
|
property: decl.property,
|
|
6849
7077
|
value: decl.value
|
|
6850
7078
|
},
|
|
6851
|
-
fix:
|
|
7079
|
+
fix: fixEmission?.fix,
|
|
6852
7080
|
attributes: {
|
|
6853
7081
|
property: decl.property,
|
|
6854
7082
|
rawValue: decl.value,
|
|
@@ -6856,7 +7084,8 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6856
7084
|
allowed,
|
|
6857
7085
|
suggestedValue: checked.suggestedValue,
|
|
6858
7086
|
matchedToken: checked.matchedToken,
|
|
6859
|
-
source: "css"
|
|
7087
|
+
source: "css",
|
|
7088
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6860
7089
|
}
|
|
6861
7090
|
})
|
|
6862
7091
|
);
|
|
@@ -6865,8 +7094,15 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6865
7094
|
for (const inline of ix.byKind("usage_inline_style")) {
|
|
6866
7095
|
if (inline.valueKind === "css-variable") continue;
|
|
6867
7096
|
if (!FONT_SIZE_PROPERTIES.has(inline.property.toLowerCase())) continue;
|
|
6868
|
-
const checked = checkFontSize(
|
|
7097
|
+
const checked = checkFontSize(
|
|
7098
|
+
inline.value,
|
|
7099
|
+
allowed,
|
|
7100
|
+
scale,
|
|
7101
|
+
tokens,
|
|
7102
|
+
inline.valueKind === "number"
|
|
7103
|
+
);
|
|
6869
7104
|
if (!checked) continue;
|
|
7105
|
+
const fixEmission = buildFix(inline.property, checked, ix);
|
|
6870
7106
|
const node = readUsageNode(ix, inline.nodeId);
|
|
6871
7107
|
if (!node) continue;
|
|
6872
7108
|
const componentEvidenceId = componentByNode.get(node.id)?.id;
|
|
@@ -6887,7 +7123,7 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6887
7123
|
property: inline.property,
|
|
6888
7124
|
value: inline.value
|
|
6889
7125
|
},
|
|
6890
|
-
fix:
|
|
7126
|
+
fix: fixEmission?.fix,
|
|
6891
7127
|
attributes: {
|
|
6892
7128
|
property: inline.property,
|
|
6893
7129
|
rawValue: inline.value,
|
|
@@ -6895,7 +7131,8 @@ function ruleStylesNoRawTypography(ix) {
|
|
|
6895
7131
|
allowed,
|
|
6896
7132
|
suggestedValue: checked.suggestedValue,
|
|
6897
7133
|
matchedToken: checked.matchedToken,
|
|
6898
|
-
source: "jsx"
|
|
7134
|
+
source: "jsx",
|
|
7135
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
|
|
6899
7136
|
}
|
|
6900
7137
|
})
|
|
6901
7138
|
);
|
|
@@ -6925,11 +7162,10 @@ function checkFontSize(raw, allowed, scale, tokens, bareNumberFix = false) {
|
|
|
6925
7162
|
const exactToken = tokens.get(magnitude);
|
|
6926
7163
|
if (matchesScale(normalized.value, allowed)) {
|
|
6927
7164
|
if (!exactToken) return null;
|
|
6928
|
-
const applicable = isApplicableTokenReference(exactToken.referenceFormat) && normalized.deterministicFix;
|
|
6929
7165
|
return {
|
|
6930
7166
|
suggestedValue: tokenReference(exactToken.name),
|
|
6931
7167
|
fixEmittable: true,
|
|
6932
|
-
deterministicFix:
|
|
7168
|
+
deterministicFix: normalized.deterministicFix,
|
|
6933
7169
|
reason: "token-equivalent",
|
|
6934
7170
|
matchedToken: exactToken.name
|
|
6935
7171
|
};
|
|
@@ -6947,15 +7183,23 @@ function checkFontSize(raw, allowed, scale, tokens, bareNumberFix = false) {
|
|
|
6947
7183
|
reason: "off-scale"
|
|
6948
7184
|
};
|
|
6949
7185
|
}
|
|
6950
|
-
function buildFix(property, checked) {
|
|
7186
|
+
function buildFix(property, checked, ix) {
|
|
6951
7187
|
if (!checked.fixEmittable || checked.suggestedValue === void 0) return void 0;
|
|
6952
|
-
return
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
7188
|
+
return emitFix(
|
|
7189
|
+
{
|
|
7190
|
+
kind: "replaceStyleValue",
|
|
7191
|
+
title: `Replace ${property} with ${checked.suggestedValue}`,
|
|
7192
|
+
property,
|
|
7193
|
+
value: checked.suggestedValue,
|
|
7194
|
+
deterministic: true
|
|
7195
|
+
},
|
|
7196
|
+
{
|
|
7197
|
+
symbols: tokenSymbolsInText(checked.suggestedValue),
|
|
7198
|
+
editKind: "replaceStyleValue",
|
|
7199
|
+
valuePreserving: checked.deterministicFix
|
|
7200
|
+
},
|
|
7201
|
+
ix
|
|
7202
|
+
);
|
|
6959
7203
|
}
|
|
6960
7204
|
function typographyMessage(property, value, allowed, unit, checked) {
|
|
6961
7205
|
if (checked.reason === "token-equivalent" && checked.matchedToken) {
|
|
@@ -7629,7 +7873,7 @@ var SASS_FILE = /\.(scss|sass)$/i;
|
|
|
7629
7873
|
function ruleTokensRequireDualFallback(ix) {
|
|
7630
7874
|
const policy = ix.policy.ruleConfig(RULE_ID19);
|
|
7631
7875
|
if (!policy?.enabled) return [];
|
|
7632
|
-
const
|
|
7876
|
+
const tokensByCssVariable = indexTokensByCssVariable(ix.tokens.list());
|
|
7633
7877
|
const findings = [];
|
|
7634
7878
|
for (const decl of ix.byKind("style_declaration")) {
|
|
7635
7879
|
if (!SASS_FILE.test(decl.file)) continue;
|
|
@@ -7639,19 +7883,35 @@ function ruleTokensRequireDualFallback(ix) {
|
|
|
7639
7883
|
const tokenName = match[1];
|
|
7640
7884
|
const hasFallback = match[2] !== void 0;
|
|
7641
7885
|
if (hasFallback) continue;
|
|
7642
|
-
|
|
7886
|
+
const token = tokensByCssVariable.get(tokenName);
|
|
7887
|
+
if (!token) continue;
|
|
7643
7888
|
const rawValue = match[0];
|
|
7644
7889
|
const scssVar = tokenName.replace(/^--/, "$");
|
|
7645
|
-
const
|
|
7646
|
-
const nextValue = decl.value.replace(rawValue,
|
|
7890
|
+
const hasScssVariable = token.referenceFormat === "scss-var";
|
|
7891
|
+
const nextValue = hasScssVariable ? decl.value.replace(rawValue, `var(${tokenName}, ${scssVar})`) : void 0;
|
|
7892
|
+
const fixEmission = nextValue ? emitFix(
|
|
7893
|
+
{
|
|
7894
|
+
kind: "replaceStyleValue",
|
|
7895
|
+
title: `Add fallback for ${tokenName}`,
|
|
7896
|
+
property: decl.property,
|
|
7897
|
+
value: nextValue,
|
|
7898
|
+
deterministic: true
|
|
7899
|
+
},
|
|
7900
|
+
{
|
|
7901
|
+
symbols: [tokenName, scssVar],
|
|
7902
|
+
editKind: "replaceStyleValue",
|
|
7903
|
+
valuePreserving: true
|
|
7904
|
+
},
|
|
7905
|
+
ix
|
|
7906
|
+
) : void 0;
|
|
7647
7907
|
findings.push(
|
|
7648
7908
|
makeFinding({
|
|
7649
7909
|
ruleId: RULE_ID19,
|
|
7650
7910
|
ruleVersion: RULE_VERSION20,
|
|
7651
7911
|
severity: policy.severity ?? "warn",
|
|
7652
|
-
message: `var(${tokenName}) is missing an SCSS fallback. Use var(${tokenName}, ${scssVar}).`,
|
|
7912
|
+
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
7913
|
location: decl.location,
|
|
7654
|
-
evidence: ix.evidence([decl.id, policy.id]),
|
|
7914
|
+
evidence: ix.evidence([decl.id, token.id, policy.id]),
|
|
7655
7915
|
fingerprintIdentity: {
|
|
7656
7916
|
file: decl.file,
|
|
7657
7917
|
selector: decl.selector,
|
|
@@ -7659,18 +7919,13 @@ function ruleTokensRequireDualFallback(ix) {
|
|
|
7659
7919
|
property: decl.property,
|
|
7660
7920
|
tokenName
|
|
7661
7921
|
},
|
|
7662
|
-
fix:
|
|
7663
|
-
kind: "replaceStyleValue",
|
|
7664
|
-
title: `Add fallback for ${tokenName}`,
|
|
7665
|
-
property: decl.property,
|
|
7666
|
-
value: nextValue,
|
|
7667
|
-
deterministic: true
|
|
7668
|
-
},
|
|
7922
|
+
fix: fixEmission?.fix,
|
|
7669
7923
|
attributes: {
|
|
7670
7924
|
property: decl.property,
|
|
7671
7925
|
rawValue: decl.value,
|
|
7672
7926
|
tokenName,
|
|
7673
|
-
suggestedValue: nextValue,
|
|
7927
|
+
...nextValue ? { suggestedValue: nextValue } : {},
|
|
7928
|
+
...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
|
|
7674
7929
|
source: "css"
|
|
7675
7930
|
}
|
|
7676
7931
|
})
|
|
@@ -7679,6 +7934,17 @@ function ruleTokensRequireDualFallback(ix) {
|
|
|
7679
7934
|
}
|
|
7680
7935
|
return findings;
|
|
7681
7936
|
}
|
|
7937
|
+
function indexTokensByCssVariable(tokens) {
|
|
7938
|
+
const indexed = /* @__PURE__ */ new Map();
|
|
7939
|
+
for (const token of tokens) {
|
|
7940
|
+
const cssVariable = token.name.startsWith("--") ? token.name : token.name.startsWith("$") ? `--${token.name.slice(1)}` : `--${token.name}`;
|
|
7941
|
+
const existing = indexed.get(cssVariable);
|
|
7942
|
+
if (!existing || token.referenceFormat === "scss-var") {
|
|
7943
|
+
indexed.set(cssVariable, token);
|
|
7944
|
+
}
|
|
7945
|
+
}
|
|
7946
|
+
return indexed;
|
|
7947
|
+
}
|
|
7682
7948
|
|
|
7683
7949
|
// src/rules/tokens-css-vars-must-be-defined.ts
|
|
7684
7950
|
var RULE_ID20 = "tokens/css-vars-must-be-defined";
|
|
@@ -7770,6 +8036,8 @@ var BLOCKING_RULE_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
|
7770
8036
|
// imported component bypasses its canonical
|
|
7771
8037
|
]);
|
|
7772
8038
|
function gatesCi(finding, failOnWarnings) {
|
|
8039
|
+
if (!canBlock(finding.evidenceGrade ?? "source_backed")) return false;
|
|
8040
|
+
if (finding.attributes?.advisory === true) return false;
|
|
7773
8041
|
const level = severityLevel(finding.severity);
|
|
7774
8042
|
return level === "error" || failOnWarnings && level === "warn";
|
|
7775
8043
|
}
|
|
@@ -8500,6 +8768,7 @@ export {
|
|
|
8500
8768
|
DEFAULTS,
|
|
8501
8769
|
DEFAULT_ENHANCED_STYLE_PROPERTIES,
|
|
8502
8770
|
DEFAULT_STYLE_PROPERTIES,
|
|
8771
|
+
EVIDENCE_ORDER,
|
|
8503
8772
|
EXPLAIN_URL_BASE,
|
|
8504
8773
|
FRAGMENTS_INTERNAL_RULE_IDS,
|
|
8505
8774
|
FactIndex,
|
|
@@ -8551,6 +8820,7 @@ export {
|
|
|
8551
8820
|
byCode,
|
|
8552
8821
|
byRuleId,
|
|
8553
8822
|
calculateDeltaE,
|
|
8823
|
+
canBlock,
|
|
8554
8824
|
canonicalJson,
|
|
8555
8825
|
canonicalPreimage,
|
|
8556
8826
|
canonicalizeOwnedComponentId,
|