@adobe/design-data-spec 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/components/button.json +70 -0
- package/conformance/README.md +0 -1
- package/conformance/invalid/SPEC-016/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-016/tokens.tokens.json +8 -0
- package/conformance/invalid/SPEC-017/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-017/tokens.tokens.json +7 -0
- package/conformance/invalid/SPEC-018/dataset.json +9 -0
- package/conformance/invalid/SPEC-018/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-019/dataset.json +29 -0
- package/conformance/invalid/SPEC-019/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-020/dataset.json +27 -0
- package/conformance/invalid/SPEC-020/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-021/dataset.json +18 -0
- package/conformance/invalid/SPEC-021/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-022/dataset.json +33 -0
- package/conformance/invalid/SPEC-022/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-023/dataset.json +18 -0
- package/conformance/invalid/SPEC-023/expected-errors.json +10 -0
- package/conformance/invalid/SPEC-024/dataset.json +18 -0
- package/conformance/invalid/SPEC-024/expected-errors.json +10 -0
- package/conformance/valid/component-refs/dataset.json +63 -0
- package/conformance/valid/composite-drop-shadow.json +14 -0
- package/conformance/valid/composite-typography-scale.json +6 -0
- package/conformance/valid/composite-typography.json +12 -0
- package/conformance/valid/string-name-escape-hatch.json +7 -0
- package/fields/scaleIndex.json +15 -0
- package/package.json +18 -6
- package/rules/rules.yaml +113 -5
- package/schemas/anatomy-part.schema.json +35 -0
- package/schemas/component.schema.json +267 -0
- package/schemas/field.schema.json +2 -2
- package/schemas/state-declaration.schema.json +36 -0
- package/schemas/token.schema.json +26 -10
- package/schemas/value-types/drop-shadow.schema.json +20 -0
- package/schemas/value-types/typography-scale.schema.json +13 -0
- package/schemas/value-types/typography.schema.json +16 -0
- package/spec/agent-surface.md +116 -0
- package/spec/anatomy-format.md +167 -0
- package/spec/component-format.md +326 -0
- package/spec/evolution.md +0 -1
- package/spec/index.md +27 -21
- package/spec/state-model.md +245 -0
- package/spec/token-format.md +80 -15
- package/src/canonical.js +61 -0
- package/src/validate.js +166 -0
package/src/validate.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2026 Adobe. All rights reserved.
|
|
3
|
+
This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
|
|
7
|
+
Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Layer 2 cross-reference validator for design-data-spec.
|
|
15
|
+
*
|
|
16
|
+
* Implements SPEC-018 through SPEC-024: semantic rules that validate token
|
|
17
|
+
* name-object fields against component declarations, and validate component
|
|
18
|
+
* declarations internally.
|
|
19
|
+
*
|
|
20
|
+
* @see spec/component-format.md#spec-rules
|
|
21
|
+
* @see spec/anatomy-format.md#spec-rules
|
|
22
|
+
* @see spec/state-model.md#spec-rules
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
CANONICAL_SLOTS,
|
|
27
|
+
CANONICAL_ANATOMY_PARTS,
|
|
28
|
+
CANONICAL_STATES,
|
|
29
|
+
} from "./canonical.js";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {{ ruleId: string, severity: 'error'|'warning', message: string, tokenName?: string, componentName?: string }} Diagnostic
|
|
33
|
+
* @typedef {{ name: string|object, [key: string]: unknown }} Token
|
|
34
|
+
* @typedef {{ name: string, options?: object, anatomy?: Array<{name:string,description?:string}>, slots?: Array<{name:string,description?:string}>, states?: Array<{name:string,trigger?:string,precedence?:number,layered?:boolean,description?:string}> }} ComponentDeclaration
|
|
35
|
+
* @typedef {{ tokens?: Token[], components?: ComponentDeclaration[] }} Dataset
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validate a dataset for SPEC-018 through SPEC-024 compliance.
|
|
40
|
+
*
|
|
41
|
+
* @param {Dataset} dataset
|
|
42
|
+
* @returns {Diagnostic[]}
|
|
43
|
+
*/
|
|
44
|
+
export function validateDataset(dataset) {
|
|
45
|
+
const tokens = dataset.tokens ?? [];
|
|
46
|
+
const components = dataset.components ?? [];
|
|
47
|
+
|
|
48
|
+
// Build component lookup map keyed by name.
|
|
49
|
+
const componentMap = new Map(components.map((c) => [c.name, c]));
|
|
50
|
+
|
|
51
|
+
const diagnostics = [];
|
|
52
|
+
|
|
53
|
+
// --- Token cross-reference rules ---
|
|
54
|
+
for (const token of tokens) {
|
|
55
|
+
const name = token.name;
|
|
56
|
+
// String names (SPEC-017 escape hatch) skip cross-reference checks.
|
|
57
|
+
if (typeof name !== "object" || name === null) continue;
|
|
58
|
+
|
|
59
|
+
const tokenLabel = JSON.stringify(name);
|
|
60
|
+
|
|
61
|
+
if (name.component != null) {
|
|
62
|
+
// SPEC-018: component name must be declared
|
|
63
|
+
if (!componentMap.has(name.component)) {
|
|
64
|
+
diagnostics.push({
|
|
65
|
+
ruleId: "SPEC-018",
|
|
66
|
+
severity: "error",
|
|
67
|
+
message: `Token '${tokenLabel}' references undeclared component '${name.component}'`,
|
|
68
|
+
tokenName: tokenLabel,
|
|
69
|
+
});
|
|
70
|
+
// Can't validate further fields without a component declaration.
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const component = componentMap.get(name.component);
|
|
75
|
+
|
|
76
|
+
// SPEC-019: variant must be in component's variant option enum
|
|
77
|
+
if (name.variant != null) {
|
|
78
|
+
const variantEnum = component.options?.variant?.enum;
|
|
79
|
+
if (Array.isArray(variantEnum) && !variantEnum.includes(name.variant)) {
|
|
80
|
+
diagnostics.push({
|
|
81
|
+
ruleId: "SPEC-019",
|
|
82
|
+
severity: "error",
|
|
83
|
+
message: `Token '${tokenLabel}' has variant '${name.variant}' which is not declared on component '${name.component}'`,
|
|
84
|
+
tokenName: tokenLabel,
|
|
85
|
+
componentName: name.component,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// SPEC-020: anatomy must match a declared anatomy part name
|
|
91
|
+
if (name.anatomy != null) {
|
|
92
|
+
const declaredParts = new Set(
|
|
93
|
+
(component.anatomy ?? []).map((p) => p.name),
|
|
94
|
+
);
|
|
95
|
+
if (declaredParts.size > 0 && !declaredParts.has(name.anatomy)) {
|
|
96
|
+
diagnostics.push({
|
|
97
|
+
ruleId: "SPEC-020",
|
|
98
|
+
severity: "error",
|
|
99
|
+
message: `Token '${tokenLabel}' references undeclared anatomy part '${name.anatomy}' on component '${name.component}'`,
|
|
100
|
+
tokenName: tokenLabel,
|
|
101
|
+
componentName: name.component,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// SPEC-022: state must match a declared state name (only when states are declared)
|
|
107
|
+
if (name.state != null) {
|
|
108
|
+
const declaredStates = new Set(
|
|
109
|
+
(component.states ?? []).map((s) => s.name),
|
|
110
|
+
);
|
|
111
|
+
if (declaredStates.size > 0 && !declaredStates.has(name.state)) {
|
|
112
|
+
diagnostics.push({
|
|
113
|
+
ruleId: "SPEC-022",
|
|
114
|
+
severity: "error",
|
|
115
|
+
message: `Token '${tokenLabel}' references undeclared state '${name.state}' on component '${name.component}'`,
|
|
116
|
+
tokenName: tokenLabel,
|
|
117
|
+
componentName: name.component,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// --- Component declaration internal rules ---
|
|
125
|
+
for (const component of components) {
|
|
126
|
+
const cName = component.name;
|
|
127
|
+
|
|
128
|
+
// SPEC-021: custom slot names should have descriptions
|
|
129
|
+
for (const slot of component.slots ?? []) {
|
|
130
|
+
if (!CANONICAL_SLOTS.has(slot.name) && !slot.description) {
|
|
131
|
+
diagnostics.push({
|
|
132
|
+
ruleId: "SPEC-021",
|
|
133
|
+
severity: "warning",
|
|
134
|
+
message: `Component '${cName}' has custom slot '${slot.name}' with no description — add a description or use a canonical slot name`,
|
|
135
|
+
componentName: cName,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// SPEC-023: custom anatomy part names should have descriptions
|
|
141
|
+
for (const part of component.anatomy ?? []) {
|
|
142
|
+
if (!CANONICAL_ANATOMY_PARTS.has(part.name) && !part.description) {
|
|
143
|
+
diagnostics.push({
|
|
144
|
+
ruleId: "SPEC-023",
|
|
145
|
+
severity: "warning",
|
|
146
|
+
message: `Component '${cName}' has custom anatomy part '${part.name}' with no description`,
|
|
147
|
+
componentName: cName,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// SPEC-024: custom state names should have descriptions
|
|
153
|
+
for (const state of component.states ?? []) {
|
|
154
|
+
if (!CANONICAL_STATES.has(state.name) && !state.description) {
|
|
155
|
+
diagnostics.push({
|
|
156
|
+
ruleId: "SPEC-024",
|
|
157
|
+
severity: "warning",
|
|
158
|
+
message: `Component '${cName}' has custom state '${state.name}' with no description`,
|
|
159
|
+
componentName: cName,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return diagnostics;
|
|
166
|
+
}
|