@ismail-elkorchi/css-parser 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 +67 -69
- package/dist/internal/cssom/declarations.d.ts +36 -0
- package/dist/internal/cssom/declarations.js +177 -0
- package/dist/internal/generated/css-data.d.ts +2 -0
- package/dist/internal/generated/css-data.js +16229 -0
- package/dist/internal/grammar/catalog-types.d.ts +26 -0
- package/dist/internal/grammar/value-definition.d.ts +63 -0
- package/dist/internal/grammar/value-definition.js +439 -0
- package/dist/internal/properties/matcher.d.ts +29 -0
- package/dist/internal/properties/matcher.js +791 -0
- package/dist/internal/properties/registry.d.ts +24 -0
- package/dist/internal/properties/registry.js +52 -0
- package/dist/internal/selectors/matcher.d.ts +96 -0
- package/dist/internal/selectors/matcher.js +616 -0
- package/dist/internal/selectors/parser.d.ts +2 -0
- package/dist/internal/selectors/parser.js +702 -0
- package/dist/internal/selectors/specificity.d.ts +3 -0
- package/dist/internal/selectors/specificity.js +77 -0
- package/dist/internal/selectors/types.d.ts +110 -0
- package/dist/internal/syntax/ast.d.ts +70 -0
- package/dist/internal/syntax/ast.js +1 -0
- package/dist/internal/syntax/characters.d.ts +8 -0
- package/dist/internal/syntax/characters.js +45 -0
- package/dist/internal/syntax/encoding.d.ts +15 -0
- package/dist/internal/syntax/encoding.js +161 -0
- package/dist/internal/syntax/input.d.ts +23 -0
- package/dist/internal/syntax/input.js +184 -0
- package/dist/internal/syntax/parser.d.ts +26 -0
- package/dist/internal/syntax/parser.js +581 -0
- package/dist/internal/syntax/resources.d.ts +28 -0
- package/dist/internal/syntax/resources.js +135 -0
- package/dist/internal/syntax/serialize.d.ts +10 -0
- package/dist/internal/syntax/serialize.js +630 -0
- package/dist/internal/syntax/token-stream.d.ts +16 -0
- package/dist/internal/syntax/token-stream.js +66 -0
- package/dist/internal/syntax/tokenizer.d.ts +21 -0
- package/dist/internal/syntax/tokenizer.js +571 -0
- package/dist/internal/syntax/tokens.d.ts +115 -0
- package/dist/internal/syntax/tokens.js +1 -0
- package/dist/internal/syntax/types.d.ts +46 -0
- package/dist/internal/syntax/types.js +1 -0
- package/dist/mod.d.ts +7 -1
- package/dist/mod.js +7 -1
- package/dist/public/edits.d.ts +12 -0
- package/dist/public/edits.js +195 -0
- package/dist/public/mod.d.ts +13 -35
- package/dist/public/mod.js +12 -1740
- package/dist/public/parse.d.ts +37 -0
- package/dist/public/parse.js +298 -0
- package/dist/public/traversal.d.ts +13 -0
- package/dist/public/traversal.js +96 -0
- package/dist/public/types.d.ts +78 -264
- package/package.json +32 -53
- package/THIRD_PARTY_NOTICES.md +0 -19
- package/dist/internal/csstree-runtime.d.ts +0 -20
- package/dist/internal/csstree-runtime.js +0 -21
- package/dist/internal/encoding/mod.d.ts +0 -1
- package/dist/internal/encoding/mod.js +0 -1
- package/dist/internal/encoding/sniff.d.ts +0 -14
- package/dist/internal/encoding/sniff.js +0 -95
- package/dist/internal/serializer/mod.d.ts +0 -1
- package/dist/internal/serializer/mod.js +0 -1
- package/dist/internal/serializer/serialize.d.ts +0 -3
- package/dist/internal/serializer/serialize.js +0 -89
- package/dist/internal/tokenizer/mod.d.ts +0 -2
- package/dist/internal/tokenizer/mod.js +0 -1
- package/dist/internal/tokenizer/tokenize.d.ts +0 -2
- package/dist/internal/tokenizer/tokenize.js +0 -39
- package/dist/internal/tokenizer/tokens.d.ts +0 -23
- package/dist/internal/tree/build.d.ts +0 -2
- package/dist/internal/tree/build.js +0 -85
- package/dist/internal/tree/mod.d.ts +0 -2
- package/dist/internal/tree/mod.js +0 -1
- package/dist/internal/tree/types.d.ts +0 -25
- package/dist/internal/vendor/csstree/LICENSE +0 -19
- package/dist/internal/vendor/csstree/csstree.esm.js +0 -12
- package/dist/internal/version.d.ts +0 -1
- package/dist/internal/version.js +0 -1
- package/dist/public/index.d.ts +0 -1
- package/dist/public/index.js +0 -1
- /package/dist/internal/{tokenizer/tokens.js → grammar/catalog-types.js} +0 -0
- /package/dist/internal/{tree → selectors}/types.js +0 -0
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
import { isDigit, isIdent, isIdentStart, isNonAsciiIdent } from "./characters.js";
|
|
2
|
+
export class CssSerializationError extends TypeError {
|
|
3
|
+
code;
|
|
4
|
+
path;
|
|
5
|
+
constructor(code, path, message) {
|
|
6
|
+
super(`${message} at ${path}`);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.path = path;
|
|
9
|
+
this.name = "CssSerializationError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function fail(code, path, message) {
|
|
13
|
+
throw new CssSerializationError(code, path, message);
|
|
14
|
+
}
|
|
15
|
+
function record(value, path) {
|
|
16
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
17
|
+
fail("invalid-structure", path, "Expected an object");
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
function stringField(value, field, path, allowEmpty = false) {
|
|
22
|
+
const fieldValue = value[field];
|
|
23
|
+
if (typeof fieldValue !== "string" ||
|
|
24
|
+
(!allowEmpty && fieldValue.length === 0)) {
|
|
25
|
+
fail("invalid-structure", `${path}.${field}`, "Expected a non-empty string");
|
|
26
|
+
}
|
|
27
|
+
assertScalarString(fieldValue, `${path}.${field}`);
|
|
28
|
+
return fieldValue;
|
|
29
|
+
}
|
|
30
|
+
function booleanField(value, field, path) {
|
|
31
|
+
const fieldValue = value[field];
|
|
32
|
+
if (typeof fieldValue !== "boolean") {
|
|
33
|
+
fail("invalid-structure", `${path}.${field}`, "Expected a boolean");
|
|
34
|
+
}
|
|
35
|
+
return fieldValue;
|
|
36
|
+
}
|
|
37
|
+
function arrayField(value, field, path) {
|
|
38
|
+
const fieldValue = value[field];
|
|
39
|
+
if (!Array.isArray(fieldValue)) {
|
|
40
|
+
fail("invalid-structure", `${path}.${field}`, "Expected an array");
|
|
41
|
+
}
|
|
42
|
+
return fieldValue;
|
|
43
|
+
}
|
|
44
|
+
function assertScalarString(value, path) {
|
|
45
|
+
for (const character of value) {
|
|
46
|
+
const codePoint = character.codePointAt(0);
|
|
47
|
+
if (codePoint === undefined ||
|
|
48
|
+
codePoint === 0 ||
|
|
49
|
+
(codePoint >= 0xd800 && codePoint <= 0xdfff)) {
|
|
50
|
+
fail("invalid-structure", path, "Strings must contain non-null Unicode scalar values");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function assertPosition(value, path) {
|
|
55
|
+
const position = record(value, path);
|
|
56
|
+
for (const field of ["offset", "line", "column"]) {
|
|
57
|
+
const fieldValue = position[field];
|
|
58
|
+
const minimum = field === "offset" ? 0 : 1;
|
|
59
|
+
if (!Number.isSafeInteger(fieldValue) || fieldValue < minimum) {
|
|
60
|
+
fail("invalid-structure", `${path}.${field}`, "Expected a valid source coordinate");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function assertSpan(value, path) {
|
|
65
|
+
const span = record(value, path);
|
|
66
|
+
assertPosition(span.start, `${path}.start`);
|
|
67
|
+
assertPosition(span.end, `${path}.end`);
|
|
68
|
+
if (span.end.offset < span.start.offset) {
|
|
69
|
+
fail("invalid-structure", path, "A source span cannot end before it starts");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function enter(value, path, state) {
|
|
73
|
+
if (state.active.has(value)) {
|
|
74
|
+
fail("cyclic-structure", path, "CSS syntax structures must be acyclic");
|
|
75
|
+
}
|
|
76
|
+
if (state.seen.has(value)) {
|
|
77
|
+
fail("shared-structure", path, "CSS syntax structures must be trees, not shared graphs");
|
|
78
|
+
}
|
|
79
|
+
state.active.add(value);
|
|
80
|
+
state.seen.add(value);
|
|
81
|
+
}
|
|
82
|
+
function leave(value, state) {
|
|
83
|
+
state.active.delete(value);
|
|
84
|
+
}
|
|
85
|
+
function assertNode(value, path, state) {
|
|
86
|
+
const id = value.id;
|
|
87
|
+
if (!Number.isSafeInteger(id) || id <= 0) {
|
|
88
|
+
fail("invalid-structure", `${path}.id`, "Expected a positive safe node identifier");
|
|
89
|
+
}
|
|
90
|
+
if (state.nodeIds.has(id)) {
|
|
91
|
+
fail("duplicate-node-id", `${path}.id`, "Node identifiers must be unique");
|
|
92
|
+
}
|
|
93
|
+
state.nodeIds.add(id);
|
|
94
|
+
assertSpan(value.span, `${path}.span`);
|
|
95
|
+
}
|
|
96
|
+
function escapeHex(codePoint) {
|
|
97
|
+
return `\\${codePoint.toString(16)} `;
|
|
98
|
+
}
|
|
99
|
+
function serializeIdentifier(value, path) {
|
|
100
|
+
assertScalarString(value, path);
|
|
101
|
+
if (value.length === 0) {
|
|
102
|
+
fail("invalid-structure", path, "Identifiers cannot be empty");
|
|
103
|
+
}
|
|
104
|
+
const codePoints = Array.from(value, (character) => {
|
|
105
|
+
const codePoint = character.codePointAt(0);
|
|
106
|
+
if (codePoint === undefined)
|
|
107
|
+
fail("invalid-structure", path, "Invalid identifier code point");
|
|
108
|
+
return { character, codePoint };
|
|
109
|
+
});
|
|
110
|
+
if (codePoints.length === 1 && codePoints[0]?.codePoint === 0x2d)
|
|
111
|
+
return "\\-";
|
|
112
|
+
let result = "";
|
|
113
|
+
for (let index = 0; index < codePoints.length; index += 1) {
|
|
114
|
+
const entry = codePoints[index];
|
|
115
|
+
if (entry === undefined)
|
|
116
|
+
continue;
|
|
117
|
+
const { character, codePoint } = entry;
|
|
118
|
+
const mustEscapeAsHex = (index === 0 && isDigit(codePoint)) ||
|
|
119
|
+
(index === 1 &&
|
|
120
|
+
codePoints[0]?.codePoint === 0x2d &&
|
|
121
|
+
isDigit(codePoint)) ||
|
|
122
|
+
codePoint <= 0x1f ||
|
|
123
|
+
codePoint === 0x7f ||
|
|
124
|
+
(codePoint >= 0x80 && !isNonAsciiIdent(codePoint));
|
|
125
|
+
if (mustEscapeAsHex) {
|
|
126
|
+
result += escapeHex(codePoint);
|
|
127
|
+
}
|
|
128
|
+
else if (isIdent(codePoint)) {
|
|
129
|
+
result += character;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
result += `\\${character}`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
function serializeNameContinuation(value, path) {
|
|
138
|
+
assertScalarString(value, path);
|
|
139
|
+
let result = "";
|
|
140
|
+
for (const character of value) {
|
|
141
|
+
const codePoint = character.codePointAt(0);
|
|
142
|
+
if (codePoint === undefined)
|
|
143
|
+
fail("invalid-structure", path, "Invalid name code point");
|
|
144
|
+
if (codePoint <= 0x1f ||
|
|
145
|
+
codePoint === 0x7f ||
|
|
146
|
+
(codePoint >= 0x80 && !isNonAsciiIdent(codePoint))) {
|
|
147
|
+
result += escapeHex(codePoint);
|
|
148
|
+
}
|
|
149
|
+
else if (isIdent(codePoint)) {
|
|
150
|
+
result += character;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
result += `\\${character}`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
function serializeUnrestrictedHash(value, path) {
|
|
159
|
+
const codePoints = Array.from(value);
|
|
160
|
+
const first = codePoints.shift();
|
|
161
|
+
if (first === undefined)
|
|
162
|
+
fail("invalid-structure", path, "Hash values cannot be empty");
|
|
163
|
+
const firstCodePoint = first.codePointAt(0);
|
|
164
|
+
if (firstCodePoint === undefined)
|
|
165
|
+
fail("invalid-structure", path, "Invalid hash value");
|
|
166
|
+
if (isDigit(firstCodePoint)) {
|
|
167
|
+
return `${first}${serializeNameContinuation(codePoints.join(""), path)}`;
|
|
168
|
+
}
|
|
169
|
+
const second = codePoints.shift();
|
|
170
|
+
if (first !== "-" || second === undefined || !isDigit(second.codePointAt(0) ?? null)) {
|
|
171
|
+
fail("invalid-structure", path, "An unrestricted hash must begin with a digit or hyphen-digit");
|
|
172
|
+
}
|
|
173
|
+
return `-${second}${serializeNameContinuation(codePoints.join(""), path)}`;
|
|
174
|
+
}
|
|
175
|
+
function serializeDimensionUnit(value, path) {
|
|
176
|
+
if (/^[eE][+-]?\d/u.test(value)) {
|
|
177
|
+
const codePoints = Array.from(value);
|
|
178
|
+
const first = codePoints.shift();
|
|
179
|
+
const firstCodePoint = first?.codePointAt(0);
|
|
180
|
+
if (firstCodePoint === undefined) {
|
|
181
|
+
fail("invalid-structure", path, "Dimension units cannot be empty");
|
|
182
|
+
}
|
|
183
|
+
return `${escapeHex(firstCodePoint)}${serializeNameContinuation(codePoints.join(""), path)}`;
|
|
184
|
+
}
|
|
185
|
+
return serializeIdentifier(value, path);
|
|
186
|
+
}
|
|
187
|
+
function serializeString(value, path) {
|
|
188
|
+
assertScalarString(value, path);
|
|
189
|
+
let result = "\"";
|
|
190
|
+
for (const character of value) {
|
|
191
|
+
const codePoint = character.codePointAt(0);
|
|
192
|
+
if (codePoint === undefined)
|
|
193
|
+
fail("invalid-structure", path, "Invalid string code point");
|
|
194
|
+
if (character === "\"" || character === "\\") {
|
|
195
|
+
result += `\\${character}`;
|
|
196
|
+
}
|
|
197
|
+
else if (codePoint <= 0x1f || codePoint === 0x7f) {
|
|
198
|
+
result += escapeHex(codePoint);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
result += character;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return `${result}"`;
|
|
205
|
+
}
|
|
206
|
+
function serializeUrl(value, path) {
|
|
207
|
+
assertScalarString(value, path);
|
|
208
|
+
let result = "url(";
|
|
209
|
+
for (const character of value) {
|
|
210
|
+
const codePoint = character.codePointAt(0);
|
|
211
|
+
if (codePoint === undefined)
|
|
212
|
+
fail("invalid-structure", path, "Invalid URL code point");
|
|
213
|
+
if (character === "\"" ||
|
|
214
|
+
character === "'" ||
|
|
215
|
+
character === "(" ||
|
|
216
|
+
character === ")" ||
|
|
217
|
+
character === "\\" ||
|
|
218
|
+
codePoint <= 0x20 ||
|
|
219
|
+
codePoint === 0x7f) {
|
|
220
|
+
result += escapeHex(codePoint);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
result += character;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return `${result})`;
|
|
227
|
+
}
|
|
228
|
+
function validateNumericToken(token, path) {
|
|
229
|
+
const representation = stringField(token, "representation", path);
|
|
230
|
+
if (!/^[+-]?(?:\d*\.\d+|\d+)(?:[eE][+-]?\d+)?$/u.test(representation)) {
|
|
231
|
+
fail("invalid-structure", `${path}.representation`, "Expected a CSS number representation");
|
|
232
|
+
}
|
|
233
|
+
const numericValue = token.value;
|
|
234
|
+
if (typeof numericValue !== "number" || Number.isNaN(numericValue)) {
|
|
235
|
+
fail("invalid-structure", `${path}.value`, "Expected a numeric token value");
|
|
236
|
+
}
|
|
237
|
+
if (!Object.is(Number(representation), numericValue)) {
|
|
238
|
+
fail("invalid-structure", path, "Numeric value and representation disagree");
|
|
239
|
+
}
|
|
240
|
+
const numberType = token.numberType;
|
|
241
|
+
if (numberType !== "integer" &&
|
|
242
|
+
numberType !== "number") {
|
|
243
|
+
fail("invalid-structure", `${path}.numberType`, "Expected a numeric type flag");
|
|
244
|
+
}
|
|
245
|
+
const hasFractionOrExponent = /[.eE]/u.test(representation);
|
|
246
|
+
if ((numberType === "integer") === hasFractionOrExponent) {
|
|
247
|
+
fail("invalid-structure", path, "Numeric representation and type flag disagree");
|
|
248
|
+
}
|
|
249
|
+
const expectedSign = representation.startsWith("+")
|
|
250
|
+
? "+"
|
|
251
|
+
: representation.startsWith("-")
|
|
252
|
+
? "-"
|
|
253
|
+
: null;
|
|
254
|
+
if (token.sign !== expectedSign) {
|
|
255
|
+
fail("invalid-structure", `${path}.sign`, "Numeric sign metadata is inconsistent");
|
|
256
|
+
}
|
|
257
|
+
return representation;
|
|
258
|
+
}
|
|
259
|
+
function fragment(text, first, last = first) {
|
|
260
|
+
return Object.freeze({ text, first, last });
|
|
261
|
+
}
|
|
262
|
+
function columnClass(kind) {
|
|
263
|
+
return kind;
|
|
264
|
+
}
|
|
265
|
+
function rowClass(kind) {
|
|
266
|
+
return kind;
|
|
267
|
+
}
|
|
268
|
+
function needsComment(left, right) {
|
|
269
|
+
if (left === null || right === null)
|
|
270
|
+
return false;
|
|
271
|
+
const row = rowClass(left);
|
|
272
|
+
const column = columnClass(right);
|
|
273
|
+
const identColumns = new Set([
|
|
274
|
+
"ident",
|
|
275
|
+
"function",
|
|
276
|
+
"url",
|
|
277
|
+
"bad-url",
|
|
278
|
+
"delim-minus",
|
|
279
|
+
"number",
|
|
280
|
+
"percentage",
|
|
281
|
+
"dimension",
|
|
282
|
+
"cdc"
|
|
283
|
+
]);
|
|
284
|
+
if (row === "ident") {
|
|
285
|
+
return identColumns.has(column) || column === "open-paren";
|
|
286
|
+
}
|
|
287
|
+
if (row === "at-keyword" || row === "hash" || row === "dimension") {
|
|
288
|
+
return identColumns.has(column);
|
|
289
|
+
}
|
|
290
|
+
if (row === "number") {
|
|
291
|
+
return (column === "ident" ||
|
|
292
|
+
column === "function" ||
|
|
293
|
+
column === "url" ||
|
|
294
|
+
column === "bad-url" ||
|
|
295
|
+
column === "number" ||
|
|
296
|
+
column === "percentage" ||
|
|
297
|
+
column === "dimension" ||
|
|
298
|
+
column === "cdc" ||
|
|
299
|
+
column === "delim-percent");
|
|
300
|
+
}
|
|
301
|
+
if (row === "delim-hash" || row === "delim-minus") {
|
|
302
|
+
return identColumns.has(column);
|
|
303
|
+
}
|
|
304
|
+
if (row === "delim-at") {
|
|
305
|
+
return (column === "ident" ||
|
|
306
|
+
column === "function" ||
|
|
307
|
+
column === "url" ||
|
|
308
|
+
column === "bad-url" ||
|
|
309
|
+
column === "delim-minus" ||
|
|
310
|
+
column === "cdc");
|
|
311
|
+
}
|
|
312
|
+
if (row === "delim-dot" || row === "delim-plus") {
|
|
313
|
+
return column === "number" || column === "percentage" || column === "dimension";
|
|
314
|
+
}
|
|
315
|
+
if (row === "delim-less")
|
|
316
|
+
return true;
|
|
317
|
+
return row === "delim-slash" && column === "delim-star";
|
|
318
|
+
}
|
|
319
|
+
function joinFragments(parts) {
|
|
320
|
+
let text = "";
|
|
321
|
+
let first = null;
|
|
322
|
+
let last = null;
|
|
323
|
+
for (const part of parts) {
|
|
324
|
+
if (part.text.length === 0)
|
|
325
|
+
continue;
|
|
326
|
+
if (needsComment(last, part.first))
|
|
327
|
+
text += "/**/";
|
|
328
|
+
text += part.text;
|
|
329
|
+
first ??= part.first;
|
|
330
|
+
last = part.last;
|
|
331
|
+
}
|
|
332
|
+
return fragment(text, first, last);
|
|
333
|
+
}
|
|
334
|
+
function delimBoundary(value) {
|
|
335
|
+
switch (value) {
|
|
336
|
+
case 0x23:
|
|
337
|
+
return "delim-hash";
|
|
338
|
+
case 0x2d:
|
|
339
|
+
return "delim-minus";
|
|
340
|
+
case 0x40:
|
|
341
|
+
return "delim-at";
|
|
342
|
+
case 0x2e:
|
|
343
|
+
return "delim-dot";
|
|
344
|
+
case 0x2b:
|
|
345
|
+
return "delim-plus";
|
|
346
|
+
case 0x2f:
|
|
347
|
+
return "delim-slash";
|
|
348
|
+
case 0x2a:
|
|
349
|
+
return "delim-star";
|
|
350
|
+
case 0x25:
|
|
351
|
+
return "delim-percent";
|
|
352
|
+
case 0x3c:
|
|
353
|
+
return "delim-less";
|
|
354
|
+
default:
|
|
355
|
+
return "delim";
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function serializeToken(token, path) {
|
|
359
|
+
const kind = token.kind;
|
|
360
|
+
assertSpan(token.span, `${path}.span`);
|
|
361
|
+
switch (kind) {
|
|
362
|
+
case "ident":
|
|
363
|
+
return fragment(serializeIdentifier(stringField(token, "value", path), `${path}.value`), kind);
|
|
364
|
+
case "at-keyword":
|
|
365
|
+
return fragment(`@${serializeIdentifier(stringField(token, "value", path), `${path}.value`)}`, kind);
|
|
366
|
+
case "hash": {
|
|
367
|
+
const value = stringField(token, "value", path);
|
|
368
|
+
const hashType = token.hashType;
|
|
369
|
+
if (hashType !== "id" && hashType !== "unrestricted") {
|
|
370
|
+
fail("invalid-structure", `${path}.hashType`, "Expected a hash type flag");
|
|
371
|
+
}
|
|
372
|
+
if (hashType === "unrestricted" &&
|
|
373
|
+
!(/^\d/u.test(value) || /^-\d/u.test(value))) {
|
|
374
|
+
fail("invalid-structure", path, "An unrestricted hash must begin with a digit or hyphen-digit");
|
|
375
|
+
}
|
|
376
|
+
const serialized = hashType === "id"
|
|
377
|
+
? serializeIdentifier(value, `${path}.value`)
|
|
378
|
+
: serializeUnrestrictedHash(value, `${path}.value`);
|
|
379
|
+
return fragment(`#${serialized}`, kind);
|
|
380
|
+
}
|
|
381
|
+
case "string":
|
|
382
|
+
return fragment(serializeString(stringField(token, "value", path, true), `${path}.value`), kind);
|
|
383
|
+
case "bad-string":
|
|
384
|
+
fail("unserializable-token", path, "A bad-string token has no isolated round-trip serialization");
|
|
385
|
+
case "url":
|
|
386
|
+
return fragment(serializeUrl(stringField(token, "value", path, true), `${path}.value`), kind);
|
|
387
|
+
case "bad-url":
|
|
388
|
+
return fragment("url(()", kind);
|
|
389
|
+
case "delim": {
|
|
390
|
+
const value = token.value;
|
|
391
|
+
if (!Number.isSafeInteger(value) ||
|
|
392
|
+
value < 1 ||
|
|
393
|
+
value > 0x10ffff ||
|
|
394
|
+
(value >= 0xd800 && value <= 0xdfff)) {
|
|
395
|
+
fail("invalid-structure", `${path}.value`, "Expected a scalar delimiter code point");
|
|
396
|
+
}
|
|
397
|
+
const codePoint = value;
|
|
398
|
+
if (isIdentStart(codePoint) ||
|
|
399
|
+
isDigit(codePoint) ||
|
|
400
|
+
codePoint === 0x09 ||
|
|
401
|
+
codePoint === 0x0a ||
|
|
402
|
+
codePoint === 0x20 ||
|
|
403
|
+
codePoint === 0x22 ||
|
|
404
|
+
codePoint === 0x27 ||
|
|
405
|
+
codePoint === 0x28 ||
|
|
406
|
+
codePoint === 0x29 ||
|
|
407
|
+
codePoint === 0x2c ||
|
|
408
|
+
codePoint === 0x3a ||
|
|
409
|
+
codePoint === 0x3b ||
|
|
410
|
+
codePoint === 0x5b ||
|
|
411
|
+
codePoint === 0x5d ||
|
|
412
|
+
codePoint === 0x7b ||
|
|
413
|
+
codePoint === 0x7d) {
|
|
414
|
+
fail("invalid-structure", `${path}.value`, "The code point cannot form a delimiter token");
|
|
415
|
+
}
|
|
416
|
+
const text = codePoint === 0x5c ? "\\\n" : String.fromCodePoint(codePoint);
|
|
417
|
+
const boundary = delimBoundary(codePoint);
|
|
418
|
+
return fragment(text, boundary);
|
|
419
|
+
}
|
|
420
|
+
case "number":
|
|
421
|
+
return fragment(validateNumericToken(token, path), kind);
|
|
422
|
+
case "percentage":
|
|
423
|
+
return fragment(`${validateNumericToken(token, path)}%`, kind);
|
|
424
|
+
case "dimension": {
|
|
425
|
+
const number = validateNumericToken(token, path);
|
|
426
|
+
const unit = stringField(token, "unit", path);
|
|
427
|
+
return fragment(`${number}${serializeDimensionUnit(unit, `${path}.unit`)}`, kind);
|
|
428
|
+
}
|
|
429
|
+
case "unicode-range": {
|
|
430
|
+
const start = token.start;
|
|
431
|
+
const end = token.end;
|
|
432
|
+
if (!Number.isSafeInteger(start) ||
|
|
433
|
+
!Number.isSafeInteger(end) ||
|
|
434
|
+
start < 0 ||
|
|
435
|
+
end < 0 ||
|
|
436
|
+
start > 0xffffff ||
|
|
437
|
+
end > 0xffffff) {
|
|
438
|
+
fail("invalid-structure", path, "Expected six-digit Unicode range endpoints");
|
|
439
|
+
}
|
|
440
|
+
const first = start.toString(16).toUpperCase();
|
|
441
|
+
const last = end.toString(16).toUpperCase();
|
|
442
|
+
return fragment(`U+${first}${start === end ? "" : `-${last}`}`, kind);
|
|
443
|
+
}
|
|
444
|
+
case "whitespace":
|
|
445
|
+
return fragment(" ", kind);
|
|
446
|
+
case "cdo":
|
|
447
|
+
return fragment("<!--", kind);
|
|
448
|
+
case "cdc":
|
|
449
|
+
return fragment("-->", kind);
|
|
450
|
+
case "colon":
|
|
451
|
+
return fragment(":", kind);
|
|
452
|
+
case "semicolon":
|
|
453
|
+
return fragment(";", kind);
|
|
454
|
+
case "comma":
|
|
455
|
+
return fragment(",", kind);
|
|
456
|
+
case "close-square":
|
|
457
|
+
return fragment("]", kind);
|
|
458
|
+
case "close-paren":
|
|
459
|
+
return fragment(")", kind);
|
|
460
|
+
case "close-curly":
|
|
461
|
+
return fragment("}", kind);
|
|
462
|
+
default:
|
|
463
|
+
fail("invalid-structure", `${path}.kind`, "Unknown preserved token kind");
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function serializeArray(values, path, state, serializer) {
|
|
467
|
+
enter(values, path, state);
|
|
468
|
+
try {
|
|
469
|
+
return joinFragments(values.map((value, index) => serializer(value, `${path}[${String(index)}]`, state)));
|
|
470
|
+
}
|
|
471
|
+
finally {
|
|
472
|
+
leave(values, state);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function serializeComponent(value, path, state) {
|
|
476
|
+
const object = record(value, path);
|
|
477
|
+
enter(object, path, state);
|
|
478
|
+
try {
|
|
479
|
+
const kind = object.kind;
|
|
480
|
+
if (kind === "function-block") {
|
|
481
|
+
assertNode(object, path, state);
|
|
482
|
+
const name = stringField(object, "name", path);
|
|
483
|
+
const values = serializeArray(arrayField(object, "value", path), `${path}.value`, state, serializeComponent);
|
|
484
|
+
return fragment(`${serializeIdentifier(name, `${path}.name`)}(${values.text})`, "function", "close-paren");
|
|
485
|
+
}
|
|
486
|
+
if (kind === "simple-block") {
|
|
487
|
+
assertNode(object, path, state);
|
|
488
|
+
const associated = object.associatedToken;
|
|
489
|
+
if (associated !== "open-square" &&
|
|
490
|
+
associated !== "open-paren" &&
|
|
491
|
+
associated !== "open-curly") {
|
|
492
|
+
fail("invalid-structure", `${path}.associatedToken`, "Expected an opening token");
|
|
493
|
+
}
|
|
494
|
+
const delimiters = {
|
|
495
|
+
"open-square": ["[", "]"],
|
|
496
|
+
"open-paren": ["(", ")"],
|
|
497
|
+
"open-curly": ["{", "}"]
|
|
498
|
+
};
|
|
499
|
+
const values = serializeArray(arrayField(object, "value", path), `${path}.value`, state, serializeComponent);
|
|
500
|
+
const [open, close] = delimiters[associated];
|
|
501
|
+
const last = associated === "open-square"
|
|
502
|
+
? "close-square"
|
|
503
|
+
: associated === "open-paren"
|
|
504
|
+
? "close-paren"
|
|
505
|
+
: "close-curly";
|
|
506
|
+
return fragment(`${open}${values.text}${close}`, associated, last);
|
|
507
|
+
}
|
|
508
|
+
return serializeToken(object, path);
|
|
509
|
+
}
|
|
510
|
+
finally {
|
|
511
|
+
leave(object, state);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function serializeDeclaration(object, path, state) {
|
|
515
|
+
assertNode(object, path, state);
|
|
516
|
+
const name = stringField(object, "name", path);
|
|
517
|
+
if (object.originalText !== undefined) {
|
|
518
|
+
if (typeof object.originalText !== "string") {
|
|
519
|
+
fail("invalid-structure", `${path}.originalText`, "Expected original text to be a string");
|
|
520
|
+
}
|
|
521
|
+
assertScalarString(object.originalText, `${path}.originalText`);
|
|
522
|
+
}
|
|
523
|
+
const values = serializeArray(arrayField(object, "value", path), `${path}.value`, state, serializeComponent);
|
|
524
|
+
const important = booleanField(object, "important", path);
|
|
525
|
+
return fragment(`${serializeIdentifier(name, `${path}.name`)}:${values.text}${important ? "!important" : ""};`, "ident", "semicolon");
|
|
526
|
+
}
|
|
527
|
+
function serializeBlock(object, path, state) {
|
|
528
|
+
assertNode(object, path, state);
|
|
529
|
+
const items = serializeArray(arrayField(object, "items", path), `${path}.items`, state, serializeBlockItem);
|
|
530
|
+
return fragment(`{${items.text}}`, "open-curly", "close-curly");
|
|
531
|
+
}
|
|
532
|
+
function serializeAtRule(object, path, state) {
|
|
533
|
+
assertNode(object, path, state);
|
|
534
|
+
const name = stringField(object, "name", path);
|
|
535
|
+
const head = fragment(`@${serializeIdentifier(name, `${path}.name`)}`, "at-keyword");
|
|
536
|
+
const prelude = serializeArray(arrayField(object, "prelude", path), `${path}.prelude`, state, serializeComponent);
|
|
537
|
+
const block = object.block;
|
|
538
|
+
if (block === null) {
|
|
539
|
+
const joined = joinFragments([head, prelude]);
|
|
540
|
+
return fragment(`${joined.text};`, joined.first, "semicolon");
|
|
541
|
+
}
|
|
542
|
+
const serializedBlock = serializeUnknown(block, `${path}.block`, state);
|
|
543
|
+
return joinFragments([head, prelude, serializedBlock]);
|
|
544
|
+
}
|
|
545
|
+
function serializeQualifiedRule(object, path, state) {
|
|
546
|
+
assertNode(object, path, state);
|
|
547
|
+
const prelude = serializeArray(arrayField(object, "prelude", path), `${path}.prelude`, state, serializeComponent);
|
|
548
|
+
const block = serializeUnknown(object.block, `${path}.block`, state);
|
|
549
|
+
return joinFragments([prelude, block]);
|
|
550
|
+
}
|
|
551
|
+
function serializeRule(value, path, state) {
|
|
552
|
+
const object = record(value, path);
|
|
553
|
+
enter(object, path, state);
|
|
554
|
+
try {
|
|
555
|
+
if (object.kind === "at-rule")
|
|
556
|
+
return serializeAtRule(object, path, state);
|
|
557
|
+
if (object.kind === "qualified-rule") {
|
|
558
|
+
return serializeQualifiedRule(object, path, state);
|
|
559
|
+
}
|
|
560
|
+
fail("invalid-structure", `${path}.kind`, "Expected a CSS rule");
|
|
561
|
+
}
|
|
562
|
+
finally {
|
|
563
|
+
leave(object, state);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
function serializeBlockItem(value, path, state) {
|
|
567
|
+
const object = record(value, path);
|
|
568
|
+
if (object.kind === "declaration") {
|
|
569
|
+
enter(object, path, state);
|
|
570
|
+
try {
|
|
571
|
+
return serializeDeclaration(object, path, state);
|
|
572
|
+
}
|
|
573
|
+
finally {
|
|
574
|
+
leave(object, state);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return serializeRule(value, path, state);
|
|
578
|
+
}
|
|
579
|
+
function serializeUnknown(value, path, state) {
|
|
580
|
+
const object = record(value, path);
|
|
581
|
+
const kind = object.kind;
|
|
582
|
+
if (kind === "stylesheet") {
|
|
583
|
+
enter(object, path, state);
|
|
584
|
+
try {
|
|
585
|
+
assertNode(object, path, state);
|
|
586
|
+
return serializeArray(arrayField(object, "rules", path), `${path}.rules`, state, serializeRule);
|
|
587
|
+
}
|
|
588
|
+
finally {
|
|
589
|
+
leave(object, state);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (kind === "at-rule" || kind === "qualified-rule") {
|
|
593
|
+
return serializeRule(value, path, state);
|
|
594
|
+
}
|
|
595
|
+
if (kind === "block") {
|
|
596
|
+
enter(object, path, state);
|
|
597
|
+
try {
|
|
598
|
+
return serializeBlock(object, path, state);
|
|
599
|
+
}
|
|
600
|
+
finally {
|
|
601
|
+
leave(object, state);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (kind === "declaration") {
|
|
605
|
+
enter(object, path, state);
|
|
606
|
+
try {
|
|
607
|
+
return serializeDeclaration(object, path, state);
|
|
608
|
+
}
|
|
609
|
+
finally {
|
|
610
|
+
leave(object, state);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return serializeComponent(value, path, state);
|
|
614
|
+
}
|
|
615
|
+
export function serializeCssSyntax(value) {
|
|
616
|
+
const state = {
|
|
617
|
+
active: new WeakSet(),
|
|
618
|
+
seen: new WeakSet(),
|
|
619
|
+
nodeIds: new Set()
|
|
620
|
+
};
|
|
621
|
+
return serializeUnknown(value, "$", state).text;
|
|
622
|
+
}
|
|
623
|
+
export function serializeCssComponentValues(values) {
|
|
624
|
+
const state = {
|
|
625
|
+
active: new WeakSet(),
|
|
626
|
+
seen: new WeakSet(),
|
|
627
|
+
nodeIds: new Set()
|
|
628
|
+
};
|
|
629
|
+
return serializeArray(values, "$", state, serializeComponent).text;
|
|
630
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CssToken } from "./tokens.js";
|
|
2
|
+
import type { SourcePosition, SourceSpan } from "./types.js";
|
|
3
|
+
export declare class TokenStream {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(tokens: readonly CssToken[], end?: SourcePosition);
|
|
6
|
+
get index(): number;
|
|
7
|
+
get empty(): boolean;
|
|
8
|
+
get next(): CssToken;
|
|
9
|
+
consume(): CssToken;
|
|
10
|
+
discard(): void;
|
|
11
|
+
discardWhitespace(): void;
|
|
12
|
+
mark(): void;
|
|
13
|
+
restore(): void;
|
|
14
|
+
discardMark(): void;
|
|
15
|
+
spanFrom(startIndex: number): SourceSpan;
|
|
16
|
+
}
|