@jesscss/less-parser 2.0.0-alpha.6 → 2.0.0-alpha.7
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 +133 -5
- package/lib/builders.d.ts +381 -0
- package/lib/builders.d.ts.map +1 -0
- package/lib/cst.cjs +14 -0
- package/lib/cst.d.ts +6 -0
- package/lib/cst.d.ts.map +1 -0
- package/lib/cst.js +12 -0
- package/lib/functional-parser.cjs +2653 -0
- package/lib/functional-parser.d.ts +18 -0
- package/lib/functional-parser.d.ts.map +1 -0
- package/lib/functional-parser.js +2588 -0
- package/lib/grammar.cjs +30621 -0
- package/lib/grammar.d.ts +2 -0
- package/lib/grammar.d.ts.map +1 -0
- package/lib/grammar.js +30620 -0
- package/lib/index.cjs +17 -4096
- package/lib/index.d.ts +9 -149
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +6 -4091
- package/lib/jess.cjs +3968 -0
- package/lib/jess.d.ts +7 -0
- package/lib/jess.d.ts.map +1 -0
- package/lib/jess.js +3962 -0
- package/lib/lessParser.d.ts +38 -0
- package/lib/lessParser.d.ts.map +1 -0
- package/lib/lessRecursiveParser.d.ts +99 -0
- package/lib/lessRecursiveParser.d.ts.map +1 -0
- package/lib/lessTokens.d.ts +23 -0
- package/lib/lessTokens.d.ts.map +1 -0
- package/lib/productions/guards.d.ts +113 -0
- package/lib/productions/guards.d.ts.map +1 -0
- package/lib/productions/index.d.ts +5 -0
- package/lib/productions/index.d.ts.map +1 -0
- package/lib/productions/root.d.ts +38 -0
- package/lib/productions/root.d.ts.map +1 -0
- package/lib/productions/selectors.d.ts +41 -0
- package/lib/productions/selectors.d.ts.map +1 -0
- package/lib/productions/values.d.ts +35 -0
- package/lib/productions/values.d.ts.map +1 -0
- package/lib/utils.d.ts +9 -0
- package/lib/utils.d.ts.map +1 -0
- package/package.json +41 -10
- package/src/__tests__/debug-log.ts +35 -0
- package/src/__tests__/wall5-parse.test.ts +67 -0
- package/src/builders.ts +3183 -0
- package/src/cst.ts +25 -0
- package/src/functional-parser.ts +162 -0
- package/src/grammar.ts +869 -0
- package/src/index.ts +19 -0
- package/src/jess.ts +6 -0
- package/src/lessParser.ts +120 -0
- package/src/lessRecursiveParser.ts +279 -0
- package/src/lessTokens.ts +350 -0
- package/src/productions/guards.ts +1066 -0
- package/src/productions/index.ts +29 -0
- package/src/productions/root.ts +1613 -0
- package/src/productions/selectors.ts +1309 -0
- package/src/productions/values.ts +1449 -0
- package/src/utils.ts +178 -0
- package/lib/index.d.cts +0 -150
- package/lib/index.d.cts.map +0 -1
- package/lib/index.js.map +0 -1
|
@@ -0,0 +1,2588 @@
|
|
|
1
|
+
import { lessGrammar } from "./grammar.js";
|
|
2
|
+
import { LexerType, SKIPPED_LABEL, groupCapture, rawCssFragments, rawCssTokens } from "@jesscss/css-parser";
|
|
3
|
+
import { CssParser, buildLazyTriviaMap, runFunctionalParse, spannedComponents, toParseError } from "@jesscss/css-parser/jess";
|
|
4
|
+
import { Ampersand, Any, AtRule, AtRuleStatement, Block, Call, Color, ComplexSelector, CompoundSelector, Condition, CustomDeclaration, Declaration, DefaultGuard, Dimension, Expression, Extend, ExtendFlag, For, INTERPOLATION_PLACEHOLDER, Interpolated, InterpolatedSelector, JsImport, List, Mixin, N, NESTABLE_AT_RULES, Negative, Nil, Num, Operation, Paren, QueryCondition, Quoted, Reference, Rest, Rules, Ruleset, Sequence, StyleImport, Url, VarDeclaration, isNode, isSelectorListLike, nil, selectorListItems, shouldOperateWithMathFrames } from "@jesscss/core";
|
|
5
|
+
//#region src/lessTokens.ts
|
|
6
|
+
const AMPERSAND_TEMPLATE_CONTENTS_REGEX = /(?:[.#-]|\d)(?:[.#\w\u0080-\uffff-]|&)*|(?:[.#\w\u0080-\uffff-]|&)*&(?:[.#\w\u0080-\uffff-]|&)*/;
|
|
7
|
+
function $preBuildFragments() {
|
|
8
|
+
const fragments = rawCssFragments().map((f) => [...f]);
|
|
9
|
+
fragments.unshift(["lineComment", "\\/\\/[^\\n\\r]*"]);
|
|
10
|
+
fragments.push(["interpolated", "[@$]\\{(?:{{nmchar}}*)\\}"]);
|
|
11
|
+
return fragments;
|
|
12
|
+
}
|
|
13
|
+
function $preBuildTokens() {
|
|
14
|
+
const tokens = rawCssTokens();
|
|
15
|
+
/**
|
|
16
|
+
* Keyed by what to insert after
|
|
17
|
+
*
|
|
18
|
+
* @todo - Move merge utility to css-parser
|
|
19
|
+
*/
|
|
20
|
+
const merges = {
|
|
21
|
+
Assign: [{
|
|
22
|
+
name: "Ellipsis",
|
|
23
|
+
pattern: /\.\.\./,
|
|
24
|
+
categories: ["BlockMarker"]
|
|
25
|
+
}, (
|
|
26
|
+
/**
|
|
27
|
+
* Less's historical parser unfortunately allows
|
|
28
|
+
* at-keywords that are not valid in CSS. One is
|
|
29
|
+
* that Less allows at-keywords to begin with numbers.
|
|
30
|
+
* Another is that it allows an at-rule that only
|
|
31
|
+
* contains a single dash. So we capture this as
|
|
32
|
+
* a separate token.
|
|
33
|
+
*
|
|
34
|
+
* We also do this later in the token stack so that we
|
|
35
|
+
* don't accidentally grab something like
|
|
36
|
+
* @-webkit-keyframes while looking for @-.
|
|
37
|
+
*/
|
|
38
|
+
{
|
|
39
|
+
name: "AtKeywordLessExtension",
|
|
40
|
+
pattern: "@(?:-|\\d(?:{{nmchar}})*)",
|
|
41
|
+
categories: ["BlockMarker", "AtName"]
|
|
42
|
+
})],
|
|
43
|
+
PlainIdent: [
|
|
44
|
+
{
|
|
45
|
+
name: "Interpolated",
|
|
46
|
+
pattern: LexerType.NA
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "LineComment",
|
|
50
|
+
pattern: "{{lineComment}}",
|
|
51
|
+
label: SKIPPED_LABEL
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "PlusAssign",
|
|
55
|
+
pattern: "\\+{{whitespace}}*:",
|
|
56
|
+
categories: ["BlockMarker", "Assign"]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "UnderscoreAssign",
|
|
60
|
+
pattern: "\\+{{whitespace}}*_{{whitespace}}*:",
|
|
61
|
+
categories: ["BlockMarker", "Assign"]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "AnonMixinStart",
|
|
65
|
+
pattern: /[.#]\(/,
|
|
66
|
+
categories: ["BlockMarker"]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "GtEqAlias",
|
|
70
|
+
pattern: /=>/,
|
|
71
|
+
categories: ["CompareOperator"]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "LtEqAlias",
|
|
75
|
+
pattern: /=</,
|
|
76
|
+
categories: ["CompareOperator"]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "Extend",
|
|
80
|
+
pattern: /:extend\(/,
|
|
81
|
+
categories: ["BlockMarker"]
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "VarOrProp",
|
|
85
|
+
pattern: LexerType.NA
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "NestedReference",
|
|
89
|
+
pattern: ["([@$]+{{ident}}?){2,}", groupCapture],
|
|
90
|
+
start_chars_hint: ["@", "$"],
|
|
91
|
+
categories: ["VarOrProp"],
|
|
92
|
+
line_breaks: true
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "PropertyReference",
|
|
96
|
+
pattern: "\\${{ident}}",
|
|
97
|
+
categories: ["VarOrProp"]
|
|
98
|
+
},
|
|
99
|
+
(
|
|
100
|
+
/** Can be used in unit function or mod operation */
|
|
101
|
+
{
|
|
102
|
+
name: "Percent",
|
|
103
|
+
pattern: /%/
|
|
104
|
+
}),
|
|
105
|
+
{
|
|
106
|
+
name: "DefaultGuardIdent",
|
|
107
|
+
pattern: /default/,
|
|
108
|
+
longer_alt: "PlainIdent",
|
|
109
|
+
categories: ["Ident"]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "DefaultGuardFunc",
|
|
113
|
+
pattern: /default(?:\(\))/
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
Ampersand: [{
|
|
117
|
+
name: "AmpersandLParen",
|
|
118
|
+
pattern: /&\(/,
|
|
119
|
+
push_mode: "AmpersandTemplate",
|
|
120
|
+
categories: [
|
|
121
|
+
"Selector",
|
|
122
|
+
"NestedRuleStart",
|
|
123
|
+
"BlockMarker"
|
|
124
|
+
]
|
|
125
|
+
}, {
|
|
126
|
+
name: "AllFlag",
|
|
127
|
+
pattern: /!all/,
|
|
128
|
+
categories: ["BlockMarker"]
|
|
129
|
+
}],
|
|
130
|
+
UrlStart: [
|
|
131
|
+
(
|
|
132
|
+
/**
|
|
133
|
+
* Keywords that we don't identify as idents
|
|
134
|
+
* should be manually added to other places where an ident is valid.
|
|
135
|
+
*/
|
|
136
|
+
{
|
|
137
|
+
name: "When",
|
|
138
|
+
pattern: /when/i,
|
|
139
|
+
longer_alt: "PlainIdent",
|
|
140
|
+
categories: ["BlockMarker"]
|
|
141
|
+
}),
|
|
142
|
+
{
|
|
143
|
+
name: "FormatFunction",
|
|
144
|
+
pattern: /%\(/,
|
|
145
|
+
categories: ["BlockMarker", "FunctionStart"]
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: "IfFunction",
|
|
149
|
+
pattern: /if\(/,
|
|
150
|
+
categories: ["BlockMarker", "FunctionStart"]
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: "BooleanFunction",
|
|
154
|
+
pattern: /boolean\(/,
|
|
155
|
+
categories: ["BlockMarker", "FunctionStart"]
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "JavaScript",
|
|
159
|
+
pattern: /~?`[^`]*`/,
|
|
160
|
+
line_breaks: true
|
|
161
|
+
}
|
|
162
|
+
],
|
|
163
|
+
Signed: [
|
|
164
|
+
{
|
|
165
|
+
name: "InterpolatedIdent",
|
|
166
|
+
pattern: "(?:{{ident}}|-)?{{interpolated}}(?:{{interpolated}}|{{nmchar}})*",
|
|
167
|
+
categories: [
|
|
168
|
+
"Interpolated",
|
|
169
|
+
"Selector",
|
|
170
|
+
"Ident"
|
|
171
|
+
]
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "InterpolatedCustomProperty",
|
|
175
|
+
pattern: "--{{ident}}?{{interpolated}}(?:{{interpolated}}|{{nmchar}})*",
|
|
176
|
+
categories: ["Interpolated"]
|
|
177
|
+
},
|
|
178
|
+
(
|
|
179
|
+
/**
|
|
180
|
+
* Unfortunately, there's grammatical ambiguity between
|
|
181
|
+
* interpolated props and a naked interpolated selector name,
|
|
182
|
+
* making this awkward token necessary.
|
|
183
|
+
*/
|
|
184
|
+
{
|
|
185
|
+
name: "InterpolatedSelector",
|
|
186
|
+
pattern: ["[.#]{{ident}}?{{interpolated}}(?:{{interpolated}}|{{nmchar}})*", groupCapture],
|
|
187
|
+
categories: ["Interpolated", "Selector"],
|
|
188
|
+
start_chars_hint: [".", "#"],
|
|
189
|
+
line_breaks: true
|
|
190
|
+
})
|
|
191
|
+
]
|
|
192
|
+
};
|
|
193
|
+
let defaultTokens = tokens.modes.Default.slice();
|
|
194
|
+
let tokenLength = defaultTokens.length;
|
|
195
|
+
for (let i = 0; i < tokenLength; i++) {
|
|
196
|
+
let token = defaultTokens[i];
|
|
197
|
+
const { name } = token;
|
|
198
|
+
const copyToken = () => {
|
|
199
|
+
token = structuredClone(token);
|
|
200
|
+
};
|
|
201
|
+
let alterations = true;
|
|
202
|
+
switch (name) {
|
|
203
|
+
case "Ampersand":
|
|
204
|
+
copyToken();
|
|
205
|
+
/**
|
|
206
|
+
* Captures not just ampersands, but "ampersand merges", where
|
|
207
|
+
* the intent of the author was to merge the parent selector with a token
|
|
208
|
+
* suffix or prefix.
|
|
209
|
+
*
|
|
210
|
+
* e.g.
|
|
211
|
+
* 1. &-foo
|
|
212
|
+
* 2. &(foo)
|
|
213
|
+
* 3. &1
|
|
214
|
+
* 4. .foo-&
|
|
215
|
+
*/
|
|
216
|
+
token.pattern = "(?:[.#](?:{{ident}}-)?&|&){{nmchar}}*";
|
|
217
|
+
token.start_chars_hint = [
|
|
218
|
+
"&",
|
|
219
|
+
".",
|
|
220
|
+
"#"
|
|
221
|
+
];
|
|
222
|
+
break;
|
|
223
|
+
case "DotName":
|
|
224
|
+
case "HashName":
|
|
225
|
+
copyToken();
|
|
226
|
+
token.longer_alt = "Ampersand";
|
|
227
|
+
break;
|
|
228
|
+
case "Divide":
|
|
229
|
+
copyToken();
|
|
230
|
+
token.pattern = /\.?\//;
|
|
231
|
+
break;
|
|
232
|
+
case "SingleQuoteStart":
|
|
233
|
+
copyToken();
|
|
234
|
+
token.pattern = /~?'/;
|
|
235
|
+
break;
|
|
236
|
+
case "DoubleQuoteStart":
|
|
237
|
+
copyToken();
|
|
238
|
+
token.pattern = /~?"/;
|
|
239
|
+
break;
|
|
240
|
+
default: alterations = false;
|
|
241
|
+
}
|
|
242
|
+
if (alterations) defaultTokens[i] = token;
|
|
243
|
+
const merge = merges[name];
|
|
244
|
+
if (merge) {
|
|
245
|
+
/** Insert after current token */
|
|
246
|
+
defaultTokens = defaultTokens.slice(0, i + 1).concat(merge, defaultTokens.slice(i + 1));
|
|
247
|
+
tokens.modes.Default = defaultTokens;
|
|
248
|
+
const mergeLength = merge.length;
|
|
249
|
+
tokenLength += mergeLength;
|
|
250
|
+
i += mergeLength;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
tokens.modes.AmpersandTemplate = [
|
|
254
|
+
{
|
|
255
|
+
name: "AmpersandTemplateEnd",
|
|
256
|
+
pattern: /\)/,
|
|
257
|
+
pop_mode: true,
|
|
258
|
+
categories: ["FunctionLikeEnd"]
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: "AmpersandTemplateContents",
|
|
262
|
+
pattern: AMPERSAND_TEMPLATE_CONTENTS_REGEX,
|
|
263
|
+
categories: ["Selector"]
|
|
264
|
+
},
|
|
265
|
+
"SingleQuoteStart",
|
|
266
|
+
"DoubleQuoteStart",
|
|
267
|
+
"WS"
|
|
268
|
+
];
|
|
269
|
+
return tokens;
|
|
270
|
+
}
|
|
271
|
+
const Fragments = $preBuildFragments();
|
|
272
|
+
const Tokens = $preBuildTokens();
|
|
273
|
+
const lessFragments = () => Fragments;
|
|
274
|
+
const lessTokens = () => Tokens;
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/utils.ts
|
|
277
|
+
const INTERPOLATION_REGEX = /([$@])\{([^}]+)\}/g;
|
|
278
|
+
const createInterpolatedReference = (prefix, varName, location, context) => {
|
|
279
|
+
const isProperty = prefix === "$";
|
|
280
|
+
return new Reference({ key: isProperty ? new Quoted(varName, { quote: "'" }, location) : varName }, {
|
|
281
|
+
type: isProperty ? "index" : "variable",
|
|
282
|
+
role: "ident"
|
|
283
|
+
}, location);
|
|
284
|
+
};
|
|
285
|
+
const getInterpolatedNode = (name, location, context) => {
|
|
286
|
+
const replacements = [];
|
|
287
|
+
let source = name;
|
|
288
|
+
let result;
|
|
289
|
+
INTERPOLATION_REGEX.lastIndex = 0;
|
|
290
|
+
while ((result = INTERPOLATION_REGEX.exec(name)) !== null) {
|
|
291
|
+
const [match, prefix, varName] = result;
|
|
292
|
+
source = source.replace(match, INTERPOLATION_PLACEHOLDER);
|
|
293
|
+
replacements.push(createInterpolatedReference(prefix ?? "", varName ?? "", location, context));
|
|
294
|
+
}
|
|
295
|
+
return new Interpolated({
|
|
296
|
+
source,
|
|
297
|
+
replacements
|
|
298
|
+
}, { role: "ident" }, location);
|
|
299
|
+
};
|
|
300
|
+
const normalizeMixinReferenceKey = (selector) => {
|
|
301
|
+
if (isNode(selector, N.BasicSelector) || selector instanceof InterpolatedSelector) return {
|
|
302
|
+
key: selector.valueOf(),
|
|
303
|
+
rawKey: selector
|
|
304
|
+
};
|
|
305
|
+
if (isNode(selector, N.CompoundSelector)) return {
|
|
306
|
+
key: selector.value.map((node) => node.valueOf()),
|
|
307
|
+
rawKey: selector
|
|
308
|
+
};
|
|
309
|
+
if (isNode(selector, N.ComplexSelector)) {
|
|
310
|
+
const path = [];
|
|
311
|
+
let canUsePath = true;
|
|
312
|
+
for (const node of selector.value) {
|
|
313
|
+
if (isNode(node, N.BasicSelector) || node instanceof InterpolatedSelector) {
|
|
314
|
+
path.push(node.valueOf());
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (isNode(node, N.CompoundSelector)) {
|
|
318
|
+
path.push(...node.value.map((child) => child.valueOf()));
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
if (isNode(node, N.Combinator) && (node.value === ">" || node.value === " ")) continue;
|
|
322
|
+
canUsePath = false;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
if (canUsePath && path.length > 0) return {
|
|
326
|
+
key: path,
|
|
327
|
+
rawKey: selector
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
key: selector.valueOf(),
|
|
332
|
+
rawKey: selector
|
|
333
|
+
};
|
|
334
|
+
};
|
|
335
|
+
const getInterpolatedOrString = (name, location, context) => {
|
|
336
|
+
const matches = [];
|
|
337
|
+
INTERPOLATION_REGEX.lastIndex = 0;
|
|
338
|
+
let result;
|
|
339
|
+
while ((result = INTERPOLATION_REGEX.exec(name)) !== null) {
|
|
340
|
+
const [fullMatch, prefix, varName] = result;
|
|
341
|
+
if (varName && prefix) matches.push({
|
|
342
|
+
fullMatch,
|
|
343
|
+
prefix,
|
|
344
|
+
varName,
|
|
345
|
+
index: result.index
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
if (matches.length > 0) {
|
|
349
|
+
let source = name;
|
|
350
|
+
const replacements = [];
|
|
351
|
+
let offset = 0;
|
|
352
|
+
for (let i = 0; i < matches.length; i++) {
|
|
353
|
+
const match = matches[i];
|
|
354
|
+
const adjustedIndex = match.index - offset;
|
|
355
|
+
const beforeMatch = source.substring(0, adjustedIndex);
|
|
356
|
+
const afterMatch = source.substring(adjustedIndex + match.fullMatch.length);
|
|
357
|
+
source = beforeMatch + INTERPOLATION_PLACEHOLDER + afterMatch;
|
|
358
|
+
offset += match.fullMatch.length - INTERPOLATION_PLACEHOLDER.length;
|
|
359
|
+
const ref = createInterpolatedReference(match.prefix, match.varName, location, context);
|
|
360
|
+
replacements.push(ref);
|
|
361
|
+
}
|
|
362
|
+
return new Interpolated({
|
|
363
|
+
source,
|
|
364
|
+
replacements
|
|
365
|
+
}, { role: "ident" }, location);
|
|
366
|
+
}
|
|
367
|
+
const atPos = name.indexOf("@", 1);
|
|
368
|
+
const dollarPos = name.indexOf("$", 1);
|
|
369
|
+
if (atPos === -1 && dollarPos === -1) if (name.startsWith("@") || name.startsWith("$")) return name.slice(1);
|
|
370
|
+
else return name;
|
|
371
|
+
const nextPos = atPos !== -1 ? atPos : dollarPos;
|
|
372
|
+
const start = name.slice(1, nextPos);
|
|
373
|
+
const end = name.slice(nextPos);
|
|
374
|
+
const type = end.startsWith("@") ? "variable" : "index";
|
|
375
|
+
const endResult = getInterpolatedOrString(end, location, context);
|
|
376
|
+
if (typeof endResult === "string") {
|
|
377
|
+
const endKey = type === "index" ? new Quoted(endResult, { quote: "'" }, location) : endResult;
|
|
378
|
+
return new Interpolated({
|
|
379
|
+
source: start + INTERPOLATION_PLACEHOLDER,
|
|
380
|
+
replacements: [new Reference({ key: endKey }, {
|
|
381
|
+
type,
|
|
382
|
+
role: "ident"
|
|
383
|
+
}, location)]
|
|
384
|
+
}, { role: "ident" });
|
|
385
|
+
} else
|
|
386
|
+
/**
|
|
387
|
+
* endResult is already an Interpolated node, so we need to handle this
|
|
388
|
+
* differently.
|
|
389
|
+
*
|
|
390
|
+
* @todo - test deep nesting
|
|
391
|
+
*/
|
|
392
|
+
return new Interpolated({
|
|
393
|
+
source: start + INTERPOLATION_PLACEHOLDER,
|
|
394
|
+
replacements: [type === "index" ? new Quoted(endResult, { quote: "'" }, location) : endResult]
|
|
395
|
+
}, { role: "ident" });
|
|
396
|
+
};
|
|
397
|
+
//#endregion
|
|
398
|
+
//#region src/builders.ts
|
|
399
|
+
const KNOWN_AT_RULE_VAR_NAME_RE = /^(?:(?:-moz-)?document|(?:-[a-z]+-)?keyframes|(?:-ms-)?viewport|import|media|supports|layer|container|scope|page|font-face|starting-style|property|counter-style|color-profile|font-palette-values|namespace)$/i;
|
|
400
|
+
function spanToLocation(span) {
|
|
401
|
+
return {
|
|
402
|
+
start: span.start,
|
|
403
|
+
end: span.end
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function nodeChildren(children) {
|
|
407
|
+
return children.filter((c) => c._tag === "node");
|
|
408
|
+
}
|
|
409
|
+
var LessGrammar = class LessGrammar extends CssParser {
|
|
410
|
+
/** Math mode governing when arithmetic operates / when `/` divides. Less default. */
|
|
411
|
+
mathMode = "parens-division";
|
|
412
|
+
/** Bare ident/keyword token in value or guard position. */
|
|
413
|
+
_lessKeyword(text, loc) {
|
|
414
|
+
return this._valueKeyword(text, loc);
|
|
415
|
+
}
|
|
416
|
+
_isKeywordLike(node) {
|
|
417
|
+
return !!node && typeof node === "object" && (node.type === "Keyword" || node.type === "Any");
|
|
418
|
+
}
|
|
419
|
+
_isEmptyKeywordLike(node) {
|
|
420
|
+
return !node || this._isKeywordLike(node) && !String(node.value ?? "").trim();
|
|
421
|
+
}
|
|
422
|
+
buildNode(type, span, children, _state, _rawChildren, fields, triviaLog = []) {
|
|
423
|
+
const loc = spanToLocation(span);
|
|
424
|
+
const raw = _rawChildren;
|
|
425
|
+
switch (type) {
|
|
426
|
+
case "VarDeclaration": return this._buildVarDeclaration(children, raw, loc);
|
|
427
|
+
case "Reference": return this._buildReference(children, loc);
|
|
428
|
+
case "LessAmpersand": return this._buildAmpersand(children, loc);
|
|
429
|
+
case "ComplexSelector": return this._buildComplexSelector(raw, loc);
|
|
430
|
+
case "SelectorList": return this._buildSelectorList(raw, loc);
|
|
431
|
+
case "Ruleset": return this._buildRuleset(children, raw, loc);
|
|
432
|
+
case "Declaration":
|
|
433
|
+
this._warnDeprecatedValue(span);
|
|
434
|
+
return this._buildLessDeclaration(raw, loc);
|
|
435
|
+
case "CustomDeclaration":
|
|
436
|
+
this._warnCustomPropVars(span);
|
|
437
|
+
return this._buildLessCustomDecl(children, loc);
|
|
438
|
+
case "Block": return this._buildLessCustomBlock(children, loc);
|
|
439
|
+
case "AtRuleBlock":
|
|
440
|
+
this._warnAtRulePreludeVars(span);
|
|
441
|
+
return this._buildAtRuleBlock(children, loc);
|
|
442
|
+
case "QueryAtRuleBlock":
|
|
443
|
+
this._warnAtRulePreludeVars(span);
|
|
444
|
+
return this._buildLessQueryAtRuleBlock(children, raw, loc);
|
|
445
|
+
case "NamedColor": return this._buildNamedColor(children, loc);
|
|
446
|
+
case "Comparison": return this._buildComparison(raw, loc);
|
|
447
|
+
case "GuardDefault": return new DefaultGuard("default()", {}, loc);
|
|
448
|
+
case "GuardInParens": return this._buildGuardInParens(children, loc);
|
|
449
|
+
case "GuardTerm": return this._buildGuardTerm(raw, loc);
|
|
450
|
+
case "GuardAnd": return this._buildGuardJoin(children, loc, "and");
|
|
451
|
+
case "GuardOr": return this._buildGuardJoin(children, loc, "or");
|
|
452
|
+
case "Guard": return this._buildGuard(children, loc);
|
|
453
|
+
case "CondArgTerm": return this._buildCondArgTerm(raw, loc);
|
|
454
|
+
case "CondArgAnd": return this._buildCondArgJoin(children, loc, "and");
|
|
455
|
+
case "CondArgOr": return this._buildCondArgJoin(children, loc, "or");
|
|
456
|
+
case "UnicodeRange": return this._lessKeyword(this._source.slice(span.start, span.end), loc);
|
|
457
|
+
case "PseudoSelector": return this._buildLessPseudo(type, span, children, _state, raw, fields, triviaLog, loc);
|
|
458
|
+
case "InterpolatedSelector": return this._buildInterpolatedSelector(children, loc);
|
|
459
|
+
case "VarCall": return this._buildVarCall(children, raw, loc);
|
|
460
|
+
case "MixinCall": return this._buildMixinCall(children, raw, loc);
|
|
461
|
+
case "Rest": return this._buildRest(children, loc);
|
|
462
|
+
case "NamedArg": return this._buildNamedArg(raw, loc);
|
|
463
|
+
case "MixinArgs": return this._buildMixinArgs(raw, loc);
|
|
464
|
+
case "AnonymousMixinDefinition": return this._buildAnonMixin(children, loc);
|
|
465
|
+
case "DetachedRuleset": return this._buildDetachedRuleset(children, loc);
|
|
466
|
+
case "For": return this._buildEachFor(children, loc);
|
|
467
|
+
case "FormatCall": return this._buildFormatCall(raw, loc);
|
|
468
|
+
case "MixinOrQualifiedRule": return this._buildMixinOrQualified(children, loc);
|
|
469
|
+
case "Negative": return new Negative(this._negativeOperand(children), void 0, loc);
|
|
470
|
+
case "OperationTop": return this._buildOperation(children, loc, this.mathMode === "always");
|
|
471
|
+
case "EscapedValue": return this._buildEscapedValue(children, loc);
|
|
472
|
+
case "InterpValue": return this._buildInterpValue(raw, loc);
|
|
473
|
+
case "NsAccessor": return this._buildNsAccessor(children, loc);
|
|
474
|
+
case "AtRuleStatement": return this._buildAtRuleStatement(children, loc);
|
|
475
|
+
case "ExtendTarget": return this._buildExtendTarget(children, raw, loc);
|
|
476
|
+
case "ExtendPseudo": return this._buildExtendPseudo(children, loc);
|
|
477
|
+
case "ExtendStatement": return this._buildExtendStatement(children, raw, loc);
|
|
478
|
+
default: return super.buildNode(type, span, children, _state, raw, fields, triviaLog);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* The operand of a `Negative` (`-value`). The grammar emits the leading `-`
|
|
483
|
+
* as a leaf followed by the operand, which may itself be a node or a bare
|
|
484
|
+
* string terminal (e.g. `-@color` → `var(--color)`'s inner `-color-accent`).
|
|
485
|
+
* Prefer a node child; otherwise take the operand leaf's text so `Negative`
|
|
486
|
+
* coerces it to the canonical node form rather than receiving `undefined`.
|
|
487
|
+
*/
|
|
488
|
+
_negativeOperand(children) {
|
|
489
|
+
const node = nodeChildren(children)[0];
|
|
490
|
+
if (node) return node;
|
|
491
|
+
return children.find((c) => c._tag === "leaf" && c.value !== "-")?.value ?? "";
|
|
492
|
+
}
|
|
493
|
+
_buildVarDeclaration(children, rawChildren, loc) {
|
|
494
|
+
const items = spannedComponents(rawChildren);
|
|
495
|
+
const rawName = typeof items[0]?.comp === "string" ? items[0].comp : "";
|
|
496
|
+
const name = rawName.startsWith("@") ? rawName.slice(1) : rawName;
|
|
497
|
+
if (/^-?\d/.test(name)) this._warn(`Variable name "@${name}" starts with a digit; digit-leading variable names are deprecated.`, "digit-leading-variable");
|
|
498
|
+
const colonIdx = items.findIndex((i) => i.comp === ":");
|
|
499
|
+
if (items[colonIdx + 1]?.comp === "{") {
|
|
500
|
+
const ruleNodes = nodeChildren(children);
|
|
501
|
+
const openBrace = items[colonIdx + 1];
|
|
502
|
+
const closeBrace = items[items.length - 1].comp === "}" ? items[items.length - 1] : void 0;
|
|
503
|
+
if (ruleNodes.length === 0 && closeBrace) {
|
|
504
|
+
if (this._source.slice(openBrace.span.end, closeBrace.span.start).trim() !== "") {
|
|
505
|
+
const rawBlock = this._source.slice(openBrace.span.start, closeBrace.span.end);
|
|
506
|
+
return new VarDeclaration({
|
|
507
|
+
name: (name || void 0) ?? name,
|
|
508
|
+
value: new Quoted(rawBlock, {}, loc)
|
|
509
|
+
}, {}, loc);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const mixin = new Mixin({ rules: ruleNodes }, {}, loc);
|
|
513
|
+
return new VarDeclaration({
|
|
514
|
+
name: (name || void 0) ?? name,
|
|
515
|
+
value: mixin
|
|
516
|
+
}, {}, loc);
|
|
517
|
+
}
|
|
518
|
+
let end = items.length;
|
|
519
|
+
let bangIdx = -1;
|
|
520
|
+
for (let i = colonIdx + 1; i < items.length; i++) {
|
|
521
|
+
const c = items[i].comp;
|
|
522
|
+
if (c === "!") {
|
|
523
|
+
end = i;
|
|
524
|
+
bangIdx = i;
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
if (c === "important" || c === ";") {
|
|
528
|
+
end = i;
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
const valItems = items.slice(colonIdx + 1, end);
|
|
533
|
+
if (valItems.length) {
|
|
534
|
+
const vText = this._source.slice(valItems[0].span.start, valItems[valItems.length - 1].span.end);
|
|
535
|
+
if (/(?:^|[\s,])\.-?[_a-zA-Z]/.test(vText)) this._warn(`Unquoted selector capture in variable "@${name}" is deprecated; wrap the value in quotes or ~"...".`, "unquoted-selector-capture");
|
|
536
|
+
}
|
|
537
|
+
const nsRef = this._tryParseNamespaceRef(valItems, loc);
|
|
538
|
+
let { value: rawValue } = nsRef ? { value: nsRef } : this._assembleLessValue(valItems, loc);
|
|
539
|
+
if (!nsRef && valItems.length > 0) {
|
|
540
|
+
const lastSpan = valItems[valItems.length - 1].span;
|
|
541
|
+
const afterVal = this._source.slice(lastSpan.end);
|
|
542
|
+
const rv = rawValue;
|
|
543
|
+
const rvKey = rv?.key;
|
|
544
|
+
const isEmptyKey = rvKey === "" || rvKey === void 0 || rvKey && typeof rvKey === "object" && rvKey.type === "Quoted" && (rvKey.value === "" || rvKey.valueOf?.() === "");
|
|
545
|
+
const grammarPartialAccessor = rv && rv.type === "Reference" && rv.target !== void 0 && isEmptyKey;
|
|
546
|
+
const accMatch = /^\s*\[([^\]]+)\]/.exec(afterVal);
|
|
547
|
+
if (accMatch) {
|
|
548
|
+
const accText = accMatch[1].trim();
|
|
549
|
+
const accessorKey = this._decodeAccessorKey(accText, loc);
|
|
550
|
+
const accessorRefOptions = typeof accessorKey === "number" ? { type: "index" } : {};
|
|
551
|
+
if (grammarPartialAccessor) rawValue = new Reference({
|
|
552
|
+
target: rv.target,
|
|
553
|
+
key: accessorKey
|
|
554
|
+
}, accessorRefOptions, loc);
|
|
555
|
+
else rawValue = new Reference({
|
|
556
|
+
target: rawValue,
|
|
557
|
+
key: accessorKey
|
|
558
|
+
}, accessorRefOptions, loc);
|
|
559
|
+
const afterAcc = afterVal.slice(accMatch[0].length);
|
|
560
|
+
if (/^\s*\(\s*\)/.test(afterAcc)) rawValue = new Call({ name: rawValue }, {}, loc);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const value = typeof rawValue === "string" && rawValue ? this._lessKeyword(rawValue, loc) : rawValue;
|
|
564
|
+
let important;
|
|
565
|
+
if (bangIdx >= 0) {
|
|
566
|
+
const bang = items[bangIdx];
|
|
567
|
+
const kw = items[bangIdx + 1];
|
|
568
|
+
const impEnd = kw && typeof kw.comp === "string" && kw.comp.toLowerCase() === "important" ? kw.span.end : bang.span.end;
|
|
569
|
+
important = this._source.slice(bang.span.start, impEnd);
|
|
570
|
+
}
|
|
571
|
+
return new VarDeclaration({
|
|
572
|
+
name: (name || void 0) ?? name,
|
|
573
|
+
value,
|
|
574
|
+
important
|
|
575
|
+
}, {}, loc);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Build a Less variable reference and its accessor/call chain. Faithful 1:1
|
|
579
|
+
* port of the Chevrotain `varReference` + `lookupOrCall` productions
|
|
580
|
+
* (productions/values.ts, productions/guards.ts): a `@var` base glued to a
|
|
581
|
+
* left-folded chain of `[index]` accessors and `(call)`s. The grammar's
|
|
582
|
+
* noTrivia() guarantees the chain is adjacent (no whitespace between segments).
|
|
583
|
+
*/
|
|
584
|
+
_buildReference(children, loc) {
|
|
585
|
+
const ls = children.filter((c) => c._tag === "leaf");
|
|
586
|
+
const varName = ls[0]?.value ?? "";
|
|
587
|
+
const isVar = varName.startsWith("@");
|
|
588
|
+
let base = varName.startsWith("$") ? new Reference({ key: new Quoted(varName.slice(1), { quote: "'" }, loc) }, { type: "index" }, loc) : new Reference(isVar ? varName.slice(1) : varName, isVar ? { type: "variable" } : {}, loc);
|
|
589
|
+
let i = 1;
|
|
590
|
+
while (i < ls.length) {
|
|
591
|
+
const tok = ls[i].value;
|
|
592
|
+
if (tok === "[") if (ls[i + 1]?.value === "]") {
|
|
593
|
+
base = new Reference({
|
|
594
|
+
target: base,
|
|
595
|
+
key: -1
|
|
596
|
+
}, { type: "index" }, loc);
|
|
597
|
+
i += 2;
|
|
598
|
+
} else {
|
|
599
|
+
base = this._applyReferenceAccessor(base, ls[i + 1].value, loc);
|
|
600
|
+
i += 3;
|
|
601
|
+
}
|
|
602
|
+
else if (tok === "(") {
|
|
603
|
+
const payload = { name: base };
|
|
604
|
+
if (ls[i + 1]?.value === ")") i += 2;
|
|
605
|
+
else {
|
|
606
|
+
const args = this._buildRefCallArgs(ls[i + 1].value, loc);
|
|
607
|
+
if (args) payload.args = args;
|
|
608
|
+
i += 3;
|
|
609
|
+
}
|
|
610
|
+
base = new Call(payload, {}, loc);
|
|
611
|
+
} else break;
|
|
612
|
+
}
|
|
613
|
+
return base;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Build a namespace INDEXED-accessor value: a `.`/`#` selector-path head glued to
|
|
617
|
+
* a `[accessor]` (and any further `[accessor]`/`(call)` chain), e.g.
|
|
618
|
+
* `#ns.options[val1]`. The grammar (NsAccessor) captures this as ONE value operand
|
|
619
|
+
* so it survives arithmetic folding; here we reassemble it into the mixin-ruleset
|
|
620
|
+
* name Reference + accessor chain — the SAME shape the declaration-value
|
|
621
|
+
* _assembleSegment path produces for a lone `#ns.options[val1]`. Call-headed forms
|
|
622
|
+
* (`.mixin()`, `.mixin()[k]`) do NOT reach here (they keep the GluedParen path).
|
|
623
|
+
* Leaves: [ headText, '[', key?, ']', '(', content?, ')' … ].
|
|
624
|
+
*/
|
|
625
|
+
_buildNsAccessor(children, loc) {
|
|
626
|
+
const ls = children.filter((c) => c._tag === "leaf");
|
|
627
|
+
const headText = (ls[0]?.value ?? "").trim();
|
|
628
|
+
const pathSegs = headText.match(/[#.][^#.>+~\s]*/g) ?? [headText];
|
|
629
|
+
const nameKey = pathSegs.length === 1 ? pathSegs[0] : pathSegs;
|
|
630
|
+
const rawKey = pathSegs.length > 1 ? pathSegs.join("") : void 0;
|
|
631
|
+
let base = new Reference({
|
|
632
|
+
key: nameKey,
|
|
633
|
+
...rawKey ? { rawKey } : {}
|
|
634
|
+
}, {
|
|
635
|
+
type: "mixin-ruleset",
|
|
636
|
+
role: "name"
|
|
637
|
+
}, loc);
|
|
638
|
+
let i = 1;
|
|
639
|
+
while (i < ls.length) {
|
|
640
|
+
const tok = ls[i].value;
|
|
641
|
+
if (tok === "[") if (ls[i + 1]?.value === "]") {
|
|
642
|
+
base = new Reference({
|
|
643
|
+
target: base,
|
|
644
|
+
key: -1
|
|
645
|
+
}, { type: "index" }, loc);
|
|
646
|
+
i += 2;
|
|
647
|
+
} else {
|
|
648
|
+
base = this._applyReferenceAccessor(base, ls[i + 1].value, loc);
|
|
649
|
+
i += 3;
|
|
650
|
+
}
|
|
651
|
+
else if (tok === "(") {
|
|
652
|
+
const payload = { name: base };
|
|
653
|
+
if (ls[i + 1]?.value === ")") i += 2;
|
|
654
|
+
else {
|
|
655
|
+
const args = this._buildRefCallArgs(ls[i + 1].value, loc);
|
|
656
|
+
if (args) payload.args = args;
|
|
657
|
+
i += 3;
|
|
658
|
+
}
|
|
659
|
+
base = new Call(payload, {}, loc);
|
|
660
|
+
} else break;
|
|
661
|
+
}
|
|
662
|
+
return base;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Apply one `[key]` accessor to `base`, reproducing lookupOrCall's key logic:
|
|
666
|
+
* type is `variable` when the key token starts with `@`, else `index`; the key
|
|
667
|
+
* text runs through getInterpolatedOrString (handling `$@x`/`@{x}` interpolation),
|
|
668
|
+
* and index keys are wrapped in a Quoted.
|
|
669
|
+
*/
|
|
670
|
+
_applyReferenceAccessor(base, keyStr, loc) {
|
|
671
|
+
const type = keyStr.startsWith("@") ? "variable" : "index";
|
|
672
|
+
let result = getInterpolatedOrString(keyStr, loc);
|
|
673
|
+
if (type === "index") result = new Quoted(result, { quote: "'" }, loc);
|
|
674
|
+
return new Reference({
|
|
675
|
+
target: base,
|
|
676
|
+
key: result
|
|
677
|
+
}, { type }, loc);
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Build mixin-call args for a `(…)` segment in a reference chain from the raw
|
|
681
|
+
* captured content. Comma-separated values become a List; an empty segment
|
|
682
|
+
* yields null (no args). Sub-structure here is intentionally shallow — the
|
|
683
|
+
* accessor-chain call form is rare and no consumer inspects nested arg shape.
|
|
684
|
+
*/
|
|
685
|
+
_buildRefCallArgs(content, loc) {
|
|
686
|
+
const trimmed = content.trim();
|
|
687
|
+
if (!trimmed) return null;
|
|
688
|
+
return new List(trimmed.split(",").map((p) => p.trim()).filter(Boolean).map((p) => p.startsWith("@") ? new Reference(p.slice(1), { type: "variable" }, loc) : this._lessKeyword(p, loc)), void 0, loc);
|
|
689
|
+
}
|
|
690
|
+
_buildNamedColor(children, loc) {
|
|
691
|
+
return new Color({ node: children.filter((c) => c._tag === "leaf")[0]?.value ?? "" }, {}, loc);
|
|
692
|
+
}
|
|
693
|
+
_normalizeCompareOp(op) {
|
|
694
|
+
switch (op) {
|
|
695
|
+
case "=>":
|
|
696
|
+
case ">=": return ">=";
|
|
697
|
+
case "=<":
|
|
698
|
+
case "<=": return "<=";
|
|
699
|
+
case ">": return ">";
|
|
700
|
+
case "<": return "<";
|
|
701
|
+
default: return "=";
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/** A guard comparison operator leaf (`=`, `<`, `>=`, `=<`, `=~`, …). */
|
|
705
|
+
_isCompareOpLeaf(text) {
|
|
706
|
+
return />=|<=|=>|=<|=~|[<>=]/.test(text);
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Split a guard term's ordered components into `left [op right]`. A bare-keyword
|
|
710
|
+
* operand (`foo`, `true`) is a leaf string — not a node — so `nodeChildren`
|
|
711
|
+
* alone drops it; walk the ordered stream so string operands become real
|
|
712
|
+
* keyword nodes (their guard truthiness is decided at eval by `Condition`).
|
|
713
|
+
*/
|
|
714
|
+
_guardComparison(raw, loc) {
|
|
715
|
+
const items = spannedComponents(raw);
|
|
716
|
+
let op;
|
|
717
|
+
const operands = [];
|
|
718
|
+
for (const it of items) if (typeof it.comp === "string") {
|
|
719
|
+
if (this._isCompareOpLeaf(it.comp)) {
|
|
720
|
+
op = this._normalizeCompareOp(it.comp);
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
operands.push(this._lessKeyword(it.comp, loc));
|
|
724
|
+
} else operands.push(it.comp);
|
|
725
|
+
const left = this._maybeDefaultGuard(operands[0] ?? this._lessKeyword("", loc), loc);
|
|
726
|
+
const right = operands[1] !== void 0 ? this._maybeDefaultGuard(operands[1], loc) : void 0;
|
|
727
|
+
return {
|
|
728
|
+
left,
|
|
729
|
+
op,
|
|
730
|
+
right
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
_buildComparison(raw, loc) {
|
|
734
|
+
const { left, op, right } = this._guardComparison(raw, loc);
|
|
735
|
+
if (op && right) return new Condition([
|
|
736
|
+
left,
|
|
737
|
+
op,
|
|
738
|
+
right
|
|
739
|
+
], {}, loc);
|
|
740
|
+
return new Condition([left], {}, loc);
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Coerce a `_assembleValue` result — a single Component or a raw space-group
|
|
744
|
+
* array — into ONE Node, so it can be a Condition operand. A single string
|
|
745
|
+
* becomes a keyword; a space-group array becomes a `Sequence` (the same coercion
|
|
746
|
+
* the List serializer applies to a raw group).
|
|
747
|
+
*/
|
|
748
|
+
_condOperandNode(comps, loc) {
|
|
749
|
+
const { value } = this._assembleValue(comps, loc);
|
|
750
|
+
if (Array.isArray(value)) return new Sequence(value.map((c) => typeof c === "string" ? this._lessKeyword(c, loc) : c), void 0, loc);
|
|
751
|
+
if (typeof value === "string") return this._lessKeyword(value, loc);
|
|
752
|
+
return value;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* `CondArgTerm` — a name-independent condition-argument term: optional leading
|
|
756
|
+
* `not`, a bounded value operand, and an optional `<op> right` comparison. Builds
|
|
757
|
+
* a `Condition` (comparison → `[left, op, right]`; bare `not` → `{negate:true}`)
|
|
758
|
+
* or, when neither a `not` nor a `compareOp` is present, the plain operand value
|
|
759
|
+
* (byte-identical to an ordinary value arg). Multi-token operands (`1px solid`)
|
|
760
|
+
* survive as a `Sequence`, unlike the single-operand guard path.
|
|
761
|
+
*/
|
|
762
|
+
_buildCondArgTerm(raw, loc) {
|
|
763
|
+
const items = spannedComponents(raw);
|
|
764
|
+
const hasNot = items.length > 0 && items[0].comp === "not";
|
|
765
|
+
const rest = hasNot ? items.slice(1) : items;
|
|
766
|
+
const opIdx = rest.findIndex((it) => typeof it.comp === "string" && this._isCompareOpLeaf(it.comp));
|
|
767
|
+
let term;
|
|
768
|
+
if (opIdx >= 0) term = new Condition([
|
|
769
|
+
this._condOperandNode(rest.slice(0, opIdx), loc),
|
|
770
|
+
this._normalizeCompareOp(rest[opIdx].comp),
|
|
771
|
+
this._condOperandNode(rest.slice(opIdx + 1), loc)
|
|
772
|
+
], {}, loc);
|
|
773
|
+
else term = this._condOperandNode(rest, loc);
|
|
774
|
+
if (hasNot) return new Condition([term], { negate: true }, loc);
|
|
775
|
+
return term;
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Fold a left-associative `and`/`or` chain of condition-arg terms into Conditions.
|
|
779
|
+
*
|
|
780
|
+
* Less accepts a bare `and`/`or` join in value-position condition args (`if(@a > 5
|
|
781
|
+
* and @b < 2, …)`) — verified against less@4.6.7 (`if`/`boolean` route their arg
|
|
782
|
+
* through `condition()` with no `needsParens`, so bare comparisons split on `and`/
|
|
783
|
+
* `or`). We keep accepting the bare form for Less parity, but NORMALIZE the AST so
|
|
784
|
+
* each join operand is `Paren`-wrapped: `@a > 5 and @b < 2` builds the SAME tree as
|
|
785
|
+
* the explicitly-parenthesised `(@a > 5) and (@b < 2)`. This is a structural
|
|
786
|
+
* normalisation only — `Paren(Condition)` evaluates to the same boolean as the bare
|
|
787
|
+
* `Condition`, so rendered CSS is byte-identical. A single unjoined operand (one
|
|
788
|
+
* node) is untouched (no synthetic Paren).
|
|
789
|
+
*/
|
|
790
|
+
_buildCondArgJoin(children, loc, op) {
|
|
791
|
+
const nodes = nodeChildren(children);
|
|
792
|
+
if (nodes.length === 0) return this._lessKeyword("", loc);
|
|
793
|
+
if (nodes.length === 1) return nodes[0];
|
|
794
|
+
const wrap = (n) => n.type === "Paren" ? n : new Paren(n, {}, loc);
|
|
795
|
+
let left = wrap(nodes[0]);
|
|
796
|
+
for (let i = 1; i < nodes.length; i++) left = new Condition([
|
|
797
|
+
left,
|
|
798
|
+
op,
|
|
799
|
+
wrap(nodes[i])
|
|
800
|
+
], {}, loc);
|
|
801
|
+
return left;
|
|
802
|
+
}
|
|
803
|
+
/** Coerce a default() call/reference into a DefaultGuard, mirroring isDefaultGuardCall. */
|
|
804
|
+
_maybeDefaultGuard(node, loc) {
|
|
805
|
+
const n = node;
|
|
806
|
+
if (n?.type === "Call") {
|
|
807
|
+
const name = n.name;
|
|
808
|
+
const nameStr = String(typeof name === "object" && name !== null && "valueOf" in name ? name.valueOf() : name ?? "");
|
|
809
|
+
if (nameStr === "default" || nameStr === "??") return new DefaultGuard("default()", {}, loc);
|
|
810
|
+
}
|
|
811
|
+
return node;
|
|
812
|
+
}
|
|
813
|
+
/** guardInParens: `(` guardOr `)` → Paren, or a bare default() → Paren(DefaultGuard). */
|
|
814
|
+
_buildGuardInParens(children, loc) {
|
|
815
|
+
let inner = nodeChildren(children)[0] ?? this._lessKeyword("", loc);
|
|
816
|
+
inner = this._maybeDefaultGuard(inner, loc);
|
|
817
|
+
const innerAny = inner;
|
|
818
|
+
if (innerAny?.type === "Paren" && innerAny.value?.type === "DefaultGuard") return inner;
|
|
819
|
+
return new Paren(inner, {}, loc);
|
|
820
|
+
}
|
|
821
|
+
/** A single guard term: optional `not`, then a paren-guard or a comparison/value. */
|
|
822
|
+
_buildGuardTerm(raw, loc) {
|
|
823
|
+
const hasNot = spannedComponents(raw).some((i) => typeof i.comp === "string" && i.comp === "not");
|
|
824
|
+
const rest = raw.filter((rc) => !(rc._tag === "leaf" && rc.value === "not"));
|
|
825
|
+
const nodes = nodeChildren(rest);
|
|
826
|
+
let term;
|
|
827
|
+
if (nodes.length >= 1 && nodes[0].type === "Paren") term = nodes[0];
|
|
828
|
+
else {
|
|
829
|
+
const { left, op, right } = this._guardComparison(rest, loc);
|
|
830
|
+
term = op && right ? new Condition([
|
|
831
|
+
left,
|
|
832
|
+
op,
|
|
833
|
+
right
|
|
834
|
+
], {}, loc) : left;
|
|
835
|
+
}
|
|
836
|
+
if (hasNot) return new Condition([term], { negate: true }, loc);
|
|
837
|
+
return term;
|
|
838
|
+
}
|
|
839
|
+
/** Fold a left-associative chain of terms joined by `and` / `or`. */
|
|
840
|
+
_buildGuardJoin(children, loc, op) {
|
|
841
|
+
const nodes = nodeChildren(children);
|
|
842
|
+
if (nodes.length === 0) return this._lessKeyword("", loc);
|
|
843
|
+
let left = nodes[0];
|
|
844
|
+
for (let i = 1; i < nodes.length; i++) left = new Condition([
|
|
845
|
+
left,
|
|
846
|
+
op,
|
|
847
|
+
nodes[i]
|
|
848
|
+
], {}, loc);
|
|
849
|
+
return left;
|
|
850
|
+
}
|
|
851
|
+
/** guard: `when` guardOr — returns the single guardOr child. */
|
|
852
|
+
_buildGuard(children, loc) {
|
|
853
|
+
return nodeChildren(children)[0] ?? this._lessKeyword("", loc);
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Deprecated Less `%(format, args…)` string formatting, LOWERED at build time.
|
|
857
|
+
*
|
|
858
|
+
* Jess already has full string interpolation, so `%()` is redundant Less-4 legacy —
|
|
859
|
+
* we emit a `percent-format` deprecation warning and, when the format is a string
|
|
860
|
+
* literal, splice its `%[sda]` directives into the canonical `Interpolated` node
|
|
861
|
+
* (the same one `@{var}` string interpolation builds), wrapped in a `Quoted` that
|
|
862
|
+
* preserves the literal's quote char / escaped flag:
|
|
863
|
+
* - `%s` → bare interpolation slot (a Quoted arg inserts with its quotes stripped);
|
|
864
|
+
* - `%d`/`%a` → identical bare slot (d/a are the same in Less);
|
|
865
|
+
* - `%S`/`%D`/`%A` → the arg WRAPPED in `escape(…)` (URL-encode);
|
|
866
|
+
* - `%%` → a literal `%`.
|
|
867
|
+
* A dynamic (non-literal) format (`%(hello)`, `%(e("…"), …)`) can't be lowered at
|
|
868
|
+
* parse time, so it falls back to a best-effort runtime `%` Call with the same warning.
|
|
869
|
+
*/
|
|
870
|
+
_buildFormatCall(raw, loc) {
|
|
871
|
+
this._warn("%() string formatting is deprecated — use string interpolation", "percent-format");
|
|
872
|
+
const argList = this._assembleArgs(this._betweenParens(spannedComponents(raw)), loc);
|
|
873
|
+
const args = argList.value ?? [];
|
|
874
|
+
const format = args[0];
|
|
875
|
+
const rest = args.slice(1);
|
|
876
|
+
if (format instanceof Quoted && typeof format.value === "string") {
|
|
877
|
+
const { source, replacements } = this._lowerFormatString(format.value, rest, loc);
|
|
878
|
+
return new Quoted(new Interpolated({
|
|
879
|
+
source,
|
|
880
|
+
replacements
|
|
881
|
+
}, { role: "ident" }, loc), {
|
|
882
|
+
quote: format.quote,
|
|
883
|
+
escaped: format.escaped
|
|
884
|
+
}, loc);
|
|
885
|
+
}
|
|
886
|
+
return new Call({
|
|
887
|
+
name: new Reference("%", {
|
|
888
|
+
type: "function",
|
|
889
|
+
fallbackValue: true
|
|
890
|
+
}, loc),
|
|
891
|
+
args: argList
|
|
892
|
+
}, { silentFail: true }, loc);
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Turn a printf-style format string into an `Interpolated` source + replacements.
|
|
896
|
+
* `%[sda]` → one `INTERPOLATION_PLACEHOLDER` slot consuming the next positional arg
|
|
897
|
+
* (uppercase → wrapped in `escape(…)` to URL-encode); `%%` → a literal `%`.
|
|
898
|
+
*/
|
|
899
|
+
_lowerFormatString(formatText, restArgs, loc) {
|
|
900
|
+
let source = "";
|
|
901
|
+
const replacements = [];
|
|
902
|
+
let argIndex = 0;
|
|
903
|
+
for (let i = 0; i < formatText.length; i++) {
|
|
904
|
+
const ch = formatText[i];
|
|
905
|
+
if (ch !== "%") {
|
|
906
|
+
source += ch;
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
const next = formatText[i + 1];
|
|
910
|
+
if (next === "%") {
|
|
911
|
+
source += "%";
|
|
912
|
+
i++;
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
if (next && /[sda]/i.test(next)) {
|
|
916
|
+
const arg = restArgs[argIndex++];
|
|
917
|
+
if (arg) {
|
|
918
|
+
source += INTERPOLATION_PLACEHOLDER;
|
|
919
|
+
if (/[A-Z]/.test(next)) {
|
|
920
|
+
const escapeRef = new Reference("escape", {
|
|
921
|
+
type: "function",
|
|
922
|
+
fallbackValue: true
|
|
923
|
+
}, loc);
|
|
924
|
+
const escapeArgs = new List([arg], {}, loc);
|
|
925
|
+
replacements.push(new Call({
|
|
926
|
+
name: escapeRef,
|
|
927
|
+
args: escapeArgs
|
|
928
|
+
}, { silentFail: true }, loc));
|
|
929
|
+
} else replacements.push(arg);
|
|
930
|
+
} else source += ch + next;
|
|
931
|
+
i++;
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
source += ch;
|
|
935
|
+
}
|
|
936
|
+
return {
|
|
937
|
+
source,
|
|
938
|
+
replacements
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
_buildInterpolatedSelector(children, loc) {
|
|
942
|
+
const ls = children.filter((c) => c._tag === "leaf");
|
|
943
|
+
const replacements = [];
|
|
944
|
+
let source = "";
|
|
945
|
+
for (const l of ls) if (l.value.startsWith("@{")) {
|
|
946
|
+
const varName = l.value.slice(2, -1);
|
|
947
|
+
replacements.push(new Reference(varName, { role: "ident" }, loc));
|
|
948
|
+
source += INTERPOLATION_PLACEHOLDER;
|
|
949
|
+
} else source += l.value;
|
|
950
|
+
return new InterpolatedSelector(new Interpolated({
|
|
951
|
+
source,
|
|
952
|
+
replacements
|
|
953
|
+
}, { role: "ident" }, loc), {}, loc);
|
|
954
|
+
}
|
|
955
|
+
_buildLessPseudo(type, span, children, state, raw, fields, triviaLog, loc) {
|
|
956
|
+
const pseudo = super.buildNode(type, span, children, state, raw, fields, triviaLog);
|
|
957
|
+
let pseudoArg = pseudo.arg;
|
|
958
|
+
if (pseudoArg === void 0) {
|
|
959
|
+
const open = children.findIndex((c) => c._tag === "leaf" && c.value === "(");
|
|
960
|
+
if (open >= 0) {
|
|
961
|
+
const inner = children[open + 1];
|
|
962
|
+
const isClose = inner?._tag === "leaf" && inner.value === ")";
|
|
963
|
+
if (inner !== void 0 && !isClose) {
|
|
964
|
+
const recovered = typeof inner === "string" ? this._lessKeyword(inner, loc) : inner._tag === "leaf" ? this._lessKeyword(inner.value, loc) : inner;
|
|
965
|
+
pseudo.arg = recovered;
|
|
966
|
+
pseudoArg = recovered;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
if (Array.isArray(pseudoArg)) pseudo.arg = pseudoArg.map((item) => typeof item === "string" && item !== " " ? this._lessKeyword(item, loc) : item);
|
|
971
|
+
return pseudo;
|
|
972
|
+
}
|
|
973
|
+
_buildLessDeclaration(raw, loc) {
|
|
974
|
+
const items = spannedComponents(raw);
|
|
975
|
+
const decl = this._buildDeclaration(raw, loc);
|
|
976
|
+
const colonIdx = items.findIndex((i) => i.comp === ":");
|
|
977
|
+
const merge = colonIdx > 0 ? items[colonIdx - 1]?.comp : void 0;
|
|
978
|
+
const assign = merge === "+_" ? "+_:" : merge === "+" ? "+,:" : ":";
|
|
979
|
+
const d = decl;
|
|
980
|
+
d._options = {
|
|
981
|
+
...d._options ?? {},
|
|
982
|
+
assign
|
|
983
|
+
};
|
|
984
|
+
if (typeof d.name === "string" && d.name) {
|
|
985
|
+
const nameStr = d.name;
|
|
986
|
+
decl.name = nameStr.includes("@{") || nameStr.includes("${") ? getInterpolatedNode(nameStr, loc) : nameStr;
|
|
987
|
+
}
|
|
988
|
+
const dvRaw = decl.value;
|
|
989
|
+
if (dvRaw && typeof dvRaw === "object" && dvRaw.type === "Operation") {
|
|
990
|
+
const f = dvRaw;
|
|
991
|
+
if (shouldOperateWithMathFrames({
|
|
992
|
+
mathMode: this.mathMode,
|
|
993
|
+
parenFrames: [],
|
|
994
|
+
calcFrames: 0
|
|
995
|
+
}, f.operator, f.left, f.right)) decl.value = new Expression(dvRaw, { parens: true }, loc);
|
|
996
|
+
return decl;
|
|
997
|
+
}
|
|
998
|
+
const dvList = decl.value;
|
|
999
|
+
if (dvList && dvList.type === "List" && dvList.options?.sep === "/" && this.mathMode === "always") {
|
|
1000
|
+
const items = dvList.value;
|
|
1001
|
+
if (items.length >= 2 && items.every((it) => this._isDivisionLike(it))) {
|
|
1002
|
+
let op = items[0];
|
|
1003
|
+
for (let i = 1; i < items.length; i++) op = new Operation([
|
|
1004
|
+
op,
|
|
1005
|
+
"/",
|
|
1006
|
+
items[i]
|
|
1007
|
+
], void 0, loc);
|
|
1008
|
+
const f = op;
|
|
1009
|
+
decl.value = shouldOperateWithMathFrames({
|
|
1010
|
+
mathMode: this.mathMode,
|
|
1011
|
+
parenFrames: [],
|
|
1012
|
+
calcFrames: 0
|
|
1013
|
+
}, f.operator, f.left, f.right) ? new Expression(op, { parens: true }, loc) : op;
|
|
1014
|
+
return decl;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
const dv = decl.value;
|
|
1018
|
+
const dvIsArray = Array.isArray(dv);
|
|
1019
|
+
const dvHasNode = dvIsArray && dv.some((p) => !!p && typeof p === "object" && "type" in p);
|
|
1020
|
+
if (dvIsArray && dvHasNode) {
|
|
1021
|
+
const src = this._source.slice(loc.start, loc.end);
|
|
1022
|
+
const colonPos = src.indexOf(":");
|
|
1023
|
+
const rawVal = colonPos >= 0 ? src.slice(colonPos + 1).trim().replace(/;\s*$/, "").trim() : src;
|
|
1024
|
+
if (/^progid:/i.test(rawVal)) decl.value = this._buildLegacyMSFilter(rawVal, loc);
|
|
1025
|
+
}
|
|
1026
|
+
return decl;
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Port of `processLegacyMSFilterToken` (lessRecursiveParser.ts): a `progid:…`
|
|
1030
|
+
* filter value string → Interpolated(role=any) with `@var` runs templated out,
|
|
1031
|
+
* or a plain Keyword when the run has no variables.
|
|
1032
|
+
*/
|
|
1033
|
+
_buildLegacyMSFilter(source, loc) {
|
|
1034
|
+
source = source.replace(/\s*=\s*/g, "=");
|
|
1035
|
+
const varRe = /@([_a-zA-Z\xA0-][-_a-zA-Z0-9\xA0-]*)/g;
|
|
1036
|
+
const matches = [...source.matchAll(varRe)];
|
|
1037
|
+
if (matches.length === 0) return this._lessKeyword(source, loc);
|
|
1038
|
+
return new Interpolated({
|
|
1039
|
+
source: source.replace(varRe, (_full, _name, offset, fullSource) => {
|
|
1040
|
+
const key = fullSource.slice(0, offset).match(/([A-Za-z]+)=$/)?.[1];
|
|
1041
|
+
if (key && /colorstr$/i.test(key)) return `"${INTERPOLATION_PLACEHOLDER}"`;
|
|
1042
|
+
return INTERPOLATION_PLACEHOLDER;
|
|
1043
|
+
}),
|
|
1044
|
+
replacements: matches.map((match) => createInterpolatedReference("@", match[1], loc))
|
|
1045
|
+
}, { role: "any" }, loc);
|
|
1046
|
+
}
|
|
1047
|
+
_buildAmpersand(children, loc) {
|
|
1048
|
+
const ls = children.filter((c) => c._tag === "leaf");
|
|
1049
|
+
if (!ls.some((l) => l.value === "(")) {
|
|
1050
|
+
const image = ls[0]?.value ?? "&";
|
|
1051
|
+
return new Ampersand(this._ampersandTemplateValue(image), {}, loc);
|
|
1052
|
+
}
|
|
1053
|
+
const trimmed = (ls.find((l) => l.value !== "&" && l.value !== "(" && l.value !== ")")?.value ?? "").trim();
|
|
1054
|
+
return new Ampersand(trimmed === "nil" ? "" : trimmed.replace(/^(['"])([\s\S]*)\1$/, "$2"), {}, loc);
|
|
1055
|
+
}
|
|
1056
|
+
/** The append value of a `&`-led ampersand token: the suffix after `&` (`&-bar` →
|
|
1057
|
+
* `-bar`, `&1` → `1`), or undefined for a bare `&`. */
|
|
1058
|
+
_ampersandTemplateValue(image) {
|
|
1059
|
+
return image === "&" ? void 0 : image.slice(1) || void 0;
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* A single extend target inside `extend( … )`: a complex selector plus its
|
|
1063
|
+
* optional `all` / `!all` flag (selectors.ts `complexSelector`'s OPTION2 flag).
|
|
1064
|
+
* Produced as an `Extend` carrier the surrounding pseudo/statement groups.
|
|
1065
|
+
*/
|
|
1066
|
+
_buildExtendTarget(_children, raw, loc) {
|
|
1067
|
+
const comps = spannedComponents(raw);
|
|
1068
|
+
const isFlag = (c) => typeof c === "string" && /^!?all$/.test(c);
|
|
1069
|
+
const flag = comps.some((c) => isFlag(c.comp)) ? ExtendFlag.All : ExtendFlag.Exact;
|
|
1070
|
+
const targetComp = comps.find((c) => !isFlag(c.comp))?.comp;
|
|
1071
|
+
return new Extend({
|
|
1072
|
+
target: typeof targetComp === "string" ? targetComp : targetComp ?? "&",
|
|
1073
|
+
flag
|
|
1074
|
+
}, {}, loc);
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* `:extend( … )` pseudo form (selectors.ts `extend`): groups its ExtendTarget
|
|
1078
|
+
* children. Targets sharing one flag collapse to a single Extend whose target is
|
|
1079
|
+
* a SelectorList (or the lone selector); mixed flags stay as one Extend each,
|
|
1080
|
+
* returned in a List. Mirrors mergeExtends' target-and-flag grouping.
|
|
1081
|
+
*/
|
|
1082
|
+
_buildExtendPseudo(children, loc) {
|
|
1083
|
+
const targets = nodeChildren(children).filter((n) => n.type === "Extend");
|
|
1084
|
+
const firstFlag = targets[0].flag;
|
|
1085
|
+
if (targets.every((t) => t.flag === firstFlag)) return new Extend({
|
|
1086
|
+
target: targets.length === 1 ? targets[0].target : this._makeSelectorList(targets.map((t) => t.target), loc),
|
|
1087
|
+
flag: firstFlag
|
|
1088
|
+
}, {}, loc);
|
|
1089
|
+
return new List(targets.map((t) => new Extend({
|
|
1090
|
+
target: t.target,
|
|
1091
|
+
flag: t.flag
|
|
1092
|
+
}, {}, loc)), {}, loc);
|
|
1093
|
+
}
|
|
1094
|
+
/**
|
|
1095
|
+
* `&:extend( … );` (or bare `:extend( … );`) statement form (selectors.ts
|
|
1096
|
+
* `ampersandExtend`). The ExtendPseudo child already carries the grouped
|
|
1097
|
+
* Extend(s); the leading `&` is just the statement marker.
|
|
1098
|
+
*/
|
|
1099
|
+
_buildExtendStatement(children, _raw, _loc) {
|
|
1100
|
+
return nodeChildren(children).find((n) => n.type === "Extend" || n.type === "List");
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Decode a single `[key]` accessor into its Less lookup-key AST — the ONE shared
|
|
1104
|
+
* decoder for every accessor-chain builder path (var-decl value, declaration value,
|
|
1105
|
+
* at-rule prelude, namespace ref). Accepts either a raw authored key STRING (the
|
|
1106
|
+
* text inside the brackets, e.g. `@@foo`, `$@x`, `bar`, ``), or a SquareParen `Paren`
|
|
1107
|
+
* node whose inner content the grammar already parsed (a raw string or a `@var`
|
|
1108
|
+
* Reference). Returns the key exactly as the reference lookupOrCall production would:
|
|
1109
|
+
*
|
|
1110
|
+
* `[]` → -1 (index, empty)
|
|
1111
|
+
* `[@@name]` → Reference{type:variable,key:name} (dynamic variable lookup)
|
|
1112
|
+
* `[$@name]` / `[@$name]` → Quoted(Interpolated(@name)) (dynamic property lookup)
|
|
1113
|
+
* `[@name]` → 'name' (static variable lookup; bare string key)
|
|
1114
|
+
* `[$name]` → Quoted('name') (property lookup; `$` marker dropped)
|
|
1115
|
+
* `[name]` → Quoted('name') (index)
|
|
1116
|
+
*
|
|
1117
|
+
* Exactly one `@`/`$` marker is the lookup marker and is never kept.
|
|
1118
|
+
*/
|
|
1119
|
+
_decodeAccessorKey(rawTextOrNode, loc) {
|
|
1120
|
+
let rawText;
|
|
1121
|
+
let innerVal;
|
|
1122
|
+
if (typeof rawTextOrNode === "string") rawText = rawTextOrNode.trim();
|
|
1123
|
+
else {
|
|
1124
|
+
innerVal = rawTextOrNode.node ?? rawTextOrNode.value;
|
|
1125
|
+
if (!innerVal || this._isEmptyKeywordLike(innerVal)) return -1;
|
|
1126
|
+
if (typeof innerVal === "string") rawText = innerVal.trim();
|
|
1127
|
+
else if (typeof innerVal === "object" && innerVal.type === "Reference" && typeof innerVal.key === "string") rawText = "@" + innerVal.key;
|
|
1128
|
+
else if (this._isKeywordLike(innerVal) && typeof innerVal.value === "string") rawText = innerVal.value.trim();
|
|
1129
|
+
}
|
|
1130
|
+
if (rawText === void 0 || rawText === "") {
|
|
1131
|
+
if (rawText === "") return -1;
|
|
1132
|
+
return innerVal?.key ?? innerVal;
|
|
1133
|
+
}
|
|
1134
|
+
if (rawText.startsWith("@@")) return new Reference(rawText.slice(2), { type: "variable" }, loc);
|
|
1135
|
+
if (rawText.startsWith("$@") || rawText.startsWith("@$")) return new Quoted(new Interpolated({
|
|
1136
|
+
source: INTERPOLATION_PLACEHOLDER,
|
|
1137
|
+
replacements: [new Reference(rawText.slice(2), { role: "ident" }, loc)]
|
|
1138
|
+
}, { role: "ident" }, loc), {}, loc);
|
|
1139
|
+
if (rawText.startsWith("@")) return rawText.slice(1);
|
|
1140
|
+
return new Quoted(rawText.replace(/^\$/, ""), {}, loc);
|
|
1141
|
+
}
|
|
1142
|
+
_assembleSegment(seg, loc) {
|
|
1143
|
+
const result = super._assembleSegment(seg, loc);
|
|
1144
|
+
const isNsNameEarly = (c) => typeof c === "string" && /^[#.]-?[_a-zA-Z]/.test(c.trim());
|
|
1145
|
+
if (!Array.isArray(result)) {
|
|
1146
|
+
if (isNsNameEarly(result)) {
|
|
1147
|
+
const segs = result.trim().match(/[#.][^#.]*/g) ?? [result.trim()];
|
|
1148
|
+
const nameKey = segs.length === 1 ? segs[0] : segs;
|
|
1149
|
+
const rawKey = segs.length > 1 ? segs.join("") : void 0;
|
|
1150
|
+
return new Reference({
|
|
1151
|
+
key: nameKey,
|
|
1152
|
+
...rawKey ? { rawKey } : {}
|
|
1153
|
+
}, {
|
|
1154
|
+
type: "mixin-ruleset",
|
|
1155
|
+
role: "name"
|
|
1156
|
+
}, loc);
|
|
1157
|
+
}
|
|
1158
|
+
return result;
|
|
1159
|
+
}
|
|
1160
|
+
if (result.length < 2) return result;
|
|
1161
|
+
const comps = result;
|
|
1162
|
+
const isNsName = (c) => typeof c === "string" && /^[#.]-?[_a-zA-Z-]/.test(c.trim());
|
|
1163
|
+
const isSquareParen = (c) => !!c && typeof c === "object" && c.type === "Paren" && c._options?.delimiter === "square";
|
|
1164
|
+
const isRoundParen = (c) => !!c && typeof c === "object" && c.type === "Paren" && c._options?.delimiter !== "square";
|
|
1165
|
+
if (!isNsName(comps[0])) return comps;
|
|
1166
|
+
const splitNsToken = (s) => s.match(/[#.][^#.]*/g) ?? [s];
|
|
1167
|
+
let i = 0;
|
|
1168
|
+
const pathSegs = [];
|
|
1169
|
+
while (i < comps.length && isNsName(comps[i])) {
|
|
1170
|
+
pathSegs.push(...splitNsToken(comps[i].trim()));
|
|
1171
|
+
i++;
|
|
1172
|
+
}
|
|
1173
|
+
const rawPathText = pathSegs.join("");
|
|
1174
|
+
const nameKey = pathSegs.length === 1 ? pathSegs[0] : pathSegs;
|
|
1175
|
+
const rawKey = pathSegs.length > 1 ? rawPathText : void 0;
|
|
1176
|
+
let base = new Reference({
|
|
1177
|
+
key: nameKey,
|
|
1178
|
+
...rawKey ? { rawKey } : {}
|
|
1179
|
+
}, {
|
|
1180
|
+
type: "mixin-ruleset",
|
|
1181
|
+
role: "name"
|
|
1182
|
+
}, loc);
|
|
1183
|
+
if (i >= comps.length || !isSquareParen(comps[i]) && !isRoundParen(comps[i])) return i === comps.length ? base : comps;
|
|
1184
|
+
while (i < comps.length) {
|
|
1185
|
+
const item = comps[i];
|
|
1186
|
+
if (isSquareParen(item)) {
|
|
1187
|
+
const innerKey = this._decodeAccessorKey(item, loc);
|
|
1188
|
+
const accType = typeof innerKey === "string" || innerKey != null && typeof innerKey === "object" && innerKey.type === "Reference" ? "variable" : "index";
|
|
1189
|
+
base = new Reference({
|
|
1190
|
+
target: base,
|
|
1191
|
+
key: innerKey
|
|
1192
|
+
}, { type: accType }, loc);
|
|
1193
|
+
i++;
|
|
1194
|
+
} else if (isRoundParen(item)) {
|
|
1195
|
+
const innerContent = item.value ?? item.node;
|
|
1196
|
+
const argsNode = this._isEmptyKeywordLike(innerContent) ? null : this._parenToArgs(item, loc);
|
|
1197
|
+
const callPayload = { name: base };
|
|
1198
|
+
if (argsNode) callPayload.args = argsNode;
|
|
1199
|
+
base = new Call(callPayload, {}, loc);
|
|
1200
|
+
i++;
|
|
1201
|
+
} else break;
|
|
1202
|
+
}
|
|
1203
|
+
return i === comps.length ? base : comps;
|
|
1204
|
+
}
|
|
1205
|
+
_buildEscapedValue(children, loc) {
|
|
1206
|
+
const inner = nodeChildren(children)[0];
|
|
1207
|
+
if (!inner) return this._lessKeyword("", loc);
|
|
1208
|
+
if (inner instanceof Quoted) return new Quoted(inner.value, {
|
|
1209
|
+
quote: inner.quote,
|
|
1210
|
+
escaped: true
|
|
1211
|
+
}, loc);
|
|
1212
|
+
const n = inner;
|
|
1213
|
+
n._options = {
|
|
1214
|
+
...n._options ?? {},
|
|
1215
|
+
escaped: true
|
|
1216
|
+
};
|
|
1217
|
+
return inner;
|
|
1218
|
+
}
|
|
1219
|
+
_buildInterpValue(raw, loc) {
|
|
1220
|
+
const result = getInterpolatedOrString(spannedComponents(raw).map((i) => typeof i.comp === "string" ? i.comp : "").join(""), loc);
|
|
1221
|
+
if (typeof result === "string") return this._lessKeyword(result, loc);
|
|
1222
|
+
return result;
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* A quoted string value. Unlike plain CSS, Less interpolates `@{var}` / `${prop}`
|
|
1226
|
+
* inside quoted (and escaped `~"…"`) strings and inside `@import` paths. When the
|
|
1227
|
+
* raw content holds an interpolation, split it into an `Interpolated` value the same
|
|
1228
|
+
* way the reference parser's `processStringInterpolation` does (source with
|
|
1229
|
+
* INTERPOLATION_PLACEHOLDER, `@var`/`$prop` references in `replacements`); otherwise
|
|
1230
|
+
* fall through to the plain CSS builder (bare-string value).
|
|
1231
|
+
*/
|
|
1232
|
+
/**
|
|
1233
|
+
* The Less `Url` grammar tokenizes the inner string as bare leaves (no child
|
|
1234
|
+
* node), so the css base builder wraps a quoted url body in a raw-string
|
|
1235
|
+
* `Quoted` — which never interpolates `@{var}`/`${prop}`. Less 4.x DOES resolve
|
|
1236
|
+
* interpolation inside a QUOTED url body (`url("@{base}/@{i}.svg")`), the same as
|
|
1237
|
+
* any other quoted string, so route the quoted inner through the same
|
|
1238
|
+
* interpolation-aware construction `_buildQuoted` uses. Unquoted url bodies stay
|
|
1239
|
+
* verbatim (Less 4.x leaves `url(@{x})` literal), as does a quoted body with no
|
|
1240
|
+
* interpolation.
|
|
1241
|
+
*/
|
|
1242
|
+
_buildUrl(children, loc) {
|
|
1243
|
+
if (nodeChildren(children)[0]) return super._buildUrl(children, loc);
|
|
1244
|
+
const inner = children.filter((c) => c._tag === "leaf").filter((l) => !/^url\($/i.test(l.value) && l.value !== ")").map((l) => l.value).join("").trim();
|
|
1245
|
+
const quote = inner[0];
|
|
1246
|
+
if ((quote === "\"" || quote === "'") && inner.at(-1) === quote) {
|
|
1247
|
+
const body = inner.slice(1, -1);
|
|
1248
|
+
if (body.includes("@{") || body.includes("${")) return new Url(new Quoted(this._buildStringInterpolation(body, loc), { quote }, loc), void 0, loc);
|
|
1249
|
+
}
|
|
1250
|
+
return super._buildUrl(children, loc);
|
|
1251
|
+
}
|
|
1252
|
+
_buildQuoted(children, loc) {
|
|
1253
|
+
const text = children.filter((c) => c._tag === "leaf").map((l) => l.value).join("");
|
|
1254
|
+
const inner = text.slice(1, -1);
|
|
1255
|
+
if (inner.includes("@{") || inner.includes("${")) return new Quoted(this._buildStringInterpolation(inner, loc), { quote: text[0] }, loc);
|
|
1256
|
+
return super._buildQuoted(children, loc);
|
|
1257
|
+
}
|
|
1258
|
+
/**
|
|
1259
|
+
* Build an escaped `~'…'` Quoted (at-rule prelude position), interpolating any
|
|
1260
|
+
* `@{var}` / `${prop}` in its body the same way `_buildQuoted` does — so
|
|
1261
|
+
* `~'@{a} / @{b}'` renders its substituted values instead of literal text.
|
|
1262
|
+
*/
|
|
1263
|
+
_buildEscapedQuoted(inner, quote, loc) {
|
|
1264
|
+
return new Quoted(inner.includes("@{") || inner.includes("${") ? this._buildStringInterpolation(inner, loc) : inner, {
|
|
1265
|
+
quote,
|
|
1266
|
+
escaped: true
|
|
1267
|
+
}, loc);
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Split a quoted-string body on `@{…}` / `${…}` interpolations into an
|
|
1271
|
+
* `Interpolated` (source + reference replacements). Port of the reference parser's
|
|
1272
|
+
* `processStringInterpolation`/`findInterpolations` (productions/values.ts): brace
|
|
1273
|
+
* matching is nesting-aware, and a nested-interpolated name resolves through a
|
|
1274
|
+
* variable Reference wrapped in an Expression.
|
|
1275
|
+
*/
|
|
1276
|
+
_buildStringInterpolation(value, loc) {
|
|
1277
|
+
const matches = this._findInterpolations(value);
|
|
1278
|
+
const replacements = [];
|
|
1279
|
+
let source = value;
|
|
1280
|
+
let offset = 0;
|
|
1281
|
+
for (const match of matches) {
|
|
1282
|
+
const adjustedStart = match.start - offset;
|
|
1283
|
+
const adjustedEnd = match.end - offset;
|
|
1284
|
+
source = source.slice(0, adjustedStart) + INTERPOLATION_PLACEHOLDER + source.slice(adjustedEnd);
|
|
1285
|
+
offset += match.end - match.start - INTERPOLATION_PLACEHOLDER.length;
|
|
1286
|
+
if (match.content.includes("@{") || match.content.includes("${")) {
|
|
1287
|
+
const nestedRef = new Reference({ key: this._buildStringInterpolation(match.content, loc) }, {
|
|
1288
|
+
type: "variable",
|
|
1289
|
+
role: "ident"
|
|
1290
|
+
}, loc);
|
|
1291
|
+
replacements.push(new Expression(nestedRef, void 0, loc));
|
|
1292
|
+
} else replacements.push(createInterpolatedReference(match.prefix, match.content, loc));
|
|
1293
|
+
}
|
|
1294
|
+
return new Interpolated({
|
|
1295
|
+
source,
|
|
1296
|
+
replacements
|
|
1297
|
+
}, { role: "ident" }, loc);
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Locate `@{…}` / `${…}` interpolation runs in a string, counting nested braces so
|
|
1301
|
+
* `@{@{x}}` and `@{fn(a, b)}` are matched whole. Returns start/end/prefix/content.
|
|
1302
|
+
*/
|
|
1303
|
+
_findInterpolations(value) {
|
|
1304
|
+
const matches = [];
|
|
1305
|
+
let i = 0;
|
|
1306
|
+
while (i < value.length) if ((value[i] === "@" || value[i] === "$") && value[i + 1] === "{") {
|
|
1307
|
+
const prefix = value[i];
|
|
1308
|
+
const start = i;
|
|
1309
|
+
i += 2;
|
|
1310
|
+
let braceCount = 1;
|
|
1311
|
+
const contentStart = i;
|
|
1312
|
+
while (i < value.length && braceCount > 0) {
|
|
1313
|
+
if (value[i] === "{") braceCount++;
|
|
1314
|
+
else if (value[i] === "}") braceCount--;
|
|
1315
|
+
i++;
|
|
1316
|
+
}
|
|
1317
|
+
if (braceCount === 0) matches.push({
|
|
1318
|
+
start,
|
|
1319
|
+
end: i,
|
|
1320
|
+
prefix,
|
|
1321
|
+
content: value.slice(contentStart, i - 1)
|
|
1322
|
+
});
|
|
1323
|
+
} else i++;
|
|
1324
|
+
return matches;
|
|
1325
|
+
}
|
|
1326
|
+
_buildCall(rawChildren, loc) {
|
|
1327
|
+
const call = super._buildCall(rawChildren, loc);
|
|
1328
|
+
const key = typeof call.name === "string" ? call.name : "";
|
|
1329
|
+
this._checkMixedArgDelimiters(call.args, "function", loc);
|
|
1330
|
+
const loweredArgs = this._lowerSemiArgs(call.args, loc);
|
|
1331
|
+
return new Call({
|
|
1332
|
+
name: new Reference(key, {
|
|
1333
|
+
type: "function",
|
|
1334
|
+
fallbackValue: true
|
|
1335
|
+
}, loc),
|
|
1336
|
+
args: loweredArgs
|
|
1337
|
+
}, { silentFail: true }, loc);
|
|
1338
|
+
}
|
|
1339
|
+
_buildLessCustomDecl(children, loc) {
|
|
1340
|
+
const ls = children.filter((c) => c._tag === "leaf");
|
|
1341
|
+
const propNameText = ls[0]?.value ?? "";
|
|
1342
|
+
const name = propNameText.includes("@") || propNameText.includes("$") ? getInterpolatedNode(propNameText, loc) : propNameText;
|
|
1343
|
+
const valueNodes = nodeChildren(children);
|
|
1344
|
+
if (valueNodes.length > 0) return new CustomDeclaration({
|
|
1345
|
+
name,
|
|
1346
|
+
value: valueNodes.length === 1 ? valueNodes[0] : valueNodes
|
|
1347
|
+
}, void 0, loc);
|
|
1348
|
+
const valueText = ls.slice(2).filter((l) => l.value !== ";").map((l) => l.value).join("").trim();
|
|
1349
|
+
return new CustomDeclaration({
|
|
1350
|
+
name,
|
|
1351
|
+
value: this._lessKeyword(valueText, loc)
|
|
1352
|
+
}, void 0, loc);
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* `--foo: { color: @a; }` — a curly-brace custom-property value whose body
|
|
1356
|
+
* opportunistically structured as a declaration list (customCurlyBlock in the
|
|
1357
|
+
* grammar), so nested `@var`/calls evaluate normally instead of staying opaque
|
|
1358
|
+
* text. Wrapped in a Block(type: 'curly') so `{`/`}` re-render around it.
|
|
1359
|
+
*/
|
|
1360
|
+
_buildLessCustomBlock(children, loc) {
|
|
1361
|
+
return new Block(new Sequence(nodeChildren(children), void 0, loc), { type: "curly" }, loc);
|
|
1362
|
+
}
|
|
1363
|
+
_warnDeprecatedValue(span) {
|
|
1364
|
+
const text = this._source.slice(span.start, span.end);
|
|
1365
|
+
if (/\d\s*\.\//.test(text)) this._warn("The ./ operator is deprecated and will be removed.", "dot-slash-operator");
|
|
1366
|
+
}
|
|
1367
|
+
_warnCustomPropVars(span) {
|
|
1368
|
+
const text = this._source.slice(span.start, span.end);
|
|
1369
|
+
const colon = text.indexOf(":");
|
|
1370
|
+
const value = colon >= 0 ? text.slice(colon + 1) : text;
|
|
1371
|
+
const at = value.match(/@[a-zA-Z][\w-]*/);
|
|
1372
|
+
if (at && !value.includes("@{")) this._warn(`"${at[0]}" in custom property values is treated as literal text. Use @{${at[0].slice(1)}} for interpolation.`, "variable-in-unknown-value");
|
|
1373
|
+
const dollar = value.match(/\$[a-zA-Z][\w-]*/);
|
|
1374
|
+
if (dollar && !value.includes("${")) this._warn(`"${dollar[0]}" in custom property values is treated as literal text. Use \${${dollar[0].slice(1)}} for interpolation.`, "property-in-unknown-value");
|
|
1375
|
+
}
|
|
1376
|
+
_warnAtRulePreludeVars(span) {
|
|
1377
|
+
const text = this._source.slice(span.start, span.end);
|
|
1378
|
+
const varName = this._firstTopLevelBareAtVar(text);
|
|
1379
|
+
if (varName !== null) this._warn(`A bare "@${varName}" in an at-rule prelude is deprecated. Use @{${varName}} interpolation instead.`, "variable-in-at-rule-prelude");
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* The first bare `@ident` reference in an at-rule prelude that is deprecated
|
|
1383
|
+
* under Less 4.x PR #4462 (`variable-in-at-rule-prelude`), or null when there
|
|
1384
|
+
* is none. A bare `@var` in a *structural* (top-level) prelude position still
|
|
1385
|
+
* resolves but is deprecated in favour of `@{var}` interpolation; the scan
|
|
1386
|
+
* therefore ignores, mirroring `hasTopLevelBareVariable` / `warnBareAtRuleVariable`:
|
|
1387
|
+
* - the leading at-rule name itself (`@media`, `@-moz-document`, …);
|
|
1388
|
+
* - `@{ident}` interpolation — the supported migration target;
|
|
1389
|
+
* - a `@var` inside `(...)` — a declaration/feature value (e.g. the `@size`
|
|
1390
|
+
* in `@media (min-width: @size)`), which stays valid;
|
|
1391
|
+
* - `@`/`(` characters inside string literals, which are not structural.
|
|
1392
|
+
*/
|
|
1393
|
+
_firstTopLevelBareAtVar(text) {
|
|
1394
|
+
let depth = 0;
|
|
1395
|
+
let i = /^\s*@-?[\w-]+/.exec(text)?.[0].length ?? 0;
|
|
1396
|
+
for (; i < text.length; i++) {
|
|
1397
|
+
const c = text[i];
|
|
1398
|
+
if (c === "\"" || c === "'") {
|
|
1399
|
+
i++;
|
|
1400
|
+
while (i < text.length && text[i] !== c) i++;
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
if (c === "@") {
|
|
1404
|
+
if (text[i + 1] === "{") {
|
|
1405
|
+
i += 2;
|
|
1406
|
+
while (i < text.length && text[i] !== "}") i++;
|
|
1407
|
+
continue;
|
|
1408
|
+
}
|
|
1409
|
+
if (depth === 0) {
|
|
1410
|
+
const m = /^@(-?[a-zA-Z\x80-][\w-]*)/.exec(text.slice(i));
|
|
1411
|
+
if (m) return m[1];
|
|
1412
|
+
}
|
|
1413
|
+
continue;
|
|
1414
|
+
}
|
|
1415
|
+
if (c === "{") break;
|
|
1416
|
+
if (c === "(") depth++;
|
|
1417
|
+
else if (c === ")" && depth > 0) depth--;
|
|
1418
|
+
}
|
|
1419
|
+
return null;
|
|
1420
|
+
}
|
|
1421
|
+
_buildMixinCall(children, raw, loc) {
|
|
1422
|
+
const ls = children.filter((c) => c._tag === "leaf");
|
|
1423
|
+
const markImportant = ls.some((l) => l.value === "!");
|
|
1424
|
+
const nameParts = [];
|
|
1425
|
+
for (const l of ls) {
|
|
1426
|
+
if (l.value === "(" || l.value === ";" || l.value === "!") break;
|
|
1427
|
+
nameParts.push(l.value);
|
|
1428
|
+
}
|
|
1429
|
+
const name = nameParts.join("");
|
|
1430
|
+
const argsList = nodeChildren(children).find((n) => n.type === "List");
|
|
1431
|
+
const hasArgs = argsList && argsList.value?.length;
|
|
1432
|
+
if (argsList === void 0) this._warn("Calling a mixin without parentheses is deprecated", "mixin-call-no-parens");
|
|
1433
|
+
else {
|
|
1434
|
+
const src = this._source.slice(loc.start, loc.end);
|
|
1435
|
+
if (/^\S+\s+\(/.test(src)) this._warn("Whitespace between a mixin name and parentheses is deprecated", "mixin-call-whitespace");
|
|
1436
|
+
}
|
|
1437
|
+
return new Call({
|
|
1438
|
+
name: new Reference({ key: name }, {
|
|
1439
|
+
type: "mixin-ruleset",
|
|
1440
|
+
role: "name"
|
|
1441
|
+
}, loc),
|
|
1442
|
+
args: hasArgs ? this._convertArgsForCall(argsList, loc) : void 0
|
|
1443
|
+
}, { markImportant }, loc);
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* `@name(...)` (no `:`) → a detached-ruleset variable CALL. Faithful port of
|
|
1447
|
+
* `varDeclarationOrCall`'s LParen branch (selectors.ts): build a `Reference`
|
|
1448
|
+
* over the var name (`type: 'variable', role: 'name'`), wrap in a `Call` with
|
|
1449
|
+
* the (optional) args, and wrap THAT in an `Expression` (a top-level variable
|
|
1450
|
+
* call is an expression, not a parenthesized one). `!important` sets
|
|
1451
|
+
* `markImportant` on the Call, mirroring the production.
|
|
1452
|
+
*/
|
|
1453
|
+
_buildVarCall(children, raw, loc) {
|
|
1454
|
+
const ls = children.filter((c) => c._tag === "leaf");
|
|
1455
|
+
const markImportant = ls.some((l) => l.value === "!");
|
|
1456
|
+
const rawName = ls.find((l) => l.value.startsWith("@"))?.value ?? "";
|
|
1457
|
+
const name = rawName.startsWith("@") ? rawName.slice(1) : rawName;
|
|
1458
|
+
const nameRef = new Reference({ key: this._lessKeyword(name, loc) }, {
|
|
1459
|
+
type: "variable",
|
|
1460
|
+
role: "name"
|
|
1461
|
+
}, loc);
|
|
1462
|
+
const argsList = nodeChildren(children).find((n) => n.type === "List");
|
|
1463
|
+
const hasArgs = argsList && argsList.value?.length;
|
|
1464
|
+
if (!hasArgs && KNOWN_AT_RULE_VAR_NAME_RE.test(name)) this._warn("Using known at-rule names as variables is deprecated", "at-rule-variable");
|
|
1465
|
+
return new Expression(new Call({
|
|
1466
|
+
name: nameRef,
|
|
1467
|
+
args: hasArgs ? this._convertArgsForCall(argsList, loc) : void 0
|
|
1468
|
+
}, markImportant ? { markImportant: true } : void 0, loc), void 0, loc);
|
|
1469
|
+
}
|
|
1470
|
+
/** `...` or `@name...` variadic arg → `Rest`. Definition-shape (string name); a
|
|
1471
|
+
* CALL turns it into `Rest(Reference)` via `_convertArgsForCall`. */
|
|
1472
|
+
_buildRest(raw, loc) {
|
|
1473
|
+
const nameItem = spannedComponents(raw).find((i) => typeof i.comp === "string" && i.comp.startsWith("@"));
|
|
1474
|
+
return new Rest(nameItem ? String(nameItem.comp).slice(1) : "", {}, loc);
|
|
1475
|
+
}
|
|
1476
|
+
/** `@name: value` named arg/param → `VarDeclaration`. The value is assembled by the
|
|
1477
|
+
* shared value builder (`_assembleValue`) — the same machinery as a declaration
|
|
1478
|
+
* value, so trivia and Keyword-ification are handled and no manual trimming is
|
|
1479
|
+
* needed. Named args flow through function calls too; the runtime decides whether
|
|
1480
|
+
* the target accepts them. */
|
|
1481
|
+
_buildNamedArg(raw, loc) {
|
|
1482
|
+
const items = spannedComponents(raw);
|
|
1483
|
+
const colonIdx = items.findIndex((i) => i.comp === ":");
|
|
1484
|
+
const nameItem = items.find((i) => typeof i.comp === "string" && i.comp.startsWith("@"));
|
|
1485
|
+
const name = String(nameItem?.comp ?? "").slice(1);
|
|
1486
|
+
const valueItems = colonIdx >= 0 ? items.slice(colonIdx + 1) : [];
|
|
1487
|
+
const { value } = this._assembleValue(valueItems, loc);
|
|
1488
|
+
let paramValue;
|
|
1489
|
+
if (typeof value === "string") paramValue = this._valueKeyword(value, loc);
|
|
1490
|
+
else if (Array.isArray(value)) paramValue = new Sequence(value.map((c) => this._argComponent(c, loc)), void 0, loc);
|
|
1491
|
+
else paramValue = value;
|
|
1492
|
+
return new VarDeclaration({
|
|
1493
|
+
name,
|
|
1494
|
+
value: paramValue
|
|
1495
|
+
}, {}, loc);
|
|
1496
|
+
}
|
|
1497
|
+
/** Mixin-call args are assembled by the SAME builder as function-call args
|
|
1498
|
+
* (`_assembleArgs` via `_betweenParens`) — identical comma/semicolon and value
|
|
1499
|
+
* handling. Named args are `VarDeclaration`s and variadic args `Rest`, which pass
|
|
1500
|
+
* through as single components. A bare `@name` is a `Reference` (the call shape);
|
|
1501
|
+
* the mixin-DEFINITION builder reinterprets a lone `@name` as a param. */
|
|
1502
|
+
_buildMixinArgs(raw, loc) {
|
|
1503
|
+
const inner = this._betweenParens(spannedComponents(raw));
|
|
1504
|
+
const args = this._assembleArgs(inner, loc);
|
|
1505
|
+
this._checkMixedArgDelimiters(args, "mixin", loc);
|
|
1506
|
+
return this._lowerSemiArgs(args, loc);
|
|
1507
|
+
}
|
|
1508
|
+
/** Less forbids mixing the COMMA and SEMICOLON argument separators: once a
|
|
1509
|
+
* semicolon separates args, a comma is a value-list separator, so a semicolon-group
|
|
1510
|
+
* may not hold 2+ named params (`@a: 1, @b: 2`). `_assembleArgs` renders such a group
|
|
1511
|
+
* as a List of ≥2 VarDeclarations. (This is purely about the `,` vs `;` argument
|
|
1512
|
+
* separators — a `/` inside a value is unrelated and never checked.) Applies to BOTH
|
|
1513
|
+
* mixin and function calls (args are unified). */
|
|
1514
|
+
_checkMixedArgDelimiters(args, kind, loc) {
|
|
1515
|
+
const list = args;
|
|
1516
|
+
if (list?.type !== "List" || list.options?.sep !== ";" || !Array.isArray(list.value)) return;
|
|
1517
|
+
for (const el of list.value) {
|
|
1518
|
+
const group = el;
|
|
1519
|
+
if (group?.type === "List" && Array.isArray(group.value) && group.value.filter((n) => n?.type === "VarDeclaration").length >= 2) {
|
|
1520
|
+
this._error(`Cannot mix ; and , as delimiter types in ${kind} arguments`, loc.start);
|
|
1521
|
+
break;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Lower Less `;`-separated call args to the unified Jess representation: the outer
|
|
1527
|
+
* args `List{ sep: ';' }` becomes comma-separated, and each element that is itself
|
|
1528
|
+
* a comma-`List` (a `;`-group that held a comma-list) is wrapped in an escaped
|
|
1529
|
+
* `Paren` — the same shape Jess authors write as `~(1, 2)`. So
|
|
1530
|
+
* `.mixin(1, 2; 3, 4)` and Jess `mixin(~(1, 2), ~(3, 4))` converge on one AST.
|
|
1531
|
+
*
|
|
1532
|
+
* The escaped `Paren` evaluates to its inner value STRIPPED (paren.ts §escaped),
|
|
1533
|
+
* so `~(1, 2)` binds/renders identically to the bare list `1, 2` — representation
|
|
1534
|
+
* only, semantics unchanged. Scalar (non-List) elements pass through untouched.
|
|
1535
|
+
*
|
|
1536
|
+
* MUST run AFTER `_checkMixedArgDelimiters` (which inspects the `;`-List).
|
|
1537
|
+
*/
|
|
1538
|
+
_lowerSemiArgs(args, loc) {
|
|
1539
|
+
const list = args;
|
|
1540
|
+
if (!list || list.type !== "List" || list.options?.sep !== ";" || !Array.isArray(list.value)) return args;
|
|
1541
|
+
return new List(list.value.map((el) => {
|
|
1542
|
+
if (el?.type === "List") return new Paren(el, { escaped: true }, loc);
|
|
1543
|
+
return el;
|
|
1544
|
+
}), void 0, loc);
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
1547
|
+
* Mixin-DEFINITION param conversion. With combinator-composed args a bare `@name`
|
|
1548
|
+
* value parses as a `Reference{variable}` (the CALL shape); in a DEFINITION it is a
|
|
1549
|
+
* param, so convert it to `VarDeclaration(name, Nil)`. Named params (`@a: 1`),
|
|
1550
|
+
* variadic (`Rest`) and pattern-match values stay as-is. Returns a NEW List (the
|
|
1551
|
+
* def/call split must not mutate a shared node).
|
|
1552
|
+
*/
|
|
1553
|
+
_convertArgsForDefinition(argsList, loc) {
|
|
1554
|
+
if (!argsList || argsList.type !== "List") return argsList;
|
|
1555
|
+
const list = argsList;
|
|
1556
|
+
const value = list.value;
|
|
1557
|
+
if (!value || value.length === 0) return argsList;
|
|
1558
|
+
let changed = false;
|
|
1559
|
+
const converted = value.map((node) => {
|
|
1560
|
+
if (node.type === "Reference" && node.options?.type === "variable") {
|
|
1561
|
+
const key = node.key;
|
|
1562
|
+
const name = typeof key === "string" ? key : String(key?.valueOf?.() ?? "");
|
|
1563
|
+
changed = true;
|
|
1564
|
+
return new VarDeclaration({
|
|
1565
|
+
name,
|
|
1566
|
+
value: new Nil("", {}, loc)
|
|
1567
|
+
}, {}, loc);
|
|
1568
|
+
}
|
|
1569
|
+
return node;
|
|
1570
|
+
});
|
|
1571
|
+
if (!changed) return argsList;
|
|
1572
|
+
return new List(converted, list.options, loc);
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* Mixin-CALL argument conversion (reference `convertArgsForCall`, root.ts).
|
|
1576
|
+
* `_buildMixinArgs` builds bare `@name` args as definition-style VarDeclarations
|
|
1577
|
+
* (Nil value) — correct for a DEFINITION param, but in a CALL a bare `@name` is a
|
|
1578
|
+
* variable being PASSED, i.e. a `Reference{type:variable}`. Named args (`@a: 1`)
|
|
1579
|
+
* and value args stay as-is; a `Rest('name')` becomes `Rest(Reference{variable})`.
|
|
1580
|
+
* Returns a NEW List (the def/call split must not mutate a shared node).
|
|
1581
|
+
*/
|
|
1582
|
+
_convertArgsForCall(argsList, loc) {
|
|
1583
|
+
if (!argsList || argsList.type !== "List") return argsList;
|
|
1584
|
+
const list = argsList;
|
|
1585
|
+
const value = list.value;
|
|
1586
|
+
if (!value || value.length === 0) return argsList;
|
|
1587
|
+
let changed = false;
|
|
1588
|
+
const converted = value.map((node) => {
|
|
1589
|
+
if (node.type === "VarDeclaration") {
|
|
1590
|
+
const decl = node;
|
|
1591
|
+
const val = decl.value;
|
|
1592
|
+
if (!val || val.type === "Nil") {
|
|
1593
|
+
const key = decl.name.valueOf();
|
|
1594
|
+
changed = true;
|
|
1595
|
+
return new Reference({ key }, { type: "variable" }, loc);
|
|
1596
|
+
}
|
|
1597
|
+
return node;
|
|
1598
|
+
}
|
|
1599
|
+
if (node.type === "Rest") {
|
|
1600
|
+
const restVal = node.value;
|
|
1601
|
+
if (typeof restVal === "string") {
|
|
1602
|
+
changed = true;
|
|
1603
|
+
return new Rest(new Reference({ key: restVal }, { type: "variable" }, loc), {}, loc);
|
|
1604
|
+
}
|
|
1605
|
+
return node;
|
|
1606
|
+
}
|
|
1607
|
+
return node;
|
|
1608
|
+
});
|
|
1609
|
+
if (!changed) return argsList;
|
|
1610
|
+
return new List(converted, list.options, loc);
|
|
1611
|
+
}
|
|
1612
|
+
_buildAnonMixin(children, loc) {
|
|
1613
|
+
const nodes = nodeChildren(children);
|
|
1614
|
+
const argsList = nodes.find((n) => n.type === "List");
|
|
1615
|
+
const rules = nodes.filter((n) => n !== argsList);
|
|
1616
|
+
const defParams = this._convertArgsForDefinition(argsList, loc);
|
|
1617
|
+
return new Mixin({
|
|
1618
|
+
params: defParams?.value?.length ? defParams : void 0,
|
|
1619
|
+
rules
|
|
1620
|
+
}, void 0, loc);
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* `each(<iterable>, { … })` → a `For` control node (the $for shape), not a Call.
|
|
1624
|
+
* The value(s) before the comma are the iterable; the callback block's body becomes
|
|
1625
|
+
* the loop rules. A literal block callback carries no captured params here, so the
|
|
1626
|
+
* pattern defaults to the Less `[value, key, index]` triple.
|
|
1627
|
+
*/
|
|
1628
|
+
/** A bare detached ruleset `{ … }` in value / argument position → a Mixin holding
|
|
1629
|
+
* its rules (same shape `@var: { … }` produces in `_buildVarDeclaration`). */
|
|
1630
|
+
_buildDetachedRuleset(children, loc) {
|
|
1631
|
+
return new Mixin({ rules: nodeChildren(children) }, {}, loc);
|
|
1632
|
+
}
|
|
1633
|
+
_buildEachFor(children, loc) {
|
|
1634
|
+
const nodes = nodeChildren(children);
|
|
1635
|
+
const callback = nodes.find((n) => n.type === "Mixin");
|
|
1636
|
+
const iterableNodes = nodes.filter((n) => n !== callback);
|
|
1637
|
+
const paramsList = (callback?.params)?.type === "List" ? callback.params : void 0;
|
|
1638
|
+
const ruleNodes = callback?.rules ?? [];
|
|
1639
|
+
const iterable = iterableNodes.length === 1 ? iterableNodes[0] : new List(iterableNodes, void 0, loc);
|
|
1640
|
+
return new For({
|
|
1641
|
+
pattern: this._eachPattern(paramsList, loc),
|
|
1642
|
+
iterable: {
|
|
1643
|
+
kind: "node",
|
|
1644
|
+
value: iterable
|
|
1645
|
+
},
|
|
1646
|
+
rules: ruleNodes
|
|
1647
|
+
}, void 0, loc);
|
|
1648
|
+
}
|
|
1649
|
+
_eachPattern(paramsList, loc) {
|
|
1650
|
+
const params = (paramsList?.value ?? []).filter((p) => p?.type === "VarDeclaration");
|
|
1651
|
+
if (params.length === 1) return {
|
|
1652
|
+
kind: "single",
|
|
1653
|
+
value: params[0]
|
|
1654
|
+
};
|
|
1655
|
+
if (params.length >= 2) return {
|
|
1656
|
+
kind: "tuple",
|
|
1657
|
+
values: [params[0], ...params.slice(1)]
|
|
1658
|
+
};
|
|
1659
|
+
const paramVar = (name) => new VarDeclaration({
|
|
1660
|
+
name,
|
|
1661
|
+
value: this._lessKeyword("", loc)
|
|
1662
|
+
}, { paramVar: true }, loc);
|
|
1663
|
+
return {
|
|
1664
|
+
kind: "tuple",
|
|
1665
|
+
values: [
|
|
1666
|
+
paramVar("value"),
|
|
1667
|
+
paramVar("key"),
|
|
1668
|
+
paramVar("index")
|
|
1669
|
+
]
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
_buildMixinOrQualified(children, loc) {
|
|
1673
|
+
const ls = children.filter((c) => c._tag === "leaf");
|
|
1674
|
+
const nodes = nodeChildren(children);
|
|
1675
|
+
const hasBlock = ls.some((l) => l.value === "{");
|
|
1676
|
+
const markImportant = ls.some((l) => l.value === "!");
|
|
1677
|
+
const nameParts = [];
|
|
1678
|
+
for (const l of ls) {
|
|
1679
|
+
if (l.value === "(" || l.value === "{" || l.value === "}" || l.value === ";" || l.value === ")" || l.value === "!") break;
|
|
1680
|
+
nameParts.push(l.value);
|
|
1681
|
+
}
|
|
1682
|
+
const name = nameParts.join("");
|
|
1683
|
+
const argsList = nodes.find((n) => n.type === "List");
|
|
1684
|
+
const guard = nodes.find((n) => n.type === "Paren" || n.type === "Condition" || n.type === "DefaultGuard");
|
|
1685
|
+
const hasExplicitParens = argsList !== void 0;
|
|
1686
|
+
if (hasBlock) {
|
|
1687
|
+
const rawRuleNodes = nodes.filter((n) => n !== argsList && n !== guard);
|
|
1688
|
+
const braceIdx = this._source.indexOf("{", loc.start);
|
|
1689
|
+
const bodyStart = braceIdx >= 0 ? braceIdx + 1 : loc.start;
|
|
1690
|
+
const closeIdx = this._source.lastIndexOf("}", loc.end - 1);
|
|
1691
|
+
const bodyEnd = closeIdx >= bodyStart ? closeIdx : loc.end;
|
|
1692
|
+
const ruleNodes = this._liftStandaloneComments(rawRuleNodes, bodyStart, bodyEnd, loc);
|
|
1693
|
+
if (hasExplicitParens) {
|
|
1694
|
+
const hasDefault = (guard !== void 0 ? guard.toTrimmedString?.() ?? "" : "").includes("default");
|
|
1695
|
+
const defParams = this._convertArgsForDefinition(argsList, loc);
|
|
1696
|
+
return new Mixin({
|
|
1697
|
+
name,
|
|
1698
|
+
params: defParams?.value?.length ? defParams : void 0,
|
|
1699
|
+
rules: ruleNodes,
|
|
1700
|
+
guard
|
|
1701
|
+
}, { hasDefault: !!hasDefault }, loc);
|
|
1702
|
+
}
|
|
1703
|
+
const { cleanedSelector, extractedExtends } = this._extractExtendsFromSelectorText(name || "&", loc);
|
|
1704
|
+
const finalRules = extractedExtends.length > 0 ? [...extractedExtends, ...ruleNodes] : ruleNodes;
|
|
1705
|
+
return new Ruleset({
|
|
1706
|
+
selector: cleanedSelector || "&",
|
|
1707
|
+
rules: finalRules,
|
|
1708
|
+
guard
|
|
1709
|
+
}, void 0, loc);
|
|
1710
|
+
}
|
|
1711
|
+
const combinatorValues = new Set([
|
|
1712
|
+
">",
|
|
1713
|
+
"+",
|
|
1714
|
+
"~"
|
|
1715
|
+
]);
|
|
1716
|
+
const selectorTokens = nameParts.filter((p) => !combinatorValues.has(p.trim()) && p.trim() !== "");
|
|
1717
|
+
const hasComplexPath = selectorTokens.length > 1;
|
|
1718
|
+
const refKey = hasComplexPath ? selectorTokens : name;
|
|
1719
|
+
const rawKey = hasComplexPath ? new ComplexSelector(nameParts, void 0, loc) : void 0;
|
|
1720
|
+
const ref = name.startsWith(".") || name.startsWith("#") ? new Reference({
|
|
1721
|
+
key: refKey,
|
|
1722
|
+
...rawKey ? { rawKey } : {}
|
|
1723
|
+
}, {
|
|
1724
|
+
type: "mixin-ruleset",
|
|
1725
|
+
role: "name"
|
|
1726
|
+
}, loc) : new Reference({ key: refKey }, {
|
|
1727
|
+
type: "function",
|
|
1728
|
+
silentFail: true,
|
|
1729
|
+
fallbackValue: true
|
|
1730
|
+
}, loc);
|
|
1731
|
+
const hasArgs2 = argsList && argsList.value?.length;
|
|
1732
|
+
const hasSemi = ls.some((l) => l.value === ";");
|
|
1733
|
+
if (hasSemi && !hasExplicitParens) this._warn("Calling a mixin without parentheses is deprecated", "mixin-call-no-parens");
|
|
1734
|
+
else if (hasSemi && hasExplicitParens) {
|
|
1735
|
+
const src = this._source.slice(loc.start, loc.end);
|
|
1736
|
+
if (/^\S+\s+\(/.test(src)) this._warn("Whitespace between a mixin name and parentheses is deprecated", "mixin-call-whitespace");
|
|
1737
|
+
}
|
|
1738
|
+
return new Call({
|
|
1739
|
+
name: ref,
|
|
1740
|
+
args: hasArgs2 ? this._convertArgsForCall(argsList, loc) : void 0
|
|
1741
|
+
}, { markImportant }, loc);
|
|
1742
|
+
}
|
|
1743
|
+
_extractExtendsFromSelectorText(selectorText, _loc) {
|
|
1744
|
+
return {
|
|
1745
|
+
cleanedSelector: selectorText,
|
|
1746
|
+
extractedExtends: []
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
_selectorHasNestedExtend(sel) {
|
|
1750
|
+
if (!sel || typeof sel === "string") return false;
|
|
1751
|
+
if (sel.type === "PseudoSelector") {
|
|
1752
|
+
const arg = sel.arg;
|
|
1753
|
+
return arg ? this._treeHasExtend(arg) : false;
|
|
1754
|
+
}
|
|
1755
|
+
if (sel instanceof CompoundSelector || sel instanceof ComplexSelector) return sel.value.some((p) => this._selectorHasNestedExtend(p));
|
|
1756
|
+
if (isSelectorListLike(sel)) return selectorListItems(sel).some((p) => this._selectorHasNestedExtend(p));
|
|
1757
|
+
return false;
|
|
1758
|
+
}
|
|
1759
|
+
_treeHasExtend(node) {
|
|
1760
|
+
if (node instanceof Extend) return true;
|
|
1761
|
+
if (node instanceof CompoundSelector || node instanceof ComplexSelector) return node.value.some((p) => this._treeHasExtend(p));
|
|
1762
|
+
if (isSelectorListLike(node)) return selectorListItems(node).some((p) => this._treeHasExtend(p));
|
|
1763
|
+
return false;
|
|
1764
|
+
}
|
|
1765
|
+
/**
|
|
1766
|
+
* A guarded ruleset (`sel when …`) is parsed by the shared CSS builder, which
|
|
1767
|
+
* has no `when` concept, so the Guard CST child folds into the body as the
|
|
1768
|
+
* first rule — always a Paren/Condition/DefaultGuard. Lift it into the
|
|
1769
|
+
* ruleset's `guard` field (rebuilt through the canonical Ruleset ctor so the
|
|
1770
|
+
* guard is adopted) so it gates output instead of rendering as a `{ true }`
|
|
1771
|
+
* body. Non-guarded rulesets never begin their body with one of these node
|
|
1772
|
+
* types, so the leading-node check is unambiguous.
|
|
1773
|
+
*/
|
|
1774
|
+
_liftRulesetGuard(base, loc) {
|
|
1775
|
+
const rules = base.rules;
|
|
1776
|
+
if (!Array.isArray(rules) || rules.length === 0) return base;
|
|
1777
|
+
const first = rules[0];
|
|
1778
|
+
if (first?.type !== "Paren" && first?.type !== "Condition" && first?.type !== "DefaultGuard") return base;
|
|
1779
|
+
return new Ruleset({
|
|
1780
|
+
selector: base.selector,
|
|
1781
|
+
rules: rules.slice(1),
|
|
1782
|
+
guard: first
|
|
1783
|
+
}, void 0, loc);
|
|
1784
|
+
}
|
|
1785
|
+
_buildRuleset(children, rawChildren, loc) {
|
|
1786
|
+
let base = super._buildRuleset(children, rawChildren, loc);
|
|
1787
|
+
const selector = base.selector;
|
|
1788
|
+
if (!selector) return base;
|
|
1789
|
+
base = this._liftRulesetGuard(base, loc);
|
|
1790
|
+
if (typeof selector === "string") return base;
|
|
1791
|
+
if (this._selectorHasNestedExtend(selector)) this._error(":extend() is not allowed inside a pseudo-class selector", loc.start);
|
|
1792
|
+
const baseRules = Array.isArray(base.rules) ? base.rules : [];
|
|
1793
|
+
const baseGuard = base.guard;
|
|
1794
|
+
const withGuard = (rs) => {
|
|
1795
|
+
if (baseGuard !== void 0) rs.guard = baseGuard;
|
|
1796
|
+
return rs;
|
|
1797
|
+
};
|
|
1798
|
+
const extendKey = (e) => {
|
|
1799
|
+
const ext = e;
|
|
1800
|
+
return `${String(ext.target?.valueOf?.() ?? ext.target)}:${ext.flag}`;
|
|
1801
|
+
};
|
|
1802
|
+
if (!isSelectorListLike(selector)) {
|
|
1803
|
+
const { cleanedSelector, extractedExtends } = this._extractExtendsFromSelector(selector, loc);
|
|
1804
|
+
if (extractedExtends.length === 0) return base;
|
|
1805
|
+
return withGuard(new Ruleset({
|
|
1806
|
+
selector: cleanedSelector,
|
|
1807
|
+
rules: [...extractedExtends, ...baseRules]
|
|
1808
|
+
}, void 0, loc));
|
|
1809
|
+
}
|
|
1810
|
+
const perSelector = [];
|
|
1811
|
+
let anyExtends = false;
|
|
1812
|
+
for (const item of selectorListItems(selector)) {
|
|
1813
|
+
const { cleanedSelector: cs, extractedExtends: ee } = this._extractExtendsFromSelector(item, loc);
|
|
1814
|
+
perSelector.push({
|
|
1815
|
+
clean: cs,
|
|
1816
|
+
extends: ee
|
|
1817
|
+
});
|
|
1818
|
+
if (ee.length > 0) anyExtends = true;
|
|
1819
|
+
}
|
|
1820
|
+
if (!anyExtends) return base;
|
|
1821
|
+
const allExtendKeys = perSelector.map((s) => s.extends.map(extendKey).sort().join("|"));
|
|
1822
|
+
if (allExtendKeys.every((k) => k === allExtendKeys[0])) {
|
|
1823
|
+
const uniqueExtends = perSelector[0].extends;
|
|
1824
|
+
const cleanedItems = perSelector.map((s) => s.clean).filter((c) => c !== void 0);
|
|
1825
|
+
return withGuard(new Ruleset({
|
|
1826
|
+
selector: cleanedItems.length === 1 ? cleanedItems[0] : this._makeSelectorList(cleanedItems, loc),
|
|
1827
|
+
rules: [...uniqueExtends, ...baseRules]
|
|
1828
|
+
}, void 0, loc));
|
|
1829
|
+
}
|
|
1830
|
+
const wrapperRules = [];
|
|
1831
|
+
const cleanedItems = [];
|
|
1832
|
+
for (const { clean, extends: exts } of perSelector) {
|
|
1833
|
+
for (const ext of exts) {
|
|
1834
|
+
const extNode = ext;
|
|
1835
|
+
wrapperRules.push(new Extend({
|
|
1836
|
+
target: extNode.target,
|
|
1837
|
+
flag: extNode.flag,
|
|
1838
|
+
selector: clean
|
|
1839
|
+
}, {}, loc));
|
|
1840
|
+
}
|
|
1841
|
+
if (clean !== void 0) cleanedItems.push(clean);
|
|
1842
|
+
}
|
|
1843
|
+
const combinedSel = cleanedItems.length === 1 ? cleanedItems[0] : this._makeSelectorList(cleanedItems, loc);
|
|
1844
|
+
wrapperRules.push(withGuard(new Ruleset({
|
|
1845
|
+
selector: combinedSel,
|
|
1846
|
+
rules: baseRules
|
|
1847
|
+
}, void 0, loc)));
|
|
1848
|
+
return new Rules(wrapperRules, void 0, loc);
|
|
1849
|
+
}
|
|
1850
|
+
_extractExtendsFromSelector(selector, loc) {
|
|
1851
|
+
if (!selector || typeof selector === "string") return {
|
|
1852
|
+
cleanedSelector: selector,
|
|
1853
|
+
extractedExtends: []
|
|
1854
|
+
};
|
|
1855
|
+
if (selector instanceof CompoundSelector) {
|
|
1856
|
+
const extractedExtends = [];
|
|
1857
|
+
const newParts = [];
|
|
1858
|
+
for (const part of selector.value) if (part instanceof Extend) extractedExtends.push(part);
|
|
1859
|
+
else if (part instanceof List) for (const item of part.value ?? []) if (item instanceof Extend) extractedExtends.push(item);
|
|
1860
|
+
else newParts.push(item);
|
|
1861
|
+
else newParts.push(part);
|
|
1862
|
+
if (extractedExtends.length === 0) return {
|
|
1863
|
+
cleanedSelector: selector,
|
|
1864
|
+
extractedExtends: []
|
|
1865
|
+
};
|
|
1866
|
+
return {
|
|
1867
|
+
cleanedSelector: newParts.length === 0 ? "&" : newParts.length === 1 ? newParts[0] : new CompoundSelector(newParts, void 0, loc),
|
|
1868
|
+
extractedExtends
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
if (selector instanceof ComplexSelector) {
|
|
1872
|
+
const allExtends = [];
|
|
1873
|
+
const newParts = [];
|
|
1874
|
+
for (const part of selector.value) if (part instanceof Extend) allExtends.push(part);
|
|
1875
|
+
else if (part instanceof List) for (const item of part.value ?? []) if (item instanceof Extend) allExtends.push(item);
|
|
1876
|
+
else newParts.push(item);
|
|
1877
|
+
else if (part instanceof CompoundSelector) {
|
|
1878
|
+
const { cleanedSelector: cs, extractedExtends: ee } = this._extractExtendsFromSelector(part, loc);
|
|
1879
|
+
allExtends.push(...ee);
|
|
1880
|
+
if (cs !== void 0) newParts.push(cs);
|
|
1881
|
+
} else newParts.push(part);
|
|
1882
|
+
if (allExtends.length === 0) return {
|
|
1883
|
+
cleanedSelector: selector,
|
|
1884
|
+
extractedExtends: []
|
|
1885
|
+
};
|
|
1886
|
+
return {
|
|
1887
|
+
cleanedSelector: newParts.length === 1 ? newParts[0] : new ComplexSelector(newParts, void 0, loc),
|
|
1888
|
+
extractedExtends: allExtends
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
if (isSelectorListLike(selector)) {
|
|
1892
|
+
const allExtends = [];
|
|
1893
|
+
const cleanedItems = [];
|
|
1894
|
+
let changed = false;
|
|
1895
|
+
for (const item of selectorListItems(selector)) {
|
|
1896
|
+
const { cleanedSelector: cs, extractedExtends: ee } = this._extractExtendsFromSelector(item, loc);
|
|
1897
|
+
allExtends.push(...ee);
|
|
1898
|
+
if (ee.length > 0) changed = true;
|
|
1899
|
+
if (cs !== void 0) cleanedItems.push(cs);
|
|
1900
|
+
}
|
|
1901
|
+
if (!changed) return {
|
|
1902
|
+
cleanedSelector: selector,
|
|
1903
|
+
extractedExtends: []
|
|
1904
|
+
};
|
|
1905
|
+
return {
|
|
1906
|
+
cleanedSelector: cleanedItems.length === 1 ? cleanedItems[0] : this._makeSelectorList(cleanedItems, loc),
|
|
1907
|
+
extractedExtends: allExtends
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
return {
|
|
1911
|
+
cleanedSelector: selector,
|
|
1912
|
+
extractedExtends: []
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
static _isCssUrl(url, opts) {
|
|
1916
|
+
if (opts.includes("inline") || opts.includes("less")) return false;
|
|
1917
|
+
return url.endsWith(".css") || url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//");
|
|
1918
|
+
}
|
|
1919
|
+
_buildImportAtRuleFromPrelude(children, raw, loc, name) {
|
|
1920
|
+
const preludeText = this._source.slice(loc.start, loc.end);
|
|
1921
|
+
const optMatch = /^\s*\(([^)]+)\)/.exec(preludeText.replace(/^@import\s*/, ""));
|
|
1922
|
+
const opts = optMatch ? optMatch[1].split(",").map((s) => s.trim()) : [];
|
|
1923
|
+
const builtNodes = nodeChildren(children);
|
|
1924
|
+
const urlNode = builtNodes.find((n) => n.type === "Url");
|
|
1925
|
+
const quotedNode = builtNodes.find((n) => n.type === "Quoted");
|
|
1926
|
+
let pathNode;
|
|
1927
|
+
if (quotedNode) pathNode = new Quoted(quotedNode.value, { quote: quotedNode.quote ?? "\"" }, loc);
|
|
1928
|
+
else {
|
|
1929
|
+
const _qm = preludeText.match(/(['"])([^'"]+)\1/);
|
|
1930
|
+
if (_qm) {
|
|
1931
|
+
const quote = _qm[1] === "'" ? "'" : "\"";
|
|
1932
|
+
const innerNode = _qm[2];
|
|
1933
|
+
pathNode = new Quoted(innerNode, { quote }, loc);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
let mediaNode;
|
|
1937
|
+
{
|
|
1938
|
+
let rest = preludeText.replace(/^@-?[_a-zA-Z][-_a-zA-Z0-9]*\s*/, "");
|
|
1939
|
+
rest = rest.replace(/^\([^)]*\)\s*/, "");
|
|
1940
|
+
rest = urlNode ? rest.replace(/url\(\s*(['"])[^'"]*\1\s*\)\s*/i, "") : rest.replace(/(['"])[^'"]*\1\s*/, "");
|
|
1941
|
+
rest = rest.replace(/\bas\s+[^\s;(]+\s*/g, "");
|
|
1942
|
+
rest = rest.replace(/;\s*$/, "").trim();
|
|
1943
|
+
if (rest) mediaNode = this._buildAtRulePrelude(rest, loc);
|
|
1944
|
+
}
|
|
1945
|
+
const pathMatch2 = /['"]([^'"]+)['"]/.exec(preludeText);
|
|
1946
|
+
const pathStr = pathMatch2 ? pathMatch2[1] : "";
|
|
1947
|
+
const isCssImport = pathStr ? LessGrammar._isCssUrl(pathStr, opts) : false;
|
|
1948
|
+
if (!opts.includes("inline") && (isCssImport || opts.includes("css"))) {
|
|
1949
|
+
const preludeItems = [];
|
|
1950
|
+
const pathPrelude = urlNode ?? pathNode;
|
|
1951
|
+
if (pathPrelude) preludeItems.push(pathPrelude);
|
|
1952
|
+
if (mediaNode) preludeItems.push(mediaNode);
|
|
1953
|
+
let prelude;
|
|
1954
|
+
if (preludeItems.length === 1) prelude = preludeItems[0];
|
|
1955
|
+
else {
|
|
1956
|
+
const joined = preludeItems.map((item) => item.toTrimmedString()).join(" ");
|
|
1957
|
+
prelude = preludeItems.every((item) => item.structuralStaticFlag()) ? new Any(joined, void 0, loc) : joined;
|
|
1958
|
+
}
|
|
1959
|
+
return new AtRuleStatement({
|
|
1960
|
+
name,
|
|
1961
|
+
prelude
|
|
1962
|
+
}, void 0, loc);
|
|
1963
|
+
}
|
|
1964
|
+
const isForward = name === "@-export";
|
|
1965
|
+
const importType = isForward ? "compose" : "import";
|
|
1966
|
+
const importOpts = { once: !opts.includes("multiple") };
|
|
1967
|
+
if (opts.includes("reference")) importOpts.reference = true;
|
|
1968
|
+
if (opts.includes("multiple")) importOpts.multiple = true;
|
|
1969
|
+
if (opts.includes("optional")) importOpts.optional = true;
|
|
1970
|
+
if (opts.includes("inline")) importOpts.inline = true;
|
|
1971
|
+
if (opts.includes("less")) importOpts.type = "less";
|
|
1972
|
+
if (mediaNode) importOpts.postlude = mediaNode;
|
|
1973
|
+
if (isForward) importOpts.forward = true;
|
|
1974
|
+
const namespace = /\bas\s+([^\s;(]+)/.exec(preludeText)?.[1];
|
|
1975
|
+
const styleImportOptions = {
|
|
1976
|
+
type: importType,
|
|
1977
|
+
importOptions: importOpts
|
|
1978
|
+
};
|
|
1979
|
+
if (namespace) styleImportOptions.namespace = namespace;
|
|
1980
|
+
return new StyleImport({ path: pathNode ?? urlNode }, styleImportOptions, loc);
|
|
1981
|
+
}
|
|
1982
|
+
_buildAtRuleBlock(children, loc) {
|
|
1983
|
+
const ls = children.filter((c) => c._tag === "leaf");
|
|
1984
|
+
const nameLf = ls[0];
|
|
1985
|
+
const name = nameLf?.value ?? "";
|
|
1986
|
+
if ([
|
|
1987
|
+
"@import",
|
|
1988
|
+
"@-import",
|
|
1989
|
+
"@-export"
|
|
1990
|
+
].includes(name)) return this._buildImportAtRuleFromPrelude(children, ls, loc, name);
|
|
1991
|
+
if (["@use", "@-use"].includes(name)) return this._buildUseAtRuleFromPrelude(children, loc, name);
|
|
1992
|
+
const nameSpan = nameLf?.span;
|
|
1993
|
+
const braceStart = ls.find((l) => l.value === "{")?.span?.start;
|
|
1994
|
+
let rawPreludeText;
|
|
1995
|
+
if (nameSpan && typeof braceStart === "number") {
|
|
1996
|
+
const sliced = this._source.slice(nameSpan.end, braceStart).trim();
|
|
1997
|
+
rawPreludeText = sliced.length > 0 ? sliced : void 0;
|
|
1998
|
+
} else rawPreludeText = ls.slice(1).find((l) => l.value !== "{" && l.value !== "}")?.value.trim();
|
|
1999
|
+
return this._buildAtRuleFromParts(name, rawPreludeText, nodeChildren(children), loc);
|
|
2000
|
+
}
|
|
2001
|
+
/**
|
|
2002
|
+
* Shared AtRule assembly used by both the flat `AtRuleBlock` builder and the
|
|
2003
|
+
* structured, committed `QueryAtRuleBlock` builder. `preludeText` is the raw
|
|
2004
|
+
* prelude source (already `{`/`}` stripped); routing it through
|
|
2005
|
+
* `_buildAtRulePrelude` keeps the emitted AST identical regardless of which
|
|
2006
|
+
* grammar rule matched.
|
|
2007
|
+
*/
|
|
2008
|
+
_buildAtRuleFromParts(name, preludeText, ruleNodes, loc) {
|
|
2009
|
+
const nestableOpts = NESTABLE_AT_RULES.includes(name) ? { nestable: true } : void 0;
|
|
2010
|
+
return new AtRule({
|
|
2011
|
+
name,
|
|
2012
|
+
prelude: preludeText ? this._buildAtRulePrelude(preludeText, loc) : void 0,
|
|
2013
|
+
rules: ruleNodes
|
|
2014
|
+
}, nestableOpts, loc);
|
|
2015
|
+
}
|
|
2016
|
+
/**
|
|
2017
|
+
* Builder for the structured, committed `@media`/`@container`/`@supports`
|
|
2018
|
+
* query block. The grammar rule parses the prelude with real query structure
|
|
2019
|
+
* (so a stray/unbalanced bracket is rejected instead of swallowed) and commits
|
|
2020
|
+
* on `expect('{')`, but the AST is reconstructed from the prelude source text
|
|
2021
|
+
* via the shared `_buildAtRuleFromParts` path — so well-formed queries emit the
|
|
2022
|
+
* exact same AtRule the flat `AtRuleBlock` builder would.
|
|
2023
|
+
*/
|
|
2024
|
+
_buildLessQueryAtRuleBlock(children, raw, loc) {
|
|
2025
|
+
const name = children.filter((c) => c._tag === "leaf")[0]?.value ?? "";
|
|
2026
|
+
const comps = spannedComponents(raw);
|
|
2027
|
+
const keywordEnd = comps[0]?.span.end ?? loc.start;
|
|
2028
|
+
const braceStart = comps.find((c) => c.comp === "{")?.span.start ?? loc.end;
|
|
2029
|
+
const preludeText = this._source.slice(keywordEnd, braceStart).trim();
|
|
2030
|
+
const bodyNodes = comps.filter((c) => typeof c.comp !== "string" && c.span.start >= braceStart).map((c) => c.comp);
|
|
2031
|
+
return this._buildAtRuleFromParts(name, preludeText || void 0, bodyNodes, loc);
|
|
2032
|
+
}
|
|
2033
|
+
_buildAtRulePrelude(text, loc) {
|
|
2034
|
+
const singleVarRe = /^@(-?[_a-zA-Z\x80-][-_a-zA-Z0-9\x80-]*)$/;
|
|
2035
|
+
const MEDIA_KEYWORDS = new Set([
|
|
2036
|
+
"and",
|
|
2037
|
+
"or",
|
|
2038
|
+
"not",
|
|
2039
|
+
"only",
|
|
2040
|
+
"all",
|
|
2041
|
+
"print",
|
|
2042
|
+
"screen",
|
|
2043
|
+
"speech"
|
|
2044
|
+
]);
|
|
2045
|
+
const COMPARISON_OPS = new Set([
|
|
2046
|
+
">",
|
|
2047
|
+
"<",
|
|
2048
|
+
">=",
|
|
2049
|
+
"<=",
|
|
2050
|
+
"=",
|
|
2051
|
+
"!="
|
|
2052
|
+
]);
|
|
2053
|
+
const escapedStrRe = /^~(['"])([\s\S]*)\1$/;
|
|
2054
|
+
const buildWord = (w) => {
|
|
2055
|
+
const es = escapedStrRe.exec(w);
|
|
2056
|
+
if (es) return this._buildEscapedQuoted(es[2], es[1], loc);
|
|
2057
|
+
const mv = singleVarRe.exec(w);
|
|
2058
|
+
if (mv) return new Reference(mv[1], {
|
|
2059
|
+
type: "index",
|
|
2060
|
+
role: "ident"
|
|
2061
|
+
}, loc);
|
|
2062
|
+
if (MEDIA_KEYWORDS.has(w.toLowerCase())) return this._lessKeyword(w, loc);
|
|
2063
|
+
if (COMPARISON_OPS.has(w)) return w;
|
|
2064
|
+
return this._lessKeyword(w, loc);
|
|
2065
|
+
};
|
|
2066
|
+
const varAccRe = /^@(-?[_a-zA-Z\x80-][-_a-zA-Z0-9\x80-]*)\[([^\]]*)\]$/;
|
|
2067
|
+
const buildAccessor = (varName, accInner) => {
|
|
2068
|
+
const varBase = new Reference({ key: varName }, {}, loc);
|
|
2069
|
+
const inner = accInner.trim();
|
|
2070
|
+
let accKey;
|
|
2071
|
+
let accType;
|
|
2072
|
+
if (inner === "") {
|
|
2073
|
+
accKey = -1;
|
|
2074
|
+
accType = "index";
|
|
2075
|
+
} else if (inner.startsWith("@")) {
|
|
2076
|
+
accKey = inner.slice(1);
|
|
2077
|
+
accType = "variable";
|
|
2078
|
+
} else {
|
|
2079
|
+
accKey = new Quoted(inner, {}, loc);
|
|
2080
|
+
accType = "index";
|
|
2081
|
+
}
|
|
2082
|
+
return new Reference({
|
|
2083
|
+
target: varBase,
|
|
2084
|
+
key: accKey
|
|
2085
|
+
}, { type: accType }, loc);
|
|
2086
|
+
};
|
|
2087
|
+
const prodOps = new Set(["*", "/"]);
|
|
2088
|
+
const buildFeatureValue = (raw) => {
|
|
2089
|
+
const propVal = raw.trim();
|
|
2090
|
+
if (escapedStrRe.test(propVal) || singleVarRe.test(propVal)) return buildWord(propVal);
|
|
2091
|
+
const vam = varAccRe.exec(propVal);
|
|
2092
|
+
if (vam) return buildAccessor(vam[1], vam[2] ?? "");
|
|
2093
|
+
const paren = /^\(([\s\S]*)\)$/.exec(propVal);
|
|
2094
|
+
if (paren) {
|
|
2095
|
+
const op = buildMathExpr(paren[1].trim());
|
|
2096
|
+
if (op) return new Expression(op, { parens: true }, loc);
|
|
2097
|
+
}
|
|
2098
|
+
const ratio = /^(\d+(?:\.\d+)?)\s*\/\s*(\d+(?:\.\d+)?)$/.exec(propVal);
|
|
2099
|
+
if (ratio) return this._lessKeyword(`${ratio[1]} / ${ratio[2]}`, loc);
|
|
2100
|
+
return this._lessKeyword(propVal, loc);
|
|
2101
|
+
};
|
|
2102
|
+
const buildMathExpr = (expr) => {
|
|
2103
|
+
const parts = expr.split(/\s+/).filter(Boolean);
|
|
2104
|
+
if (parts.length < 3 || parts.length % 2 === 0) return;
|
|
2105
|
+
const buildOperand = (t) => {
|
|
2106
|
+
const es = escapedStrRe.exec(t);
|
|
2107
|
+
if (es) return this._buildEscapedQuoted(es[2], es[1], loc);
|
|
2108
|
+
const mv = singleVarRe.exec(t);
|
|
2109
|
+
if (mv) return new Reference(mv[1], {
|
|
2110
|
+
type: "index",
|
|
2111
|
+
role: "ident"
|
|
2112
|
+
}, loc);
|
|
2113
|
+
const va = varAccRe.exec(t);
|
|
2114
|
+
if (va) return buildAccessor(va[1], va[2] ?? "");
|
|
2115
|
+
const dim = /^([+-]?(?:\d*\.\d+|\d+))([_a-zA-Z%][-_a-zA-Z0-9%]*)?$/.exec(t);
|
|
2116
|
+
if (dim) return dim[2] ? new Dimension({
|
|
2117
|
+
number: parseFloat(dim[1]),
|
|
2118
|
+
unit: dim[2]
|
|
2119
|
+
}, void 0, loc) : new Num(parseFloat(dim[1]), void 0, loc);
|
|
2120
|
+
};
|
|
2121
|
+
const nodes = parts.map((p, i) => i % 2 === 0 ? buildOperand(p) : /^[-+*/]$/.test(p) ? p : void 0);
|
|
2122
|
+
if (nodes.some((n) => n === void 0)) return;
|
|
2123
|
+
const foldPass = (matchOp) => {
|
|
2124
|
+
for (let i = 1; i < nodes.length - 1; i += 2) {
|
|
2125
|
+
const op = nodes[i];
|
|
2126
|
+
if (matchOp(op)) {
|
|
2127
|
+
const left = nodes[i - 1];
|
|
2128
|
+
const right = nodes[i + 1];
|
|
2129
|
+
const combined = new Operation([
|
|
2130
|
+
left,
|
|
2131
|
+
op,
|
|
2132
|
+
right
|
|
2133
|
+
], void 0, loc);
|
|
2134
|
+
nodes.splice(i - 1, 3, combined);
|
|
2135
|
+
return true;
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
return false;
|
|
2139
|
+
};
|
|
2140
|
+
while (foldPass((op) => prodOps.has(op)));
|
|
2141
|
+
while (foldPass((op) => op === "+" || op === "-"));
|
|
2142
|
+
return nodes.length === 1 ? nodes[0] : void 0;
|
|
2143
|
+
};
|
|
2144
|
+
const buildParen = (inner) => {
|
|
2145
|
+
const trimmed = inner.trim();
|
|
2146
|
+
const colonIdx = trimmed.indexOf(":");
|
|
2147
|
+
if (colonIdx > 0 && !/[><=!]/.test(trimmed.slice(0, colonIdx))) {
|
|
2148
|
+
const propName = trimmed.slice(0, colonIdx).trim();
|
|
2149
|
+
const valueNode = buildFeatureValue(trimmed.slice(colonIdx + 1).trim());
|
|
2150
|
+
if (propName.startsWith("--")) return new Paren(new QueryCondition([this._lessKeyword(`${propName}:`, loc), valueNode], void 0, loc), void 0, loc);
|
|
2151
|
+
return new Paren(new Declaration({
|
|
2152
|
+
name: propName,
|
|
2153
|
+
value: valueNode
|
|
2154
|
+
}, void 0, loc), void 0, loc);
|
|
2155
|
+
}
|
|
2156
|
+
return new Paren(new QueryCondition(trimmed.replace(/\(\s+/g, "(").replace(/\s+\)/g, ")").replace(/\s*(<=|>=|!=|[<>=])\s*/g, " $1 ").trim().split(/\s+/).filter(Boolean).map((w) => buildWord(w)), void 0, loc), void 0, loc);
|
|
2157
|
+
};
|
|
2158
|
+
const tokenize = (t) => {
|
|
2159
|
+
const tokens = [];
|
|
2160
|
+
let i = 0;
|
|
2161
|
+
while (i < t.length) if (t[i] === "(") {
|
|
2162
|
+
let depth = 1;
|
|
2163
|
+
let j = i + 1;
|
|
2164
|
+
while (j < t.length && depth > 0) {
|
|
2165
|
+
if (t[j] === "(") depth++;
|
|
2166
|
+
else if (t[j] === ")") depth--;
|
|
2167
|
+
j++;
|
|
2168
|
+
}
|
|
2169
|
+
tokens.push(buildParen(t.slice(i + 1, j - 1)));
|
|
2170
|
+
i = j;
|
|
2171
|
+
} else if (t[i] === "\"" || t[i] === "'" || t[i] === "~" && (t[i + 1] === "\"" || t[i + 1] === "'")) {
|
|
2172
|
+
const start = i;
|
|
2173
|
+
if (t[i] === "~") i++;
|
|
2174
|
+
const quote = t[i];
|
|
2175
|
+
i++;
|
|
2176
|
+
while (i < t.length && t[i] !== quote) i++;
|
|
2177
|
+
i = Math.min(i + 1, t.length);
|
|
2178
|
+
tokens.push(buildWord(t.slice(start, i)));
|
|
2179
|
+
} else if (/\s/.test(t[i])) i++;
|
|
2180
|
+
else {
|
|
2181
|
+
let j = i;
|
|
2182
|
+
while (j < t.length && !/\s/.test(t[j]) && t[j] !== "(") j++;
|
|
2183
|
+
tokens.push(buildWord(t.slice(i, j)));
|
|
2184
|
+
i = j;
|
|
2185
|
+
}
|
|
2186
|
+
return tokens;
|
|
2187
|
+
};
|
|
2188
|
+
const splitCommas = (t) => {
|
|
2189
|
+
const parts = [];
|
|
2190
|
+
let depth = 0;
|
|
2191
|
+
let start = 0;
|
|
2192
|
+
for (let i = 0; i < t.length; i++) if (t[i] === "(") depth++;
|
|
2193
|
+
else if (t[i] === ")") depth--;
|
|
2194
|
+
else if (t[i] === "," && depth === 0) {
|
|
2195
|
+
parts.push(t.slice(start, i).trim());
|
|
2196
|
+
start = i + 1;
|
|
2197
|
+
}
|
|
2198
|
+
parts.push(t.slice(start).trim());
|
|
2199
|
+
return parts.filter(Boolean);
|
|
2200
|
+
};
|
|
2201
|
+
const nsMediaRe = /^([#.][^(\[,\s]*)(\([^)]*\))?(\[[^\]]*\])?$/;
|
|
2202
|
+
const buildItem = (t) => {
|
|
2203
|
+
const trimmed = t.trim();
|
|
2204
|
+
const mv = singleVarRe.exec(trimmed);
|
|
2205
|
+
if (mv) return new QueryCondition([new Reference(mv[1], {
|
|
2206
|
+
type: "index",
|
|
2207
|
+
role: "ident"
|
|
2208
|
+
}, loc)], void 0, loc);
|
|
2209
|
+
const vam = /^@(-?[_a-zA-Z\x80-][-_a-zA-Z0-9\x80-]*)(\[([^\]]*)\])$/.exec(trimmed);
|
|
2210
|
+
if (vam) {
|
|
2211
|
+
const varBase = new Reference({ key: vam[1] }, {}, loc);
|
|
2212
|
+
const accInner = (vam[3] ?? "").trim();
|
|
2213
|
+
let accKey;
|
|
2214
|
+
let accType;
|
|
2215
|
+
if (accInner === "") {
|
|
2216
|
+
accKey = -1;
|
|
2217
|
+
accType = "index";
|
|
2218
|
+
} else if (accInner.startsWith("@")) {
|
|
2219
|
+
accKey = accInner.slice(1);
|
|
2220
|
+
accType = "variable";
|
|
2221
|
+
} else {
|
|
2222
|
+
accKey = new Quoted(accInner, {}, loc);
|
|
2223
|
+
accType = "index";
|
|
2224
|
+
}
|
|
2225
|
+
return new Expression(new Reference({
|
|
2226
|
+
target: varBase,
|
|
2227
|
+
key: accKey
|
|
2228
|
+
}, { type: accType }, loc), void 0, loc);
|
|
2229
|
+
}
|
|
2230
|
+
const nsm = nsMediaRe.exec(trimmed);
|
|
2231
|
+
if (nsm) {
|
|
2232
|
+
const nsPath = nsm[1];
|
|
2233
|
+
const argsText = nsm[2];
|
|
2234
|
+
const accText = nsm[3];
|
|
2235
|
+
const segments = nsPath.match(/[#.][^#.]*/g) ?? [nsPath];
|
|
2236
|
+
const nameKey = segments.length === 1 ? segments[0] : segments;
|
|
2237
|
+
const rawKey = segments.length > 1 ? nsPath : void 0;
|
|
2238
|
+
let base = new Reference({
|
|
2239
|
+
key: nameKey,
|
|
2240
|
+
...rawKey ? { rawKey } : {}
|
|
2241
|
+
}, {
|
|
2242
|
+
type: "mixin-ruleset",
|
|
2243
|
+
role: "name"
|
|
2244
|
+
}, loc);
|
|
2245
|
+
if (argsText) {
|
|
2246
|
+
const argsInner = argsText.slice(1, -1).trim();
|
|
2247
|
+
let argsNode = null;
|
|
2248
|
+
if (argsInner) {
|
|
2249
|
+
const argRefMatch = /^([.#][^\[\]()\s]+)(\[([^\]]*)\])?$/.exec(argsInner);
|
|
2250
|
+
if (argRefMatch) {
|
|
2251
|
+
let argBase = new Reference({ key: argRefMatch[1] }, { role: "name" }, loc);
|
|
2252
|
+
if (argRefMatch[2] !== void 0) {
|
|
2253
|
+
const argAcc = argRefMatch[3] ?? "";
|
|
2254
|
+
argBase = new Reference({
|
|
2255
|
+
target: argBase,
|
|
2256
|
+
key: argAcc === "" ? -1 : argAcc
|
|
2257
|
+
}, {}, loc);
|
|
2258
|
+
}
|
|
2259
|
+
argsNode = new List([argBase], void 0, loc);
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
const callPayload = { name: base };
|
|
2263
|
+
if (argsNode) callPayload.args = argsNode;
|
|
2264
|
+
base = new Call(callPayload, {}, loc);
|
|
2265
|
+
}
|
|
2266
|
+
if (accText) {
|
|
2267
|
+
const inner = accText.slice(1, -1).trim();
|
|
2268
|
+
const key = inner.startsWith("@") ? inner.slice(1) : inner === "" ? -1 : inner;
|
|
2269
|
+
base = new Reference({
|
|
2270
|
+
target: base,
|
|
2271
|
+
key
|
|
2272
|
+
}, { type: "variable" }, loc);
|
|
2273
|
+
}
|
|
2274
|
+
return new Expression(base, void 0, loc);
|
|
2275
|
+
}
|
|
2276
|
+
const tokens = tokenize(t);
|
|
2277
|
+
const nameMatch = /^(@?-?[_a-zA-Z\x80-\uffff][-_a-zA-Z0-9\x80-\uffff]*)\s+(?:\(|not(?![-\w]))/i.exec(trimmed);
|
|
2278
|
+
return new QueryCondition(tokens, !!nameMatch && tokens.length > 1 && ![
|
|
2279
|
+
"not",
|
|
2280
|
+
"and",
|
|
2281
|
+
"or",
|
|
2282
|
+
"only"
|
|
2283
|
+
].includes(nameMatch[1].replace(/^@/, "").toLowerCase()) ? { leadingContainerName: true } : void 0, loc);
|
|
2284
|
+
};
|
|
2285
|
+
const commaItems = splitCommas(text);
|
|
2286
|
+
if (commaItems.length === 1) return buildItem(commaItems[0]);
|
|
2287
|
+
return new List(commaItems.map(buildItem), void 0, loc);
|
|
2288
|
+
}
|
|
2289
|
+
_buildAtRuleStatement(children, loc) {
|
|
2290
|
+
const ls = children.filter((c) => c._tag === "leaf");
|
|
2291
|
+
const name = ls[0]?.value ?? "";
|
|
2292
|
+
if ([
|
|
2293
|
+
"@import",
|
|
2294
|
+
"@-import",
|
|
2295
|
+
"@-export"
|
|
2296
|
+
].includes(name)) return this._buildImportAtRuleFromPrelude(children, ls, loc, name);
|
|
2297
|
+
if (["@use", "@-use"].includes(name)) return this._buildUseAtRuleFromPrelude(children, loc, name);
|
|
2298
|
+
return super._buildAtRuleStatement(children, loc);
|
|
2299
|
+
}
|
|
2300
|
+
_buildUseAtRuleFromPrelude(children, loc, name) {
|
|
2301
|
+
const preludeText = this._source.slice(loc.start, loc.end);
|
|
2302
|
+
const quotedNode = nodeChildren(children).find((n) => n.type === "Quoted");
|
|
2303
|
+
let rawPath = "";
|
|
2304
|
+
let pathNode;
|
|
2305
|
+
if (quotedNode) {
|
|
2306
|
+
const quote = quotedNode.quote ?? "\"";
|
|
2307
|
+
const innerVal = quotedNode.value;
|
|
2308
|
+
const inner = typeof innerVal === "string" ? innerVal : innerVal?.value ?? String(innerVal?.valueOf?.() ?? "");
|
|
2309
|
+
rawPath = inner;
|
|
2310
|
+
pathNode = new Quoted(inner, { quote }, loc);
|
|
2311
|
+
} else {
|
|
2312
|
+
const qm = /(['"])((?:[^'"\\]|\\.)*)\1/.exec(preludeText);
|
|
2313
|
+
if (qm) {
|
|
2314
|
+
const quote = qm[1] === "'" ? "'" : "\"";
|
|
2315
|
+
const inner = qm[2];
|
|
2316
|
+
rawPath = inner;
|
|
2317
|
+
pathNode = new Quoted(inner, { quote }, loc);
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
const explicitNs = /\bas\s+([^\s;]+)/.exec(preludeText)?.[1];
|
|
2321
|
+
if (/\.[cm]?[jt]sx?$/.test(rawPath) || rawPath.startsWith("#")) {
|
|
2322
|
+
let ns = explicitNs;
|
|
2323
|
+
if (!ns) ns = (rawPath.split("/").pop() ?? "").replace(/\.[^.]+$/, "").replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
2324
|
+
return new JsImport({ path: pathNode }, { namespace: ns }, loc);
|
|
2325
|
+
}
|
|
2326
|
+
return new AtRule({
|
|
2327
|
+
name,
|
|
2328
|
+
prelude: pathNode,
|
|
2329
|
+
rules: []
|
|
2330
|
+
}, void 0, loc);
|
|
2331
|
+
}
|
|
2332
|
+
_parenToArgs(paren, loc) {
|
|
2333
|
+
const inner = paren.value ?? paren.node;
|
|
2334
|
+
if (!inner) return null;
|
|
2335
|
+
const items = [];
|
|
2336
|
+
const isSeq = inner && typeof inner === "object" && inner.type === "Sequence";
|
|
2337
|
+
if (isSeq || Array.isArray(inner)) {
|
|
2338
|
+
const seqItems = (isSeq ? inner.value ?? [] : inner).map((it) => typeof it === "string" ? this._lessKeyword(it.trim(), loc) : it);
|
|
2339
|
+
let j = 0;
|
|
2340
|
+
while (j < seqItems.length) {
|
|
2341
|
+
const item = seqItems[j];
|
|
2342
|
+
if (item.type === "Reference" && j + 2 < seqItems.length && seqItems[j + 1]?.value === ":") {
|
|
2343
|
+
const nameAny = item.key ?? "";
|
|
2344
|
+
const valNode = seqItems[j + 2];
|
|
2345
|
+
const vd = new VarDeclaration({
|
|
2346
|
+
name: nameAny,
|
|
2347
|
+
value: this._isKeywordLike(valNode) ? this._lessKeyword(String(valNode.value ?? "").trim(), loc) : this._lessKeyword(String(valNode.value ?? ""), loc)
|
|
2348
|
+
}, {}, loc);
|
|
2349
|
+
items.push(vd);
|
|
2350
|
+
j += 3;
|
|
2351
|
+
} else {
|
|
2352
|
+
items.push(item);
|
|
2353
|
+
j++;
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
} else if (inner && inner.type === "List") for (const it of inner.value ?? []) items.push(it);
|
|
2357
|
+
else if (inner) {
|
|
2358
|
+
if (!this._isEmptyKeywordLike(inner)) items.push(inner);
|
|
2359
|
+
}
|
|
2360
|
+
if (items.length === 0) return null;
|
|
2361
|
+
return new List(items, void 0, loc);
|
|
2362
|
+
}
|
|
2363
|
+
_tryParseNamespaceRef(valItems, loc) {
|
|
2364
|
+
const isSel = (s) => typeof s === "string" && /^[#.]-?[_a-zA-Z\u0080-\uffff]/.test(s.trim());
|
|
2365
|
+
const isCombinator = (s) => typeof s === "string" && /^[>+~|]$|^\|\|$/.test(String(s).trim());
|
|
2366
|
+
const isJessNodeVal = (x) => !!x && typeof x === "object" && "type" in x;
|
|
2367
|
+
const first = valItems[0]?.comp;
|
|
2368
|
+
const isVarRef = (x) => !!x && typeof x === "object" && x.type === "Reference";
|
|
2369
|
+
if (!isSel(first) && !isVarRef(first)) return null;
|
|
2370
|
+
if (isVarRef(first) && valItems.length < 2) return null;
|
|
2371
|
+
const hasSquareAfterVar = isVarRef(first) && valItems.slice(1).some((vi) => isJessNodeVal(vi.comp) && vi.comp.type === "Paren" && vi.comp._options?.delimiter === "square");
|
|
2372
|
+
if (isVarRef(first) && !hasSquareAfterVar) return null;
|
|
2373
|
+
let i = 0;
|
|
2374
|
+
let base = null;
|
|
2375
|
+
let pendingSegments = [];
|
|
2376
|
+
let hasMidCall = false;
|
|
2377
|
+
const flushPendingAsRef = () => {
|
|
2378
|
+
const k = pendingSegments.length === 1 ? pendingSegments[0] : pendingSegments;
|
|
2379
|
+
pendingSegments = [];
|
|
2380
|
+
if (base === null) return new Reference({ key: k }, {
|
|
2381
|
+
type: "mixin-ruleset",
|
|
2382
|
+
role: "name"
|
|
2383
|
+
}, loc);
|
|
2384
|
+
return new Reference({
|
|
2385
|
+
target: base,
|
|
2386
|
+
key: k
|
|
2387
|
+
}, {
|
|
2388
|
+
type: "mixin-ruleset",
|
|
2389
|
+
role: "name"
|
|
2390
|
+
}, loc);
|
|
2391
|
+
};
|
|
2392
|
+
while (i < valItems.length) {
|
|
2393
|
+
const c = valItems[i].comp;
|
|
2394
|
+
if (isVarRef(c) && base === null && pendingSegments.length === 0) {
|
|
2395
|
+
const rv = c;
|
|
2396
|
+
base = rv.target !== void 0 && (rv.key === "" || rv.key === void 0 || rv.key && typeof rv.key === "object" && rv.key.type === "Quoted" && !String(rv.key.value ?? "").trim()) ? rv.target : c;
|
|
2397
|
+
i++;
|
|
2398
|
+
} else if (isSel(c)) {
|
|
2399
|
+
const seg = c.trim();
|
|
2400
|
+
const splitSeg = seg.match(/[#.][^#.]*/g) ?? [seg];
|
|
2401
|
+
for (const s of splitSeg) pendingSegments.push(s);
|
|
2402
|
+
i++;
|
|
2403
|
+
} else if (isCombinator(c) && i + 1 < valItems.length && isSel(valItems[i + 1]?.comp)) i++;
|
|
2404
|
+
else if (isJessNodeVal(c) && c.type === "Paren" && c._options?.delimiter === "square") {
|
|
2405
|
+
if (pendingSegments.length > 0) base = flushPendingAsRef();
|
|
2406
|
+
if (base === null) break;
|
|
2407
|
+
const innerKey = this._decodeAccessorKey(c, loc);
|
|
2408
|
+
base = new Reference({
|
|
2409
|
+
target: base,
|
|
2410
|
+
key: innerKey
|
|
2411
|
+
}, { type: typeof innerKey === "number" ? "index" : "variable" }, loc);
|
|
2412
|
+
i++;
|
|
2413
|
+
} else if (isJessNodeVal(c) && c.type === "Paren") {
|
|
2414
|
+
if (pendingSegments.length === 0 && base === null) break;
|
|
2415
|
+
if (pendingSegments.length > 0) base = flushPendingAsRef();
|
|
2416
|
+
const argsNode = this._parenToArgs(c, loc);
|
|
2417
|
+
const callPayload = { name: base };
|
|
2418
|
+
if (argsNode) callPayload.args = argsNode;
|
|
2419
|
+
base = new Call(callPayload, {}, loc);
|
|
2420
|
+
hasMidCall = true;
|
|
2421
|
+
i++;
|
|
2422
|
+
} else break;
|
|
2423
|
+
}
|
|
2424
|
+
if (i !== valItems.length) return null;
|
|
2425
|
+
if (pendingSegments.length === 0 && base === null) return null;
|
|
2426
|
+
if (pendingSegments.length > 0) base = flushPendingAsRef();
|
|
2427
|
+
if (valItems.length > 0) {
|
|
2428
|
+
const lastSpan = valItems[valItems.length - 1].span;
|
|
2429
|
+
let afterVal = this._source.slice(lastSpan.end).trimStart();
|
|
2430
|
+
if (!hasMidCall && afterVal.startsWith("(")) {
|
|
2431
|
+
const closeIdx = afterVal.indexOf(")", 1);
|
|
2432
|
+
if (closeIdx > 0) {
|
|
2433
|
+
const callInner = afterVal.slice(1, closeIdx).trim();
|
|
2434
|
+
afterVal = afterVal.slice(closeIdx + 1).trimStart();
|
|
2435
|
+
let argsNode = null;
|
|
2436
|
+
if (callInner) {
|
|
2437
|
+
const argRefMatch = /^([.#][^\[\]()\s]+)(\[([^\]]*)\])?$/.exec(callInner);
|
|
2438
|
+
if (argRefMatch) {
|
|
2439
|
+
const argSel = argRefMatch[1];
|
|
2440
|
+
const argAccContent = argRefMatch[3];
|
|
2441
|
+
let argBase = new Reference({ key: argSel }, { role: "name" }, loc);
|
|
2442
|
+
if (argRefMatch[2] !== void 0) {
|
|
2443
|
+
const argAccKey = argAccContent === void 0 || argAccContent === "" ? -1 : argAccContent.startsWith("@") ? argAccContent.slice(1) : argAccContent;
|
|
2444
|
+
argBase = new Reference({
|
|
2445
|
+
target: argBase,
|
|
2446
|
+
key: argAccKey
|
|
2447
|
+
}, {}, loc);
|
|
2448
|
+
}
|
|
2449
|
+
argsNode = new List([argBase], void 0, loc);
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
const callPayload = { name: base };
|
|
2453
|
+
if (argsNode) callPayload.args = argsNode;
|
|
2454
|
+
base = new Call(callPayload, {}, loc);
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
const accMatch = /^\[([^\]]*)\]/.exec(afterVal);
|
|
2458
|
+
if (accMatch) {
|
|
2459
|
+
const accText = accMatch[1].trim();
|
|
2460
|
+
let accessorKey;
|
|
2461
|
+
if (accText === "") accessorKey = -1;
|
|
2462
|
+
else if (accText.startsWith("@")) accessorKey = accText.slice(1);
|
|
2463
|
+
else accessorKey = new Quoted(accText, {}, loc);
|
|
2464
|
+
base = new Reference({
|
|
2465
|
+
target: base,
|
|
2466
|
+
key: accessorKey
|
|
2467
|
+
}, {}, loc);
|
|
2468
|
+
const afterAcc = afterVal.slice(accMatch[0].length).trimStart();
|
|
2469
|
+
if (/^\(\s*\)/.test(afterAcc)) base = new Call({ name: base }, {}, loc);
|
|
2470
|
+
} else if (!hasMidCall) {
|
|
2471
|
+
if (/^\(\s*\)/.exec(afterVal)) base = new Call({ name: base }, {}, loc);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
return base;
|
|
2475
|
+
}
|
|
2476
|
+
_assembleLessValue(valItems, loc) {
|
|
2477
|
+
const parts = [];
|
|
2478
|
+
for (const item of valItems) {
|
|
2479
|
+
const c = item.comp;
|
|
2480
|
+
if (typeof c === "string") {
|
|
2481
|
+
if (c.trim()) parts.push(this._lessKeyword(c.trim(), loc));
|
|
2482
|
+
} else parts.push(c);
|
|
2483
|
+
}
|
|
2484
|
+
if (parts.length === 0) return { value: "" };
|
|
2485
|
+
if (parts.length === 1) return { value: parts[0] };
|
|
2486
|
+
return { value: parts };
|
|
2487
|
+
}
|
|
2488
|
+
};
|
|
2489
|
+
//#endregion
|
|
2490
|
+
//#region src/functional-parser.ts
|
|
2491
|
+
var BuilderHost = class extends LessGrammar {
|
|
2492
|
+
/**
|
|
2493
|
+
* The TreeContext threaded in by the caller (the plugin's per-file context),
|
|
2494
|
+
* held for the duration of one parse. Currently a pass-through: it's carried
|
|
2495
|
+
* back out in the result so a caller can hand one context in and read it back.
|
|
2496
|
+
* Grammar rules (e.g. a future `@compose`/`@use` rule) can read/mutate it —
|
|
2497
|
+
* e.g. set `context.opts.strict` — and the mutation is visible to the caller
|
|
2498
|
+
* since it's the same object.
|
|
2499
|
+
*/
|
|
2500
|
+
context;
|
|
2501
|
+
setSource(src) {
|
|
2502
|
+
this._source = src;
|
|
2503
|
+
}
|
|
2504
|
+
resetWarnings() {
|
|
2505
|
+
this._warnings = [];
|
|
2506
|
+
this._errors = [];
|
|
2507
|
+
this._liftedCommentRanges = [];
|
|
2508
|
+
}
|
|
2509
|
+
getWarnings() {
|
|
2510
|
+
return this._warnings.slice();
|
|
2511
|
+
}
|
|
2512
|
+
getErrors() {
|
|
2513
|
+
return this._errors.slice();
|
|
2514
|
+
}
|
|
2515
|
+
/** `ctx.build` host: every structural `node(type, …)` builds through this,
|
|
2516
|
+
* reusing LessGrammar's (Less + inherited CSS) `buildNode` verbatim. */
|
|
2517
|
+
captureTriviaForNode(type) {
|
|
2518
|
+
return type === "CompoundSelector";
|
|
2519
|
+
}
|
|
2520
|
+
build(type, children, fields, span, rawChildren, triviaLog) {
|
|
2521
|
+
return this.buildNode(type, {
|
|
2522
|
+
start: span.start,
|
|
2523
|
+
end: span.end
|
|
2524
|
+
}, children, void 0, rawChildren, fields, triviaLog);
|
|
2525
|
+
}
|
|
2526
|
+
};
|
|
2527
|
+
const host = new BuilderHost();
|
|
2528
|
+
function parseLessFn(input, rule = "Stylesheet", mathMode = "parens-division", context) {
|
|
2529
|
+
host.mathMode = context?.options?.mathMode ?? mathMode;
|
|
2530
|
+
host.context = context;
|
|
2531
|
+
const g = lessGrammar;
|
|
2532
|
+
const result = runFunctionalParse(input, g[rule], host, { trivia: g.rw });
|
|
2533
|
+
const threaded = host.context;
|
|
2534
|
+
host.context = void 0;
|
|
2535
|
+
return {
|
|
2536
|
+
...result,
|
|
2537
|
+
context: threaded
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
/**
|
|
2541
|
+
* Index of the first backtick in CODE position (i.e. real inline JS), or -1.
|
|
2542
|
+
* Skips `//` line comments, `/* … */` block comments, and quoted strings so a
|
|
2543
|
+
* backtick inside a comment/string (common in Less doc comments) is not
|
|
2544
|
+
* mistaken for inline JavaScript.
|
|
2545
|
+
*/
|
|
2546
|
+
function firstInlineJsBacktick(text) {
|
|
2547
|
+
for (let i = 0; i < text.length; i++) {
|
|
2548
|
+
const c = text[i];
|
|
2549
|
+
if (c === "`") return i;
|
|
2550
|
+
if (c === "/" && text[i + 1] === "/") {
|
|
2551
|
+
const nl = text.indexOf("\n", i + 2);
|
|
2552
|
+
if (nl === -1) return -1;
|
|
2553
|
+
i = nl;
|
|
2554
|
+
} else if (c === "/" && text[i + 1] === "*") {
|
|
2555
|
+
const end = text.indexOf("*/", i + 2);
|
|
2556
|
+
if (end === -1) return -1;
|
|
2557
|
+
i = end + 1;
|
|
2558
|
+
} else if (c === "\"" || c === "'") {
|
|
2559
|
+
i++;
|
|
2560
|
+
while (i < text.length && text[i] !== c) {
|
|
2561
|
+
if (text[i] === "\\") i++;
|
|
2562
|
+
i++;
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
return -1;
|
|
2567
|
+
}
|
|
2568
|
+
/** Functional Less parser — call .parse(text) to get a Jess AST. */
|
|
2569
|
+
var LessParser = class {
|
|
2570
|
+
_mathMode;
|
|
2571
|
+
constructor(config) {
|
|
2572
|
+
this._mathMode = config?.mathMode ?? "parens-division";
|
|
2573
|
+
}
|
|
2574
|
+
parse = (text, rule = "Stylesheet", options) => {
|
|
2575
|
+
const backtick = firstInlineJsBacktick(text);
|
|
2576
|
+
if (backtick !== -1) return {
|
|
2577
|
+
tree: nil(),
|
|
2578
|
+
errors: [toParseError("Inline JavaScript using backticks is not supported. Use @use / @-use to import a script module instead.", backtick, text)],
|
|
2579
|
+
warnings: [],
|
|
2580
|
+
trivia: buildLazyTriviaMap([], text),
|
|
2581
|
+
liftedCommentRanges: [],
|
|
2582
|
+
context: options?.context
|
|
2583
|
+
};
|
|
2584
|
+
return parseLessFn(text, rule, this._mathMode, options?.context);
|
|
2585
|
+
};
|
|
2586
|
+
};
|
|
2587
|
+
//#endregion
|
|
2588
|
+
export { getInterpolatedNode as a, Fragments as c, lessTokens as d, createInterpolatedReference as i, Tokens as l, parseLessFn as n, getInterpolatedOrString as o, LessGrammar as r, normalizeMixinReferenceKey as s, LessParser as t, lessFragments as u };
|