@navecss/tokens 0.1.0 → 0.2.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 +1 -1
- package/dist/core-contract.json +1 -1
- package/dist/lib/bin.js +35 -6
- package/dist/lib/core-version.d.ts +5 -1
- package/dist/lib/core-version.js +7 -1
- package/dist/lib/facade.d.ts +5 -0
- package/dist/lib/facade.js +17 -11
- package/dist/lib/formats.js +27 -6
- package/dist/lib/out-path-conflict.d.ts +3 -0
- package/dist/lib/out-path-conflict.js +7 -0
- package/dist/lib/read-manifest.d.ts +2 -0
- package/dist/lib/read-manifest.js +17 -0
- package/dist/lib/reader.d.ts +6 -0
- package/dist/lib/reader.js +41 -41
- package/dist/lib/reserved-keys.d.ts +1 -0
- package/dist/lib/reserved-keys.js +18 -0
- package/dist/lib/resolve-value.d.ts +8 -0
- package/dist/lib/resolve-value.js +56 -0
- package/dist/lib/theming/comment-open-tracker.js +230 -9
- package/dist/lib/theming/core-contract.js +127 -4
- package/dist/lib/theming/guard-message-probes.js +18 -1
- package/dist/lib/theming/neutral.js +1 -1
- package/dist/lib/theming/seed-channel-split.js +8 -3
- package/dist/lib/validate-report.d.ts +1 -0
- package/dist/lib/validate-report.js +7 -3
- package/dist/lib/validate.js +17 -3
- package/dist/palette-record.json +52 -52
- package/dist/tokens.css +33 -33
- package/dist/tokens.d.ts +46 -2
- package/dist/tokens.js +5 -2
- package/package.json +2 -2
|
@@ -1,15 +1,37 @@
|
|
|
1
1
|
export function linesThatBeginInsideAnUnclosedComment(lines) {
|
|
2
2
|
const beginningsInsideAnUnclosedComment = new Set();
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
const readings = [
|
|
4
|
+
AS_THE_TOKENIZER_READS_IT,
|
|
5
|
+
RESUMING_PAST_AN_UNCLOSED_QUOTE,
|
|
6
|
+
RESUMING_WITH_NO_FURTHER_STRINGS,
|
|
7
|
+
];
|
|
8
|
+
for (const reading of readings) {
|
|
9
|
+
let isInsideAnOpenComment = false;
|
|
10
|
+
for (const [index, line] of lines.entries()) {
|
|
11
|
+
if (isInsideAnOpenComment)
|
|
12
|
+
beginningsInsideAnUnclosedComment.add(index);
|
|
13
|
+
isInsideAnOpenComment = isLineStillInsideAnOpenComment(line, isInsideAnOpenComment, reading);
|
|
14
|
+
}
|
|
8
15
|
}
|
|
16
|
+
markLinesFlaggedByTheUrlOpaqueReading(lines, beginningsInsideAnUnclosedComment);
|
|
17
|
+
markLinesFlaggedByTheEscapedLineBreakReading(lines, beginningsInsideAnUnclosedComment);
|
|
9
18
|
return beginningsInsideAnUnclosedComment;
|
|
10
19
|
}
|
|
11
|
-
|
|
20
|
+
const AS_THE_TOKENIZER_READS_IT = {
|
|
21
|
+
resumesAtEndOfLineAfterAnUnclosedQuote: true,
|
|
22
|
+
aLaterQuoteCanStillOpenAString: false,
|
|
23
|
+
};
|
|
24
|
+
const RESUMING_PAST_AN_UNCLOSED_QUOTE = {
|
|
25
|
+
resumesAtEndOfLineAfterAnUnclosedQuote: false,
|
|
26
|
+
aLaterQuoteCanStillOpenAString: true,
|
|
27
|
+
};
|
|
28
|
+
const RESUMING_WITH_NO_FURTHER_STRINGS = {
|
|
29
|
+
resumesAtEndOfLineAfterAnUnclosedQuote: false,
|
|
30
|
+
aLaterQuoteCanStillOpenAString: false,
|
|
31
|
+
};
|
|
32
|
+
function isLineStillInsideAnOpenComment(line, wasInsideAnOpenComment, reading) {
|
|
12
33
|
let isInsideAnOpenComment = wasInsideAnOpenComment;
|
|
34
|
+
let hasPassedAnUnclosedQuoteOnThisLine = false;
|
|
13
35
|
let cursor = 0;
|
|
14
36
|
while (cursor < line.length) {
|
|
15
37
|
if (isInsideAnOpenComment) {
|
|
@@ -21,8 +43,17 @@ function isLineStillInsideAnOpenCommentAfterScanning(line, wasInsideAnOpenCommen
|
|
|
21
43
|
continue;
|
|
22
44
|
}
|
|
23
45
|
const character = line[cursor];
|
|
24
|
-
|
|
25
|
-
|
|
46
|
+
const canThisQuoteOpenAString = isAQuoteCharacter(character) &&
|
|
47
|
+
(reading.aLaterQuoteCanStillOpenAString || !hasPassedAnUnclosedQuoteOnThisLine);
|
|
48
|
+
if (canThisQuoteOpenAString) {
|
|
49
|
+
const afterTheString = indexAfterAStringLiteral(line, cursor, character);
|
|
50
|
+
if (afterTheString === -1) {
|
|
51
|
+
hasPassedAnUnclosedQuoteOnThisLine = true;
|
|
52
|
+
cursor = reading.resumesAtEndOfLineAfterAnUnclosedQuote ? line.length : cursor + 1;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
cursor = afterTheString;
|
|
56
|
+
}
|
|
26
57
|
continue;
|
|
27
58
|
}
|
|
28
59
|
if (line.startsWith('/*', cursor)) {
|
|
@@ -34,6 +65,9 @@ function isLineStillInsideAnOpenCommentAfterScanning(line, wasInsideAnOpenCommen
|
|
|
34
65
|
}
|
|
35
66
|
return isInsideAnOpenComment;
|
|
36
67
|
}
|
|
68
|
+
function isAQuoteCharacter(character) {
|
|
69
|
+
return character === '"' || character === "'";
|
|
70
|
+
}
|
|
37
71
|
function indexAfterAStringLiteral(line, quoteIndex, quote) {
|
|
38
72
|
let cursor = quoteIndex + 1;
|
|
39
73
|
while (cursor < line.length) {
|
|
@@ -45,5 +79,192 @@ function indexAfterAStringLiteral(line, quoteIndex, quote) {
|
|
|
45
79
|
return cursor + 1;
|
|
46
80
|
cursor += 1;
|
|
47
81
|
}
|
|
48
|
-
return
|
|
82
|
+
return -1;
|
|
83
|
+
}
|
|
84
|
+
function markLinesFlaggedByTheUrlOpaqueReading(lines, into) {
|
|
85
|
+
let state = { isInsideAnOpenComment: false, urlCarry: 'none' };
|
|
86
|
+
for (const [index, line] of lines.entries()) {
|
|
87
|
+
if (state.isInsideAnOpenComment)
|
|
88
|
+
into.add(index);
|
|
89
|
+
state = scanLineWithOpaqueUrlContent(line, state);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function resumeUrlCarry(line, urlCarry) {
|
|
93
|
+
if (urlCarry === 'none')
|
|
94
|
+
return 0;
|
|
95
|
+
return urlCarry === 'pending' ? resolveUrlContent(line, 0) : indexAfterUnquotedUrlContent(line, 0);
|
|
96
|
+
}
|
|
97
|
+
function scanLineWithOpaqueUrlContent(line, state) {
|
|
98
|
+
const cursor = resumeUrlCarry(line, state.urlCarry);
|
|
99
|
+
if (typeof cursor === 'string')
|
|
100
|
+
return { isInsideAnOpenComment: false, urlCarry: cursor };
|
|
101
|
+
return scanLineForOpaqueUrlContentAndComments(line, state.isInsideAnOpenComment, cursor);
|
|
102
|
+
}
|
|
103
|
+
function scanLineForOpaqueUrlContentAndComments(line, wasInsideAnOpenComment, cursor) {
|
|
104
|
+
let isInsideAnOpenComment = wasInsideAnOpenComment;
|
|
105
|
+
let index = cursor;
|
|
106
|
+
while (index < line.length) {
|
|
107
|
+
if (isInsideAnOpenComment) {
|
|
108
|
+
const closerIndex = line.indexOf('*/', index);
|
|
109
|
+
if (closerIndex === -1)
|
|
110
|
+
return { isInsideAnOpenComment: true, urlCarry: 'none' };
|
|
111
|
+
isInsideAnOpenComment = false;
|
|
112
|
+
index = closerIndex + 2;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const urlLookup = tryToSkipAnUnquotedUrlToken(line, index);
|
|
116
|
+
if (typeof urlLookup === 'string')
|
|
117
|
+
return { isInsideAnOpenComment: false, urlCarry: urlLookup };
|
|
118
|
+
if (urlLookup !== undefined) {
|
|
119
|
+
index = urlLookup;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const character = line[index];
|
|
123
|
+
if (isAQuoteCharacter(character)) {
|
|
124
|
+
const afterTheString = indexAfterAStringLiteral(line, index, character);
|
|
125
|
+
index = afterTheString === -1 ? line.length : afterTheString;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (line.startsWith('/*', index)) {
|
|
129
|
+
isInsideAnOpenComment = true;
|
|
130
|
+
index += 2;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
index += 1;
|
|
134
|
+
}
|
|
135
|
+
return { isInsideAnOpenComment, urlCarry: 'none' };
|
|
136
|
+
}
|
|
137
|
+
function isAnIdentifierCharacter(character) {
|
|
138
|
+
return (character !== undefined && (/[A-Za-z0-9_-]/.test(character) || character.codePointAt(0) > 0x7f));
|
|
139
|
+
}
|
|
140
|
+
function tryToSkipAnUnquotedUrlToken(line, cursor) {
|
|
141
|
+
if (isAnIdentifierCharacter(line[cursor - 1]) || /[#@]/.test(line[cursor - 1]))
|
|
142
|
+
return undefined;
|
|
143
|
+
if (!/^url\(/i.test(line.slice(cursor, cursor + 4)))
|
|
144
|
+
return undefined;
|
|
145
|
+
return resolveUrlContent(line, cursor + 4);
|
|
146
|
+
}
|
|
147
|
+
function resolveUrlContent(line, cursor) {
|
|
148
|
+
let index = cursor;
|
|
149
|
+
while (index < line.length && /\s/.test(line[index]))
|
|
150
|
+
index += 1;
|
|
151
|
+
if (index === line.length)
|
|
152
|
+
return 'pending';
|
|
153
|
+
if (isAQuoteCharacter(line[index]))
|
|
154
|
+
return index;
|
|
155
|
+
return indexAfterUnquotedUrlContent(line, index);
|
|
156
|
+
}
|
|
157
|
+
function indexAfterUnquotedUrlContent(line, contentStart) {
|
|
158
|
+
let index = contentStart;
|
|
159
|
+
while (index < line.length) {
|
|
160
|
+
if (line[index] === '\\' && index + 1 < line.length) {
|
|
161
|
+
index += 2;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (line[index] === ')')
|
|
165
|
+
return index + 1;
|
|
166
|
+
index += 1;
|
|
167
|
+
}
|
|
168
|
+
return 'contentOpen';
|
|
169
|
+
}
|
|
170
|
+
function markLinesFlaggedByTheEscapedLineBreakReading(lines, into) {
|
|
171
|
+
let state = NOTHING_IS_OPEN;
|
|
172
|
+
for (const [index, line] of lines.entries()) {
|
|
173
|
+
if (state.isInsideAnOpenComment)
|
|
174
|
+
into.add(index);
|
|
175
|
+
state = scanLineAcrossEscapedLineBreaksAndUrls(line, state);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const NOTHING_IS_OPEN = {
|
|
179
|
+
isInsideAnOpenComment: false,
|
|
180
|
+
urlCarry: 'none',
|
|
181
|
+
openStringQuote: undefined,
|
|
182
|
+
};
|
|
183
|
+
const STILL_INSIDE_AN_OPEN_COMMENT = {
|
|
184
|
+
...NOTHING_IS_OPEN,
|
|
185
|
+
isInsideAnOpenComment: true,
|
|
186
|
+
};
|
|
187
|
+
function scanLineAcrossEscapedLineBreaksAndUrls(line, state) {
|
|
188
|
+
if (state.openStringQuote !== undefined) {
|
|
189
|
+
return resumeAnOpenStringCarriedIntoThisLine(line, state.isInsideAnOpenComment, state.openStringQuote);
|
|
190
|
+
}
|
|
191
|
+
const cursor = resumeUrlCarry(line, state.urlCarry);
|
|
192
|
+
if (typeof cursor === 'string')
|
|
193
|
+
return { ...NOTHING_IS_OPEN, urlCarry: cursor };
|
|
194
|
+
return scanFromStartOfLine(line, state.isInsideAnOpenComment, cursor);
|
|
195
|
+
}
|
|
196
|
+
function resumeAnOpenStringCarriedIntoThisLine(line, isInsideAnOpenComment, openStringQuote) {
|
|
197
|
+
const outcome = scanAPossiblyContinuedStringLiteral(line, -1, openStringQuote);
|
|
198
|
+
if (outcome.kind === 'continuesAcrossAnEscapedLineBreak') {
|
|
199
|
+
return { ...NOTHING_IS_OPEN, isInsideAnOpenComment, openStringQuote };
|
|
200
|
+
}
|
|
201
|
+
if (outcome.kind === 'doesNotClose') {
|
|
202
|
+
return { ...NOTHING_IS_OPEN, isInsideAnOpenComment };
|
|
203
|
+
}
|
|
204
|
+
return scanFromStartOfLine(line, isInsideAnOpenComment, outcome.indexAfter);
|
|
205
|
+
}
|
|
206
|
+
function stringStep(outcome, endOfLine) {
|
|
207
|
+
if (outcome.kind === 'closes')
|
|
208
|
+
return outcome.indexAfter;
|
|
209
|
+
if (outcome.kind === 'continuesAcrossAnEscapedLineBreak')
|
|
210
|
+
return undefined;
|
|
211
|
+
return endOfLine;
|
|
212
|
+
}
|
|
213
|
+
function scanFromStartOfLine(line, wasInsideAnOpenComment, cursor) {
|
|
214
|
+
let isInsideAnOpenComment = wasInsideAnOpenComment;
|
|
215
|
+
let index = cursor;
|
|
216
|
+
while (index < line.length) {
|
|
217
|
+
if (isInsideAnOpenComment) {
|
|
218
|
+
const closerIndex = line.indexOf('*/', index);
|
|
219
|
+
if (closerIndex === -1)
|
|
220
|
+
return STILL_INSIDE_AN_OPEN_COMMENT;
|
|
221
|
+
isInsideAnOpenComment = false;
|
|
222
|
+
index = closerIndex + 2;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const urlLookup = tryToSkipAnUnquotedUrlToken(line, index);
|
|
226
|
+
if (urlLookup !== undefined) {
|
|
227
|
+
if (typeof urlLookup === 'string')
|
|
228
|
+
return { ...NOTHING_IS_OPEN, urlCarry: urlLookup };
|
|
229
|
+
index = urlLookup;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
const character = line[index];
|
|
233
|
+
if (isAQuoteCharacter(character)) {
|
|
234
|
+
const outcome = scanAPossiblyContinuedStringLiteral(line, index, character);
|
|
235
|
+
const target = stringStep(outcome, line.length);
|
|
236
|
+
if (target === undefined)
|
|
237
|
+
return { ...NOTHING_IS_OPEN, openStringQuote: character };
|
|
238
|
+
index = target;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (line.startsWith('/*', index)) {
|
|
242
|
+
isInsideAnOpenComment = true;
|
|
243
|
+
index += 2;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
index += 1;
|
|
247
|
+
}
|
|
248
|
+
return { ...NOTHING_IS_OPEN, isInsideAnOpenComment };
|
|
249
|
+
}
|
|
250
|
+
function scanAPossiblyContinuedStringLiteral(line, quoteIndex, quote) {
|
|
251
|
+
let cursor = quoteIndex + 1;
|
|
252
|
+
while (cursor < line.length) {
|
|
253
|
+
if (line[cursor] === '\\') {
|
|
254
|
+
if (isAnEscapedLineBreakAtEndOfLine(line, cursor)) {
|
|
255
|
+
return { kind: 'continuesAcrossAnEscapedLineBreak' };
|
|
256
|
+
}
|
|
257
|
+
cursor += 2;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (line[cursor] === quote)
|
|
261
|
+
return { kind: 'closes', indexAfter: cursor + 1 };
|
|
262
|
+
cursor += 1;
|
|
263
|
+
}
|
|
264
|
+
return { kind: 'doesNotClose' };
|
|
265
|
+
}
|
|
266
|
+
function isAnEscapedLineBreakAtEndOfLine(line, cursor) {
|
|
267
|
+
if (cursor === line.length - 1)
|
|
268
|
+
return true;
|
|
269
|
+
return cursor === line.length - 2 && line[cursor + 1] === '\r';
|
|
49
270
|
}
|
|
@@ -1,13 +1,136 @@
|
|
|
1
1
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
const NAVE_VAR_PATTERN = /var\((--nave-[a-z0-9-]+)/g;
|
|
4
|
-
|
|
4
|
+
function isIdentifierChar(ch) {
|
|
5
|
+
return ch !== undefined && /[A-Za-z0-9_$-]/.test(ch);
|
|
6
|
+
}
|
|
7
|
+
function findSpanEnd(content, found, offset) {
|
|
8
|
+
return found === -1 ? content.length : found + offset;
|
|
9
|
+
}
|
|
10
|
+
function findUrlTokenEnd(content, start) {
|
|
11
|
+
let end = start;
|
|
12
|
+
while (end < content.length) {
|
|
13
|
+
const ch = content[end];
|
|
14
|
+
if (ch === '\\' && end + 1 < content.length && content[end + 1] !== '\n') {
|
|
15
|
+
end += 2;
|
|
16
|
+
}
|
|
17
|
+
else if (ch === ')') {
|
|
18
|
+
return end + 1;
|
|
19
|
+
}
|
|
20
|
+
else if (['\n', '"', "'"].includes(ch)) {
|
|
21
|
+
return end;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
end++;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return end;
|
|
28
|
+
}
|
|
29
|
+
function matchUrlToken(content, index) {
|
|
30
|
+
if (!/^url\(/i.test(content.slice(index, index + 4)))
|
|
31
|
+
return undefined;
|
|
32
|
+
if (isIdentifierChar(content[index - 1]))
|
|
33
|
+
return undefined;
|
|
34
|
+
let i = index + 4;
|
|
35
|
+
while (i < content.length && /\s/.test(content[i]))
|
|
36
|
+
i++;
|
|
37
|
+
if (content[i] === "'" || content[i] === '"')
|
|
38
|
+
return undefined;
|
|
39
|
+
return findUrlTokenEnd(content, index + 4);
|
|
40
|
+
}
|
|
41
|
+
function stepInString(content, index, stack) {
|
|
42
|
+
const frame = stack.at(-1);
|
|
43
|
+
const ch = content[index];
|
|
44
|
+
if (ch === '\\' && index + 1 < content.length) {
|
|
45
|
+
return { index: index + 2, out: ch + content[index + 1] };
|
|
46
|
+
}
|
|
47
|
+
if (ch === '\n' || ch === frame.quote)
|
|
48
|
+
stack.pop();
|
|
49
|
+
return { index: index + 1, out: ch };
|
|
50
|
+
}
|
|
51
|
+
function stepInTemplateText(content, index, stack) {
|
|
52
|
+
const ch = content[index];
|
|
53
|
+
if (ch === '\\' && index + 1 < content.length) {
|
|
54
|
+
return { index: index + 2, out: ch + content[index + 1] };
|
|
55
|
+
}
|
|
56
|
+
if (ch === '`') {
|
|
57
|
+
stack.pop();
|
|
58
|
+
return { index: index + 1, out: ch };
|
|
59
|
+
}
|
|
60
|
+
if (ch === '$' && content[index + 1] === '{') {
|
|
61
|
+
stack.push({ kind: 'interp', depth: 0 });
|
|
62
|
+
return { index: index + 2, out: '${' };
|
|
63
|
+
}
|
|
64
|
+
return { index: index + 1, out: ch };
|
|
65
|
+
}
|
|
66
|
+
function stepBraceInCode(ch, frame, stack) {
|
|
67
|
+
if (frame.kind !== 'interp')
|
|
68
|
+
return;
|
|
69
|
+
if (ch === '{')
|
|
70
|
+
frame.depth++;
|
|
71
|
+
else if (ch === '}' && frame.depth === 0)
|
|
72
|
+
stack.pop();
|
|
73
|
+
else if (ch === '}')
|
|
74
|
+
frame.depth--;
|
|
75
|
+
}
|
|
76
|
+
function didOpenQuoteOrTemplate(ch, stack) {
|
|
77
|
+
if (ch === '`') {
|
|
78
|
+
stack.push({ kind: 'template' });
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
if (ch === "'" || ch === '"') {
|
|
82
|
+
stack.push({ kind: 'string', quote: ch });
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
function trySkipCommentOrUrl(content, index) {
|
|
88
|
+
const ch = content[index];
|
|
89
|
+
if (ch === '/' && content[index + 1] === '*') {
|
|
90
|
+
return { index: findSpanEnd(content, content.indexOf('*/', index + 2), 2), out: '' };
|
|
91
|
+
}
|
|
92
|
+
if (ch === '/' && content[index + 1] === '/') {
|
|
93
|
+
return { index: findSpanEnd(content, content.indexOf('\n', index), 0), out: '' };
|
|
94
|
+
}
|
|
95
|
+
const urlEnd = matchUrlToken(content, index);
|
|
96
|
+
if (urlEnd !== undefined)
|
|
97
|
+
return { index: urlEnd, out: content.slice(index, urlEnd) };
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
function stepInCode(content, index, stack) {
|
|
101
|
+
const ch = content[index];
|
|
102
|
+
if (didOpenQuoteOrTemplate(ch, stack))
|
|
103
|
+
return { index: index + 1, out: ch };
|
|
104
|
+
const skipped = trySkipCommentOrUrl(content, index);
|
|
105
|
+
if (skipped)
|
|
106
|
+
return skipped;
|
|
107
|
+
stepBraceInCode(ch, stack.at(-1), stack);
|
|
108
|
+
return { index: index + 1, out: ch };
|
|
109
|
+
}
|
|
110
|
+
function step(content, index, stack) {
|
|
111
|
+
const frame = stack.at(-1);
|
|
112
|
+
if (frame.kind === 'string')
|
|
113
|
+
return stepInString(content, index, stack);
|
|
114
|
+
if (frame.kind === 'template')
|
|
115
|
+
return stepInTemplateText(content, index, stack);
|
|
116
|
+
return stepInCode(content, index, stack);
|
|
117
|
+
}
|
|
118
|
+
function stripComments(content) {
|
|
119
|
+
const stack = [{ kind: 'code' }];
|
|
120
|
+
let out = '';
|
|
121
|
+
let i = 0;
|
|
122
|
+
while (i < content.length) {
|
|
123
|
+
const result = step(content, i, stack);
|
|
124
|
+
out += result.out;
|
|
125
|
+
i = result.index;
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
5
129
|
const CONTRACT_AUDIT_PATTERN = /var\((--nave-[a-z0-9-]+)/g;
|
|
6
|
-
const CONTRACT_AUDIT_BLOCK_COMMENT_RE = /\/\*[\s\S]*?\*\//g;
|
|
7
130
|
export function scanCoreContract(fileContents) {
|
|
8
131
|
const found = new Set();
|
|
9
132
|
for (const content of fileContents) {
|
|
10
|
-
const stripped = content
|
|
133
|
+
const stripped = stripComments(content);
|
|
11
134
|
for (const match of stripped.matchAll(NAVE_VAR_PATTERN)) {
|
|
12
135
|
found.add(match[1]);
|
|
13
136
|
}
|
|
@@ -50,7 +173,7 @@ export function assertContractPreconditions(names, rawSourceText) {
|
|
|
50
173
|
if (unprefixed.length > 0) {
|
|
51
174
|
throw new StaleContractPreconditionError(`these scanned names are not yet prefixed --nave-: ${unprefixed.join(', ')}`);
|
|
52
175
|
}
|
|
53
|
-
const stripped = rawSourceText
|
|
176
|
+
const stripped = stripComments(rawSourceText);
|
|
54
177
|
const referenced = new Set(stripped.matchAll(CONTRACT_AUDIT_PATTERN).map((match) => match[1]));
|
|
55
178
|
const scanned = new Set(names);
|
|
56
179
|
const unscanned = referenced.difference(scanned);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ADJACENCY, assertNoOrphanedSemanticSlot } from "./adjacency.js";
|
|
1
|
+
import { ADJACENCY, assertCoverageFloor, assertNoFocusableAdjacentToActionFill, assertNoForbiddenAdjacency, assertNoOrphanedSemanticSlot, } from "./adjacency.js";
|
|
2
2
|
import { assertContrastFloors, assertFloorProvenance, assertNoSameStepViolations, checkSameStepLint, runContrastHarness, } from "./contrast.js";
|
|
3
3
|
import { assertNoticeIsEmitted, RETHEMING_NOTICE } from "./copy-lint.js";
|
|
4
4
|
import { TINT_SEED_COMMENT_STEM } from "./emit.js";
|
|
@@ -59,6 +59,23 @@ export const ACCESSIBILITY_GUARD_MESSAGE_PROBES = [
|
|
|
59
59
|
label: 'the orphaned-semantic-slot output',
|
|
60
60
|
capture: () => capturedFailureMessage(() => assertNoOrphanedSemanticSlot(['content.brandNew'])),
|
|
61
61
|
},
|
|
62
|
+
{
|
|
63
|
+
label: 'the adjacency coverage-floor output',
|
|
64
|
+
capture: () => capturedFailureMessage(() => assertCoverageFloor(ADJACENCY.filter((a) => a.subject !== 'border.control'))),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
label: 'the forbidden-adjacency output',
|
|
68
|
+
capture: () => capturedFailureMessage(() => assertNoForbiddenAdjacency([
|
|
69
|
+
{ subject: 'content.secondary', against: 'surface.inverse', class: 'text' },
|
|
70
|
+
])),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
label: 'the focusable-adjacent-to-action-fill output',
|
|
74
|
+
capture: () => capturedFailureMessage(() => assertNoFocusableAdjacentToActionFill([
|
|
75
|
+
...ADJACENCY,
|
|
76
|
+
{ subject: 'border.focus', against: 'action.primary', class: 'non-text' },
|
|
77
|
+
])),
|
|
78
|
+
},
|
|
62
79
|
{
|
|
63
80
|
label: "the same-step lint's real violation output (build-step.ts's runResultGuards)",
|
|
64
81
|
capture: () => {
|
|
@@ -68,7 +68,7 @@ export function neutralRcsFormula(step, literals) {
|
|
|
68
68
|
const found = literals.find((s) => s.step === step);
|
|
69
69
|
if (!found)
|
|
70
70
|
throw new Error(`Neutral step ${step} is not in the shared step table.`);
|
|
71
|
-
return `oklch(from var(--nave-color-tint) ${roundTo(found.l, 4)} ${roundTo(found.c, 4)} h)`;
|
|
71
|
+
return `oklch(from var(--nave-color-tint) ${roundTo(found.l, 4)} ${roundTo(found.c, 4)} h / 1)`;
|
|
72
72
|
}
|
|
73
73
|
function roundTo(n, d) {
|
|
74
74
|
const f = 10 ** d;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { clamp01 } from "./seed-refusal.js";
|
|
2
|
-
const
|
|
2
|
+
const CSS_WHITESPACE_CHARS = ' \t\n\r\f';
|
|
3
3
|
function cssTrim(text) {
|
|
4
|
-
|
|
4
|
+
let start = 0;
|
|
5
|
+
let end = text.length;
|
|
6
|
+
while (start < end && CSS_WHITESPACE_CHARS.includes(text[start]))
|
|
7
|
+
start += 1;
|
|
8
|
+
while (end > start && CSS_WHITESPACE_CHARS.includes(text[end - 1]))
|
|
9
|
+
end -= 1;
|
|
10
|
+
return text.slice(start, end);
|
|
5
11
|
}
|
|
6
12
|
const CSS_NUMBER = /^[+-]?(?:\d+(?:\.\d+)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
7
13
|
export function readNumericToken(text) {
|
|
@@ -22,7 +28,6 @@ function parseAlphaToken(token) {
|
|
|
22
28
|
return clamp01(readNumericToken(t.slice(0, -1)) / 100);
|
|
23
29
|
return clamp01(readNumericToken(t));
|
|
24
30
|
}
|
|
25
|
-
const CSS_WHITESPACE_CHARS = ' \t\n\r\f';
|
|
26
31
|
function splitTopLevel(text, isSeparator) {
|
|
27
32
|
const tokens = [];
|
|
28
33
|
let current = '';
|
|
@@ -14,6 +14,7 @@ export type CoreVersionReportStatus = {
|
|
|
14
14
|
message: string;
|
|
15
15
|
status: 'unreadable';
|
|
16
16
|
};
|
|
17
|
+
export declare function formatVersionSkewFact(producerName: string, versionSkew: ManifestVersionSkew): string;
|
|
17
18
|
export declare function formatValidateReport(manifest: CoreContractManifest, missing: readonly string[], versionSkew: ManifestVersionSkew | undefined, coreVersionStatus?: CoreVersionReportStatus & {
|
|
18
19
|
supplied?: readonly string[];
|
|
19
20
|
}): string[];
|
|
@@ -7,6 +7,11 @@ export function checkManifestFormatSupport(manifest) {
|
|
|
7
7
|
const NAMESPACE_PREAMBLE = 'Contract names below are the emitted CSS custom-property spelling (for example ' +
|
|
8
8
|
'--nave-color-surface-base). Your DTCG 2025.10 source names the same node with the "--nave-" ' +
|
|
9
9
|
'prefix dropped and hyphens read as a dotted path (color.surface.base).';
|
|
10
|
+
export function formatVersionSkewFact(producerName, versionSkew) {
|
|
11
|
+
return (`the core contract this @navecss/tokens ships was recorded against ` +
|
|
12
|
+
`${producerName}@${versionSkew.recorded}, but the installed ${producerName} is ` +
|
|
13
|
+
`@${versionSkew.installed}`);
|
|
14
|
+
}
|
|
10
15
|
const SCOPE_STATEMENT = 'This check is one-directional: it reports every core contract name missing from your ' +
|
|
11
16
|
'source. A token in your source beyond the contract is never an error.';
|
|
12
17
|
const NO_INSTALLED_CORE_DISCLAIMER = 'No installed @navecss/core was found, so no version-skew check ran.';
|
|
@@ -79,9 +84,8 @@ export function formatValidateReport(manifest, missing, versionSkew, coreVersion
|
|
|
79
84
|
}
|
|
80
85
|
if (versionSkew) {
|
|
81
86
|
return [
|
|
82
|
-
`Version skew:
|
|
83
|
-
|
|
84
|
-
`${versionSkew.installed}. Reinstall matching versions before trusting this result.`,
|
|
87
|
+
`Version skew: ${formatVersionSkewFact(manifest.producer.name, versionSkew)}. Reinstall ` +
|
|
88
|
+
`matching versions before trusting this result.`,
|
|
85
89
|
];
|
|
86
90
|
}
|
|
87
91
|
return [
|
package/dist/lib/validate.js
CHANGED
|
@@ -10,11 +10,25 @@ export function detectSourceKind(filePath) {
|
|
|
10
10
|
return 'json';
|
|
11
11
|
return undefined;
|
|
12
12
|
}
|
|
13
|
-
const DECLARED_CUSTOM_PROPERTY_RE = /(--[a-zA-Z0-9-]+)\s*:/
|
|
14
|
-
|
|
13
|
+
const DECLARED_CUSTOM_PROPERTY_RE = /(?<![\w\u{80}-\u{10FFFF}-])(--[a-zA-Z0-9-]+)\s*:/gu;
|
|
14
|
+
function stripCssComments(css) {
|
|
15
|
+
let kept = '';
|
|
16
|
+
let from = 0;
|
|
17
|
+
for (;;) {
|
|
18
|
+
const open = css.indexOf('/*', from);
|
|
19
|
+
if (open === -1)
|
|
20
|
+
break;
|
|
21
|
+
const close = css.indexOf('*/', open + 2);
|
|
22
|
+
if (close === -1)
|
|
23
|
+
break;
|
|
24
|
+
kept += css.slice(from, open);
|
|
25
|
+
from = close + 2;
|
|
26
|
+
}
|
|
27
|
+
return kept + css.slice(from);
|
|
28
|
+
}
|
|
15
29
|
export function scanDeclaredCustomProperties(css) {
|
|
16
30
|
const found = new Set();
|
|
17
|
-
const declarations = css
|
|
31
|
+
const declarations = stripCssComments(css);
|
|
18
32
|
for (const match of declarations.matchAll(DECLARED_CUSTOM_PROPERTY_RE))
|
|
19
33
|
found.add(match[1]);
|
|
20
34
|
return found;
|