@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.
- package/CHANGELOG.md +6 -0
- package/dist/index.d.ts +82 -333
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2203 -3660
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
- package/src/build/index.ts +32 -41
- package/src/config.ts +13 -6
- package/src/lib/code-frame.ts +5 -2
- package/src/lib/momoa.ts +10 -0
- package/src/lint/index.ts +41 -37
- package/src/lint/plugin-core/index.ts +73 -16
- package/src/lint/plugin-core/rules/colorspace.ts +4 -0
- package/src/lint/plugin-core/rules/duplicate-values.ts +2 -0
- package/src/lint/plugin-core/rules/max-gamut.ts +24 -4
- package/src/lint/plugin-core/rules/no-type-on-alias.ts +29 -0
- package/src/lint/plugin-core/rules/required-modes.ts +2 -0
- package/src/lint/plugin-core/rules/required-typography-properties.ts +13 -3
- package/src/lint/plugin-core/rules/valid-boolean.ts +41 -0
- package/src/lint/plugin-core/rules/valid-border.ts +57 -0
- package/src/lint/plugin-core/rules/valid-color.ts +265 -0
- package/src/lint/plugin-core/rules/valid-cubic-bezier.ts +83 -0
- package/src/lint/plugin-core/rules/valid-dimension.ts +199 -0
- package/src/lint/plugin-core/rules/valid-duration.ts +123 -0
- package/src/lint/plugin-core/rules/valid-font-family.ts +68 -0
- package/src/lint/plugin-core/rules/valid-font-weight.ts +89 -0
- package/src/lint/plugin-core/rules/valid-gradient.ts +79 -0
- package/src/lint/plugin-core/rules/valid-link.ts +41 -0
- package/src/lint/plugin-core/rules/valid-number.ts +63 -0
- package/src/lint/plugin-core/rules/valid-shadow.ts +67 -0
- package/src/lint/plugin-core/rules/valid-string.ts +41 -0
- package/src/lint/plugin-core/rules/valid-stroke-style.ts +104 -0
- package/src/lint/plugin-core/rules/valid-transition.ts +61 -0
- package/src/lint/plugin-core/rules/valid-typography.ts +67 -0
- package/src/logger.ts +70 -59
- package/src/parse/index.ts +23 -318
- package/src/parse/load.ts +257 -0
- package/src/parse/normalize.ts +134 -170
- package/src/parse/token.ts +530 -0
- package/src/types.ts +76 -10
- package/src/parse/alias.ts +0 -369
- package/src/parse/json.ts +0 -211
- 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.
|
|
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.
|
|
37
|
+
"@humanwhocodes/momoa": "^3.3.9",
|
|
38
38
|
"@types/babel__code-frame": "^7.0.6",
|
|
39
|
-
"@types/culori": "^4.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.
|
|
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": "
|
|
49
|
+
"yaml-to-momoa": "0.0.6"
|
|
49
50
|
},
|
|
50
51
|
"scripts": {
|
|
51
52
|
"build": "rolldown -c && attw --profile esm-only --pack .",
|
package/src/build/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
}
|
package/src/lib/code-frame.ts
CHANGED
|
@@ -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-
|
|
65
|
+
// @ts-expect-error this is fine
|
|
66
66
|
column: 0,
|
|
67
|
-
// @ts-
|
|
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';
|
package/src/lib/momoa.ts
ADDED
|
@@ -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
|
|
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
|
-
|
|
6
|
+
export { RECOMMENDED_CONFIG } from './plugin-core/index.js';
|
|
7
7
|
|
|
8
8
|
export interface LintRunnerOptions {
|
|
9
|
-
tokens:
|
|
9
|
+
tokens: TokenNormalizedSet;
|
|
10
10
|
filename?: URL;
|
|
11
11
|
config: ConfigInit;
|
|
12
|
-
|
|
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
|
-
|
|
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.
|
|
100
|
+
src: sourceByFilename[descriptor.filename!]?.src,
|
|
90
101
|
});
|
|
91
102
|
},
|
|
92
103
|
tokens,
|
|
93
104
|
filename,
|
|
94
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
2
|
-
|
|
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
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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
|
}
|