@usefragments/core 1.0.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/LICENSE +21 -0
- package/dist/chunk-243QYRUF.js +2145 -0
- package/dist/chunk-243QYRUF.js.map +1 -0
- package/dist/chunk-3LLRNCPX.js +111 -0
- package/dist/chunk-3LLRNCPX.js.map +1 -0
- package/dist/chunk-EIYNNS77.js +48 -0
- package/dist/chunk-EIYNNS77.js.map +1 -0
- package/dist/chunk-JNBFJ34I.js +326 -0
- package/dist/chunk-JNBFJ34I.js.map +1 -0
- package/dist/chunk-R3X7UBHU.js +228 -0
- package/dist/chunk-R3X7UBHU.js.map +1 -0
- package/dist/chunk-SH4KPIYH.js +393 -0
- package/dist/chunk-SH4KPIYH.js.map +1 -0
- package/dist/chunk-V4VQB57N.js +56 -0
- package/dist/chunk-V4VQB57N.js.map +1 -0
- package/dist/chunk-X34IA4LR.js +319 -0
- package/dist/chunk-X34IA4LR.js.map +1 -0
- package/dist/chunk-YF65VYRY.js +493 -0
- package/dist/chunk-YF65VYRY.js.map +1 -0
- package/dist/codes/index.d.ts +40 -0
- package/dist/codes/index.js +23 -0
- package/dist/codes/index.js.map +1 -0
- package/dist/compiled-types/index.d.ts +5 -0
- package/dist/compiled-types/index.js +1 -0
- package/dist/compiled-types/index.js.map +1 -0
- package/dist/generate/index.d.ts +47 -0
- package/dist/generate/index.js +11 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/governance-BOa3KyiJ.d.ts +3279 -0
- package/dist/graph/index.d.ts +66 -0
- package/dist/graph/index.js +17 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/index.d.ts +11388 -0
- package/dist/index.js +8711 -0
- package/dist/index.js.map +1 -0
- package/dist/package-identity.d.ts +42 -0
- package/dist/package-identity.js +23 -0
- package/dist/package-identity.js.map +1 -0
- package/dist/preview/index.d.ts +484 -0
- package/dist/preview/index.js +1129 -0
- package/dist/preview/index.js.map +1 -0
- package/dist/react-types.d.ts +22 -0
- package/dist/react-types.js +1 -0
- package/dist/react-types.js.map +1 -0
- package/dist/registry.d.ts +2367 -0
- package/dist/registry.js +62 -0
- package/dist/registry.js.map +1 -0
- package/dist/schemas/index.d.ts +2584 -0
- package/dist/schemas/index.js +55 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/severity.d.ts +18 -0
- package/dist/severity.js +27 -0
- package/dist/severity.js.map +1 -0
- package/dist/test-utils.d.ts +14 -0
- package/dist/test-utils.js +63 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/topology/index.d.ts +48 -0
- package/dist/topology/index.js +7 -0
- package/dist/topology/index.js.map +1 -0
- package/dist/types-xJ2xyp_G.d.ts +119 -0
- package/package.json +120 -0
- package/src/__tests__/contract-parser.test.ts +614 -0
- package/src/__tests__/dtcg-generators.test.ts +439 -0
- package/src/__tests__/dtcg-parser.test.ts +884 -0
- package/src/__tests__/preview-runtime.test.tsx +111 -0
- package/src/__tests__/schema.test.ts +31 -0
- package/src/__tests__/severity.test.ts +34 -0
- package/src/__tests__/style-comparison.test.ts +662 -0
- package/src/__tests__/tokens-compat.test.ts +51 -0
- package/src/agent-format.test.ts +105 -0
- package/src/agent-format.ts +141 -0
- package/src/bundle.ts +149 -0
- package/src/codes/__tests__/codes.test.ts +73 -0
- package/src/codes/codes.ts +463 -0
- package/src/codes/index.ts +7 -0
- package/src/codes/source-finding-bridge.ts +113 -0
- package/src/compiled-types/index.ts +293 -0
- package/src/component-contract.ts +231 -0
- package/src/component-discovery.ts +80 -0
- package/src/composition.test.ts +262 -0
- package/src/composition.ts +318 -0
- package/src/config-paths.test.ts +78 -0
- package/src/config-paths.ts +47 -0
- package/src/config.ts +18 -0
- package/src/conform-prove.test.ts +274 -0
- package/src/conform-prove.ts +326 -0
- package/src/conform.ts +160 -0
- package/src/constants.ts +114 -0
- package/src/context.ts +2 -0
- package/src/contract/fixture.ts +110 -0
- package/src/contract/hash.test.ts +86 -0
- package/src/contract/hash.ts +195 -0
- package/src/contract/index.ts +43 -0
- package/src/contract/preimage.test.ts +254 -0
- package/src/contract/preimage.ts +365 -0
- package/src/contract/stamp.test.ts +100 -0
- package/src/contract/stamp.ts +105 -0
- package/src/contract-parser.ts +141 -0
- package/src/defineFragment.test.ts +257 -0
- package/src/defineFragment.ts +293 -0
- package/src/dtcg-generators.ts +324 -0
- package/src/dtcg-parser.ts +1 -0
- package/src/dtcg.ts +237 -0
- package/src/facts/__snapshots__/facts.test.ts.snap +124 -0
- package/src/facts/builders.ts +835 -0
- package/src/facts/compile.ts +422 -0
- package/src/facts/fact-index.ts +394 -0
- package/src/facts/facts.test.ts +494 -0
- package/src/facts/ids.ts +125 -0
- package/src/facts/index.ts +119 -0
- package/src/facts/types.ts +614 -0
- package/src/figma.ts +263 -0
- package/src/fragment-types.ts +214 -0
- package/src/generate/generate.test.ts +594 -0
- package/src/generate/index.ts +438 -0
- package/src/governance-integrity.test.ts +186 -0
- package/src/governance-integrity.ts +282 -0
- package/src/governance.test.ts +300 -0
- package/src/governance.ts +605 -0
- package/src/governance.type-test.ts +75 -0
- package/src/graph/engine.ts +446 -0
- package/src/graph/graph.test.ts +746 -0
- package/src/graph/index.ts +32 -0
- package/src/graph/serialization.ts +55 -0
- package/src/graph/types.ts +178 -0
- package/src/index.ts +891 -0
- package/src/local-canonical.ts +102 -0
- package/src/mcp-snapshot.ts +222 -0
- package/src/package-identity-match.ts +33 -0
- package/src/package-identity.generated.ts +233 -0
- package/src/package-identity.test.ts +99 -0
- package/src/package-identity.ts +157 -0
- package/src/performance-presets.ts +142 -0
- package/src/preview/comparison-artifact.test.ts +228 -0
- package/src/preview/comparison-artifact.ts +181 -0
- package/src/preview/comparison-context.ts +93 -0
- package/src/preview/comparison-evidence.ts +112 -0
- package/src/preview/comparison-format.ts +21 -0
- package/src/preview/comparison-presentation.ts +187 -0
- package/src/preview/comparison-references.test.ts +430 -0
- package/src/preview/comparison-references.ts +72 -0
- package/src/preview/comparison-score.ts +59 -0
- package/src/preview/comparison-styles.ts +197 -0
- package/src/preview/comparison.test.ts +559 -0
- package/src/preview/comparison.ts +488 -0
- package/src/preview/index.ts +10 -0
- package/src/preview/types.ts +309 -0
- package/src/preview/validation.test.ts +290 -0
- package/src/preview/validation.ts +356 -0
- package/src/preview-runtime.tsx +144 -0
- package/src/raw-html-canonical.test.ts +41 -0
- package/src/raw-html-canonical.ts +179 -0
- package/src/react-types.ts +21 -0
- package/src/registry-install-plan.ts +344 -0
- package/src/registry.test.ts +245 -0
- package/src/registry.ts +369 -0
- package/src/rules/a11y-required-accessible-name.ts +93 -0
- package/src/rules/color-math.test.ts +72 -0
- package/src/rules/color-math.ts +192 -0
- package/src/rules/components-forbidden-prop-value.ts +108 -0
- package/src/rules/components-prefer-library.ts +943 -0
- package/src/rules/components-unknown-prop.ts +94 -0
- package/src/rules/composition-pattern-schema.ts +125 -0
- package/src/rules/composition-pattern.test.ts +320 -0
- package/src/rules/composition-pattern.ts +271 -0
- package/src/rules/emit-gate.test.ts +42 -0
- package/src/rules/emit-gate.ts +60 -0
- package/src/rules/finding.ts +54 -0
- package/src/rules/index.ts +235 -0
- package/src/rules/jsx-preferred-component.ts +68 -0
- package/src/rules/jsx-preferred-import-path.ts +67 -0
- package/src/rules/presets.ts +57 -0
- package/src/rules/props-invalid-value.ts +115 -0
- package/src/rules/rules.test.ts +3636 -0
- package/src/rules/spacing-resolution.ts +184 -0
- package/src/rules/styles-no-raw-color.ts +329 -0
- package/src/rules/styles-no-raw-dimensions.ts +357 -0
- package/src/rules/styles-no-raw-spacing.ts +236 -0
- package/src/rules/styles-no-raw-typography.ts +237 -0
- package/src/rules/tailwind-arbitrary-color.test.ts +128 -0
- package/src/rules/tailwind-arbitrary-color.ts +93 -0
- package/src/rules/tailwind-arbitrary-spacing.test.ts +157 -0
- package/src/rules/tailwind-arbitrary-spacing.ts +120 -0
- package/src/rules/tailwind-forbidden-palette.test.ts +116 -0
- package/src/rules/tailwind-forbidden-palette.ts +107 -0
- package/src/rules/tailwind-off-scale-spacing-token.test.ts +100 -0
- package/src/rules/tailwind-off-scale-spacing-token.ts +108 -0
- package/src/rules/tailwind-raw-color-via-token.test.ts +108 -0
- package/src/rules/tailwind-raw-color-via-token.ts +82 -0
- package/src/rules/tailwind-rule-utils.ts +106 -0
- package/src/rules/tailwind-unknown-class.test.ts +157 -0
- package/src/rules/tailwind-unknown-class.ts +121 -0
- package/src/rules/taxonomy.test.ts +238 -0
- package/src/rules/theme-no-theme-coupled-literal.ts +132 -0
- package/src/rules/tiers.ts +111 -0
- package/src/rules/tokens-css-vars-must-be-defined.test.ts +244 -0
- package/src/rules/tokens-css-vars-must-be-defined.ts +156 -0
- package/src/rules/tokens-require-dual-fallback.ts +78 -0
- package/src/rules/types.ts +27 -0
- package/src/rules/utils.test.ts +110 -0
- package/src/rules/utils.ts +599 -0
- package/src/schema.ts +427 -0
- package/src/schemas/index.ts +275 -0
- package/src/schemas/schemas.test.ts +124 -0
- package/src/severity.ts +54 -0
- package/src/storyAdapter.test.ts +571 -0
- package/src/storyAdapter.ts +761 -0
- package/src/storyFilters.test.ts +350 -0
- package/src/storyFilters.ts +253 -0
- package/src/storybook-csf.ts +11 -0
- package/src/style-comparison.ts +423 -0
- package/src/test-utils.ts +68 -0
- package/src/token-parser.ts +2 -0
- package/src/token-types.ts +287 -0
- package/src/tokens/categories.ts +131 -0
- package/src/tokens/color.ts +193 -0
- package/src/tokens/design-token-parser.test.ts +159 -0
- package/src/tokens/design-token-parser.ts +402 -0
- package/src/tokens/dtcg.ts +428 -0
- package/src/tokens/index.ts +130 -0
- package/src/tokens/lookup.ts +19 -0
- package/src/tokens/scss.ts +171 -0
- package/src/tokens/tailwind-v4.ts +187 -0
- package/src/tokens/types.ts +43 -0
- package/src/topology/index.ts +9 -0
- package/src/topology/resolve-area.test.ts +174 -0
- package/src/topology/resolve-area.ts +215 -0
- package/src/types.ts +1057 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `composition/cardinality` + `composition/co-occurrence` — region-scoped rules
|
|
3
|
+
* over how components are arranged inside a container, not just per node.
|
|
4
|
+
*
|
|
5
|
+
* Both share the same region machinery: `regionsByPrefix` groups usage into
|
|
6
|
+
* `(container, members)`; a `CompositionPattern` names the container (by
|
|
7
|
+
* canonical `componentId`), a child `select`or, and a `constraint`. The config
|
|
8
|
+
* rides in `governance_rule_config.options.patterns` — no new fact kind. Brief
|
|
9
|
+
* 04 delivers that config from cloud policy / fragment prose; this brief drives
|
|
10
|
+
* the rules from it directly.
|
|
11
|
+
*
|
|
12
|
+
* Static-resolution ceiling: a child is "selected" only when its prop resolves
|
|
13
|
+
* statically to the configured value. A dynamic `variant={kind}` is unknown —
|
|
14
|
+
* never a match, never treated as absent. Dynamic composition is the
|
|
15
|
+
* render-time validator's job (doc 14), not this static rule's.
|
|
16
|
+
*
|
|
17
|
+
* Matching is on the resolved canonical component (its `componentId`), never the
|
|
18
|
+
* raw `element` string — that is what makes the rule survive wrappers and
|
|
19
|
+
* renames. A policy component ref may be fully qualified (`pkg#Name`) or a bare
|
|
20
|
+
* name (the common authoring case); see `componentMatches`.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type {
|
|
24
|
+
FactId,
|
|
25
|
+
FactIndex,
|
|
26
|
+
UsageComponentFact,
|
|
27
|
+
UsageNodeFact,
|
|
28
|
+
UsagePropResolvedFact,
|
|
29
|
+
} from "../facts/index.js";
|
|
30
|
+
import type { GovernanceSeverity } from "../governance.js";
|
|
31
|
+
import { ownedComponentIdsEqual } from "../package-identity-match.js";
|
|
32
|
+
|
|
33
|
+
import {
|
|
34
|
+
type CompositionChildSelector,
|
|
35
|
+
type CompositionConstraint,
|
|
36
|
+
type CompositionPattern,
|
|
37
|
+
type CompositionRegionSelector,
|
|
38
|
+
isCompositionPattern,
|
|
39
|
+
} from "./composition-pattern-schema.js";
|
|
40
|
+
import { makeFinding } from "./finding.js";
|
|
41
|
+
import type { Finding } from "./types.js";
|
|
42
|
+
import {
|
|
43
|
+
indexComponentByNodeId,
|
|
44
|
+
indexPropsByNodeId,
|
|
45
|
+
regionsByPrefix,
|
|
46
|
+
type Region,
|
|
47
|
+
} from "./utils.js";
|
|
48
|
+
|
|
49
|
+
export const CARDINALITY_RULE_ID = "composition/cardinality";
|
|
50
|
+
export const CO_OCCURRENCE_RULE_ID = "composition/co-occurrence";
|
|
51
|
+
const RULE_VERSION = "1";
|
|
52
|
+
|
|
53
|
+
/** ≤ `max` / ≥ `min` of a selected child per matching region. */
|
|
54
|
+
export function ruleCompositionCardinality(ix: FactIndex): Finding[] {
|
|
55
|
+
return evaluate(ix, CARDINALITY_RULE_ID, "cardinality");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A selected child requires a co-occurring sibling in the same region. */
|
|
59
|
+
export function ruleCompositionCoOccurrence(ix: FactIndex): Finding[] {
|
|
60
|
+
return evaluate(ix, CO_OCCURRENCE_RULE_ID, "co-occurrence");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface EvalCtx {
|
|
64
|
+
ix: FactIndex;
|
|
65
|
+
ruleId: string;
|
|
66
|
+
severity: GovernanceSeverity;
|
|
67
|
+
componentByNode: Map<FactId, UsageComponentFact>;
|
|
68
|
+
propsByNode: Map<FactId, UsagePropResolvedFact[]>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function evaluate(ix: FactIndex, ruleId: string, kind: CompositionConstraint["kind"]): Finding[] {
|
|
72
|
+
const config = ix.policy.ruleConfig(ruleId);
|
|
73
|
+
if (!config || !config.enabled) return [];
|
|
74
|
+
const patterns = readPatterns(config.options);
|
|
75
|
+
if (patterns.length === 0) return [];
|
|
76
|
+
|
|
77
|
+
const ctx: EvalCtx = {
|
|
78
|
+
ix,
|
|
79
|
+
ruleId,
|
|
80
|
+
severity: config.severity ?? "warn",
|
|
81
|
+
componentByNode: indexComponentByNodeId(ix),
|
|
82
|
+
propsByNode: indexPropsByNodeId(ix),
|
|
83
|
+
};
|
|
84
|
+
const regions = regionsByPrefix(ix);
|
|
85
|
+
|
|
86
|
+
const findings: Finding[] = [];
|
|
87
|
+
for (const pattern of patterns) {
|
|
88
|
+
const constraint = pattern.constraint;
|
|
89
|
+
if (constraint.kind !== kind) continue;
|
|
90
|
+
for (const region of regions) {
|
|
91
|
+
if (!regionMatches(region, pattern.region, ctx.componentByNode)) continue;
|
|
92
|
+
const selected = region.members.filter((m) => memberMatches(m.id, pattern.select, ctx));
|
|
93
|
+
const finding =
|
|
94
|
+
constraint.kind === "cardinality"
|
|
95
|
+
? checkCardinality(ctx, region, pattern, constraint, selected)
|
|
96
|
+
: checkCoOccurrence(ctx, region, pattern, constraint, selected);
|
|
97
|
+
if (finding) findings.push(finding);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return findings;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function readPatterns(options: Record<string, unknown> | undefined): CompositionPattern[] {
|
|
104
|
+
const raw = options?.patterns;
|
|
105
|
+
// Validate each pattern rather than trusting the shape: config arrives from
|
|
106
|
+
// cloud policy / fragment prose (brief 04) and the facts layer passes
|
|
107
|
+
// `options` through untyped, so a malformed entry would otherwise emit garbage
|
|
108
|
+
// findings. A bad pattern is skipped and the rest keep scanning (architecture
|
|
109
|
+
// failure mode); a non-array yields nothing.
|
|
110
|
+
if (!Array.isArray(raw)) return [];
|
|
111
|
+
return raw.filter(isCompositionPattern);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function regionMatches(
|
|
115
|
+
region: Region,
|
|
116
|
+
selector: CompositionRegionSelector,
|
|
117
|
+
componentByNode: Map<FactId, UsageComponentFact>
|
|
118
|
+
): boolean {
|
|
119
|
+
// v0 matches the container by canonical componentId only. `marker`/`role` are
|
|
120
|
+
// typed for brief 04 but unexercised by ACCEPTANCE §3–§8; a marker policy will
|
|
121
|
+
// land with its own case (see brief 03 Deviations).
|
|
122
|
+
if (!selector.component) return false;
|
|
123
|
+
return componentMatches(
|
|
124
|
+
componentByNode.get(region.container.id)?.componentId,
|
|
125
|
+
selector.component
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Match a usage node's resolved `componentId` against a policy component ref.
|
|
131
|
+
* A ref may be fully qualified (`@scope/pkg#Name`) or a bare name (`Name`) — the
|
|
132
|
+
* common authoring case (ACCEPTANCE §3 and the §10 fragment sugar both write
|
|
133
|
+
* bare names). Matching the resolved componentId — not the raw JSX element —
|
|
134
|
+
* survives import aliasing and wrapper renames.
|
|
135
|
+
*/
|
|
136
|
+
function componentMatches(componentId: string | undefined, ref: string): boolean {
|
|
137
|
+
if (componentId === undefined) return false;
|
|
138
|
+
if (componentId === ref) return true;
|
|
139
|
+
if (ref.includes("#") && ownedComponentIdsEqual(componentId, ref)) return true;
|
|
140
|
+
const hash = componentId.indexOf("#");
|
|
141
|
+
return hash !== -1 && componentId.slice(hash + 1) === ref;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function memberMatches(nodeId: FactId, select: CompositionChildSelector, ctx: EvalCtx): boolean {
|
|
145
|
+
if (!componentMatches(ctx.componentByNode.get(nodeId)?.componentId, select.component))
|
|
146
|
+
return false;
|
|
147
|
+
if (select.prop === undefined) return true;
|
|
148
|
+
// Static ceiling: only a statically-resolved matching prop counts. A dynamic
|
|
149
|
+
// or spread prop is unknown — not a match (and not treated as absent).
|
|
150
|
+
const props = ctx.propsByNode.get(nodeId) ?? [];
|
|
151
|
+
return props.some((p) => {
|
|
152
|
+
if (p.prop !== select.prop || p.resolution !== "static") return false;
|
|
153
|
+
return select.value === undefined || p.value === select.value;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function checkCardinality(
|
|
158
|
+
ctx: EvalCtx,
|
|
159
|
+
region: Region,
|
|
160
|
+
pattern: CompositionPattern,
|
|
161
|
+
constraint: { kind: "cardinality"; max?: number; min?: number },
|
|
162
|
+
selected: UsageNodeFact[]
|
|
163
|
+
): Finding | null {
|
|
164
|
+
const count = selected.length;
|
|
165
|
+
if (constraint.max !== undefined && count > constraint.max) {
|
|
166
|
+
const extras = selected.slice(constraint.max); // the (max+1)th and later
|
|
167
|
+
return makeFinding({
|
|
168
|
+
ruleId: ctx.ruleId,
|
|
169
|
+
ruleVersion: RULE_VERSION,
|
|
170
|
+
severity: ctx.severity,
|
|
171
|
+
message: cardinalityMaxMessage(pattern, constraint.max, count),
|
|
172
|
+
location: extras[0].location,
|
|
173
|
+
evidence: ctx.ix.evidence([region.container.id, ...extras.map((e) => e.id)]),
|
|
174
|
+
fingerprintIdentity: cardinalityFingerprint(region, pattern, "max"),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (constraint.min !== undefined && count < constraint.min) {
|
|
178
|
+
return makeFinding({
|
|
179
|
+
ruleId: ctx.ruleId,
|
|
180
|
+
ruleVersion: RULE_VERSION,
|
|
181
|
+
severity: ctx.severity,
|
|
182
|
+
message: cardinalityMinMessage(pattern, constraint.min, count),
|
|
183
|
+
location: region.container.location,
|
|
184
|
+
evidence: ctx.ix.evidence([region.container.id, ...selected.map((s) => s.id)]),
|
|
185
|
+
fingerprintIdentity: cardinalityFingerprint(region, pattern, "min"),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function checkCoOccurrence(
|
|
192
|
+
ctx: EvalCtx,
|
|
193
|
+
region: Region,
|
|
194
|
+
pattern: CompositionPattern,
|
|
195
|
+
constraint: { kind: "co-occurrence"; requires: { prop: string; value: string } },
|
|
196
|
+
selected: UsageNodeFact[]
|
|
197
|
+
): Finding | null {
|
|
198
|
+
if (selected.length === 0) return null; // nothing triggers the requirement
|
|
199
|
+
const requiredSelect: CompositionChildSelector = {
|
|
200
|
+
component: pattern.select.component,
|
|
201
|
+
prop: constraint.requires.prop,
|
|
202
|
+
value: constraint.requires.value,
|
|
203
|
+
};
|
|
204
|
+
const satisfied = region.members.some((m) => memberMatches(m.id, requiredSelect, ctx));
|
|
205
|
+
if (satisfied) return null;
|
|
206
|
+
return makeFinding({
|
|
207
|
+
ruleId: ctx.ruleId,
|
|
208
|
+
ruleVersion: RULE_VERSION,
|
|
209
|
+
severity: ctx.severity,
|
|
210
|
+
message: coOccurrenceMessage(pattern, constraint),
|
|
211
|
+
location: region.container.location,
|
|
212
|
+
evidence: ctx.ix.evidence([region.container.id, ...selected.map((s) => s.id)]),
|
|
213
|
+
fingerprintIdentity: {
|
|
214
|
+
kind: "co-occurrence",
|
|
215
|
+
file: region.container.file,
|
|
216
|
+
container: region.container.nodePath,
|
|
217
|
+
region: pattern.region.component,
|
|
218
|
+
requires: `${constraint.requires.prop}=${constraint.requires.value}`,
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function cardinalityFingerprint(
|
|
224
|
+
region: Region,
|
|
225
|
+
pattern: CompositionPattern,
|
|
226
|
+
bound: "max" | "min"
|
|
227
|
+
): Record<string, unknown> {
|
|
228
|
+
return {
|
|
229
|
+
kind: "cardinality",
|
|
230
|
+
bound,
|
|
231
|
+
file: region.container.file,
|
|
232
|
+
container: region.container.nodePath,
|
|
233
|
+
region: pattern.region.component,
|
|
234
|
+
select: `${pattern.select.prop ?? ""}=${pattern.select.value ?? ""}`,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function childLabel(select: CompositionChildSelector, plural: boolean): string {
|
|
239
|
+
const base = select.value ? `${select.value} ${select.component}` : select.component;
|
|
240
|
+
return plural ? `${base}s` : base;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** The human-facing region name; falls back to a generic word when unnamed. */
|
|
244
|
+
function regionLabel(pattern: CompositionPattern): string {
|
|
245
|
+
return pattern.region.component ?? "region";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function cardinalityMaxMessage(pattern: CompositionPattern, max: number, count: number): string {
|
|
249
|
+
const region = regionLabel(pattern);
|
|
250
|
+
const limit = max === 1 ? "one" : String(max);
|
|
251
|
+
const label = childLabel(pattern.select, max !== 1);
|
|
252
|
+
const verb = max === 1 ? "is" : "are";
|
|
253
|
+
return `At most ${limit} ${label} ${verb} allowed per ${region} (found ${count}).`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function cardinalityMinMessage(pattern: CompositionPattern, min: number, count: number): string {
|
|
257
|
+
const region = regionLabel(pattern);
|
|
258
|
+
const label = childLabel(pattern.select, min !== 1);
|
|
259
|
+
const verb = min === 1 ? "is" : "are";
|
|
260
|
+
return `At least ${min} ${label} ${verb} required per ${region} (found ${count}).`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function coOccurrenceMessage(
|
|
264
|
+
pattern: CompositionPattern,
|
|
265
|
+
constraint: { requires: { value: string } }
|
|
266
|
+
): string {
|
|
267
|
+
const region = regionLabel(pattern);
|
|
268
|
+
const selectLabel = childLabel(pattern.select, false);
|
|
269
|
+
const requiresLabel = `${constraint.requires.value} ${pattern.select.component}`;
|
|
270
|
+
return `A ${region} with a ${selectLabel} should also contain a ${requiresLabel}.`;
|
|
271
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { isDenyEligible } from "./emit-gate.js";
|
|
4
|
+
import type { Finding } from "./types.js";
|
|
5
|
+
|
|
6
|
+
// isDenyEligible reads only ruleId / severity / fix / attributes, so a minimal
|
|
7
|
+
// cast keeps the fixtures focused on the deny criteria under test.
|
|
8
|
+
const base = {
|
|
9
|
+
ruleId: "components/prefer-library",
|
|
10
|
+
severity: "serious",
|
|
11
|
+
fix: { deterministic: true },
|
|
12
|
+
} as unknown as Finding;
|
|
13
|
+
|
|
14
|
+
describe("isDenyEligible", () => {
|
|
15
|
+
it("denies a confident, allowlisted, CI-gating, deterministic finding", () => {
|
|
16
|
+
expect(isDenyEligible(base, false)).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("never denies an advisory-flagged finding, even at error severity", () => {
|
|
20
|
+
const advisory = { ...base, attributes: { advisory: true } } as unknown as Finding;
|
|
21
|
+
expect(isDenyEligible(advisory, false)).toBe(false);
|
|
22
|
+
// failOnWarnings must not resurrect it either.
|
|
23
|
+
expect(isDenyEligible(advisory, true)).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("does not deny a rule outside the blocking allowlist", () => {
|
|
27
|
+
expect(isDenyEligible({ ...base, ruleId: "composition/x" } as unknown as Finding, false)).toBe(
|
|
28
|
+
false
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("only denies a warn-level finding under failOnWarnings", () => {
|
|
33
|
+
const warn = { ...base, severity: "moderate" } as unknown as Finding;
|
|
34
|
+
expect(isDenyEligible(warn, false)).toBe(false);
|
|
35
|
+
expect(isDenyEligible(warn, true)).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("does not deny an explicitly non-deterministic (heuristic) fix", () => {
|
|
39
|
+
const heuristic = { ...base, fix: { deterministic: false } } as unknown as Finding;
|
|
40
|
+
expect(isDenyEligible(heuristic, false)).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The blocking deny-set — the SINGLE source of truth for "which findings may
|
|
3
|
+
* hard-deny a UI write" across every altitude of the contract: the CLI
|
|
4
|
+
* pre-write hook (Node) AND the Contract Mode emit gate (a browser-safe
|
|
5
|
+
* serverless route). Lifted out of `packages/cli/src/hook/check.ts`
|
|
6
|
+
* (Contract Mode Phase 0) so the two cannot fork — a deny here must imply a CI
|
|
7
|
+
* failure, and that invariant only holds if both read the same criteria.
|
|
8
|
+
*
|
|
9
|
+
* Pure and browser-safe: it reads only a `Finding`'s `ruleId` / `severity` /
|
|
10
|
+
* `fix` shape — no Node APIs, no config off disk, no clock. The CLI hook
|
|
11
|
+
* re-exports `isDenyEligible` from here.
|
|
12
|
+
*/
|
|
13
|
+
import { severityLevel } from "../severity.js";
|
|
14
|
+
import type { Finding } from "./types.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The ONLY rules whose findings may hard-deny a write: deterministic,
|
|
18
|
+
* single-file-decidable, high-confidence classes (architecture Open Q2). A rule
|
|
19
|
+
* is added here DELIBERATELY — new rules gain no blocking power by default, so a
|
|
20
|
+
* future heuristic rule can't silently start denying writes. Heuristic/INFERRED
|
|
21
|
+
* classes (`composition/*`, `tailwind/*arbitrary*`) are absent on purpose: at
|
|
22
|
+
* most advisory, never a block.
|
|
23
|
+
*/
|
|
24
|
+
export const BLOCKING_RULE_ALLOWLIST: ReadonlySet<string> = new Set([
|
|
25
|
+
"styles/no-raw-color", // raw hex/rgb/hsl color literal
|
|
26
|
+
"styles/no-raw-spacing", // off-scale / untokenized spacing literal
|
|
27
|
+
"tailwind/off-scale-spacing-token", // off-scale Tailwind spacing utility
|
|
28
|
+
"components/prefer-library", // raw element bypasses a canonical component
|
|
29
|
+
"components/preferred-component", // imported component bypasses its canonical
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Mirrors `scanWithFacts`'s own CI exit logic: a finding gates CI when it is
|
|
34
|
+
* error-severity, or warn-severity under `ci.failOnWarnings`. Gating on exactly
|
|
35
|
+
* this set is what makes a local/emit deny imply a CI failure.
|
|
36
|
+
*/
|
|
37
|
+
export function gatesCi(finding: Finding, failOnWarnings: boolean): boolean {
|
|
38
|
+
const level = severityLevel(finding.severity);
|
|
39
|
+
return level === "error" || (failOnWarnings && level === "warn");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A finding may deny iff it is allowlisted, gates CI, and is not a flagged-heuristic fix. */
|
|
43
|
+
export function isDenyEligible(finding: Finding, failOnWarnings: boolean): boolean {
|
|
44
|
+
if (!BLOCKING_RULE_ALLOWLIST.has(finding.ruleId)) return false;
|
|
45
|
+
// Advisory findings — the low-confidence precision tiers a rule flags with
|
|
46
|
+
// `attributes.advisory` (prefer-library's role-reimpl / interactive-div /
|
|
47
|
+
// html-advisory / icon-only tiers, and the reimplements-primitive className
|
|
48
|
+
// heuristic) — are surfaced but NEVER block a write, even if a project raises
|
|
49
|
+
// their severity or sets failOnWarnings. Same "only deny when the engine is
|
|
50
|
+
// confident" principle as the non-deterministic-fix guard below.
|
|
51
|
+
if (finding.attributes?.advisory === true) return false;
|
|
52
|
+
if (!gatesCi(finding, failOnWarnings)) return false;
|
|
53
|
+
// An explicitly non-deterministic fix is the engine's own "low confidence /
|
|
54
|
+
// ambiguous" signal (e.g. several tokens share a value, or a heuristic swap).
|
|
55
|
+
// Those stay advisory — only deny when the engine is confident. A finding with
|
|
56
|
+
// no fix at all (e.g. a raw color with no matching token) still gates CI, so it
|
|
57
|
+
// remains deny-eligible.
|
|
58
|
+
if (finding.fix?.deterministic === false) return false;
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding builder — assembles a `Finding` and computes its fingerprint.
|
|
3
|
+
*
|
|
4
|
+
* The fingerprint is a content-addressed hash of a small identity tuple chosen
|
|
5
|
+
* by each rule. It is *not* the source location: two findings at different
|
|
6
|
+
* lines for the same offending value should have different fingerprints, but
|
|
7
|
+
* adding a blank line above them must not change either fingerprint. Rules
|
|
8
|
+
* pick identity inputs that capture "what's wrong" (file, ruleId, componentId,
|
|
9
|
+
* prop, property, value) without leaning on lineNumber/column.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { GovernanceSeverity } from "../governance.js";
|
|
13
|
+
import type { FactEvidence, FactLocation } from "../facts/index.js";
|
|
14
|
+
import { canonicalJson, hash64Hex } from "../facts/ids.js";
|
|
15
|
+
import { byRuleId } from "../codes/index.js";
|
|
16
|
+
import { normalizeFinding, normalizeSeverity } from "../schemas/index.js";
|
|
17
|
+
import type { Severity } from "../schemas/index.js";
|
|
18
|
+
|
|
19
|
+
import type { Finding, FindingFix } from "./types.js";
|
|
20
|
+
|
|
21
|
+
export interface MakeFindingInput {
|
|
22
|
+
ruleId: string;
|
|
23
|
+
ruleVersion: string;
|
|
24
|
+
severity: GovernanceSeverity | Severity;
|
|
25
|
+
message: string;
|
|
26
|
+
location: FactLocation;
|
|
27
|
+
evidence: FactEvidence[];
|
|
28
|
+
fingerprintIdentity: Record<string, unknown>;
|
|
29
|
+
fix?: FindingFix;
|
|
30
|
+
attributes?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function makeFinding(input: MakeFindingInput): Finding {
|
|
34
|
+
if (input.evidence.length === 0) {
|
|
35
|
+
throw new Error(`makeFinding(${input.ruleId}): findings must carry at least one evidence fact`);
|
|
36
|
+
}
|
|
37
|
+
const fingerprint = hash64Hex(
|
|
38
|
+
canonicalJson({ ruleId: input.ruleId, ...input.fingerprintIdentity })
|
|
39
|
+
);
|
|
40
|
+
const code = byRuleId.get(input.ruleId);
|
|
41
|
+
return normalizeFinding({
|
|
42
|
+
ruleId: input.ruleId,
|
|
43
|
+
ruleVersion: input.ruleVersion,
|
|
44
|
+
severity: normalizeSeverity(input.severity),
|
|
45
|
+
code: code?.code,
|
|
46
|
+
helpUrl: code?.explainUrl,
|
|
47
|
+
message: input.message,
|
|
48
|
+
fingerprint,
|
|
49
|
+
location: input.location,
|
|
50
|
+
evidence: input.evidence,
|
|
51
|
+
fix: input.fix,
|
|
52
|
+
attributes: input.attributes,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rules — pure functions over `FactIndex` that return `Finding[]`.
|
|
3
|
+
*
|
|
4
|
+
* Phase 5 ships five generic rules. They are content-addressed: scanning the
|
|
5
|
+
* same source twice produces the same fingerprints, so Cloud can dedupe and
|
|
6
|
+
* preserve event history. Rules never reach into raw fact arrays — every
|
|
7
|
+
* lookup goes through `FactIndex`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { FactIndex } from "../facts/index.js";
|
|
11
|
+
|
|
12
|
+
import { ruleA11yRequiredAccessibleName } from "./a11y-required-accessible-name.js";
|
|
13
|
+
import { ruleComponentsPreferLibrary } from "./components-prefer-library.js";
|
|
14
|
+
import { ruleComponentsForbiddenPropValue } from "./components-forbidden-prop-value.js";
|
|
15
|
+
import { ruleComponentsUnknownProp } from "./components-unknown-prop.js";
|
|
16
|
+
import { ruleCompositionCardinality, ruleCompositionCoOccurrence } from "./composition-pattern.js";
|
|
17
|
+
import { ruleJsxPreferredComponent } from "./jsx-preferred-component.js";
|
|
18
|
+
import { ruleJsxPreferredImportPath } from "./jsx-preferred-import-path.js";
|
|
19
|
+
import { rulePropsInvalidValue } from "./props-invalid-value.js";
|
|
20
|
+
import { ruleStylesNoRawColor } from "./styles-no-raw-color.js";
|
|
21
|
+
import { ruleStylesNoRawDimensions } from "./styles-no-raw-dimensions.js";
|
|
22
|
+
import { ruleStylesNoRawSpacing } from "./styles-no-raw-spacing.js";
|
|
23
|
+
import { ruleStylesNoRawTypography } from "./styles-no-raw-typography.js";
|
|
24
|
+
import { ruleThemeNoThemeCoupledLiteral } from "./theme-no-theme-coupled-literal.js";
|
|
25
|
+
import { ruleTailwindArbitraryColor } from "./tailwind-arbitrary-color.js";
|
|
26
|
+
import { ruleTailwindArbitrarySpacing } from "./tailwind-arbitrary-spacing.js";
|
|
27
|
+
import { ruleTailwindForbiddenPalette } from "./tailwind-forbidden-palette.js";
|
|
28
|
+
import { ruleTailwindOffScaleSpacingToken } from "./tailwind-off-scale-spacing-token.js";
|
|
29
|
+
import { ruleTailwindRawColorViaToken } from "./tailwind-raw-color-via-token.js";
|
|
30
|
+
import { ruleTailwindUnknownClass } from "./tailwind-unknown-class.js";
|
|
31
|
+
import { ruleTokensRequireDualFallback } from "./tokens-require-dual-fallback.js";
|
|
32
|
+
import { ruleTokensCssVarsMustBeDefined } from "./tokens-css-vars-must-be-defined.js";
|
|
33
|
+
import type { Finding } from "./types.js";
|
|
34
|
+
|
|
35
|
+
export type RuleFn = (ix: FactIndex) => Finding[];
|
|
36
|
+
|
|
37
|
+
export interface Rule {
|
|
38
|
+
id: string;
|
|
39
|
+
version: string;
|
|
40
|
+
run: RuleFn;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const RULES: readonly Rule[] = [
|
|
44
|
+
{
|
|
45
|
+
id: "components/unknown-prop",
|
|
46
|
+
version: "1",
|
|
47
|
+
run: ruleComponentsUnknownProp,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "components/forbidden-prop-value",
|
|
51
|
+
version: "1",
|
|
52
|
+
run: ruleComponentsForbiddenPropValue,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "props/invalid-value",
|
|
56
|
+
version: "1",
|
|
57
|
+
run: rulePropsInvalidValue,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "imports/preferred-path",
|
|
61
|
+
version: "1",
|
|
62
|
+
run: ruleJsxPreferredImportPath,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "components/preferred-component",
|
|
66
|
+
version: "1",
|
|
67
|
+
run: ruleJsxPreferredComponent,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "components/prefer-library",
|
|
71
|
+
version: "1",
|
|
72
|
+
run: ruleComponentsPreferLibrary,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "styles/no-raw-color",
|
|
76
|
+
version: "1",
|
|
77
|
+
run: ruleStylesNoRawColor,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "styles/no-raw-dimensions",
|
|
81
|
+
version: "1",
|
|
82
|
+
run: ruleStylesNoRawDimensions,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "styles/no-raw-spacing",
|
|
86
|
+
version: "1",
|
|
87
|
+
run: ruleStylesNoRawSpacing,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "styles/no-raw-typography",
|
|
91
|
+
version: "1",
|
|
92
|
+
run: ruleStylesNoRawTypography,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "tailwind/arbitrary-color",
|
|
96
|
+
version: "1",
|
|
97
|
+
run: ruleTailwindArbitraryColor,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: "tailwind/arbitrary-spacing",
|
|
101
|
+
version: "1",
|
|
102
|
+
run: ruleTailwindArbitrarySpacing,
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "tailwind/forbidden-palette",
|
|
106
|
+
version: "1",
|
|
107
|
+
run: ruleTailwindForbiddenPalette,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "tailwind/off-scale-spacing-token",
|
|
111
|
+
version: "1",
|
|
112
|
+
run: ruleTailwindOffScaleSpacingToken,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: "tailwind/raw-color-via-token",
|
|
116
|
+
version: "1",
|
|
117
|
+
run: ruleTailwindRawColorViaToken,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "tailwind/unknown-class",
|
|
121
|
+
version: "1",
|
|
122
|
+
run: ruleTailwindUnknownClass,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "tokens/require-dual-fallback",
|
|
126
|
+
version: "1",
|
|
127
|
+
run: ruleTokensRequireDualFallback,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: "tokens/css-vars-must-be-defined",
|
|
131
|
+
version: "1",
|
|
132
|
+
run: ruleTokensCssVarsMustBeDefined,
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: "theme/no-theme-coupled-literal",
|
|
136
|
+
version: "1",
|
|
137
|
+
run: ruleThemeNoThemeCoupledLiteral,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: "a11y/required-accessible-name",
|
|
141
|
+
version: "1",
|
|
142
|
+
run: ruleA11yRequiredAccessibleName,
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: "composition/cardinality",
|
|
146
|
+
version: "1",
|
|
147
|
+
run: ruleCompositionCardinality,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: "composition/co-occurrence",
|
|
151
|
+
version: "1",
|
|
152
|
+
run: ruleCompositionCoOccurrence,
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Run every Phase 5 rule and return findings sorted by `(file, line, column,
|
|
158
|
+
* fingerprint)` so output is stable across runs.
|
|
159
|
+
*/
|
|
160
|
+
export function runRules(ix: FactIndex): Finding[] {
|
|
161
|
+
const findings: Finding[] = [];
|
|
162
|
+
for (const rule of RULES) findings.push(...rule.run(ix));
|
|
163
|
+
return sortFindings(findings);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function sortFindings(findings: Finding[]): Finding[] {
|
|
167
|
+
return findings.slice().sort((a, b) => {
|
|
168
|
+
const fileCmp = a.location.file.localeCompare(b.location.file);
|
|
169
|
+
if (fileCmp !== 0) return fileCmp;
|
|
170
|
+
if (a.location.line !== b.location.line) return a.location.line - b.location.line;
|
|
171
|
+
if (a.location.column !== b.location.column) return a.location.column - b.location.column;
|
|
172
|
+
return a.fingerprint.localeCompare(b.fingerprint);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export type {
|
|
177
|
+
Finding,
|
|
178
|
+
FindingFix,
|
|
179
|
+
FindingReplaceComponentFix,
|
|
180
|
+
FindingReplaceImportFix,
|
|
181
|
+
FindingReplacePropValueFix,
|
|
182
|
+
FindingReplaceStyleValueFix,
|
|
183
|
+
FindingReplaceClassTokenFix,
|
|
184
|
+
} from "./types.js";
|
|
185
|
+
export { makeFinding } from "./finding.js";
|
|
186
|
+
// Blocking deny-set (Contract Mode Phase 0): the shared "may this finding
|
|
187
|
+
// hard-deny a write" criteria, used by the CLI pre-write hook and the emit gate.
|
|
188
|
+
export { BLOCKING_RULE_ALLOWLIST, gatesCi, isDenyEligible } from "./emit-gate.js";
|
|
189
|
+
export { ruleComponentsUnknownProp } from "./components-unknown-prop.js";
|
|
190
|
+
export { ruleComponentsForbiddenPropValue } from "./components-forbidden-prop-value.js";
|
|
191
|
+
export { ruleComponentsPreferLibrary } from "./components-prefer-library.js";
|
|
192
|
+
export { rulePropsInvalidValue } from "./props-invalid-value.js";
|
|
193
|
+
export { ruleJsxPreferredImportPath } from "./jsx-preferred-import-path.js";
|
|
194
|
+
export { ruleJsxPreferredComponent } from "./jsx-preferred-component.js";
|
|
195
|
+
export { ruleStylesNoRawColor } from "./styles-no-raw-color.js";
|
|
196
|
+
export { ruleStylesNoRawDimensions } from "./styles-no-raw-dimensions.js";
|
|
197
|
+
export { ruleStylesNoRawSpacing } from "./styles-no-raw-spacing.js";
|
|
198
|
+
export { ruleStylesNoRawTypography } from "./styles-no-raw-typography.js";
|
|
199
|
+
export { ruleTailwindArbitraryColor } from "./tailwind-arbitrary-color.js";
|
|
200
|
+
export { ruleTailwindArbitrarySpacing } from "./tailwind-arbitrary-spacing.js";
|
|
201
|
+
export { ruleTailwindForbiddenPalette } from "./tailwind-forbidden-palette.js";
|
|
202
|
+
export { ruleTailwindOffScaleSpacingToken } from "./tailwind-off-scale-spacing-token.js";
|
|
203
|
+
export { ruleTailwindRawColorViaToken } from "./tailwind-raw-color-via-token.js";
|
|
204
|
+
export { ruleTailwindUnknownClass } from "./tailwind-unknown-class.js";
|
|
205
|
+
export { ruleTokensRequireDualFallback } from "./tokens-require-dual-fallback.js";
|
|
206
|
+
export { ruleTokensCssVarsMustBeDefined } from "./tokens-css-vars-must-be-defined.js";
|
|
207
|
+
export { ruleThemeNoThemeCoupledLiteral } from "./theme-no-theme-coupled-literal.js";
|
|
208
|
+
export { ruleA11yRequiredAccessibleName } from "./a11y-required-accessible-name.js";
|
|
209
|
+
export { ruleCompositionCardinality, ruleCompositionCoOccurrence } from "./composition-pattern.js";
|
|
210
|
+
export {
|
|
211
|
+
RULE_TIER,
|
|
212
|
+
FRAGMENTS_INTERNAL_RULE_IDS,
|
|
213
|
+
tierFor,
|
|
214
|
+
isContractTierRule,
|
|
215
|
+
contractTierRuleIds,
|
|
216
|
+
hygieneTierRuleIds,
|
|
217
|
+
vocabularyRank,
|
|
218
|
+
compareByVocabularyRank,
|
|
219
|
+
} from "./tiers.js";
|
|
220
|
+
export type { RuleTier } from "./tiers.js";
|
|
221
|
+
export { customerDefaultRuleStates, fragmentsPresetRuleStates } from "./presets.js";
|
|
222
|
+
export type { PresetRuleState } from "./presets.js";
|
|
223
|
+
export {
|
|
224
|
+
compositionPatternSchema,
|
|
225
|
+
compositionAuthoringEntrySchema,
|
|
226
|
+
isCompositionPattern,
|
|
227
|
+
lowerCompositionContract,
|
|
228
|
+
} from "./composition-pattern-schema.js";
|
|
229
|
+
export type {
|
|
230
|
+
CompositionPattern,
|
|
231
|
+
CompositionRegionSelector,
|
|
232
|
+
CompositionChildSelector,
|
|
233
|
+
CompositionConstraint,
|
|
234
|
+
CompositionAuthoringEntry,
|
|
235
|
+
} from "./composition-pattern-schema.js";
|