@terrazzo/parser 0.10.3 → 2.0.0-alpha.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 (43) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/index.d.ts +82 -333
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +2203 -3660
  5. package/dist/index.js.map +1 -1
  6. package/package.json +6 -5
  7. package/src/build/index.ts +32 -41
  8. package/src/config.ts +13 -6
  9. package/src/lib/code-frame.ts +5 -2
  10. package/src/lib/momoa.ts +10 -0
  11. package/src/lint/index.ts +41 -37
  12. package/src/lint/plugin-core/index.ts +73 -16
  13. package/src/lint/plugin-core/rules/colorspace.ts +4 -0
  14. package/src/lint/plugin-core/rules/duplicate-values.ts +2 -0
  15. package/src/lint/plugin-core/rules/max-gamut.ts +24 -4
  16. package/src/lint/plugin-core/rules/no-type-on-alias.ts +29 -0
  17. package/src/lint/plugin-core/rules/required-modes.ts +2 -0
  18. package/src/lint/plugin-core/rules/required-typography-properties.ts +13 -3
  19. package/src/lint/plugin-core/rules/valid-boolean.ts +41 -0
  20. package/src/lint/plugin-core/rules/valid-border.ts +57 -0
  21. package/src/lint/plugin-core/rules/valid-color.ts +265 -0
  22. package/src/lint/plugin-core/rules/valid-cubic-bezier.ts +83 -0
  23. package/src/lint/plugin-core/rules/valid-dimension.ts +199 -0
  24. package/src/lint/plugin-core/rules/valid-duration.ts +123 -0
  25. package/src/lint/plugin-core/rules/valid-font-family.ts +68 -0
  26. package/src/lint/plugin-core/rules/valid-font-weight.ts +89 -0
  27. package/src/lint/plugin-core/rules/valid-gradient.ts +79 -0
  28. package/src/lint/plugin-core/rules/valid-link.ts +41 -0
  29. package/src/lint/plugin-core/rules/valid-number.ts +63 -0
  30. package/src/lint/plugin-core/rules/valid-shadow.ts +67 -0
  31. package/src/lint/plugin-core/rules/valid-string.ts +41 -0
  32. package/src/lint/plugin-core/rules/valid-stroke-style.ts +104 -0
  33. package/src/lint/plugin-core/rules/valid-transition.ts +61 -0
  34. package/src/lint/plugin-core/rules/valid-typography.ts +67 -0
  35. package/src/logger.ts +70 -59
  36. package/src/parse/index.ts +23 -318
  37. package/src/parse/load.ts +257 -0
  38. package/src/parse/normalize.ts +134 -170
  39. package/src/parse/token.ts +530 -0
  40. package/src/types.ts +76 -10
  41. package/src/parse/alias.ts +0 -369
  42. package/src/parse/json.ts +0 -211
  43. package/src/parse/validate.ts +0 -961
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terrazzo/parser",
3
- "version": "0.10.3",
3
+ "version": "2.0.0-alpha.0",
4
4
  "description": "Parser/validator for the Design Tokens Community Group (DTCG) standard.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -34,18 +34,19 @@
34
34
  "directory": "./packages/parser/"
35
35
  },
36
36
  "dependencies": {
37
- "@humanwhocodes/momoa": "^3.3.8",
37
+ "@humanwhocodes/momoa": "^3.3.9",
38
38
  "@types/babel__code-frame": "^7.0.6",
39
- "@types/culori": "^4.0.0",
39
+ "@types/culori": "^4.0.1",
40
40
  "culori": "^4.0.2",
41
41
  "merge-anything": "^5.1.7",
42
42
  "picocolors": "^1.1.1",
43
43
  "scule": "^1.3.0",
44
44
  "wildcard-match": "^5.1.4",
45
- "@terrazzo/token-tools": "^0.10.3"
45
+ "@terrazzo/token-tools": "^2.0.0-alpha.0",
46
+ "@terrazzo/json-schema-tools": "^0.0.1"
46
47
  },
47
48
  "devDependencies": {
48
- "yaml-to-momoa": "^0.0.3"
49
+ "yaml-to-momoa": "0.0.6"
49
50
  },
