@terrazzo/parser 2.0.0-alpha.7 → 2.0.0-beta.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.
Files changed (53) hide show
  1. package/dist/index.d.ts +39 -6
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +578 -512
  4. package/dist/index.js.map +1 -1
  5. package/package.json +3 -3
  6. package/src/build/index.ts +0 -209
  7. package/src/config.ts +0 -304
  8. package/src/index.ts +0 -95
  9. package/src/lib/code-frame.ts +0 -177
  10. package/src/lib/momoa.ts +0 -10
  11. package/src/lib/resolver-utils.ts +0 -35
  12. package/src/lint/index.ts +0 -142
  13. package/src/lint/plugin-core/index.ts +0 -103
  14. package/src/lint/plugin-core/lib/docs.ts +0 -3
  15. package/src/lint/plugin-core/rules/a11y-min-contrast.ts +0 -91
  16. package/src/lint/plugin-core/rules/a11y-min-font-size.ts +0 -66
  17. package/src/lint/plugin-core/rules/colorspace.ts +0 -108
  18. package/src/lint/plugin-core/rules/consistent-naming.ts +0 -65
  19. package/src/lint/plugin-core/rules/descriptions.ts +0 -43
  20. package/src/lint/plugin-core/rules/duplicate-values.ts +0 -85
  21. package/src/lint/plugin-core/rules/max-gamut.ts +0 -144
  22. package/src/lint/plugin-core/rules/required-children.ts +0 -106
  23. package/src/lint/plugin-core/rules/required-modes.ts +0 -75
  24. package/src/lint/plugin-core/rules/required-type.ts +0 -28
  25. package/src/lint/plugin-core/rules/required-typography-properties.ts +0 -65
  26. package/src/lint/plugin-core/rules/valid-boolean.ts +0 -41
  27. package/src/lint/plugin-core/rules/valid-border.ts +0 -57
  28. package/src/lint/plugin-core/rules/valid-color.ts +0 -265
  29. package/src/lint/plugin-core/rules/valid-cubic-bezier.ts +0 -83
  30. package/src/lint/plugin-core/rules/valid-dimension.ts +0 -199
  31. package/src/lint/plugin-core/rules/valid-duration.ts +0 -123
  32. package/src/lint/plugin-core/rules/valid-font-family.ts +0 -68
  33. package/src/lint/plugin-core/rules/valid-font-weight.ts +0 -89
  34. package/src/lint/plugin-core/rules/valid-gradient.ts +0 -79
  35. package/src/lint/plugin-core/rules/valid-link.ts +0 -41
  36. package/src/lint/plugin-core/rules/valid-number.ts +0 -63
  37. package/src/lint/plugin-core/rules/valid-shadow.ts +0 -67
  38. package/src/lint/plugin-core/rules/valid-string.ts +0 -41
  39. package/src/lint/plugin-core/rules/valid-stroke-style.ts +0 -104
  40. package/src/lint/plugin-core/rules/valid-transition.ts +0 -61
  41. package/src/lint/plugin-core/rules/valid-typography.ts +0 -67
  42. package/src/logger.ts +0 -213
  43. package/src/parse/index.ts +0 -124
  44. package/src/parse/load.ts +0 -172
  45. package/src/parse/normalize.ts +0 -163
  46. package/src/parse/process.ts +0 -251
  47. package/src/parse/token.ts +0 -553
  48. package/src/resolver/create-synthetic-resolver.ts +0 -86
  49. package/src/resolver/index.ts +0 -7
  50. package/src/resolver/load.ts +0 -215
  51. package/src/resolver/normalize.ts +0 -133
  52. package/src/resolver/validate.ts +0 -375
  53. package/src/types.ts +0 -468
