@ismail-elkorchi/css-parser 0.1.1 → 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 +64 -78
- 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 -69
- package/dist/public/mod.js +12 -1794
- 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 -56
- 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,37 @@
|
|
|
1
|
+
import type { CommaSeparatedComponentValuesResult, ComponentValue, ComponentValuesResult, CssBlockItem, CssDeclaration, CssRule, CssStylesheet, SyntaxResult } from "../internal/syntax/ast.js";
|
|
2
|
+
import type { SyntaxParserOptions } from "../internal/syntax/parser.js";
|
|
3
|
+
import type { TokenizationResult } from "../internal/syntax/tokens.js";
|
|
4
|
+
import type { TokenizerOptions } from "../internal/syntax/tokenizer.js";
|
|
5
|
+
import type { DecodedTokenizationResult, DecodedSyntaxResult, ParseStylesheetBytesOptions, ParseStylesheetStreamOptions, TokenizeBytesOptions, TokenizeStreamOptions } from "./types.js";
|
|
6
|
+
/** Thrown when a byte stream violates the public input contract or fails. */
|
|
7
|
+
export declare class CssStreamError extends Error {
|
|
8
|
+
readonly reason: "invalid-chunk" | "read-failed";
|
|
9
|
+
readonly code = "CSS_STREAM_ERROR";
|
|
10
|
+
constructor(reason: "invalid-chunk" | "read-failed", cause?: unknown);
|
|
11
|
+
}
|
|
12
|
+
/** Parses a complete CSS stylesheet. */
|
|
13
|
+
export declare function parseStylesheet(input: string, options?: SyntaxParserOptions): SyntaxResult<CssStylesheet>;
|
|
14
|
+
/** Parses a list of stylesheet rules. */
|
|
15
|
+
export declare function parseStylesheetContents(input: string, options?: SyntaxParserOptions): SyntaxResult<readonly CssRule[]>;
|
|
16
|
+
/** Parses declarations and nested rules from a block's contents. */
|
|
17
|
+
export declare function parseBlockContents(input: string, options?: SyntaxParserOptions): SyntaxResult<readonly CssBlockItem[]>;
|
|
18
|
+
/** Parses exactly one CSS rule. */
|
|
19
|
+
export declare function parseRule(input: string, options?: SyntaxParserOptions): SyntaxResult<CssRule>;
|
|
20
|
+
/** Parses one CSS declaration. */
|
|
21
|
+
export declare function parseDeclaration(input: string, options?: SyntaxParserOptions): SyntaxResult<CssDeclaration>;
|
|
22
|
+
/** Parses exactly one CSS component value. */
|
|
23
|
+
export declare function parseComponentValue(input: string, options?: SyntaxParserOptions): SyntaxResult<ComponentValue>;
|
|
24
|
+
/** Parses a list of CSS component values. */
|
|
25
|
+
export declare function parseComponentValues(input: string, options?: SyntaxParserOptions): ComponentValuesResult;
|
|
26
|
+
/** Parses comma-separated lists of CSS component values. */
|
|
27
|
+
export declare function parseCommaSeparatedComponentValues(input: string, options?: SyntaxParserOptions): CommaSeparatedComponentValuesResult;
|
|
28
|
+
/** Tokenizes a complete CSS string. */
|
|
29
|
+
export declare function tokenize(input: string, options?: TokenizerOptions): TokenizationResult;
|
|
30
|
+
/** Decodes and parses a complete CSS stylesheet byte array. */
|
|
31
|
+
export declare function parseStylesheetBytes(bytes: Uint8Array, options?: ParseStylesheetBytesOptions): DecodedSyntaxResult<CssStylesheet>;
|
|
32
|
+
/** Reads, decodes, and parses a complete CSS stylesheet byte stream. */
|
|
33
|
+
export declare function parseStylesheetStream(stream: ReadableStream<Uint8Array>, options?: ParseStylesheetStreamOptions): Promise<DecodedSyntaxResult<CssStylesheet>>;
|
|
34
|
+
/** Decodes and tokenizes a complete CSS byte array. */
|
|
35
|
+
export declare function tokenizeBytes(bytes: Uint8Array, options?: TokenizeBytesOptions): DecodedTokenizationResult;
|
|
36
|
+
/** Reads, decodes, and tokenizes a complete CSS byte stream. */
|
|
37
|
+
export declare function tokenizeStream(stream: ReadableStream<Uint8Array>, options?: TokenizeStreamOptions): Promise<DecodedTokenizationResult>;
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { CssEncodingSniffer, decodeCssBytes } from "../internal/syntax/encoding.js";
|
|
2
|
+
import { parseCssBlockContents, parseCssCommaSeparatedComponentValues, parseCssComponentValue, parseCssComponentValues, parseCssDeclaration, parseCssRule, parseCssStylesheet, parseCssStylesheetContents } from "../internal/syntax/parser.js";
|
|
3
|
+
import { ResourceGuard, SyntaxAbortError } from "../internal/syntax/resources.js";
|
|
4
|
+
import { tokenizeCss } from "../internal/syntax/tokenizer.js";
|
|
5
|
+
/** Thrown when a byte stream violates the public input contract or fails. */
|
|
6
|
+
export class CssStreamError extends Error {
|
|
7
|
+
reason;
|
|
8
|
+
code = "CSS_STREAM_ERROR";
|
|
9
|
+
constructor(reason, cause) {
|
|
10
|
+
super(reason === "invalid-chunk"
|
|
11
|
+
? "CSS byte streams must yield Uint8Array chunks"
|
|
12
|
+
: "Unable to read the CSS byte stream", cause === undefined ? undefined : { cause });
|
|
13
|
+
this.reason = reason;
|
|
14
|
+
this.name = "CssStreamError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function assertInput(input) {
|
|
18
|
+
if (typeof input !== "string") {
|
|
19
|
+
throw new TypeError("input must be a string");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function assertOptions(options) {
|
|
23
|
+
if (options === null ||
|
|
24
|
+
typeof options !== "object" ||
|
|
25
|
+
Array.isArray(options)) {
|
|
26
|
+
throw new TypeError("options must be an object");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Parses a complete CSS stylesheet. */
|
|
30
|
+
export function parseStylesheet(input, options = {}) {
|
|
31
|
+
assertInput(input);
|
|
32
|
+
assertOptions(options);
|
|
33
|
+
return parseCssStylesheet(input, options);
|
|
34
|
+
}
|
|
35
|
+
/** Parses a list of stylesheet rules. */
|
|
36
|
+
export function parseStylesheetContents(input, options = {}) {
|
|
37
|
+
assertInput(input);
|
|
38
|
+
assertOptions(options);
|
|
39
|
+
return parseCssStylesheetContents(input, options);
|
|
40
|
+
}
|
|
41
|
+
/** Parses declarations and nested rules from a block's contents. */
|
|
42
|
+
export function parseBlockContents(input, options = {}) {
|
|
43
|
+
assertInput(input);
|
|
44
|
+
assertOptions(options);
|
|
45
|
+
return parseCssBlockContents(input, options);
|
|
46
|
+
}
|
|
47
|
+
/** Parses exactly one CSS rule. */
|
|
48
|
+
export function parseRule(input, options = {}) {
|
|
49
|
+
assertInput(input);
|
|
50
|
+
assertOptions(options);
|
|
51
|
+
return parseCssRule(input, options);
|
|
52
|
+
}
|
|
53
|
+
/** Parses one CSS declaration. */
|
|
54
|
+
export function parseDeclaration(input, options = {}) {
|
|
55
|
+
assertInput(input);
|
|
56
|
+
assertOptions(options);
|
|
57
|
+
return parseCssDeclaration(input, options);
|
|
58
|
+
}
|
|
59
|
+
/** Parses exactly one CSS component value. */
|
|
60
|
+
export function parseComponentValue(input, options = {}) {
|
|
61
|
+
assertInput(input);
|
|
62
|
+
assertOptions(options);
|
|
63
|
+
return parseCssComponentValue(input, options);
|
|
64
|
+
}
|
|
65
|
+
/** Parses a list of CSS component values. */
|
|
66
|
+
export function parseComponentValues(input, options = {}) {
|
|
67
|
+
assertInput(input);
|
|
68
|
+
assertOptions(options);
|
|
69
|
+
return parseCssComponentValues(input, options);
|
|
70
|
+
}
|
|
71
|
+
/** Parses comma-separated lists of CSS component values. */
|
|
72
|
+
export function parseCommaSeparatedComponentValues(input, options = {}) {
|
|
73
|
+
assertInput(input);
|
|
74
|
+
assertOptions(options);
|
|
75
|
+
return parseCssCommaSeparatedComponentValues(input, options);
|
|
76
|
+
}
|
|
77
|
+
/** Tokenizes a complete CSS string. */
|
|
78
|
+
export function tokenize(input, options = {}) {
|
|
79
|
+
assertInput(input);
|
|
80
|
+
assertOptions(options);
|
|
81
|
+
return tokenizeCss(input, options);
|
|
82
|
+
}
|
|
83
|
+
function encodingOptions(options) {
|
|
84
|
+
return {
|
|
85
|
+
...(options.transportEncodingLabel === undefined
|
|
86
|
+
? {}
|
|
87
|
+
: { transportEncodingLabel: options.transportEncodingLabel }),
|
|
88
|
+
...(options.environmentEncodingLabel === undefined
|
|
89
|
+
? {}
|
|
90
|
+
: { environmentEncodingLabel: options.environmentEncodingLabel }),
|
|
91
|
+
...(options.defaultEncodingLabel === undefined
|
|
92
|
+
? {}
|
|
93
|
+
: { defaultEncodingLabel: options.defaultEncodingLabel }),
|
|
94
|
+
...(options.maxCharsetBytes === undefined
|
|
95
|
+
? {}
|
|
96
|
+
: { maxCharsetBytes: options.maxCharsetBytes })
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function processingLimits(limits) {
|
|
100
|
+
if (limits === undefined)
|
|
101
|
+
return {};
|
|
102
|
+
return {
|
|
103
|
+
...(limits.maxTokens === undefined ? {} : { maxTokens: limits.maxTokens }),
|
|
104
|
+
...(limits.maxNodes === undefined ? {} : { maxNodes: limits.maxNodes }),
|
|
105
|
+
...(limits.maxDepth === undefined ? {} : { maxDepth: limits.maxDepth }),
|
|
106
|
+
...(limits.maxSteps === undefined ? {} : { maxSteps: limits.maxSteps })
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function mergeUsage(usage, inputBytes, maxBufferedBytes) {
|
|
110
|
+
return Object.freeze({
|
|
111
|
+
...usage,
|
|
112
|
+
inputBytes,
|
|
113
|
+
maxBufferedBytes: Math.max(usage.maxBufferedBytes, maxBufferedBytes)
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function withEncoding(result, decision, inputBytes, maxBufferedBytes) {
|
|
117
|
+
return Object.freeze({
|
|
118
|
+
...result,
|
|
119
|
+
usage: mergeUsage(result.usage, inputBytes, maxBufferedBytes),
|
|
120
|
+
encoding: decision
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function combine(chunks, length) {
|
|
124
|
+
const bytes = new Uint8Array(length);
|
|
125
|
+
let offset = 0;
|
|
126
|
+
for (const chunk of chunks) {
|
|
127
|
+
bytes.set(chunk, offset);
|
|
128
|
+
offset += chunk.byteLength;
|
|
129
|
+
}
|
|
130
|
+
return bytes;
|
|
131
|
+
}
|
|
132
|
+
async function decodeStream(stream, options) {
|
|
133
|
+
const input = stream;
|
|
134
|
+
if (input === null ||
|
|
135
|
+
typeof input !== "object" ||
|
|
136
|
+
!("getReader" in input) ||
|
|
137
|
+
typeof input.getReader !== "function") {
|
|
138
|
+
throw new TypeError("stream must be a readable byte stream");
|
|
139
|
+
}
|
|
140
|
+
const guard = new ResourceGuard({
|
|
141
|
+
...(options.limits?.maxInputBytes === undefined
|
|
142
|
+
? {}
|
|
143
|
+
: { maxInputBytes: options.limits.maxInputBytes }),
|
|
144
|
+
...(options.limits?.maxBufferedBytes === undefined
|
|
145
|
+
? {}
|
|
146
|
+
: { maxBufferedBytes: options.limits.maxBufferedBytes })
|
|
147
|
+
}, options.signal);
|
|
148
|
+
const sniffer = new CssEncodingSniffer(encodingOptions(options));
|
|
149
|
+
const reader = stream.getReader();
|
|
150
|
+
const pending = [];
|
|
151
|
+
const decoded = [];
|
|
152
|
+
let pendingBytes = 0;
|
|
153
|
+
let totalBytes = 0;
|
|
154
|
+
let decoder = null;
|
|
155
|
+
let decision = null;
|
|
156
|
+
try {
|
|
157
|
+
for (;;) {
|
|
158
|
+
guard.assertActive();
|
|
159
|
+
let item;
|
|
160
|
+
try {
|
|
161
|
+
item = await reader.read();
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
throw new CssStreamError("read-failed", error);
|
|
165
|
+
}
|
|
166
|
+
guard.assertActive();
|
|
167
|
+
if (item.done)
|
|
168
|
+
break;
|
|
169
|
+
if (!(item.value instanceof Uint8Array)) {
|
|
170
|
+
throw new CssStreamError("invalid-chunk");
|
|
171
|
+
}
|
|
172
|
+
totalBytes += item.value.byteLength;
|
|
173
|
+
guard.setInputBytes(totalBytes);
|
|
174
|
+
if (decision === null) {
|
|
175
|
+
pending.push(item.value);
|
|
176
|
+
pendingBytes += item.value.byteLength;
|
|
177
|
+
guard.observeBufferedBytes(pendingBytes);
|
|
178
|
+
decision = sniffer.write(item.value);
|
|
179
|
+
if (decision !== null) {
|
|
180
|
+
decoder = new TextDecoder(decision.encoding);
|
|
181
|
+
const text = decoder.decode(combine(pending, pendingBytes), {
|
|
182
|
+
stream: true
|
|
183
|
+
});
|
|
184
|
+
if (text.length > 0)
|
|
185
|
+
decoded.push(text);
|
|
186
|
+
pending.length = 0;
|
|
187
|
+
pendingBytes = 0;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const text = decoder?.decode(item.value, { stream: true }) ?? "";
|
|
192
|
+
if (text.length > 0)
|
|
193
|
+
decoded.push(text);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (decision === null) {
|
|
197
|
+
decision = sniffer.finish();
|
|
198
|
+
decoder = new TextDecoder(decision.encoding);
|
|
199
|
+
const text = decoder.decode(combine(pending, pendingBytes), {
|
|
200
|
+
stream: true
|
|
201
|
+
});
|
|
202
|
+
if (text.length > 0)
|
|
203
|
+
decoded.push(text);
|
|
204
|
+
}
|
|
205
|
+
const tail = decoder?.decode() ?? "";
|
|
206
|
+
if (tail.length > 0)
|
|
207
|
+
decoded.push(tail);
|
|
208
|
+
const usage = guard.snapshot();
|
|
209
|
+
return Object.freeze({
|
|
210
|
+
text: decoded.join(""),
|
|
211
|
+
decision,
|
|
212
|
+
inputBytes: totalBytes,
|
|
213
|
+
maxBufferedBytes: usage.maxBufferedBytes
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
try {
|
|
218
|
+
await reader.cancel(error);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Cancellation is best effort after the primary failure.
|
|
222
|
+
}
|
|
223
|
+
if (options.signal?.aborted === true && !(error instanceof SyntaxAbortError)) {
|
|
224
|
+
throw new SyntaxAbortError(options.signal.reason);
|
|
225
|
+
}
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
reader.releaseLock();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function assertBytes(bytes) {
|
|
233
|
+
if (!(bytes instanceof Uint8Array)) {
|
|
234
|
+
throw new TypeError("bytes must be a Uint8Array");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/** Decodes and parses a complete CSS stylesheet byte array. */
|
|
238
|
+
export function parseStylesheetBytes(bytes, options = {}) {
|
|
239
|
+
assertBytes(bytes);
|
|
240
|
+
assertOptions(options);
|
|
241
|
+
const guard = new ResourceGuard({
|
|
242
|
+
...(options.limits?.maxInputBytes === undefined
|
|
243
|
+
? {}
|
|
244
|
+
: { maxInputBytes: options.limits.maxInputBytes })
|
|
245
|
+
}, options.signal);
|
|
246
|
+
guard.setInputBytes(bytes.byteLength);
|
|
247
|
+
const decoded = decodeCssBytes(bytes, encodingOptions(options));
|
|
248
|
+
const result = parseCssStylesheet(decoded.text, {
|
|
249
|
+
limits: processingLimits(options.limits),
|
|
250
|
+
...(options.signal === undefined ? {} : { signal: options.signal })
|
|
251
|
+
});
|
|
252
|
+
return withEncoding(result, decoded.decision, bytes.byteLength, 0);
|
|
253
|
+
}
|
|
254
|
+
/** Reads, decodes, and parses a complete CSS stylesheet byte stream. */
|
|
255
|
+
export async function parseStylesheetStream(stream, options = {}) {
|
|
256
|
+
assertOptions(options);
|
|
257
|
+
const decoded = await decodeStream(stream, options);
|
|
258
|
+
const result = parseCssStylesheet(decoded.text, {
|
|
259
|
+
limits: processingLimits(options.limits),
|
|
260
|
+
...(options.signal === undefined ? {} : { signal: options.signal })
|
|
261
|
+
});
|
|
262
|
+
return withEncoding(result, decoded.decision, decoded.inputBytes, decoded.maxBufferedBytes);
|
|
263
|
+
}
|
|
264
|
+
/** Decodes and tokenizes a complete CSS byte array. */
|
|
265
|
+
export function tokenizeBytes(bytes, options = {}) {
|
|
266
|
+
assertBytes(bytes);
|
|
267
|
+
assertOptions(options);
|
|
268
|
+
const guard = new ResourceGuard({
|
|
269
|
+
...(options.limits?.maxInputBytes === undefined
|
|
270
|
+
? {}
|
|
271
|
+
: { maxInputBytes: options.limits.maxInputBytes })
|
|
272
|
+
}, options.signal);
|
|
273
|
+
guard.setInputBytes(bytes.byteLength);
|
|
274
|
+
const decoded = decodeCssBytes(bytes, encodingOptions(options));
|
|
275
|
+
const result = tokenizeCss(decoded.text, {
|
|
276
|
+
limits: processingLimits(options.limits),
|
|
277
|
+
...(options.signal === undefined ? {} : { signal: options.signal })
|
|
278
|
+
});
|
|
279
|
+
return Object.freeze({
|
|
280
|
+
...result,
|
|
281
|
+
usage: mergeUsage(result.usage, bytes.byteLength, 0),
|
|
282
|
+
encoding: decoded.decision
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
/** Reads, decodes, and tokenizes a complete CSS byte stream. */
|
|
286
|
+
export async function tokenizeStream(stream, options = {}) {
|
|
287
|
+
assertOptions(options);
|
|
288
|
+
const decoded = await decodeStream(stream, options);
|
|
289
|
+
const result = tokenizeCss(decoded.text, {
|
|
290
|
+
limits: processingLimits(options.limits),
|
|
291
|
+
...(options.signal === undefined ? {} : { signal: options.signal })
|
|
292
|
+
});
|
|
293
|
+
return Object.freeze({
|
|
294
|
+
...result,
|
|
295
|
+
usage: mergeUsage(result.usage, decoded.inputBytes, decoded.maxBufferedBytes),
|
|
296
|
+
encoding: decoded.decision
|
|
297
|
+
});
|
|
298
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CssAstNode, CssAstNodeOfKind, CssNodeVisitor } from "./types.js";
|
|
2
|
+
/** Thrown when traversal receives a cyclic or shared syntax graph. */
|
|
3
|
+
export declare class CssTreeStructureError extends TypeError {
|
|
4
|
+
readonly reason: "cycle" | "shared-node";
|
|
5
|
+
readonly code = "CSS_INVALID_TREE";
|
|
6
|
+
constructor(reason: "cycle" | "shared-node");
|
|
7
|
+
}
|
|
8
|
+
/** Visits every structural node in deterministic depth-first order. */
|
|
9
|
+
export declare function walkCss(root: CssAstNode, visitor: CssNodeVisitor): void;
|
|
10
|
+
/** Finds a structural node by its tree-local identifier. */
|
|
11
|
+
export declare function findNodeById(root: CssAstNode, id: number): CssAstNode | null;
|
|
12
|
+
/** Returns structural nodes with an exact `kind` discriminator. */
|
|
13
|
+
export declare function findNodesByKind<K extends CssAstNode["kind"]>(root: CssAstNode, kind: K): readonly CssAstNodeOfKind<K>[];
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Thrown when traversal receives a cyclic or shared syntax graph. */
|
|
2
|
+
export class CssTreeStructureError extends TypeError {
|
|
3
|
+
reason;
|
|
4
|
+
code = "CSS_INVALID_TREE";
|
|
5
|
+
constructor(reason) {
|
|
6
|
+
super(reason === "cycle"
|
|
7
|
+
? "CSS syntax trees cannot contain cycles"
|
|
8
|
+
: "CSS syntax trees cannot contain shared structural nodes");
|
|
9
|
+
this.reason = reason;
|
|
10
|
+
this.name = "CssTreeStructureError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function structuralValues(values) {
|
|
14
|
+
return values.filter((value) => value.kind === "function-block" || value.kind === "simple-block");
|
|
15
|
+
}
|
|
16
|
+
function childNodes(node) {
|
|
17
|
+
switch (node.kind) {
|
|
18
|
+
case "stylesheet":
|
|
19
|
+
return node.rules;
|
|
20
|
+
case "at-rule":
|
|
21
|
+
return [
|
|
22
|
+
...structuralValues(node.prelude),
|
|
23
|
+
...(node.block === null ? [] : [node.block])
|
|
24
|
+
];
|
|
25
|
+
case "qualified-rule":
|
|
26
|
+
return [...structuralValues(node.prelude), node.block];
|
|
27
|
+
case "block":
|
|
28
|
+
return node.items;
|
|
29
|
+
case "declaration":
|
|
30
|
+
case "function-block":
|
|
31
|
+
case "simple-block":
|
|
32
|
+
return structuralValues(node.value);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Visits every structural node in deterministic depth-first order. */
|
|
36
|
+
export function walkCss(root, visitor) {
|
|
37
|
+
const active = new WeakSet();
|
|
38
|
+
const seen = new WeakSet();
|
|
39
|
+
const stack = [{
|
|
40
|
+
node: root,
|
|
41
|
+
parent: null,
|
|
42
|
+
depth: 0,
|
|
43
|
+
leaving: false
|
|
44
|
+
}];
|
|
45
|
+
while (stack.length > 0) {
|
|
46
|
+
const frame = stack.pop();
|
|
47
|
+
if (frame === undefined)
|
|
48
|
+
continue;
|
|
49
|
+
if (frame.leaving) {
|
|
50
|
+
active.delete(frame.node);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (active.has(frame.node))
|
|
54
|
+
throw new CssTreeStructureError("cycle");
|
|
55
|
+
if (seen.has(frame.node))
|
|
56
|
+
throw new CssTreeStructureError("shared-node");
|
|
57
|
+
active.add(frame.node);
|
|
58
|
+
seen.add(frame.node);
|
|
59
|
+
visitor(frame.node, frame.depth, frame.parent);
|
|
60
|
+
stack.push({ ...frame, leaving: true });
|
|
61
|
+
const children = childNodes(frame.node);
|
|
62
|
+
for (let index = children.length - 1; index >= 0; index -= 1) {
|
|
63
|
+
const child = children[index];
|
|
64
|
+
if (child !== undefined) {
|
|
65
|
+
stack.push({
|
|
66
|
+
node: child,
|
|
67
|
+
parent: frame.node,
|
|
68
|
+
depth: frame.depth + 1,
|
|
69
|
+
leaving: false
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Finds a structural node by its tree-local identifier. */
|
|
76
|
+
export function findNodeById(root, id) {
|
|
77
|
+
if (!Number.isSafeInteger(id) || id <= 0) {
|
|
78
|
+
throw new RangeError("id must be a positive safe integer");
|
|
79
|
+
}
|
|
80
|
+
let found = null;
|
|
81
|
+
walkCss(root, (node) => {
|
|
82
|
+
if (found === null && node.id === id)
|
|
83
|
+
found = node;
|
|
84
|
+
});
|
|
85
|
+
return found;
|
|
86
|
+
}
|
|
87
|
+
/** Returns structural nodes with an exact `kind` discriminator. */
|
|
88
|
+
export function findNodesByKind(root, kind) {
|
|
89
|
+
const found = [];
|
|
90
|
+
walkCss(root, (node) => {
|
|
91
|
+
if (node.kind === kind) {
|
|
92
|
+
found.push(node);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return Object.freeze(found);
|
|
96
|
+
}
|