50
51
  "scripts": {
51
52
  "build": "rolldown -c && attw --profile esm-only --pack .",
@@ -1,11 +1,11 @@
1
- import type { DocumentNode } from '@humanwhocodes/momoa';
1
+ import type * as momoa from '@humanwhocodes/momoa';
2
2
  import type { TokenNormalized } from '@terrazzo/token-tools';
3
3
  import wcmatch from 'wildcard-match';
4
4
  import Logger, { type LogEntry } from '../logger.js';
5
5
  import type { BuildRunnerResult, ConfigInit, TokenTransformed, TransformParams } from '../types.js';
6
6
 
7
7
  export interface BuildRunnerOptions {
8
- sources: { filename?: URL; src: string; document: DocumentNode }[];
8
+ sources: { filename?: URL; src: string; document: momoa.DocumentNode }[];
9
9
  config: ConfigInit;
10
10
  logger?: Logger;
11
11
  }
@@ -101,17 +101,8 @@ export default async function build(
101
101
  }
102
102
  const token = tokens[id]!;
103
103
 
104
- // allow `undefined` values, but remove them here
105
104
  const cleanValue: TokenTransformed['value'] =
106
105
  typeof params.value === 'string' ? params.value : { ...(params.value as Record<string, string>) };
107
- if (typeof cleanValue === 'object') {
108
- for (const k of Object.keys(cleanValue)) {
109
- if (cleanValue[k] === undefined) {
110
- delete cleanValue[k];
111
- }
112
- }
113
- }
114
-
115
106
  validateTransformParams({
116
107
  logger,
117
108
  params: { ...(params as any), value: cleanValue },
@@ -155,32 +146,34 @@ export default async function build(
155
146
 
156
147
  // build()
157
148
  const startBuild = performance.now();
158
- for (const plugin of config.plugins) {
159
- if (typeof plugin.build === 'function') {
160
- const pluginBuildStart = performance.now();
161
- await plugin.build({
162
- tokens,
163
- sources,
164
- getTransforms,
165
- outputFile(filename, contents) {
166
- const resolved = new URL(filename, config.outDir);
167
- if (result.outputFiles.some((f) => new URL(f.filename, config.outDir).href === resolved.href)) {
168
- logger.error({
169
- group: 'plugin',
170
- message: `Can’t overwrite file "${filename}"`,
171
- label: plugin.name,
149
+ await Promise.all(
150
+ config.plugins.map(async (plugin) => {
151
+ if (typeof plugin.build === 'function') {
152
+ const pluginBuildStart = performance.now();
153
+ await plugin.build({
154
+ tokens,
155
+ sources,
156
+ getTransforms,
157
+ outputFile(filename, contents) {
158
+ const resolved = new URL(filename, config.outDir);
159
+ if (result.outputFiles.some((f) => new URL(f.filename, config.outDir).href === resolved.href)) {
160
+ logger.error({
161
+ group: 'plugin',
162
+ message: `Can’t overwrite file "${filename}"`,
163
+ label: plugin.name,
164
+ });
165
+ }
166
+ result.outputFiles.push({
167
+ filename,
168
+ contents,
169
+ plugin: plugin.name,
170
+ time: performance.now() - pluginBuildStart,
172
171
  });
173
- }
174
- result.outputFiles.push({
175
- filename,
176
- contents,
177
- plugin: plugin.name,
178
- time: performance.now() - pluginBuildStart,
179
- });
180
- },
181
- });
182
- }
183
- }
172
+ },
173
+ });
174
+ }
175
+ }),
176
+ );
184
177
  logger.debug({
185
178
  group: 'parser',
186
179
  label: 'build',
@@ -190,11 +183,9 @@ export default async function build(
190
183
 
191
184
  // buildEnd()
192
185
  const startBuildEnd = performance.now();
193
- for (const plugin of config.plugins) {
194
- if (typeof plugin.buildEnd === 'function') {
195
- await plugin.buildEnd({ outputFiles: structuredClone(result.outputFiles) });
196
- }
197
- }
186
+ await Promise.all(
187
+ config.plugins.map(async (plugin) => plugin.buildEnd?.({ outputFiles: structuredClone(result.outputFiles) })),
188
+ );
198
189
  logger.debug({
199
190
  group: 'parser',
200
191
  label: 'build',
package/src/config.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { merge } from 'merge-anything';
2
- import coreLintPlugin from './lint/plugin-core/index.js';
2
+ import coreLintPlugin, { RECOMMENDED_CONFIG } from './lint/plugin-core/index.js';
3
3
  import Logger from './logger.js';
4
4
  import type { Config, ConfigInit, ConfigOptions, LintRuleSeverity } from './types.js';
5
5
 
@@ -56,12 +56,12 @@ function normalizeTokens({
56
56
  }) {
57
57
  if (rawConfig.tokens === undefined) {
58
58
  config.tokens = [
59
- // @ts-ignore we’ll normalize in next step
59
+ // @ts-expect-error we’ll normalize in next step
60
60
  './tokens.json',
61
61
  ];
62
62
  } else if (typeof rawConfig.tokens === 'string') {
63
63
  config.tokens = [
64
- // @ts-ignore we’ll normalize in next step
64
+ // @ts-expect-error we’ll normalize in next step
65
65
  rawConfig.tokens,
66
66
  ];
67
67
  } else if (Array.isArray(rawConfig.tokens)) {
@@ -69,7 +69,7 @@ function normalizeTokens({
69
69
  for (const file of rawConfig.tokens) {
70
70
  if (typeof file === 'string' || (file as URL) instanceof URL) {
71
71
  config.tokens.push(
72
- // @ts-ignore we’ll normalize in next step
72
+ // @ts-expect-error we’ll normalize in next step
73
73
  file,
74
74
  );
75
75
  } else {
@@ -177,7 +177,7 @@ function normalizeLint({ config, logger }: { config: ConfigInit; logger: Logger
177
177
  }
178
178
 
179
179
  if (config.lint.rules === undefined) {
180
- config.lint.rules = {};
180
+ config.lint.rules = { ...RECOMMENDED_CONFIG };
181
181
  } else {
182
182
  if (config.lint.rules === null || typeof config.lint.rules !== 'object' || Array.isArray(config.lint.rules)) {
183
183
  logger.error({
@@ -268,11 +268,18 @@ function normalizeLint({ config, logger }: { config: ConfigInit; logger: Logger
268
268
  });
269
269
  }
270
270
  }
271
+
272
+ // Apply recommended config in places user hasn’t explicitly opted-out
273
+ for (const [id, severity] of Object.entries(RECOMMENDED_CONFIG)) {
274
+ if (!(id in config.lint.rules)) {
275
+ config.lint.rules[id] = severity;
276
+ }
277
+ }
271
278
  }
272
279
  } else {
273
280
  config.lint = {
274
281
  build: { enabled: true },
275
- rules: {},
282
+ rules: { ...RECOMMENDED_CONFIG },
276
283
  };
277
284
  }
278
285
  }
@@ -62,9 +62,9 @@ export interface Options {
62
62
  */
63
63
  function getMarkerLines(loc: NodeLocation, source: string[], opts: Options = {} as Options) {
64
64
  const startLoc = {
65
- // @ts-ignore this is fine
65
+ // @ts-expect-error this is fine
66
66
  column: 0,
67
- // @ts-ignore this is fine
67
+ // @ts-expect-error this is fine
68
68
  line: -1,
69
69
  ...loc.start,
70
70
  } as Location;
@@ -132,6 +132,9 @@ function getMarkerLines(loc: NodeLocation, source: string[], opts: Options = {}
132
132
  const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
133
133
 
134
134
  export function codeFrameColumns(rawLines: string, loc: NodeLocation, opts: Options = {} as Options) {
135
+ if (typeof rawLines !== 'string') {
136
+ throw new Error(`Expected string, got ${rawLines}`);
137
+ }
135
138
  const lines = rawLines.split(NEWLINE);
136
139
  const { start, end, markerLines } = getMarkerLines(loc, lines, opts);
137
140
  const hasColumns = loc.start && typeof loc.start.column === 'number';
@@ -0,0 +1,10 @@
1
+ import * as momoa from '@humanwhocodes/momoa';
2
+
3
+ /** Momoa’s default parser, with preferred settings. */
4
+ export function toMomoa(srcRaw: any): momoa.DocumentNode {
5
+ return momoa.parse(typeof srcRaw === 'string' ? srcRaw : JSON.stringify(srcRaw, undefined, 2), {
6
+ mode: 'jsonc',
7
+ ranges: true,
8
+ tokens: true,
9
+ });
10
+ }
package/src/lint/index.ts CHANGED
@@ -1,15 +1,15 @@
1
- import { pluralize, type TokenNormalized } from '@terrazzo/token-tools';
1
+ import { pluralize, type TokenNormalizedSet } from '@terrazzo/token-tools';
2
2
  import { merge } from 'merge-anything';
3
3
  import type { LogEntry, default as Logger } from '../logger.js';
4
- import type { ConfigInit } from '../types.js';
4
+ import type { ConfigInit, InputSource } from '../types.js';
5
5
 
6
- const listFormat = new Intl.ListFormat('en-us');
6
+ export { RECOMMENDED_CONFIG } from './plugin-core/index.js';
7
7
 
8
8
  export interface LintRunnerOptions {
9
- tokens: Record<string, TokenNormalized>;
9
+ tokens: TokenNormalizedSet;
10
10
  filename?: URL;
11
11
  config: ConfigInit;
12
- src: string;
12
+ sources: InputSource[];
13
13
  logger: Logger;
14
14
  }
15
15
 
@@ -17,30 +17,41 @@ export default async function lintRunner({
17
17
  tokens,
18
18
  filename,
19
19
  config = {} as ConfigInit,
20
- src,
20
+ sources,
21
21
  logger,
22
22
  }: LintRunnerOptions): Promise<void> {
23
23
  const { plugins = [], lint } = config;
24
+ const sourceByFilename: Record<string, InputSource> = {};
25
+ for (const source of sources) {
26
+ sourceByFilename[source.filename!.href] = source;
27
+ }
24
28
  const unusedLintRules = Object.keys(lint?.rules ?? {});
25
29
 
30
+ const errors: LogEntry[] = [];
31
+ const warnings: LogEntry[] = [];
26
32
  for (const plugin of plugins) {
27
33
  if (typeof plugin.lint === 'function') {
28
34
  const s = performance.now();
29
35
 
30
36
  const linter = plugin.lint();
31
- const errors: LogEntry[] = [];
32
- const warnings: LogEntry[] = [];
33
37
 
34
38
  await Promise.all(
35
39
  Object.entries(linter).map(async ([id, rule]) => {
36
40
  if (!(id in lint.rules) || lint.rules[id] === null) {
37
41
  return;
38
42
  }
43
+
44
+ // tick off used rule
45
+ const unusedLintRuleI = unusedLintRules.indexOf(id);
46
+ if (unusedLintRuleI !== -1) {
47
+ unusedLintRules.splice(unusedLintRuleI, 1);
48
+ }
49
+
39
50
  const [severity, options] = lint.rules[id]!;
51
+
40
52
  if (severity === 'off') {
41
53
  return;
42
54
  }
43
-
44
55
  // note: this usually isn’t a Promise, but it _might_ be!
45
56
  await rule.create({
46
57
  id,
@@ -86,50 +97,43 @@ export default async function lintRunner({
86
97
  message,
87
98
  filename,
88
99
  node: descriptor.node,
89
- src: descriptor.source?.src,
100
+ src: sourceByFilename[descriptor.filename!]?.src,
90
101
  });
91
102
  },
92
103
  tokens,
93
104
  filename,
94
- src,
105
+ sources,
95
106
  options: merge(
96
107
  rule.meta?.defaultOptions ?? [],
97
108
  rule.defaultOptions ?? [], // Note: is this the correct order to merge in?
98
109
  options,
99
110
  ),
100
111
  });
101
- // tick off used rule
102
- const unusedLintRuleI = unusedLintRules.indexOf(id);
103
- if (unusedLintRuleI !== -1) {
104
- unusedLintRules.splice(unusedLintRuleI, 1);
105
- }
106
112
  }),
107
113
  );
108
114
 
109
- for (const error of errors) {
110
- logger.error({ ...error, continueOnError: true }); // print out all errors before exiting here
111
- }
112
- for (const warning of warnings) {
113
- logger.warn(warning);
114
- }
115
-
116
- logger.debug({ group: 'lint', label: plugin.name, message: 'Finished', timing: performance.now() - s });
117
-
118
- if (errors.length) {
119
- const counts = [pluralize(errors.length, 'error', 'errors')];
120
- if (warnings.length) {
121
- counts.push(pluralize(warnings.length, 'warning', 'warnings'));
122
- }
123
- logger.error({
124
- group: 'lint',
125
- message: `Lint failed with ${listFormat.format(counts)}`,
126
- label: plugin.name,
127
- continueOnError: false,
128
- });
129
- }
115
+ logger.debug({
116
+ group: 'lint',
117
+ label: plugin.name,
118
+ message: 'Finished',
119
+ timing: performance.now() - s,
120
+ });
130
121
  }
131
122
  }
132
123
 
124
+ const errCount = errors.length ? `${errors.length} ${pluralize(errors.length, 'error', 'errors')}` : '';
125
+ const warnCount = warnings.length ? `${warnings.length} ${pluralize(warnings.length, 'warning', 'warnings')}` : '';
126
+ if (errors.length > 0) {
127
+ logger.error(...errors, {
128
+ group: 'lint',
129
+ label: 'lint',
130
+ message: [errCount, warnCount].filter(Boolean).join(', '),
131
+ });
132
+ }
133
+ if (warnings.length > 0) {
134
+ logger.warn(...warnings, { group: 'lint', label: 'lint', message: warnCount });
135
+ }
136
+
133
137
  // warn user if they have unused lint rules (they might have meant to configure something!)
134
138
  for (const unusedRule of unusedLintRules) {
135
139
  logger.warn({ group: 'lint', label: 'lint', message: `Unknown lint rule "${unusedRule}"` });
@@ -1,6 +1,5 @@
1
- // Terrazzo internal plugin that powers lint rules. Always enabled (but all
2
- // rules are opt-in).
3
- import type { Plugin } from '../../types.js';
1
+ // Terrazzo internal plugin that powers lint rules. Always enabled.
2
+ import type { LintRuleLonghand, Plugin } from '../../types.js';
4
3
 
5
4
  export * from './rules/a11y-min-contrast.js';
6
5
  export * from './rules/a11y-min-font-size.js';
@@ -9,6 +8,7 @@ export * from './rules/consistent-naming.js';
9
8
  export * from './rules/descriptions.js';
10
9
  export * from './rules/duplicate-values.js';
11
10
  export * from './rules/max-gamut.js';
11
+ export * from './rules/no-type-on-alias.js';
12
12
  export * from './rules/required-children.js';
13
13
  export * from './rules/required-modes.js';
14
14
  export * from './rules/required-typography-properties.js';
@@ -20,28 +20,85 @@ import consistentNaming, { CONSISTENT_NAMING } from './rules/consistent-naming.j
20
20
  import descriptions, { DESCRIPTIONS } from './rules/descriptions.js';
21
21
  import duplicateValues, { DUPLICATE_VALUES } from './rules/duplicate-values.js';
22
22
  import maxGamut, { MAX_GAMUT } from './rules/max-gamut.js';
23
- import requiredChidlren, { REQUIRED_CHILDREN } from './rules/required-children.js';
23
+ import noTypeOnAlias, { NO_TYPE_ON_ALIAS } from './rules/no-type-on-alias.js';
24
+ import requiredChildren, { REQUIRED_CHILDREN } from './rules/required-children.js';
24
25
  import requiredModes, { REQUIRED_MODES } from './rules/required-modes.js';
25
26
  import requiredTypographyProperties, {
26
27
  REQUIRED_TYPOGRAPHY_PROPERTIES,
27
28
  } from './rules/required-typography-properties.js';
29
+ import validBoolean, { VALID_BOOLEAN } from './rules/valid-boolean.js';
30
+ import validBorder, { VALID_BORDER } from './rules/valid-border.js';
31
+ import validColor, { VALID_COLOR } from './rules/valid-color.js';
32
+ import validCubicBezier, { VALID_CUBIC_BEZIER } from './rules/valid-cubic-bezier.js';
33
+ import validDimension, { VALID_DIMENSION } from './rules/valid-dimension.js';
34
+ import validDuration, { VALID_DURATION } from './rules/valid-duration.js';
35
+ import validFontFamily, { VALID_FONT_FAMILY } from './rules/valid-font-family.js';
36
+ import validFontWeight, { VALID_FONT_WEIGHT } from './rules/valid-font-weight.js';
37
+ import validGradient, { VALID_GRADIENT } from './rules/valid-gradient.js';
38
+ import validLink, { VALID_LINK } from './rules/valid-link.js';
39
+ import validNumber, { VALID_NUMBER } from './rules/valid-number.js';
40
+ import validShadow, { VALID_SHADOW } from './rules/valid-shadow.js';
41
+ import validString, { VALID_STRING } from './rules/valid-string.js';
42
+ import validStrokeStyle, { VALID_STROKE_STYLE } from './rules/valid-stroke-style.js';
43
+ import validTransition, { VALID_TRANSITION } from './rules/valid-transition.js';
44
+ import validTypography, { VALID_TYPOGRAPHY } from './rules/valid-typography.js';
45
+
46
+ const ALL_RULES = {
47
+ [VALID_COLOR]: validColor,
48
+ [VALID_DIMENSION]: validDimension,
49
+ [VALID_FONT_FAMILY]: validFontFamily,
50
+ [VALID_FONT_WEIGHT]: validFontWeight,
51
+ [VALID_DURATION]: validDuration,
52
+ [VALID_CUBIC_BEZIER]: validCubicBezier,
53
+ [VALID_NUMBER]: validNumber,
54
+ [VALID_LINK]: validLink,
55
+ [VALID_BOOLEAN]: validBoolean,
56
+ [VALID_STRING]: validString,
57
+ [VALID_STROKE_STYLE]: validStrokeStyle,
58
+ [VALID_BORDER]: validBorder,
59
+ [VALID_TRANSITION]: validTransition,
60
+ [VALID_SHADOW]: validShadow,
61
+ [VALID_GRADIENT]: validGradient,
62
+ [VALID_TYPOGRAPHY]: validTypography,
63
+ [COLORSPACE]: colorspace,
64
+ [CONSISTENT_NAMING]: consistentNaming,
65
+ [DESCRIPTIONS]: descriptions,
66
+ [DUPLICATE_VALUES]: duplicateValues,
67
+ [MAX_GAMUT]: maxGamut,
68
+ [NO_TYPE_ON_ALIAS]: noTypeOnAlias,
69
+ [REQUIRED_CHILDREN]: requiredChildren,
70
+ [REQUIRED_MODES]: requiredModes,
71
+ [REQUIRED_TYPOGRAPHY_PROPERTIES]: requiredTypographyProperties,
72
+ [A11Y_MIN_CONTRAST]: a11yMinContrast,
73
+ [A11Y_MIN_FONT_SIZE]: a11yMinFontSize,
74
+ };
28
75
 
29
76
  export default function coreLintPlugin(): Plugin {
30
77
  return {
31
78
  name: '@terrazzo/plugin-lint-core',
32
79
  lint() {
33
- return {
34
- [COLORSPACE]: colorspace,
35
- [CONSISTENT_NAMING]: consistentNaming,
36
- [DESCRIPTIONS]: descriptions,
37
- [DUPLICATE_VALUES]: duplicateValues,
38
- [MAX_GAMUT]: maxGamut,
39
- [REQUIRED_CHILDREN]: requiredChidlren,
40
- [REQUIRED_MODES]: requiredModes,
41
- [REQUIRED_TYPOGRAPHY_PROPERTIES]: requiredTypographyProperties,
42
- [A11Y_MIN_CONTRAST]: a11yMinContrast,
43
- [A11Y_MIN_FONT_SIZE]: a11yMinFontSize,
44
- };
80
+ return ALL_RULES;
45
81
  },
46
82
  };
47
83
  }
84
+
85
+ export const RECOMMENDED_CONFIG: Record<string, LintRuleLonghand> = {
86
+ [VALID_COLOR]: ['error', {}],
87
+ [VALID_DIMENSION]: ['error', {}],
88
+ [VALID_FONT_FAMILY]: ['error', {}],
89
+ [VALID_FONT_WEIGHT]: ['error', {}],
90
+ [VALID_DURATION]: ['error', {}],
91
+ [VALID_CUBIC_BEZIER]: ['error', {}],
92
+ [VALID_NUMBER]: ['error', {}],
93
+ [VALID_LINK]: ['error', {}],
94
+ [VALID_BOOLEAN]: ['error', {}],
95
+ [VALID_STRING]: ['error', {}],
96
+ [VALID_STROKE_STYLE]: ['error', {}],
97
+ [VALID_BORDER]: ['error', {}],
98
+ [VALID_TRANSITION]: ['error', {}],
99
+ [VALID_SHADOW]: ['error', {}],
100
+ [VALID_GRADIENT]: ['error', {}],
101
+ [VALID_TYPOGRAPHY]: ['error', {}],
102
+ [CONSISTENT_NAMING]: ['warn', { format: 'kebab-case' }],
103
+ [NO_TYPE_ON_ALIAS]: ['warn', {}],
104
+ };
@@ -58,6 +58,7 @@ const rule: LintRule<
58
58
  messageId: ERROR_COLOR,
59
59
  data: { id: t.id, colorSpace: options.colorSpace },
60
60
  node: t.source.node,
61
+ filename: t.source.filename,
61
62
  });
62
63
  }
63
64
  break;
@@ -68,6 +69,7 @@ const rule: LintRule<
68
69
  messageId: ERROR_BORDER,
69
70
  data: { id: t.id, colorSpace: options.colorSpace },
70
71
  node: t.source.node,
72
+ filename: t.source.filename,
71
73
  });
72
74
  }
73
75
  break;
@@ -79,6 +81,7 @@ const rule: LintRule<
79
81
  messageId: ERROR_GRADIENT,
80
82
  data: { id: t.id, colorSpace: options.colorSpace },
81
83
  node: t.source.node,
84
+ filename: t.source.filename,
82
85
  });
83
86
  }
84
87
  }
@@ -91,6 +94,7 @@ const rule: LintRule<
91
94
  messageId: ERROR_SHADOW,
92
95
  data: { id: t.id, colorSpace: options.colorSpace },
93
96
  node: t.source.node,
97
+ filename: t.source.filename,
94
98
  });
95
99
  }
96
100
  }
@@ -57,6 +57,7 @@ const rule: LintRule<typeof ERROR_DUPLICATE_VALUE, RuleDuplicateValueOptions> =
57
57
  messageId: ERROR_DUPLICATE_VALUE,
58
58
  data: { id: t.id },
59
59
  node: t.source.node,
60
+ filename: t.source.filename,
60
61
  });
61
62
  }
62
63
 
@@ -70,6 +71,7 @@ const rule: LintRule<typeof ERROR_DUPLICATE_VALUE, RuleDuplicateValueOptions> =
70
71
  messageId: ERROR_DUPLICATE_VALUE,
71
72
  data: { id: t.id },
72
73
  node: t.source.node,
74
+ filename: t.source.filename,
73
75
  });
74
76
  break;
75
77
  }
@@ -90,20 +90,35 @@ const rule: LintRule<
90
90
  switch (t.$type) {
91
91
  case 'color': {
92
92
  if (!isWithinGamut(t.$value, options.gamut)) {
93
- report({ messageId: ERROR_COLOR, data: { id: t.id, gamut: options.gamut }, node: t.source.node });
93
+ report({
94
+ messageId: ERROR_COLOR,
95
+ data: { id: t.id, gamut: options.gamut },
96
+ node: t.source.node,
97
+ filename: t.source.filename,
98
+ });
94
99
  }
95
100
  break;
96
101
  }
97
102
  case 'border': {
98
103
  if (!t.partialAliasOf?.color && !isWithinGamut(t.$value.color, options.gamut)) {
99
- report({ messageId: ERROR_BORDER, data: { id: t.id, gamut: options.gamut }, node: t.source.node });
104
+ report({
105
+ messageId: ERROR_BORDER,
106
+ data: { id: t.id, gamut: options.gamut },
107
+ node: t.source.node,
108
+ filename: t.source.filename,
109
+ });
100
110
  }
101
111
  break;
102
112
  }
103
113
  case 'gradient': {
104
114
  for (let stopI = 0; stopI < t.$value.length; stopI++) {
105
115
  if (!t.partialAliasOf?.[stopI]?.color && !isWithinGamut(t.$value[stopI]!.color, options.gamut)) {
106
- report({ messageId: ERROR_GRADIENT, data: { id: t.id, gamut: options.gamut }, node: t.source.node });
116
+ report({
117
+ messageId: ERROR_GRADIENT,
118
+ data: { id: t.id, gamut: options.gamut },
119
+ node: t.source.node,
120
+ filename: t.source.filename,
121
+ });
107
122
  }
108
123
  }
109
124
  break;
@@ -111,7 +126,12 @@ const rule: LintRule<
111
126
  case 'shadow': {
112
127
  for (let shadowI = 0; shadowI < t.$value.length; shadowI++) {
113
128
  if (!t.partialAliasOf?.[shadowI]?.color && !isWithinGamut(t.$value[shadowI]!.color, options.gamut)) {
114
- report({ messageId: ERROR_SHADOW, data: { id: t.id, gamut: options.gamut }, node: t.source.node });
129
+ report({
130
+ messageId: ERROR_SHADOW,
131
+ data: { id: t.id, gamut: options.gamut },
132
+ node: t.source.node,
133
+ filename: t.source.filename,
134
+ });
115
135
  }
116
136
  }
117
137
  break;
@@ -0,0 +1,29 @@
1
+ import { isAlias } from '@terrazzo/token-tools';
2
+ import type { LintRule } from '../../../types.js';
3
+ import { docsLink } from '../lib/docs.js';
4
+
5
+ export const NO_TYPE_ON_ALIAS = 'core/no-type-on-alias';
6
+
7
+ export const ERROR = 'ERROR';
8
+
9
+ const rule: LintRule<typeof ERROR> = {
10
+ meta: {
11
+ messages: {
12
+ [ERROR]: 'Remove $type from aliased value.',
13
+ },
14
+ docs: {
15
+ description: 'If a $value is aliased it already has a $type defined.',
16
+ url: docsLink(NO_TYPE_ON_ALIAS),
17
+ },
18
+ },
19
+ defaultOptions: {},
20
+ create({ tokens, report }) {
21
+ for (const t of Object.values(tokens)) {
22
+ if (isAlias(t.originalValue!.$value as any) && t.originalValue?.$type) {
23
+ report({ messageId: ERROR, node: t.source.node, filename: t.source.filename });
24
+ }
25
+ }
26
+ },
27
+ };
28
+
29
+ export default rule;
@@ -55,6 +55,7 @@ const rule: LintRule<never, RuleRequiredModesOptions> = {
55
55
  report({
56
56
  message: `Token ${t.id}: missing required mode "${mode}"`,
57
57
  node: t.source.node,
58
+ filename: t.source.filename,
58
59
  });
59
60
  }
60
61
  }
@@ -63,6 +64,7 @@ const rule: LintRule<never, RuleRequiredModesOptions> = {
63
64
  report({
64
65
  message: `Match "${matchI}": no tokens matched ${JSON.stringify(match)}`,
65
66
  node: t.source.node,
67
+ filename: t.source.filename,
66
68
  });
67
69
  }
68
70
  }