@usefragments/core 1.5.0 → 1.5.2
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-HXGE3O2F.js → chunk-BAHCOAVG.js} +133 -7
- package/dist/chunk-BAHCOAVG.js.map +1 -0
- package/dist/{chunk-57QDBEHQ.js → chunk-ZHS52OT4.js} +3 -9
- package/dist/chunk-ZHS52OT4.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-BLsyk56o.d.ts → governance-pKrfh517.d.ts} +264 -4
- package/dist/{index-h_yWj15D.d.ts → index-DbkPE46t.d.ts} +40 -0
- package/dist/index.d.ts +472 -28
- package/dist/index.js +561 -39
- package/dist/index.js.map +1 -1
- package/dist/react-types.d.ts +1 -1
- package/dist/schemas/index.d.ts +1 -1
- 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 +1 -0
- package/src/canonical-bridge.ts +46 -0
- package/src/canonical-direction.ts +118 -0
- package/src/codes/__tests__/codes.test.ts +1 -1
- package/src/codes/codes.ts +29 -0
- package/src/config.ts +20 -0
- package/src/contract/preimage.test.ts +40 -0
- package/src/contract/preimage.ts +20 -0
- package/src/effective-governance-inputs.test.ts +52 -0
- package/src/effective-governance-inputs.ts +106 -0
- package/src/facts/builders.ts +25 -1
- package/src/facts/compile.ts +20 -2
- package/src/facts/fact-index.ts +12 -1
- package/src/facts/facts.test.ts +14 -18
- package/src/facts/types.ts +25 -6
- package/src/governance-integrity.test.ts +127 -0
- package/src/governance-integrity.ts +326 -4
- package/src/governance.test.ts +87 -1
- package/src/governance.ts +121 -0
- package/src/index.ts +34 -5
- package/src/rules/__tests__/fix-emission-invariant.test.ts +35 -4
- package/src/rules/components-prefer-library.test.ts +103 -0
- package/src/rules/components-prefer-library.ts +30 -4
- package/src/rules/fix-availability.ts +1 -0
- package/src/rules/index.ts +7 -0
- package/src/rules/jsx-preferred-import-path.ts +45 -9
- package/src/rules/rules.test.ts +202 -6
- package/src/rules/spacing-resolution.ts +2 -2
- package/src/rules/styles-no-raw-color.test.ts +6 -1
- package/src/rules/styles-no-raw-color.ts +3 -2
- package/src/rules/styles-no-raw-dimensions.ts +5 -7
- package/src/rules/styles-no-raw-spacing.test.ts +48 -0
- package/src/rules/styles-no-raw-spacing.ts +10 -7
- package/src/rules/styles-no-raw-typography.ts +7 -4
- package/src/rules/taxonomy.test.ts +3 -0
- package/src/rules/tiers.ts +2 -0
- package/src/rules/token-candidates.ts +36 -0
- package/src/rules/tokens-require-dual-fallback.ts +2 -1
- package/src/rules/tokens-upstream-drift.test.ts +111 -0
- package/src/rules/tokens-upstream-drift.ts +57 -0
- package/src/rules/utils.ts +22 -14
- package/src/schema.ts +13 -0
- package/src/schemas/index.ts +5 -8
- package/src/token-types.ts +71 -1
- package/src/types.ts +27 -3
- package/dist/chunk-57QDBEHQ.js.map +0 -1
- package/dist/chunk-HXGE3O2F.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -227,6 +227,12 @@ export type {
|
|
|
227
227
|
// Token types
|
|
228
228
|
export type {
|
|
229
229
|
TokenCategory,
|
|
230
|
+
TokenCatalog,
|
|
231
|
+
TokenCatalogDefinition,
|
|
232
|
+
TokenCatalogDiagnostic,
|
|
233
|
+
TokenCatalogLocation,
|
|
234
|
+
TokenCatalogReference,
|
|
235
|
+
TokenCatalogSourceState,
|
|
230
236
|
DesignToken,
|
|
231
237
|
TokenRegistry,
|
|
232
238
|
TokenRegistryMeta,
|
|
@@ -419,7 +425,7 @@ export {
|
|
|
419
425
|
} from "./schema.js";
|
|
420
426
|
|
|
421
427
|
// Main API
|
|
422
|
-
export { defineConfig } from "./config.js";
|
|
428
|
+
export { configDeclarationForDiagnostics, defineConfig } from "./config.js";
|
|
423
429
|
export {
|
|
424
430
|
defineFragment,
|
|
425
431
|
compileFragment,
|
|
@@ -435,6 +441,7 @@ export type { InferProps } from "./defineFragment.js";
|
|
|
435
441
|
export {
|
|
436
442
|
componentGovernanceRecordSchema,
|
|
437
443
|
componentGovernanceRecordsSchema,
|
|
444
|
+
canonicalBridgeV1Schema,
|
|
438
445
|
g,
|
|
439
446
|
globalGovernanceRecordSchema,
|
|
440
447
|
globalJsxGovernanceRecordSchema,
|
|
@@ -446,6 +453,7 @@ export {
|
|
|
446
453
|
} from "./governance.js";
|
|
447
454
|
export type {
|
|
448
455
|
AccessibilityGovernanceBuilder,
|
|
456
|
+
CanonicalBridgeV1,
|
|
449
457
|
CanonicalSource,
|
|
450
458
|
ComponentGovernanceBuilder,
|
|
451
459
|
ComponentGovernanceRecord,
|
|
@@ -645,10 +653,7 @@ export {
|
|
|
645
653
|
ruleTokensRequireDualFallback,
|
|
646
654
|
ruleA11yRequiredAccessibleName,
|
|
647
655
|
} from "./rules/index.js";
|
|
648
|
-
export {
|
|
649
|
-
indexComponentIdentityUsage,
|
|
650
|
-
type ComponentIdentityUsage,
|
|
651
|
-
} from "./identity/usage.js";
|
|
656
|
+
export { indexComponentIdentityUsage, type ComponentIdentityUsage } from "./identity/usage.js";
|
|
652
657
|
export { nearestSignedScaleValue } from "./rules/utils.js";
|
|
653
658
|
export {
|
|
654
659
|
buildSpacingTokenLookup,
|
|
@@ -708,6 +713,8 @@ export { BLOCKING_RULE_ALLOWLIST, gatesCi, isDenyEligible } from "./rules/index.
|
|
|
708
713
|
// Governance integrity — armed-vs-declared verdict over a fully-resolved policy.
|
|
709
714
|
// Encodes "enabled ≠ armed": a rule with no vocabulary enforces nothing.
|
|
710
715
|
export {
|
|
716
|
+
detectOrphanGovernanceScales,
|
|
717
|
+
detectUnconsumedConfigKeys,
|
|
711
718
|
evaluateGovernanceIntegrity,
|
|
712
719
|
hasEffectiveComponentVocabulary,
|
|
713
720
|
isEffectiveCanonicalSource,
|
|
@@ -716,10 +723,32 @@ export type {
|
|
|
716
723
|
GovernanceIntegrityFamily,
|
|
717
724
|
GovernanceIntegrityFamilyId,
|
|
718
725
|
GovernanceIntegrityInput,
|
|
726
|
+
GovernanceIntegrityRoster,
|
|
719
727
|
GovernanceIntegrityStatus,
|
|
720
728
|
GovernanceIntegrityVerdict,
|
|
729
|
+
InertConfigDiagnostic,
|
|
730
|
+
InertConfigDiagnosticCode,
|
|
731
|
+
InertConfigDiagnosticKind,
|
|
721
732
|
} from "./governance-integrity.js";
|
|
722
733
|
|
|
734
|
+
export {
|
|
735
|
+
canonicalDirectionConflictsTarget,
|
|
736
|
+
projectCanonicalDirectionConflicts,
|
|
737
|
+
} from "./canonical-direction.js";
|
|
738
|
+
export type { CanonicalDirectionConflict } from "./canonical-direction.js";
|
|
739
|
+
|
|
740
|
+
export { canonicalBridgeContractMappings } from "./canonical-bridge.js";
|
|
741
|
+
|
|
742
|
+
export {
|
|
743
|
+
compileEffectiveGovernanceInputs,
|
|
744
|
+
EFFECTIVE_GOVERNANCE_INPUTS_SCHEMA,
|
|
745
|
+
} from "./effective-governance-inputs.js";
|
|
746
|
+
export type {
|
|
747
|
+
CompileEffectiveGovernanceInputsOptions,
|
|
748
|
+
EffectiveGovernanceInputs,
|
|
749
|
+
EffectiveGovernanceInputsSourceState,
|
|
750
|
+
} from "./effective-governance-inputs.js";
|
|
751
|
+
|
|
723
752
|
// Fact IR + index (Phase 3 of the governance compiler refactor)
|
|
724
753
|
export {
|
|
725
754
|
componentId,
|
|
@@ -65,6 +65,34 @@ describe("proof-carrying fix invariant", () => {
|
|
|
65
65
|
"token reference is not directly applicable"
|
|
66
66
|
);
|
|
67
67
|
});
|
|
68
|
+
|
|
69
|
+
it("keeps JavaScript member tokens advisory without inventing a CSS variable", () => {
|
|
70
|
+
const ix = buildFixtureCorpus("js-member", "radii.md");
|
|
71
|
+
const dimension = RULES.find((rule) => rule.id === "styles/no-raw-dimensions")
|
|
72
|
+
?.run(ix)
|
|
73
|
+
.find((finding) => finding.fix);
|
|
74
|
+
|
|
75
|
+
expect(dimension?.fix).toMatchObject({
|
|
76
|
+
value: "radii.md",
|
|
77
|
+
deterministic: false,
|
|
78
|
+
});
|
|
79
|
+
expect(JSON.stringify(dimension?.fix)).not.toContain("var(--radii-md)");
|
|
80
|
+
expect(dimension?.attributes?.downgradeReason).toContain(
|
|
81
|
+
"token reference is not directly applicable"
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("fails closed when a legacy token fact has no authored reference format", () => {
|
|
86
|
+
const ix = buildFixtureCorpus("unknown");
|
|
87
|
+
const dimension = RULES.find((rule) => rule.id === "styles/no-raw-dimensions")
|
|
88
|
+
?.run(ix)
|
|
89
|
+
.find((finding) => finding.fix);
|
|
90
|
+
|
|
91
|
+
expect(dimension?.fix?.deterministic).toBe(false);
|
|
92
|
+
expect(dimension?.attributes?.downgradeReason).toContain(
|
|
93
|
+
"token reference is not directly applicable"
|
|
94
|
+
);
|
|
95
|
+
});
|
|
68
96
|
});
|
|
69
97
|
|
|
70
98
|
function runEveryRule(ix: FactIndex): Finding[] {
|
|
@@ -98,7 +126,10 @@ function withoutDeterminism(fix: FindingFix): Omit<FindingFix, "deterministic">
|
|
|
98
126
|
return rest;
|
|
99
127
|
}
|
|
100
128
|
|
|
101
|
-
function buildFixtureCorpus(
|
|
129
|
+
function buildFixtureCorpus(
|
|
130
|
+
radiusReferenceFormat: "css-var" | "dtcg" | "js-member" | "unknown" = "css-var",
|
|
131
|
+
radiusName = "--proof-radius"
|
|
132
|
+
): FactIndex {
|
|
102
133
|
const ix = new FactIndex();
|
|
103
134
|
ix.addMany([
|
|
104
135
|
makeTokenDefinitionFact({
|
|
@@ -116,11 +147,11 @@ function buildFixtureCorpus(radiusReferenceFormat: "css-var" | "dtcg" = "css-var
|
|
|
116
147
|
sourceNames: ["--proof-space"],
|
|
117
148
|
}),
|
|
118
149
|
makeTokenDefinitionFact({
|
|
119
|
-
name:
|
|
150
|
+
name: radiusName,
|
|
120
151
|
value: "4px",
|
|
121
152
|
category: "radius",
|
|
122
|
-
referenceFormat: radiusReferenceFormat,
|
|
123
|
-
sourceNames: [
|
|
153
|
+
referenceFormat: radiusReferenceFormat === "unknown" ? undefined : radiusReferenceFormat,
|
|
154
|
+
sourceNames: [radiusName],
|
|
124
155
|
}),
|
|
125
156
|
makeTokenDefinitionFact({
|
|
126
157
|
name: "--proof-font",
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
asComponentId,
|
|
5
5
|
compileGlobalGovernanceFacts,
|
|
6
6
|
FactIndex,
|
|
7
|
+
makeComponentDefinitionFact,
|
|
7
8
|
makeComponentIdentityFact,
|
|
8
9
|
makeUsageComponentFact,
|
|
9
10
|
makeUsageImportFact,
|
|
@@ -11,6 +12,108 @@ import {
|
|
|
11
12
|
ruleComponentsPreferLibrary,
|
|
12
13
|
} from "../index.js";
|
|
13
14
|
|
|
15
|
+
describe("components/prefer-library — canonical direction containment", () => {
|
|
16
|
+
const componentKey = "src/components/Button.tsx#Button";
|
|
17
|
+
|
|
18
|
+
function indexFor(decision?: "sanction" | "reject", exported = true) {
|
|
19
|
+
const ix = new FactIndex();
|
|
20
|
+
ix.addMany(
|
|
21
|
+
compileGlobalGovernanceFacts({
|
|
22
|
+
rules: {
|
|
23
|
+
"components/prefer-library": {
|
|
24
|
+
enabled: true,
|
|
25
|
+
severity: "warning",
|
|
26
|
+
options: {
|
|
27
|
+
canonicalSources: [
|
|
28
|
+
{ kind: "npm", specifier: "@mui/material", include: ["Button", "Stepper"] },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
const definition = makeComponentDefinitionFact({
|
|
36
|
+
file: "src/components/Button.tsx",
|
|
37
|
+
exportName: "Button",
|
|
38
|
+
exported,
|
|
39
|
+
componentKey,
|
|
40
|
+
renderRoot: {
|
|
41
|
+
resolution: "canonical",
|
|
42
|
+
canonical: "@mui/material#Button",
|
|
43
|
+
importSource: "@mui/material",
|
|
44
|
+
},
|
|
45
|
+
propSurface: [],
|
|
46
|
+
});
|
|
47
|
+
ix.add(definition);
|
|
48
|
+
ix.add(
|
|
49
|
+
makeComponentIdentityFact({
|
|
50
|
+
componentKey,
|
|
51
|
+
state: decision === "reject" ? "shadow" : "variant",
|
|
52
|
+
confidence: decision ? "confirmed" : "review",
|
|
53
|
+
canonicalTarget: "Button",
|
|
54
|
+
...(decision ? { decisionId: `local:${decision}:${componentKey}` } : {}),
|
|
55
|
+
evidence: [definition.id],
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
return ix;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function addLocalUsage(ix: FactIndex, name: "Button" | "Stepper") {
|
|
62
|
+
ix.add(
|
|
63
|
+
makeUsageNodeFact({
|
|
64
|
+
file: "src/App.tsx",
|
|
65
|
+
nodePath: `0:${name}`,
|
|
66
|
+
element: name,
|
|
67
|
+
location: { file: "src/App.tsx", line: 3, column: 2 },
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
ix.add(
|
|
71
|
+
makeUsageImportFact({
|
|
72
|
+
file: "src/App.tsx",
|
|
73
|
+
local: name,
|
|
74
|
+
imported: name,
|
|
75
|
+
source: `./components/${name}`,
|
|
76
|
+
location: { file: "src/App.tsx", line: 1, column: 0 },
|
|
77
|
+
})
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
it("suppresses only the exact conflicted npm export when direction is unresolved", () => {
|
|
82
|
+
const ix = indexFor();
|
|
83
|
+
addLocalUsage(ix, "Button");
|
|
84
|
+
addLocalUsage(ix, "Stepper");
|
|
85
|
+
ix.add(
|
|
86
|
+
makeUsageNodeFact({
|
|
87
|
+
file: "src/App.tsx",
|
|
88
|
+
nodePath: "0:raw-button",
|
|
89
|
+
element: "button",
|
|
90
|
+
interactive: true,
|
|
91
|
+
location: { file: "src/App.tsx", line: 4, column: 2 },
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const findings = ruleComponentsPreferLibrary(ix);
|
|
96
|
+
expect(findings).toHaveLength(1);
|
|
97
|
+
expect(findings[0]?.attributes?.suggestedComponent).toBe("Stepper");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("keeps a sanctioned wrapper exempt and restores npm guidance after explicit rejection", () => {
|
|
101
|
+
const sanctioned = indexFor("sanction");
|
|
102
|
+
addLocalUsage(sanctioned, "Button");
|
|
103
|
+
expect(ruleComponentsPreferLibrary(sanctioned)).toHaveLength(0);
|
|
104
|
+
|
|
105
|
+
const rejected = indexFor("reject");
|
|
106
|
+
addLocalUsage(rejected, "Button");
|
|
107
|
+
expect(ruleComponentsPreferLibrary(rejected)).toHaveLength(1);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("does not let a private helper deactivate canonical policy", () => {
|
|
111
|
+
const ix = indexFor(undefined, false);
|
|
112
|
+
addLocalUsage(ix, "Button");
|
|
113
|
+
expect(ruleComponentsPreferLibrary(ix)).toHaveLength(1);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
14
117
|
// #9e — import-path identity for a DIRECTORY canonical source. A directory
|
|
15
118
|
// source (e.g. `src/components/ui`) declares its canonical component NAMES via an
|
|
16
119
|
// include list. The rule must:
|
|
@@ -15,6 +15,11 @@ import {
|
|
|
15
15
|
type RawHtmlPrecisionTier,
|
|
16
16
|
} from "../raw-html-canonical.js";
|
|
17
17
|
import { ownedImportMatchesRoot, ownedImportsEqual } from "../package-identity-match.js";
|
|
18
|
+
import {
|
|
19
|
+
canonicalDirectionConflictsTarget,
|
|
20
|
+
projectCanonicalDirectionConflicts,
|
|
21
|
+
type CanonicalDirectionConflict,
|
|
22
|
+
} from "../canonical-direction.js";
|
|
18
23
|
|
|
19
24
|
import { makeFinding } from "./finding.js";
|
|
20
25
|
import type { Finding } from "./types.js";
|
|
@@ -254,8 +259,12 @@ function findClassNameReimpl(
|
|
|
254
259
|
export function ruleComponentsPreferLibrary(ix: FactIndex): Finding[] {
|
|
255
260
|
const policy = ix.policy.ruleConfig(RULE_ID);
|
|
256
261
|
if (!policy?.enabled) return [];
|
|
257
|
-
const
|
|
258
|
-
const
|
|
262
|
+
const configuredSources = canonicalSourcesFromPolicy(policy.options?.canonicalSources);
|
|
263
|
+
const conflicts = projectCanonicalDirectionConflicts(ix, configuredSources);
|
|
264
|
+
const sources = configuredSources.map((source) => suppressConflictedExports(source, conflicts));
|
|
265
|
+
const mappings = canonicalMappingsFromPolicy(policy.options?.canonicalMappings).filter(
|
|
266
|
+
(mapping) => !canonicalDirectionConflictsTarget(conflicts, mapping.importPath, mapping.name)
|
|
267
|
+
);
|
|
259
268
|
if (sources.length === 0 && mappings.length === 0) return [];
|
|
260
269
|
|
|
261
270
|
const importsByFileAndLocal = indexImportsByFileAndLocal(ix);
|
|
@@ -605,6 +614,22 @@ export function ruleComponentsPreferLibrary(ix: FactIndex): Finding[] {
|
|
|
605
614
|
return findings;
|
|
606
615
|
}
|
|
607
616
|
|
|
617
|
+
function suppressConflictedExports(
|
|
618
|
+
source: CanonicalSource,
|
|
619
|
+
conflicts: readonly CanonicalDirectionConflict[]
|
|
620
|
+
): CanonicalSource {
|
|
621
|
+
if (source.kind !== "npm") return source;
|
|
622
|
+
const names = conflicts
|
|
623
|
+
.filter((conflict) => ownedImportMatchesRoot(source.specifier, conflict.packageName))
|
|
624
|
+
.map((conflict) => conflict.exportName);
|
|
625
|
+
if (names.length === 0) return source;
|
|
626
|
+
const blocked = new Set(names);
|
|
627
|
+
if (source.include) {
|
|
628
|
+
return { ...source, include: source.include.filter((name) => !blocked.has(name)) };
|
|
629
|
+
}
|
|
630
|
+
return { ...source, exclude: [...new Set([...(source.exclude ?? []), ...names])].sort() };
|
|
631
|
+
}
|
|
632
|
+
|
|
608
633
|
function indexShadowRenderRootNodeIds(ix: FactIndex): Set<FactId> {
|
|
609
634
|
const identities = ix.byKind("component_identity");
|
|
610
635
|
if (identities.length === 0) return new Set();
|
|
@@ -1113,8 +1138,9 @@ function indexCanonicalDirectoryResolvedNodes(
|
|
|
1113
1138
|
): Set<FactId> {
|
|
1114
1139
|
const out = new Set<FactId>();
|
|
1115
1140
|
const directoryPaths = sources
|
|
1116
|
-
.filter(
|
|
1117
|
-
source
|
|
1141
|
+
.filter(
|
|
1142
|
+
(source): source is Extract<CanonicalSource, { kind: "directory" }> =>
|
|
1143
|
+
source.kind === "directory"
|
|
1118
1144
|
)
|
|
1119
1145
|
.map((source) => normalizePath(source.path));
|
|
1120
1146
|
if (directoryPaths.length === 0) return out;
|
|
@@ -25,6 +25,7 @@ export const RULE_FIX_AVAILABLE = {
|
|
|
25
25
|
"tailwind/unknown-class": false,
|
|
26
26
|
"tokens/require-dual-fallback": true,
|
|
27
27
|
"tokens/css-vars-must-be-defined": false,
|
|
28
|
+
"tokens/upstream-drift": false,
|
|
28
29
|
"theme/no-theme-coupled-literal": false,
|
|
29
30
|
"a11y/required-accessible-name": false,
|
|
30
31
|
"composition/cardinality": false,
|
package/src/rules/index.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { ruleTailwindRawColorViaToken } from "./tailwind-raw-color-via-token.js"
|
|
|
31
31
|
import { ruleTailwindUnknownClass } from "./tailwind-unknown-class.js";
|
|
32
32
|
import { ruleTokensRequireDualFallback } from "./tokens-require-dual-fallback.js";
|
|
33
33
|
import { ruleTokensCssVarsMustBeDefined } from "./tokens-css-vars-must-be-defined.js";
|
|
34
|
+
import { ruleTokensUpstreamDrift } from "./tokens-upstream-drift.js";
|
|
34
35
|
import type { Finding } from "./types.js";
|
|
35
36
|
|
|
36
37
|
export type RuleFn = (ix: FactIndex) => Finding[];
|
|
@@ -137,6 +138,11 @@ export const RULES: readonly Rule[] = [
|
|
|
137
138
|
version: "1",
|
|
138
139
|
run: ruleTokensCssVarsMustBeDefined,
|
|
139
140
|
},
|
|
141
|
+
{
|
|
142
|
+
id: "tokens/upstream-drift",
|
|
143
|
+
version: "1",
|
|
144
|
+
run: ruleTokensUpstreamDrift,
|
|
145
|
+
},
|
|
140
146
|
{
|
|
141
147
|
id: "theme/no-theme-coupled-literal",
|
|
142
148
|
version: "1",
|
|
@@ -211,6 +217,7 @@ export { ruleTailwindRawColorViaToken } from "./tailwind-raw-color-via-token.js"
|
|
|
211
217
|
export { ruleTailwindUnknownClass } from "./tailwind-unknown-class.js";
|
|
212
218
|
export { ruleTokensRequireDualFallback } from "./tokens-require-dual-fallback.js";
|
|
213
219
|
export { ruleTokensCssVarsMustBeDefined } from "./tokens-css-vars-must-be-defined.js";
|
|
220
|
+
export { ruleTokensUpstreamDrift } from "./tokens-upstream-drift.js";
|
|
214
221
|
export { ruleThemeNoThemeCoupledLiteral } from "./theme-no-theme-coupled-literal.js";
|
|
215
222
|
export { ruleA11yRequiredAccessibleName } from "./a11y-required-accessible-name.js";
|
|
216
223
|
export { ruleCompositionCardinality, ruleCompositionCoOccurrence } from "./composition-pattern.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FactIndex } from "../facts/index.js";
|
|
2
|
-
import { ownedImportsEqual } from "../package-identity-match.js";
|
|
2
|
+
import { ownedImportMatchesRoot, ownedImportsEqual } from "../package-identity-match.js";
|
|
3
3
|
|
|
4
4
|
import { makeFinding } from "./finding.js";
|
|
5
5
|
import type { Finding } from "./types.js";
|
|
@@ -16,6 +16,21 @@ export function ruleJsxPreferredImportPath(ix: FactIndex): Finding[] {
|
|
|
16
16
|
|
|
17
17
|
for (const usage of ix.byKind("usage_import")) {
|
|
18
18
|
const matches = policies.filter((policy) => {
|
|
19
|
+
if (policy.bridge) {
|
|
20
|
+
if (policy.bridge.implementationFiles.some((file) => sameRepoPath(file, usage.file))) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (!ownedImportMatchesRoot(usage.source, policy.from)) return false;
|
|
24
|
+
if (ownedImportsEqual(usage.source, policy.from)) {
|
|
25
|
+
return usage.imported === policy.bridge.underlyingExportName;
|
|
26
|
+
}
|
|
27
|
+
const subpath = usage.source.slice(policy.from.length + 1);
|
|
28
|
+
const leaf = subpath.split("/").at(-1);
|
|
29
|
+
return (
|
|
30
|
+
leaf === policy.bridge.underlyingExportName &&
|
|
31
|
+
(usage.imported === "default" || usage.imported === policy.bridge.underlyingExportName)
|
|
32
|
+
);
|
|
33
|
+
}
|
|
19
34
|
if (!ownedImportsEqual(policy.from, usage.source)) return false;
|
|
20
35
|
if (policy.imported !== undefined && policy.imported !== usage.imported) return false;
|
|
21
36
|
return true;
|
|
@@ -33,7 +48,9 @@ export function ruleJsxPreferredImportPath(ix: FactIndex): Finding[] {
|
|
|
33
48
|
ruleId: RULE_ID,
|
|
34
49
|
ruleVersion: RULE_VERSION,
|
|
35
50
|
severity: policy.severity,
|
|
36
|
-
message:
|
|
51
|
+
message: policy.bridge
|
|
52
|
+
? `Import ${policy.bridge.underlyingExportName} through the confirmed local ${policy.bridge.localExportName} wrapper from "${policy.to}".`
|
|
53
|
+
: `Import from "${usage.source}" should use "${policy.to}".`,
|
|
37
54
|
location: usage.location,
|
|
38
55
|
evidence: ix.evidence([usage.id, policy.id]),
|
|
39
56
|
fingerprintIdentity: {
|
|
@@ -44,19 +61,30 @@ export function ruleJsxPreferredImportPath(ix: FactIndex): Finding[] {
|
|
|
44
61
|
line: usage.location.line,
|
|
45
62
|
column: usage.location.column,
|
|
46
63
|
},
|
|
47
|
-
fix:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
64
|
+
fix: policy.bridge
|
|
65
|
+
? undefined
|
|
66
|
+
: {
|
|
67
|
+
kind: "replaceImport",
|
|
68
|
+
title: policy.because ?? `Replace import path with "${policy.to}"`,
|
|
69
|
+
from: usage.source,
|
|
70
|
+
to: policy.to,
|
|
71
|
+
deterministic: true,
|
|
72
|
+
},
|
|
54
73
|
attributes: {
|
|
55
74
|
importPath: usage.source,
|
|
56
75
|
suggestedImport: policy.to,
|
|
57
76
|
imported: usage.imported,
|
|
58
77
|
local: usage.local,
|
|
59
78
|
rawValue: usage.source,
|
|
79
|
+
...(policy.bridge
|
|
80
|
+
? {
|
|
81
|
+
canonicalBridge: true,
|
|
82
|
+
canonicalTarget: policy.bridge.componentKey,
|
|
83
|
+
suggestedComponent: policy.bridge.localExportName,
|
|
84
|
+
implementationFiles: policy.bridge.implementationFiles,
|
|
85
|
+
decisionSource: policy.bridge.decisionSource,
|
|
86
|
+
}
|
|
87
|
+
: {}),
|
|
60
88
|
},
|
|
61
89
|
})
|
|
62
90
|
);
|
|
@@ -65,3 +93,11 @@ export function ruleJsxPreferredImportPath(ix: FactIndex): Finding[] {
|
|
|
65
93
|
|
|
66
94
|
return findings;
|
|
67
95
|
}
|
|
96
|
+
|
|
97
|
+
function sameRepoPath(left: string, right: string): boolean {
|
|
98
|
+
return normalizeRepoPath(left) === normalizeRepoPath(right);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function normalizeRepoPath(path: string): string {
|
|
102
|
+
return path.replace(/\\/gu, "/").replace(/^\.\//u, "");
|
|
103
|
+
}
|