@@ -1,65 +0,0 @@
1
- import wcmatch from 'wildcard-match';
2
- import type { LintRule } from '../../../types.js';
3
- import { docsLink } from '../lib/docs.js';
4
-
5
- export const REQUIRED_TYPOGRAPHY_PROPERTIES = 'core/required-typography-properties';
6
-
7
- export interface RuleRequiredTypographyPropertiesOptions {
8
- /**
9
- * Required typography properties.
10
- * @default ["fontFamily", "fontWeight", "fontSize", "letterSpacing", "lineHeight"]
11
- */
12
- properties: string[];
13
- /** Token globs to ignore */
14
- ignore?: string[];
15
- }
16
-
17
- /** @deprecated Use core/valid-typography instead */
18
- const rule: LintRule<never, RuleRequiredTypographyPropertiesOptions> = {
19
- meta: {
20
- docs: {
21
- description: 'Enforce typography tokens have required properties.',
22
- url: docsLink(REQUIRED_TYPOGRAPHY_PROPERTIES),
23
- },
24
- },
25
- defaultOptions: {
26
- properties: ['fontFamily', 'fontSize', 'fontWeight', 'letterSpacing', 'lineHeight'],
27
- },
28
- create({ tokens, options, report }) {
29
- if (!options) {
30
- return;
31
- }
32
-
33
- if (!options.properties.length) {
34
- throw new Error(`"properties" can’t be empty`);
35
- }
36
-
37
- const shouldIgnore = options.ignore ? wcmatch(options.ignore) : null;
38
-
39
- for (const t of Object.values(tokens)) {
40
- if (shouldIgnore?.(t.id)) {
41
- continue;
42
- }
43
-
44
- if (t.$type !== 'typography') {
45
- continue;
46
- }
47
-
48
- if (t.aliasOf) {
49
- continue;
50
- }
51
-
52
- for (const p of options.properties) {
53
- if (!t.partialAliasOf?.[p] && !(p in t.$value)) {
54
- report({
55
- message: `This rule is deprecated. Use core/valid-typography. Missing required typographic property "${p}"`,
56
- node: t.source.node,
57
- filename: t.source.filename,
58
- });
59
- }
60
- }
61
- }
62
- },
63
- };
64
-
65
- export default rule;
@@ -1,41 +0,0 @@
1
- import type * as momoa from '@humanwhocodes/momoa';
2
- import { getObjMember } from '@terrazzo/json-schema-tools';
3
- import type { LintRule } from '../../../types.js';
4
- import { docsLink } from '../lib/docs.js';
5
-
6
- export const VALID_BOOLEAN = 'core/valid-boolean';
7
-
8
- const ERROR = 'ERROR';
9
-
10
- const rule: LintRule<typeof ERROR, {}> = {
11
- meta: {
12
- messages: {
13
- [ERROR]: 'Must be a boolean.',
14
- },
15
- docs: {
16
- description: 'Require boolean tokens to follow the Terrazzo extension.',
17
- url: docsLink(VALID_BOOLEAN),
18
- },
19
- },
20
- defaultOptions: {},
21
- create({ tokens, report }) {
22
- for (const t of Object.values(tokens)) {
23
- if (t.aliasOf || !t.originalValue || t.$type !== 'boolean') {
24
- continue;
25
- }
26
-
27
- validateBoolean(t.originalValue.$value, {
28
- node: getObjMember(t.source.node, '$value'),
29
- filename: t.source.filename,
30
- });
31
-
32
- function validateBoolean(value: unknown, { node, filename }: { node?: momoa.AnyNode; filename?: string }) {
33
- if (typeof value !== 'boolean') {
34
- report({ messageId: ERROR, filename, node });
35
- }
36
- }
37
- }
38
- },
39
- };
40
-
41
- export default rule;
@@ -1,57 +0,0 @@
1
- import type * as momoa from '@humanwhocodes/momoa';
2
- import { getObjMember } from '@terrazzo/json-schema-tools';
3
- import { BORDER_REQUIRED_PROPERTIES } from '@terrazzo/token-tools';
4
- import type { LintRule } from '../../../types.js';
5
- import { docsLink } from '../lib/docs.js';
6
-
7
- export const VALID_BORDER = 'core/valid-border';
8
-
9
- const ERROR = 'ERROR';
10
- const ERROR_INVALID_PROP = 'ERROR_INVALID_PROP';
11
-
12
- const rule: LintRule<typeof ERROR | typeof ERROR_INVALID_PROP, {}> = {
13
- meta: {
14
- messages: {
15
- [ERROR]: `Border token missing required properties: ${new Intl.ListFormat('en-us', { type: 'conjunction' }).format(BORDER_REQUIRED_PROPERTIES)}.`,
16
- [ERROR_INVALID_PROP]: 'Unknown property: {{ key }}.',
17
- },
18
- docs: {
19
- description: 'Require border tokens to follow the format.',
20
- url: docsLink(VALID_BORDER),
21
- },
22
- },
23
- defaultOptions: {},
24
- create({ tokens, report }) {
25
- for (const t of Object.values(tokens)) {
26
- if (t.aliasOf || !t.originalValue || t.$type !== 'border') {
27
- continue;
28
- }
29
-
30
- validateBorder(t.originalValue.$value, {
31
- node: getObjMember(t.source.node, '$value'),
32
- filename: t.source.filename,
33
- });
34
- }
35
-
36
- // Note: we validate sub-properties using other checks like valid-dimension, valid-font-family, etc.
37
- // The only thing remaining is to check that all properties exist (since missing properties won’t appear as invalid)
38
- function validateBorder(value: unknown, { node, filename }: { node?: momoa.AnyNode; filename?: string }) {
39
- if (!value || typeof value !== 'object' || !BORDER_REQUIRED_PROPERTIES.every((property) => property in value)) {
40
- report({ messageId: ERROR, filename, node });
41
- } else {
42
- for (const key of Object.keys(value)) {
43
- if (!BORDER_REQUIRED_PROPERTIES.includes(key as (typeof BORDER_REQUIRED_PROPERTIES)[number])) {
44
- report({
45
- messageId: ERROR_INVALID_PROP,
46
- data: { key: JSON.stringify(key) },
47
- node: getObjMember(node as momoa.ObjectNode, key) ?? node,
48
- filename,
49
- });
50
- }
51
- }
52
- }
53
- }
54
- },
55
- };
56
-
57
- export default rule;
@@ -1,265 +0,0 @@
1
- import type * as momoa from '@humanwhocodes/momoa';
2
- import { getObjMember } from '@terrazzo/json-schema-tools';
3
- import {
4
- type BorderValue,
5
- COLORSPACE,
6
- type ColorSpaceDefinition,
7
- type ColorValueNormalized,
8
- type GradientStopNormalized,
9
- type GradientValueNormalized,
10
- isAlias,
11
- parseColor,
12
- type ShadowValue,
13
- } from '@terrazzo/token-tools';
14
- import type { LintRule } from '../../../types.js';
15
- import { docsLink } from '../lib/docs.js';
16
-
17
- export const VALID_COLOR = 'core/valid-color';
18
-
19
- const ERROR_ALPHA = 'ERROR_ALPHA';
20
- const ERROR_INVALID_COLOR = 'ERROR_INVALID_COLOR';
21
- const ERROR_INVALID_COLOR_SPACE = 'ERROR_INVALID_COLOR_SPACE';
22
- const ERROR_INVALID_COMPONENT_LENGTH = 'ERROR_INVALID_COMPONENT_LENGTH';
23
- const ERROR_INVALID_HEX8 = 'ERROR_INVALID_HEX8';
24
- const ERROR_INVALID_PROP = 'ERROR_INVALID_PROP';
25
- const ERROR_MISSING_COMPONENTS = 'ERROR_MISSING_COMPONENTS';
26
- const ERROR_OBJ_FORMAT = 'ERROR_OBJ_FORMAT';
27
- const ERROR_OUT_OF_RANGE = 'ERROR_OUT_OF_RANGE';
28
-
29
- export interface RuleValidColorOptions {
30
- /**
31
- * Allow the legacy format of only a string sRGB hex code
32
- * @default false
33
- */
34
- legacyFormat?: boolean;
35
- /**
36
- * Allow colors to be defined out of the expected ranges.
37
- * @default false
38
- */
39
- ignoreRanges?: boolean;
40
- }
41
-
42
- const rule: LintRule<
43
- | typeof ERROR_ALPHA
44
- | typeof ERROR_INVALID_COLOR
45
- | typeof ERROR_INVALID_COLOR_SPACE
46
- | typeof ERROR_INVALID_COMPONENT_LENGTH
47
- | typeof ERROR_INVALID_HEX8
48
- | typeof ERROR_INVALID_PROP
49
- | typeof ERROR_MISSING_COMPONENTS
50
- | typeof ERROR_OBJ_FORMAT
51
- | typeof ERROR_OUT_OF_RANGE,
52
- RuleValidColorOptions
53
- > = {
54
- meta: {
55
- messages: {
56
- [ERROR_ALPHA]: `Alpha {{ alpha }} not in range 0 – 1.`,
57
- [ERROR_INVALID_COLOR_SPACE]: `Invalid color space: {{ colorSpace }}. Expected ${new Intl.ListFormat('en-us', { type: 'disjunction' }).format(Object.keys(COLORSPACE))}`,
58
- [ERROR_INVALID_COLOR]: `Could not parse color {{ color }}.`,
59
- [ERROR_INVALID_COMPONENT_LENGTH]: 'Expected {{ expected }} components, received {{ got }}.',
60
- [ERROR_INVALID_HEX8]: `Hex value can’t be semi-transparent.`,
61
- [ERROR_INVALID_PROP]: `Unknown property {{ key }}.`,
62
- [ERROR_MISSING_COMPONENTS]: 'Expected components to be array of numbers, received {{ got }}.',
63
- [ERROR_OBJ_FORMAT]:
64
- 'Migrate to the new object format, e.g. "#ff0000" → { "colorSpace": "srgb", "components": [1, 0, 0] } }',
65
- [ERROR_OUT_OF_RANGE]: `Invalid range for color space {{ colorSpace }}. Expected {{ range }}.`,
66
- },
67
- docs: {
68
- description: 'Require color tokens to follow the format.',
69
- url: docsLink(VALID_COLOR),
70
- },
71
- },
72
- defaultOptions: {
73
- legacyFormat: false,
74
- ignoreRanges: false,
75
- },
76
- create({ tokens, options, report }) {
77
- for (const t of Object.values(tokens)) {
78
- if (t.aliasOf || !t.originalValue) {
79
- continue;
80
- }
81
-
82
- switch (t.$type) {
83
- case 'color': {
84
- validateColor(t.originalValue.$value, {
85
- node: getObjMember(t.source.node, '$value'),
86
- filename: t.source.filename,
87
- });
88
- break;
89
- }
90
- case 'border': {
91
- if ((t.originalValue.$value as any).color && !isAlias((t.originalValue.$value as any).color)) {
92
- validateColor((t.originalValue.$value as BorderValue).color, {
93
- node: getObjMember(getObjMember(t.source.node, '$value') as momoa.ObjectNode, 'color'),
94
- filename: t.source.filename,
95
- });
96
- }
97
- break;
98
- }
99
- case 'gradient': {
100
- const $valueNode = getObjMember(t.source.node, '$value') as momoa.ArrayNode;
101
- for (let i = 0; i < (t.originalValue.$value as GradientValueNormalized).length; i++) {
102
- const stop = t.originalValue.$value[i] as GradientStopNormalized;
103
- if (!stop.color || isAlias(stop.color as any)) {
104
- continue;
105
- }
106
- validateColor(stop.color, {
107
- node: getObjMember($valueNode.elements[i]!.value as momoa.ObjectNode, 'color'),
108
- filename: t.source.filename,
109
- });
110
- }
111
- break;
112
- }
113
- case 'shadow': {
114
- const $value = (
115
- Array.isArray(t.originalValue.$value) ? t.originalValue.$value : [t.originalValue.$value]
116
- ) as ShadowValue[];
117
- const $valueNode = getObjMember(t.source.node, '$value') as momoa.ObjectNode | momoa.ArrayNode;
118
- for (let i = 0; i < $value.length; i++) {
119
- const layer = $value[i]!;
120
- if (!layer.color || isAlias(layer.color as any)) {
121
- continue;
122
- }
123
- validateColor(layer.color, {
124
- node:
125
- $valueNode.type === 'Object'
126
- ? getObjMember($valueNode, 'color')
127
- : getObjMember($valueNode.elements[i]!.value as momoa.ObjectNode, 'color'),
128
- filename: t.source.filename,
129
- });
130
- }
131
- break;
132
- }
133
- }
134
-
135
- function validateColor(value: unknown, { node, filename }: { node?: momoa.AnyNode; filename?: string }) {
136
- if (!value) {
137
- report({ messageId: ERROR_INVALID_COLOR, data: { color: JSON.stringify(value) }, node, filename });
138
- } else if (typeof value === 'object') {
139
- for (const key of Object.keys(value)) {
140
- if (!['colorSpace', 'components', 'channels' /* TODO: remove */, 'hex', 'alpha'].includes(key)) {
141
- report({
142
- messageId: ERROR_INVALID_PROP,
143
- data: { key: JSON.stringify(key) },
144
- node: getObjMember(node as momoa.ObjectNode, key) ?? node,
145
- filename,
146
- });
147
- }
148
- }
149
-
150
- // Color space
151
- const colorSpace =
152
- 'colorSpace' in value && typeof value.colorSpace === 'string' ? value.colorSpace : undefined;
153
- const csData = (COLORSPACE as Record<string, ColorSpaceDefinition>)[colorSpace!] || undefined;
154
- if (!('colorSpace' in value) || !csData) {
155
- report({
156
- messageId: ERROR_INVALID_COLOR_SPACE,
157
- data: { colorSpace },
158
- node: getObjMember(node as momoa.ObjectNode, 'colorSpace') ?? node,
159
- filename,
160
- });
161
- return;
162
- }
163
-
164
- // Component ranges
165
- const components = 'components' in value ? value.components : undefined;
166
- if (Array.isArray(components)) {
167
- if (csData?.ranges && components?.length === csData.ranges.length) {
168
- for (let i = 0; i < components.length; i++) {
169
- if (
170
- !Number.isFinite(components[i]) ||
171
- components[i]! < csData.ranges[i]![0] ||
172
- components[i]! > csData.ranges[i]![1]
173
- ) {
174
- // special case for any hue-based components: allow null
175
- if (
176
- !(colorSpace === 'hsl' && components[0]! === null) &&
177
- !(colorSpace === 'hwb' && components[0]! === null) &&
178
- !(colorSpace === 'lch' && components[2]! === null) &&
179
- !(colorSpace === 'oklch' && components[2]! === null)
180
- ) {
181
- report({
182
- messageId: ERROR_OUT_OF_RANGE,
183
- data: { colorSpace, range: `[${csData.ranges.map((r) => `${r[0]}–${r[1]}`).join(', ')}]` },
184
- node: getObjMember(node as momoa.ObjectNode, 'components') ?? node,
185
- filename,
186
- });
187
- }
188
- }
189
- }
190
- } else {
191
- report({
192
- messageId: ERROR_INVALID_COMPONENT_LENGTH,
193
- data: { expected: csData?.ranges.length, got: (components as number[] | undefined)?.length ?? 0 },
194
- node: getObjMember(node as momoa.ObjectNode, 'components') ?? node,
195
- filename,
196
- });
197
- }
198
- } else {
199
- report({
200
- messageId: ERROR_MISSING_COMPONENTS,
201
- data: { got: JSON.stringify(components) },
202
- node: getObjMember(node as momoa.ObjectNode, 'components') ?? node,
203
- filename,
204
- });
205
- }
206
-
207
- // Alpha
208
- const alpha = 'alpha' in value ? value.alpha : undefined;
209
- if (alpha !== undefined && (typeof alpha !== 'number' || alpha < 0 || alpha > 1)) {
210
- report({
211
- messageId: ERROR_ALPHA,
212
- data: { alpha },
213
- node: getObjMember(node as momoa.ObjectNode, 'alpha') ?? node,
214
- filename,
215
- });
216
- }
217
-
218
- // Hex
219
- const hex = 'hex' in value ? value.hex : undefined;
220
- if (hex) {
221
- let color: ColorValueNormalized;
222
- try {
223
- color = parseColor(hex as string);
224
- } catch {
225
- report({
226
- messageId: ERROR_INVALID_COLOR,
227
- data: { color: hex },
228
- node: getObjMember(node as momoa.ObjectNode, 'hex') ?? node,
229
- filename,
230
- });
231
- return;
232
- }
233
- // since we’re only parsing hex, this should always have alpha: 1
234
- if (color.alpha !== 1) {
235
- report({
236
- messageId: ERROR_INVALID_HEX8,
237
- data: { color: hex },
238
- node: getObjMember(node as momoa.ObjectNode, 'hex') ?? node,
239
- filename,
240
- });
241
- }
242
- }
243
- } else if (typeof value === 'string') {
244
- if (isAlias(value)) {
245
- return;
246
- }
247
- if (!options.legacyFormat) {
248
- report({ messageId: ERROR_OBJ_FORMAT, data: { color: JSON.stringify(value) }, node, filename });
249
- } else {
250
- // Legacy format
251
- try {
252
- parseColor(value as string);
253
- } catch {
254
- report({ messageId: ERROR_INVALID_COLOR, data: { color: JSON.stringify(value) }, node, filename });
255
- }
256
- }
257
- } else {
258
- report({ messageId: ERROR_INVALID_COLOR, node, filename });
259
- }
260
- }
261
- }
262
- },
263
- };
264
-
265
- export default rule;
@@ -1,83 +0,0 @@
1
- import type * as momoa from '@humanwhocodes/momoa';
2
- import { getObjMember, isPure$ref } from '@terrazzo/json-schema-tools';
3
- import { isAlias } from '@terrazzo/token-tools';
4
- import type { LintRule } from '../../../types.js';
5
- import { docsLink } from '../lib/docs.js';
6
-
7
- export const VALID_CUBIC_BEZIER = 'core/valid-cubic-bezier';
8
-
9
- const ERROR = 'ERROR';
10
- const ERROR_X = 'ERROR_X';
11
- const ERROR_Y = 'ERROR_Y';
12
-
13
- const rule: LintRule<typeof ERROR | typeof ERROR_X | typeof ERROR_Y> = {
14
- meta: {
15
- messages: {
16
- [ERROR]: 'Expected [number, number, number, number].',
17
- [ERROR_X]: 'x values must be between 0-1.',
18
- [ERROR_Y]: 'y values must be a valid number.',
19
- },
20
- docs: {
21
- description: 'Require cubicBezier tokens to follow the format.',
22
- url: docsLink(VALID_CUBIC_BEZIER),
23
- },
24
- },
25
- defaultOptions: {},
26
- create({ tokens, report }) {
27
- for (const t of Object.values(tokens)) {
28
- if (t.aliasOf || !t.originalValue) {
29
- continue;
30
- }
31
-
32
- switch (t.$type) {
33
- case 'cubicBezier': {
34
- validateCubicBezier(t.originalValue.$value, {
35
- node: getObjMember(t.source.node, '$value') as momoa.ArrayNode,
36
- filename: t.source.filename,
37
- });
38
- break;
39
- }
40
- case 'transition': {
41
- if (
42
- typeof t.originalValue.$value === 'object' &&
43
- t.originalValue.$value.timingFunction &&
44
- !isAlias(t.originalValue.$value.timingFunction as string)
45
- ) {
46
- const $valueNode = getObjMember(t.source.node, '$value') as momoa.ObjectNode;
47
- validateCubicBezier(t.originalValue.$value.timingFunction, {
48
- node: getObjMember($valueNode, 'timingFunction') as momoa.ArrayNode,
49
- filename: t.source.filename,
50
- });
51
- }
52
- }
53
- }
54
-
55
- function validateCubicBezier(value: unknown, { node, filename }: { node: momoa.ArrayNode; filename?: string }) {
56
- if (Array.isArray(value) && value.length === 4) {
57
- // validate x values
58
- for (const pos of [0, 2]) {
59
- if (isAlias(value[pos]) || isPure$ref(value[pos])) {
60
- continue;
61
- }
62
- if (!(value[pos] >= 0 && value[pos] <= 1)) {
63
- report({ messageId: ERROR_X, node: (node as momoa.ArrayNode).elements[pos], filename });
64
- }
65
- }
66
- // validate y values
67
- for (const pos of [1, 3]) {
68
- if (isAlias(value[pos]) || isPure$ref(value[pos])) {
69
- continue;
70
- }
71
- if (typeof value[pos] !== 'number') {
72
- report({ messageId: ERROR_Y, node: (node as momoa.ArrayNode).elements[pos], filename });
73
- }
74
- }
75
- } else {
76
- report({ messageId: ERROR, node, filename });
77
- }
78
- }
79
- }
80
- },
81
- };
82
-
83
- export default rule;