@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,26 @@
|
|
|
1
|
+
export interface CssPropertyData {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly href: string;
|
|
4
|
+
readonly styleDeclaration: readonly string[];
|
|
5
|
+
readonly syntax?: string;
|
|
6
|
+
readonly legacyAliasOf?: string;
|
|
7
|
+
readonly longhands?: readonly string[];
|
|
8
|
+
readonly resetLonghands?: readonly string[];
|
|
9
|
+
readonly initial?: string;
|
|
10
|
+
readonly appliesTo?: string;
|
|
11
|
+
readonly inherited?: string;
|
|
12
|
+
readonly percentages?: string;
|
|
13
|
+
readonly computedValue?: string;
|
|
14
|
+
readonly animationType?: string;
|
|
15
|
+
readonly canonicalOrder?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface CssGrammarData {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly syntax?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface CssWebrefData {
|
|
22
|
+
readonly properties: readonly CssPropertyData[];
|
|
23
|
+
readonly functions: readonly CssGrammarData[];
|
|
24
|
+
readonly selectors: readonly string[];
|
|
25
|
+
readonly types: readonly CssGrammarData[];
|
|
26
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface GrammarSpan {
|
|
2
|
+
readonly start: number;
|
|
3
|
+
readonly end: number;
|
|
4
|
+
}
|
|
5
|
+
interface GrammarNodeBase {
|
|
6
|
+
readonly span: GrammarSpan;
|
|
7
|
+
}
|
|
8
|
+
export interface GrammarKeyword extends GrammarNodeBase {
|
|
9
|
+
readonly kind: "keyword";
|
|
10
|
+
readonly value: string;
|
|
11
|
+
}
|
|
12
|
+
export interface GrammarLiteral extends GrammarNodeBase {
|
|
13
|
+
readonly kind: "literal";
|
|
14
|
+
readonly value: string;
|
|
15
|
+
}
|
|
16
|
+
export interface GrammarRangeBoundary {
|
|
17
|
+
readonly value: number;
|
|
18
|
+
readonly unit: string | null;
|
|
19
|
+
}
|
|
20
|
+
export interface GrammarRange {
|
|
21
|
+
readonly minimum: GrammarRangeBoundary;
|
|
22
|
+
readonly maximum: GrammarRangeBoundary;
|
|
23
|
+
}
|
|
24
|
+
export type GrammarReferenceConstraint = {
|
|
25
|
+
readonly kind: "range";
|
|
26
|
+
readonly value: GrammarRange;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: "parameter";
|
|
29
|
+
readonly value: string;
|
|
30
|
+
};
|
|
31
|
+
export interface GrammarReference extends GrammarNodeBase {
|
|
32
|
+
readonly kind: "reference";
|
|
33
|
+
readonly name: string;
|
|
34
|
+
readonly referenceKind: "type" | "property" | "function";
|
|
35
|
+
readonly constraint: GrammarReferenceConstraint | null;
|
|
36
|
+
}
|
|
37
|
+
export interface GrammarFunction extends GrammarNodeBase {
|
|
38
|
+
readonly kind: "function";
|
|
39
|
+
readonly name: string;
|
|
40
|
+
readonly value: ValueDefinition | null;
|
|
41
|
+
}
|
|
42
|
+
export interface GrammarCombination extends GrammarNodeBase {
|
|
43
|
+
readonly kind: "sequence" | "all-of" | "any-of" | "one-of";
|
|
44
|
+
readonly values: readonly ValueDefinition[];
|
|
45
|
+
}
|
|
46
|
+
export interface GrammarMultiplier extends GrammarNodeBase {
|
|
47
|
+
readonly kind: "multiplier";
|
|
48
|
+
readonly value: ValueDefinition;
|
|
49
|
+
readonly minimum: number;
|
|
50
|
+
readonly maximum: number;
|
|
51
|
+
readonly separator: "space" | "comma";
|
|
52
|
+
}
|
|
53
|
+
export interface GrammarRequired extends GrammarNodeBase {
|
|
54
|
+
readonly kind: "required";
|
|
55
|
+
readonly value: ValueDefinition;
|
|
56
|
+
}
|
|
57
|
+
export type ValueDefinition = GrammarKeyword | GrammarLiteral | GrammarReference | GrammarFunction | GrammarCombination | GrammarMultiplier | GrammarRequired;
|
|
58
|
+
export declare class CssValueDefinitionSyntaxError extends SyntaxError {
|
|
59
|
+
readonly offset: number;
|
|
60
|
+
constructor(message: string, offset: number);
|
|
61
|
+
}
|
|
62
|
+
export declare function parseCssValueDefinition(source: string): ValueDefinition;
|
|
63
|
+
export {};
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
const INFINITY = Number.POSITIVE_INFINITY;
|
|
2
|
+
export class CssValueDefinitionSyntaxError extends SyntaxError {
|
|
3
|
+
offset;
|
|
4
|
+
constructor(message, offset) {
|
|
5
|
+
super(`${message} at offset ${String(offset)}.`);
|
|
6
|
+
this.name = "CssValueDefinitionSyntaxError";
|
|
7
|
+
this.offset = offset;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function freezeSpan(start, end) {
|
|
11
|
+
return Object.freeze({ start, end });
|
|
12
|
+
}
|
|
13
|
+
function freezeValues(values) {
|
|
14
|
+
return Object.freeze(values);
|
|
15
|
+
}
|
|
16
|
+
function boundary(value, unit) {
|
|
17
|
+
return Object.freeze({ value, unit });
|
|
18
|
+
}
|
|
19
|
+
class ValueDefinitionParser {
|
|
20
|
+
#source;
|
|
21
|
+
#offset = 0;
|
|
22
|
+
constructor(source) {
|
|
23
|
+
this.#source = source;
|
|
24
|
+
}
|
|
25
|
+
parse() {
|
|
26
|
+
this.#skipWhitespace();
|
|
27
|
+
if (this.#done) {
|
|
28
|
+
throw this.#error("A value definition cannot be empty");
|
|
29
|
+
}
|
|
30
|
+
const result = this.#parseOneOf(null);
|
|
31
|
+
this.#skipWhitespace();
|
|
32
|
+
if (this.#source.slice(this.#offset).length > 0) {
|
|
33
|
+
throw this.#error(`Unexpected ${JSON.stringify(this.#next)}`);
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
#parseOneOf(terminator) {
|
|
38
|
+
const start = this.#offset;
|
|
39
|
+
const values = [this.#parseAnyOf(terminator)];
|
|
40
|
+
while (this.#consumeCombinator("|", "||")) {
|
|
41
|
+
values.push(this.#parseAnyOf(terminator));
|
|
42
|
+
}
|
|
43
|
+
return this.#combine("one-of", values, start);
|
|
44
|
+
}
|
|
45
|
+
#parseAnyOf(terminator) {
|
|
46
|
+
const start = this.#offset;
|
|
47
|
+
const values = [this.#parseAllOf(terminator)];
|
|
48
|
+
while (this.#consumeExact("||")) {
|
|
49
|
+
values.push(this.#parseAllOf(terminator));
|
|
50
|
+
}
|
|
51
|
+
return this.#combine("any-of", values, start);
|
|
52
|
+
}
|
|
53
|
+
#parseAllOf(terminator) {
|
|
54
|
+
const start = this.#offset;
|
|
55
|
+
const values = [this.#parseSequence(terminator)];
|
|
56
|
+
while (this.#consumeExact("&&")) {
|
|
57
|
+
values.push(this.#parseSequence(terminator));
|
|
58
|
+
}
|
|
59
|
+
return this.#combine("all-of", values, start);
|
|
60
|
+
}
|
|
61
|
+
#parseSequence(terminator) {
|
|
62
|
+
const start = this.#offset;
|
|
63
|
+
const values = [];
|
|
64
|
+
while (this.#canStartTerm(terminator)) {
|
|
65
|
+
values.push(this.#parseTerm(terminator));
|
|
66
|
+
}
|
|
67
|
+
if (values.length === 0) {
|
|
68
|
+
throw this.#error("Expected a grammar component");
|
|
69
|
+
}
|
|
70
|
+
return this.#combine("sequence", values, start);
|
|
71
|
+
}
|
|
72
|
+
#parseTerm(terminator) {
|
|
73
|
+
let value = this.#parsePrimary(terminator);
|
|
74
|
+
let refinableCommaMultiplier = false;
|
|
75
|
+
for (;;) {
|
|
76
|
+
this.#skipWhitespace();
|
|
77
|
+
const start = value.span.start;
|
|
78
|
+
if (this.#consumeExact("*")) {
|
|
79
|
+
value = this.#multiplier(value, 0, INFINITY, "space", start);
|
|
80
|
+
refinableCommaMultiplier = false;
|
|
81
|
+
}
|
|
82
|
+
else if (this.#consumeExact("+")) {
|
|
83
|
+
value = this.#multiplier(value, 1, INFINITY, "space", start);
|
|
84
|
+
refinableCommaMultiplier = false;
|
|
85
|
+
}
|
|
86
|
+
else if (this.#consumeExact("?")) {
|
|
87
|
+
value = this.#multiplier(value, 0, 1, "space", start);
|
|
88
|
+
refinableCommaMultiplier = false;
|
|
89
|
+
}
|
|
90
|
+
else if (this.#consumeExact("#")) {
|
|
91
|
+
value = this.#multiplier(value, 1, INFINITY, "comma", start);
|
|
92
|
+
refinableCommaMultiplier = true;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const repetition = this.#consumeRepetition();
|
|
96
|
+
if (repetition !== null) {
|
|
97
|
+
if (refinableCommaMultiplier && value.kind === "multiplier") {
|
|
98
|
+
value = this.#multiplier(value.value, repetition.minimum, repetition.maximum, "comma", start);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
value = this.#multiplier(value, repetition.minimum, repetition.maximum, "space", start);
|
|
102
|
+
}
|
|
103
|
+
refinableCommaMultiplier = false;
|
|
104
|
+
}
|
|
105
|
+
else if (this.#consumeExact("!")) {
|
|
106
|
+
value = Object.freeze({
|
|
107
|
+
kind: "required",
|
|
108
|
+
value,
|
|
109
|
+
span: freezeSpan(start, this.#offset)
|
|
110
|
+
});
|
|
111
|
+
refinableCommaMultiplier = false;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
#parsePrimary(terminator) {
|
|
120
|
+
this.#skipWhitespace();
|
|
121
|
+
const start = this.#offset;
|
|
122
|
+
if (this.#consumeExact("[")) {
|
|
123
|
+
const value = this.#parseOneOf("]");
|
|
124
|
+
this.#skipWhitespace();
|
|
125
|
+
this.#expect("]");
|
|
126
|
+
return this.#withSpan(value, start, this.#offset);
|
|
127
|
+
}
|
|
128
|
+
if (this.#consumeExact("<")) {
|
|
129
|
+
return this.#consumeReference(start);
|
|
130
|
+
}
|
|
131
|
+
if (this.#next === "'") {
|
|
132
|
+
return this.#consumeQuotedLiteral();
|
|
133
|
+
}
|
|
134
|
+
if (this.#isLiteralPunctuation(this.#next)) {
|
|
135
|
+
this.#offset += 1;
|
|
136
|
+
return Object.freeze({
|
|
137
|
+
kind: "literal",
|
|
138
|
+
value: this.#source.slice(start, this.#offset),
|
|
139
|
+
span: freezeSpan(start, this.#offset)
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const name = this.#consumeWord(terminator);
|
|
143
|
+
if (name.length === 0) {
|
|
144
|
+
throw this.#error("Expected a grammar component");
|
|
145
|
+
}
|
|
146
|
+
if (this.#next === "(") {
|
|
147
|
+
this.#offset += 1;
|
|
148
|
+
this.#skipWhitespace();
|
|
149
|
+
let body = null;
|
|
150
|
+
if (!this.#at(")")) {
|
|
151
|
+
body = this.#parseOneOf(")");
|
|
152
|
+
this.#skipWhitespace();
|
|
153
|
+
}
|
|
154
|
+
this.#expect(")");
|
|
155
|
+
return Object.freeze({
|
|
156
|
+
kind: "function",
|
|
157
|
+
name,
|
|
158
|
+
value: body,
|
|
159
|
+
span: freezeSpan(start, this.#offset)
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return Object.freeze({
|
|
163
|
+
kind: "keyword",
|
|
164
|
+
value: name,
|
|
165
|
+
span: freezeSpan(start, this.#offset)
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
#consumeReference(start) {
|
|
169
|
+
let quote = null;
|
|
170
|
+
let nestedAngles = 0;
|
|
171
|
+
let content = "";
|
|
172
|
+
while (!this.#done) {
|
|
173
|
+
const character = this.#next;
|
|
174
|
+
if (character === "'" && quote === null) {
|
|
175
|
+
quote = "'";
|
|
176
|
+
}
|
|
177
|
+
else if (character === "'" && quote === "'") {
|
|
178
|
+
quote = null;
|
|
179
|
+
}
|
|
180
|
+
else if (quote === null) {
|
|
181
|
+
if (character === "<") {
|
|
182
|
+
nestedAngles += 1;
|
|
183
|
+
}
|
|
184
|
+
else if (character === ">") {
|
|
185
|
+
if (nestedAngles === 0)
|
|
186
|
+
break;
|
|
187
|
+
nestedAngles -= 1;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
content += character;
|
|
191
|
+
this.#offset += 1;
|
|
192
|
+
}
|
|
193
|
+
if (this.#done) {
|
|
194
|
+
throw this.#error("Unterminated type reference");
|
|
195
|
+
}
|
|
196
|
+
this.#offset += 1;
|
|
197
|
+
const trimmed = content.trim();
|
|
198
|
+
if (trimmed.length === 0) {
|
|
199
|
+
throw new CssValueDefinitionSyntaxError("A type reference cannot be empty", start);
|
|
200
|
+
}
|
|
201
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
202
|
+
const name = trimmed.slice(1, -1);
|
|
203
|
+
if (name.length === 0 || name.includes("'")) {
|
|
204
|
+
throw new CssValueDefinitionSyntaxError("Invalid property reference", start);
|
|
205
|
+
}
|
|
206
|
+
return Object.freeze({
|
|
207
|
+
kind: "reference",
|
|
208
|
+
name,
|
|
209
|
+
referenceKind: "property",
|
|
210
|
+
constraint: null,
|
|
211
|
+
span: freezeSpan(start, this.#offset)
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
const range = this.#splitConstraint(trimmed);
|
|
215
|
+
const name = range.name.endsWith("()") ? range.name.slice(0, -2) : range.name;
|
|
216
|
+
if (name.length === 0) {
|
|
217
|
+
throw new CssValueDefinitionSyntaxError("A type name cannot be empty", start);
|
|
218
|
+
}
|
|
219
|
+
return Object.freeze({
|
|
220
|
+
kind: "reference",
|
|
221
|
+
name,
|
|
222
|
+
referenceKind: range.name.endsWith("()") ? "function" : "type",
|
|
223
|
+
constraint: range.constraint,
|
|
224
|
+
span: freezeSpan(start, this.#offset)
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
#splitConstraint(value) {
|
|
228
|
+
if (!value.endsWith("]")) {
|
|
229
|
+
return { name: value, constraint: null };
|
|
230
|
+
}
|
|
231
|
+
const opening = value.indexOf("[");
|
|
232
|
+
if (opening < 1) {
|
|
233
|
+
return { name: value, constraint: null };
|
|
234
|
+
}
|
|
235
|
+
const name = value.slice(0, opening).trim();
|
|
236
|
+
const constraint = value.slice(opening + 1, -1).trim();
|
|
237
|
+
const comma = constraint.indexOf(",");
|
|
238
|
+
if (comma < 0) {
|
|
239
|
+
return {
|
|
240
|
+
name,
|
|
241
|
+
constraint: Object.freeze({ kind: "parameter", value: constraint })
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const minimum = this.#parseRangeBoundary(constraint.slice(0, comma).trim());
|
|
245
|
+
const maximum = this.#parseRangeBoundary(constraint.slice(comma + 1).trim());
|
|
246
|
+
return {
|
|
247
|
+
name,
|
|
248
|
+
constraint: Object.freeze({
|
|
249
|
+
kind: "range",
|
|
250
|
+
value: Object.freeze({ minimum, maximum })
|
|
251
|
+
})
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
#parseRangeBoundary(source) {
|
|
255
|
+
if (source === "∞") {
|
|
256
|
+
return boundary(INFINITY, null);
|
|
257
|
+
}
|
|
258
|
+
if (source === "-∞") {
|
|
259
|
+
return boundary(Number.NEGATIVE_INFINITY, null);
|
|
260
|
+
}
|
|
261
|
+
const match = /^([+-]?(?:\d+(?:\.\d*)?|\.\d+))([A-Za-z%]*)$/u.exec(source);
|
|
262
|
+
if (match === null) {
|
|
263
|
+
throw this.#error(`Invalid range boundary ${JSON.stringify(source)}`);
|
|
264
|
+
}
|
|
265
|
+
const numeric = match[1];
|
|
266
|
+
const unit = match[2];
|
|
267
|
+
if (numeric === undefined || unit === undefined) {
|
|
268
|
+
throw this.#error(`Invalid range boundary ${JSON.stringify(source)}`);
|
|
269
|
+
}
|
|
270
|
+
return boundary(Number(numeric), unit.length === 0 ? null : unit);
|
|
271
|
+
}
|
|
272
|
+
#consumeQuotedLiteral() {
|
|
273
|
+
const start = this.#offset;
|
|
274
|
+
this.#offset += 1;
|
|
275
|
+
const contentStart = this.#offset;
|
|
276
|
+
while (!this.#done && this.#next !== "'") {
|
|
277
|
+
this.#offset += 1;
|
|
278
|
+
}
|
|
279
|
+
if (this.#done) {
|
|
280
|
+
throw this.#error("Unterminated quoted literal");
|
|
281
|
+
}
|
|
282
|
+
const value = this.#source.slice(contentStart, this.#offset);
|
|
283
|
+
this.#offset += 1;
|
|
284
|
+
if (value.length === 0) {
|
|
285
|
+
throw new CssValueDefinitionSyntaxError("A quoted literal cannot be empty", start);
|
|
286
|
+
}
|
|
287
|
+
return Object.freeze({
|
|
288
|
+
kind: "literal",
|
|
289
|
+
value,
|
|
290
|
+
span: freezeSpan(start, this.#offset)
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
#consumeWord(terminator) {
|
|
294
|
+
const start = this.#offset;
|
|
295
|
+
while (!this.#done) {
|
|
296
|
+
const character = this.#next;
|
|
297
|
+
if (this.#isWhitespace(character) ||
|
|
298
|
+
character === "<" ||
|
|
299
|
+
character === "'" ||
|
|
300
|
+
character === "[" ||
|
|
301
|
+
character === "(" ||
|
|
302
|
+
character === "|" ||
|
|
303
|
+
character === "&" ||
|
|
304
|
+
character === "?" ||
|
|
305
|
+
character === "*" ||
|
|
306
|
+
character === "+" ||
|
|
307
|
+
character === "#" ||
|
|
308
|
+
character === "!" ||
|
|
309
|
+
character === "," ||
|
|
310
|
+
character === "/" ||
|
|
311
|
+
character === ":" ||
|
|
312
|
+
character === ";" ||
|
|
313
|
+
character === "{" ||
|
|
314
|
+
character === "}" ||
|
|
315
|
+
character === terminator) {
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
this.#offset += 1;
|
|
319
|
+
}
|
|
320
|
+
return this.#source.slice(start, this.#offset);
|
|
321
|
+
}
|
|
322
|
+
#consumeRepetition() {
|
|
323
|
+
this.#skipWhitespace();
|
|
324
|
+
const remainder = this.#source.slice(this.#offset);
|
|
325
|
+
const match = /^\{\s*(\d+)\s*(?:,\s*(\d*)\s*)?\}/u.exec(remainder);
|
|
326
|
+
if (match === null) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
const minimumText = match[1];
|
|
330
|
+
const maximumText = match[2];
|
|
331
|
+
if (minimumText === undefined) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
const minimum = Number(minimumText);
|
|
335
|
+
const maximum = maximumText === undefined
|
|
336
|
+
? minimum
|
|
337
|
+
: maximumText.length === 0
|
|
338
|
+
? INFINITY
|
|
339
|
+
: Number(maximumText);
|
|
340
|
+
if (minimum > maximum) {
|
|
341
|
+
throw this.#error("A repetition minimum cannot exceed its maximum");
|
|
342
|
+
}
|
|
343
|
+
this.#offset += match[0].length;
|
|
344
|
+
return { minimum, maximum };
|
|
345
|
+
}
|
|
346
|
+
#multiplier(value, minimum, maximum, separator, start) {
|
|
347
|
+
return Object.freeze({
|
|
348
|
+
kind: "multiplier",
|
|
349
|
+
value,
|
|
350
|
+
minimum,
|
|
351
|
+
maximum,
|
|
352
|
+
separator,
|
|
353
|
+
span: freezeSpan(start, this.#offset)
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
#combine(kind, values, start) {
|
|
357
|
+
if (values.length === 1) {
|
|
358
|
+
const value = values[0];
|
|
359
|
+
if (value === undefined) {
|
|
360
|
+
throw this.#error("Expected a grammar component");
|
|
361
|
+
}
|
|
362
|
+
return value;
|
|
363
|
+
}
|
|
364
|
+
return Object.freeze({
|
|
365
|
+
kind,
|
|
366
|
+
values: freezeValues(values),
|
|
367
|
+
span: freezeSpan(start, this.#offset)
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
#withSpan(value, start, end) {
|
|
371
|
+
return Object.freeze({ ...value, span: freezeSpan(start, end) });
|
|
372
|
+
}
|
|
373
|
+
#canStartTerm(terminator) {
|
|
374
|
+
this.#skipWhitespace();
|
|
375
|
+
if (this.#done || this.#next === terminator) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
if (this.#source.startsWith("&&", this.#offset)) {
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
return this.#next !== "|";
|
|
382
|
+
}
|
|
383
|
+
#consumeCombinator(single, longer) {
|
|
384
|
+
this.#skipWhitespace();
|
|
385
|
+
if (this.#source.startsWith(longer, this.#offset) ||
|
|
386
|
+
!this.#source.startsWith(single, this.#offset)) {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
this.#offset += single.length;
|
|
390
|
+
this.#skipWhitespace();
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
#consumeExact(value) {
|
|
394
|
+
this.#skipWhitespace();
|
|
395
|
+
if (!this.#source.startsWith(value, this.#offset)) {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
this.#offset += value.length;
|
|
399
|
+
this.#skipWhitespace();
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
#expect(value) {
|
|
403
|
+
if (!this.#consumeExact(value)) {
|
|
404
|
+
throw this.#error(`Expected ${JSON.stringify(value)}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
#at(value) {
|
|
408
|
+
return this.#next === value;
|
|
409
|
+
}
|
|
410
|
+
#isLiteralPunctuation(value) {
|
|
411
|
+
return value === "," ||
|
|
412
|
+
value === "/" ||
|
|
413
|
+
value === ":" ||
|
|
414
|
+
value === ";" ||
|
|
415
|
+
value === "{" ||
|
|
416
|
+
value === "}" ||
|
|
417
|
+
value === "(";
|
|
418
|
+
}
|
|
419
|
+
#skipWhitespace() {
|
|
420
|
+
while (!this.#done && this.#isWhitespace(this.#next)) {
|
|
421
|
+
this.#offset += 1;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
#isWhitespace(value) {
|
|
425
|
+
return value === " " || value === "\n" || value === "\r" || value === "\t";
|
|
426
|
+
}
|
|
427
|
+
#error(message) {
|
|
428
|
+
return new CssValueDefinitionSyntaxError(message, this.#offset);
|
|
429
|
+
}
|
|
430
|
+
get #done() {
|
|
431
|
+
return this.#offset >= this.#source.length;
|
|
432
|
+
}
|
|
433
|
+
get #next() {
|
|
434
|
+
return this.#source[this.#offset] ?? "";
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
export function parseCssValueDefinition(source) {
|
|
438
|
+
return new ValueDefinitionParser(source).parse();
|
|
439
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CssDeclaration } from "../syntax/ast.js";
|
|
2
|
+
import type { ResourceUsage } from "../syntax/types.js";
|
|
3
|
+
import type { CssPropertySemantics } from "./registry.js";
|
|
4
|
+
export interface PropertyValidationOptions {
|
|
5
|
+
readonly maxSteps?: number;
|
|
6
|
+
readonly signal?: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
interface PropertyValidationBase {
|
|
9
|
+
readonly property: CssPropertySemantics | null;
|
|
10
|
+
readonly usage: ResourceUsage;
|
|
11
|
+
}
|
|
12
|
+
export interface ValidPropertyValue extends PropertyValidationBase {
|
|
13
|
+
readonly status: "valid";
|
|
14
|
+
readonly property: CssPropertySemantics;
|
|
15
|
+
readonly valueKind: "custom" | "css-wide" | "grammar";
|
|
16
|
+
}
|
|
17
|
+
export interface InvalidPropertyValue extends PropertyValidationBase {
|
|
18
|
+
readonly status: "invalid";
|
|
19
|
+
readonly reason: "unknown-property" | "invalid-value";
|
|
20
|
+
}
|
|
21
|
+
export interface UnsupportedPropertyValue extends PropertyValidationBase {
|
|
22
|
+
readonly status: "unsupported";
|
|
23
|
+
readonly property: CssPropertySemantics;
|
|
24
|
+
readonly reason: "missing-property-syntax" | "unresolved-grammar" | "dynamic-custom-property-reference" | "arbitrary-substitution";
|
|
25
|
+
readonly unresolvedReferences: readonly string[];
|
|
26
|
+
}
|
|
27
|
+
export type PropertyValueValidation = ValidPropertyValue | InvalidPropertyValue | UnsupportedPropertyValue;
|
|
28
|
+
export declare function validateCssPropertyValue(declaration: CssDeclaration, options?: PropertyValidationOptions): PropertyValueValidation;
|
|
29
|
+
export {};
|