@jesscss/less-parser 1.0.8-alpha.6 → 2.0.0-alpha.2

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.
Files changed (41) hide show
  1. package/README.md +34 -1
  2. package/lib/__tests__/debug-log.d.ts +1 -0
  3. package/lib/__tests__/debug-log.js +34 -0
  4. package/lib/__tests__/debug-log.js.map +1 -0
  5. package/lib/index.d.ts +30 -8
  6. package/lib/index.js +66 -29
  7. package/lib/index.js.map +1 -0
  8. package/lib/lessActionsParser.d.ts +156 -0
  9. package/lib/lessActionsParser.js +145 -0
  10. package/lib/lessActionsParser.js.map +1 -0
  11. package/lib/lessErrorMessageProvider.d.ts +3 -0
  12. package/lib/lessErrorMessageProvider.js +4 -0
  13. package/lib/lessErrorMessageProvider.js.map +1 -0
  14. package/lib/lessTokens.d.ts +22 -3
  15. package/lib/lessTokens.js +242 -148
  16. package/lib/lessTokens.js.map +1 -0
  17. package/lib/productions.d.ts +181 -0
  18. package/lib/productions.js +3521 -0
  19. package/lib/productions.js.map +1 -0
  20. package/lib/utils.d.ts +2 -0
  21. package/lib/utils.js +88 -0
  22. package/lib/utils.js.map +1 -0
  23. package/package.json +41 -19
  24. package/lib/lessParser.d.ts +0 -36
  25. package/lib/lessParser.js +0 -49
  26. package/lib/productions/atRules.d.ts +0 -2
  27. package/lib/productions/atRules.js +0 -160
  28. package/lib/productions/blocks.d.ts +0 -2
  29. package/lib/productions/blocks.js +0 -60
  30. package/lib/productions/declarations.d.ts +0 -2
  31. package/lib/productions/declarations.js +0 -36
  32. package/lib/productions/interpolation.d.ts +0 -2
  33. package/lib/productions/interpolation.js +0 -18
  34. package/lib/productions/mixin.d.ts +0 -2
  35. package/lib/productions/mixin.js +0 -373
  36. package/lib/productions/root.d.ts +0 -2
  37. package/lib/productions/root.js +0 -33
  38. package/lib/productions/selectors.d.ts +0 -2
  39. package/lib/productions/selectors.js +0 -91
  40. package/lib/productions/values.d.ts +0 -2
  41. package/lib/productions/values.js +0 -305
@@ -0,0 +1,3521 @@
1
+ import { tokenMatcher, EMPTY_ALT, NoViableAltException } from 'chevrotain';
2
+ import { main as cssMain, declaration as cssDeclaration, mediaInParens as cssMediaInParens, complexSelector as cssComplexSelector, nthValue as cssNthValue, knownFunctions as cssKnownFunctions, mathValue as cssMathValue, innerAtRule as cssInnerAtRule, productions as cssProductions } from '@jesscss/css-parser';
3
+ import { Node, Ampersand, Block, Any, Ruleset, BasicSelector, Combinator, List, Sequence, Call, Paren, Operation, Quoted, AtRule, Interpolated, InterpolatedSelector, Reference, Dimension, Num, Extend, Negative, Mixin, Condition, VarDeclaration, DefaultGuard, Rest, StyleImport, Expression, SelectorCapture, ComplexSelector, CompoundSelector, SelectorList, Rules, Nil, Collection, INTERPOLATION_PLACEHOLDER, isNode, shouldOperateWithMathFrames } from '@jesscss/core';
4
+ import { getInterpolatedOrString } from './utils.js';
5
+ import { all } from 'known-css-properties';
6
+ function getParenFrames(ctx) {
7
+ return ctx?.parenFrames ?? [];
8
+ }
9
+ function getCalcFrames(ctx) {
10
+ return ctx?.calcFrames ?? 0;
11
+ }
12
+ function withCalcFrame(ctx, delta) {
13
+ const calcFrames = getCalcFrames(ctx) + delta;
14
+ return { ...(ctx ?? {}), calcFrames };
15
+ }
16
+ function guardContainsDefaultCall(node) {
17
+ if (!node) {
18
+ return false;
19
+ }
20
+ const isNodeLike = (value) => {
21
+ return Boolean(value
22
+ && typeof value === 'object'
23
+ && typeof value.type === 'string'
24
+ && typeof value.valueOf === 'function');
25
+ };
26
+ const queue = [node];
27
+ const seen = new Set();
28
+ while (queue.length > 0) {
29
+ const current = queue.shift();
30
+ if (!current || seen.has(current) || !isNodeLike(current)) {
31
+ continue;
32
+ }
33
+ seen.add(current);
34
+ if (current.type === 'DefaultGuard') {
35
+ return true;
36
+ }
37
+ if (current.type === 'Call') {
38
+ const callName = current.value.name;
39
+ const callNameStr = String(callName?.valueOf?.() ?? callName ?? '');
40
+ if (callNameStr === 'default' || callNameStr === '??') {
41
+ return true;
42
+ }
43
+ const key = callName?.value?.key;
44
+ const keyStr = String(key?.valueOf?.() ?? key ?? '');
45
+ if (keyStr === 'default' || keyStr === '??') {
46
+ return true;
47
+ }
48
+ }
49
+ const value = current.value;
50
+ if (Array.isArray(value)) {
51
+ queue.push(...value);
52
+ }
53
+ else if (value && typeof value === 'object') {
54
+ queue.push(...Object.values(value));
55
+ }
56
+ }
57
+ return false;
58
+ }
59
+ function isDefaultGuardCall(node) {
60
+ if (!node || node.type !== 'Call') {
61
+ return false;
62
+ }
63
+ const callName = node.value.name;
64
+ const callNameStr = String(callName?.valueOf?.() ?? callName ?? '');
65
+ if (callNameStr === 'default' || callNameStr === '??') {
66
+ return true;
67
+ }
68
+ const key = callName?.value?.key;
69
+ const keyStr = String(key?.valueOf?.() ?? key ?? '');
70
+ return keyStr === 'default' || keyStr === '??';
71
+ }
72
+ function loc(node) {
73
+ const location = node.location;
74
+ return location.length === 6 ? location : undefined;
75
+ }
76
+ function wrapOuterExpressionIfNeeded(node, ctx) {
77
+ if (!this.wrapOuterExpressions) {
78
+ return node;
79
+ }
80
+ if (!ctx?.wrapInExpression) {
81
+ return node;
82
+ }
83
+ // Expressions should never contain Expressions; avoid nesting.
84
+ if (node instanceof Expression) {
85
+ return node;
86
+ }
87
+ // Math expressions: only wrap if this operation would actually be performed.
88
+ if (isNode(node, 'Operation')) {
89
+ const [left, op, right] = node.value;
90
+ const mathMode = this.mathMode ?? 'parens-division';
91
+ const shouldOperate = shouldOperateWithMathFrames({
92
+ mathMode,
93
+ parenFrames: getParenFrames(ctx),
94
+ calcFrames: getCalcFrames(ctx)
95
+ }, op, left, right);
96
+ if (shouldOperate) {
97
+ return new Expression(node, { parens: true }, loc(node), this.context);
98
+ }
99
+ }
100
+ return node;
101
+ }
102
+ const isEscapedString = function (T) {
103
+ const next = this.LA(1);
104
+ return tokenMatcher(next, T.QuoteStart) && next.image.startsWith('~');
105
+ };
106
+ /** Charset moved within `main` (explained in that rule) */
107
+ export function stylesheet(T) {
108
+ const $ = this;
109
+ // stylesheet
110
+ // : CHARSET? main EOF
111
+ // ;
112
+ return (options = {}) => {
113
+ let RECORDING_PHASE = $.RECORDING_PHASE;
114
+ let context;
115
+ if (!RECORDING_PHASE) {
116
+ if (options.context) {
117
+ context = this._context = options.context;
118
+ }
119
+ else {
120
+ context = this.context;
121
+ }
122
+ // Less may set scope in context.opts; TreeContext has no scope
123
+ }
124
+ let charset;
125
+ $.OPTION({
126
+ GATE: () => !$.looseMode,
127
+ DEF: () => {
128
+ charset = $.CONSUME(T.Charset);
129
+ }
130
+ });
131
+ const ctx = { isRoot: true };
132
+ let root = $.SUBRULE($.main, { ARGS: [ctx] });
133
+ if (!RECORDING_PHASE) {
134
+ let rules = root?.value;
135
+ if (charset) {
136
+ let loc = $.getLocationInfo(charset);
137
+ let rootLoc = root.location;
138
+ rules.unshift(new Any(charset.image, { role: 'charset' }, loc, context));
139
+ rootLoc[0] = loc[0];
140
+ rootLoc[1] = loc[1];
141
+ rootLoc[2] = loc[2];
142
+ }
143
+ return root;
144
+ }
145
+ };
146
+ }
147
+ /**
148
+ * Starts with a colon, with these conditions
149
+ * 1. It is not preceded by a space or
150
+ * 2. If it is preceded by a space, then it is
151
+ * followed by a space.
152
+ */
153
+ function isVariableLike(T) {
154
+ const $ = this;
155
+ let token = $.LA(2);
156
+ let isColon = token.tokenType === T.Colon;
157
+ let isParen = token.tokenType === T.LParen;
158
+ let postToken = $.LA(3);
159
+ // During recording phase, preSkippedTokenMap might not exist
160
+ if (!$.preSkippedTokenMap) {
161
+ return false;
162
+ }
163
+ if (!isColon && !isParen) {
164
+ return false;
165
+ }
166
+ let isVariable = !$.preSkippedTokenMap.has(token.startOffset)
167
+ || (isColon && $.preSkippedTokenMap.has(postToken.startOffset));
168
+ return isVariable;
169
+ }
170
+ ;
171
+ export function main(T) {
172
+ const $ = this;
173
+ const ruleAlt = (ctx = {}) => {
174
+ let isVariable = isVariableLike.call(this, T);
175
+ return [
176
+ { ALT: () => $.SUBRULE($.functionCall, { ARGS: [ctx] }) },
177
+ { ALT: () => $.SUBRULE($.ampersandExtend, { ARGS: [ctx] }) },
178
+ {
179
+ GATE: () => {
180
+ let next = $.LA(1).tokenType;
181
+ return next === T.DotName || next === T.HashName || next === T.ColorIdentStart;
182
+ },
183
+ ALT: () => $.SUBRULE($.mixinOrQualifiedRule, { ARGS: [ctx] })
184
+ },
185
+ {
186
+ GATE: () => {
187
+ let next = $.LA(1).tokenType;
188
+ return next !== T.DotName
189
+ && next !== T.HashName
190
+ && next !== T.ColorIdentStart;
191
+ },
192
+ ALT: () => $.SUBRULE($.qualifiedRule, { ARGS: [ctx] })
193
+ },
194
+ {
195
+ GATE: () => isVariable,
196
+ ALT: () => $.SUBRULE($.varDeclarationOrCall, { ARGS: [ctx] })
197
+ },
198
+ {
199
+ GATE: () => !isVariable,
200
+ ALT: () => $.SUBRULE($.atRule, { ARGS: [ctx] })
201
+ },
202
+ /**
203
+ * Historically, Less allows `@charset` anywhere,
204
+ * to avoid outputting it in the wrong place.
205
+ * Ideally, this would result in an error if, say,
206
+ * the `@charset` was defined at the bottom of the file,
207
+ * but that wasn't the solution made.
208
+ * @see https://github.com/less/less.js/issues/2126
209
+ */
210
+ {
211
+ GATE: () => $.looseMode,
212
+ ALT: () => $.CONSUME(T.Charset)
213
+ },
214
+ { ALT: () => $.CONSUME2(T.Semi) }
215
+ // { ALT: () => $.SUBRULE($.mixinCall) }
216
+ ];
217
+ };
218
+ return (ctx = {}) => {
219
+ let RECORDING_PHASE = $.RECORDING_PHASE;
220
+ let context;
221
+ if (!RECORDING_PHASE) {
222
+ context = this.context;
223
+ }
224
+ let rules;
225
+ if (!RECORDING_PHASE) {
226
+ rules = [];
227
+ }
228
+ let requiredSemi = false;
229
+ let lastRule;
230
+ /**
231
+ * In this production rule, semi-colons are not required
232
+ * but this is repurposed by declarationList and by Less / Sass,
233
+ * so that's why this gate is here.
234
+ */
235
+ $.MANY({
236
+ GATE: () => !requiredSemi || (requiredSemi && ($.LA(1).tokenType === T.Semi
237
+ || $.LA(0).tokenType === T.Semi)),
238
+ DEF: () => {
239
+ const localAlt = typeof ruleAlt === 'function' ? ruleAlt(ctx) : ruleAlt;
240
+ let value = $.OR(localAlt);
241
+ if (!RECORDING_PHASE) {
242
+ /** @todo - When do we not have a value? */
243
+ if (value) {
244
+ if (!(value instanceof Node)) {
245
+ /** This is a semi-colon or charset token */
246
+ if (value.image.includes('@charset')) {
247
+ rules.push(new Any(value.image, { role: 'charset' }, $.getLocationInfo(value), context));
248
+ }
249
+ else {
250
+ if (lastRule) {
251
+ lastRule.options.semi = true;
252
+ }
253
+ else {
254
+ rules.push(new Any(';', { role: 'semi' }, $.getLocationInfo($.LA(1)), context));
255
+ }
256
+ }
257
+ }
258
+ else {
259
+ requiredSemi = !!value.requiredSemi;
260
+ rules.push(value);
261
+ lastRule = value;
262
+ }
263
+ }
264
+ }
265
+ }
266
+ });
267
+ if (!RECORDING_PHASE) {
268
+ // Process any extendNodes that were set (e.g., by ampersandExtend at root level)
269
+ if (ctx.extendNodes && ctx.extendNodes.length > 0) {
270
+ // Filter out Nil nodes (returned by ampersandExtend to avoid duplication)
271
+ const filteredRules = rules.filter(r => !(r instanceof Nil));
272
+ rules = [...ctx.extendNodes, ...filteredRules];
273
+ ctx.extendNodes = undefined;
274
+ }
275
+ let returnNode = $.getRulesWithComments(rules, $.getLocationInfo($.LA(1)));
276
+ // Attaches remaining whitespace at the end of rules
277
+ const wrapped = $.wrap(returnNode, true);
278
+ return wrapped;
279
+ }
280
+ };
281
+ }
282
+ export function declarationList(T) {
283
+ const $ = this;
284
+ let ruleAlt = (ctx = {}) => {
285
+ let isVariable = isVariableLike.call(this, T);
286
+ return [
287
+ {
288
+ ALT: () => {
289
+ return $.SUBRULE($.declaration, { ARGS: [ctx] });
290
+ }
291
+ },
292
+ { ALT: () => $.SUBRULE($.ampersandExtend, { ARGS: [ctx] }) },
293
+ {
294
+ ALT: () => {
295
+ const fnCall = $.SUBRULE($.functionCall, { ARGS: [ctx] });
296
+ if (!$.RECORDING_PHASE && fnCall instanceof Call) {
297
+ // Less allows function calls like `each(...){...}` in declaration lists
298
+ // without a required trailing semicolon.
299
+ fnCall.requiredSemi = false;
300
+ }
301
+ return fnCall;
302
+ }
303
+ },
304
+ {
305
+ GATE: () => isVariable,
306
+ ALT: () => $.SUBRULE($.varDeclarationOrCall, { ARGS: [ctx] })
307
+ },
308
+ {
309
+ GATE: () => !isVariable,
310
+ ALT: () => $.SUBRULE($.innerAtRule, { ARGS: [ctx] })
311
+ },
312
+ {
313
+ GATE: () => {
314
+ let next = $.LA(1).tokenType;
315
+ return next === T.DotName || next === T.HashName || next === T.ColorIdentStart;
316
+ },
317
+ ALT: () => {
318
+ return $.SUBRULE($.mixinOrQualifiedRule, { ARGS: [{ ...ctx, inner: true }] });
319
+ }
320
+ },
321
+ {
322
+ GATE: () => {
323
+ let next = $.LA(1).tokenType;
324
+ return next !== T.DotName
325
+ && next !== T.HashName
326
+ && next !== T.ColorIdentStart;
327
+ },
328
+ ALT: () => {
329
+ return $.SUBRULE($.qualifiedRule, { ARGS: [{ ...ctx, inner: true }] });
330
+ }
331
+ },
332
+ { ALT: () => $.CONSUME2(T.Semi) }
333
+ ];
334
+ };
335
+ return (ctx = {}) => cssMain.call(this, T, ruleAlt)(ctx);
336
+ }
337
+ // Wrapper to ensure a mixin call in declaration context ends with a semicolon
338
+ // export function mixinCallStatement(this: P, T: TokenMap) {
339
+ // const $ = this;
340
+ // return () => {
341
+ // const node = $.SUBRULE($.mixinCall);
342
+ // // If mixinCall did not consume a semicolon, require one now
343
+ // if ($.LA(1).tokenType === T.Semi) {
344
+ // $.CONSUME(T.Semi);
345
+ // }
346
+ // return node;
347
+ // };
348
+ // }
349
+ let interpolatedRegex = /([$@]){([^}]+)}/g;
350
+ const createInterpolatedReference = (prefix, value, location, context) => {
351
+ const isProperty = prefix === '$';
352
+ const key = isProperty
353
+ ? new Quoted(value, { quote: '\'' }, location, context)
354
+ : value;
355
+ return new Reference({ key }, { type: isProperty ? 'property' : 'variable', role: 'ident' }, location, context);
356
+ };
357
+ const getInterpolated = (name, location, context) => {
358
+ const replacements = [];
359
+ let result;
360
+ let source = name;
361
+ while (result = interpolatedRegex.exec(name)) {
362
+ const [match, propOrVar, value] = result;
363
+ source = source.replace(match, INTERPOLATION_PLACEHOLDER);
364
+ const reference = createInterpolatedReference(propOrVar, value, location, context);
365
+ replacements.push(reference);
366
+ }
367
+ return new Interpolated({ source, replacements }, { role: 'ident' }, location, context);
368
+ };
369
+ export function declaration(T) {
370
+ const $ = this;
371
+ let ruleAlt = (ctx = {}) => [
372
+ {
373
+ ALT: () => {
374
+ let name;
375
+ $.OR2([
376
+ {
377
+ ALT: () => {
378
+ name = $.CONSUME(T.Ident);
379
+ }
380
+ },
381
+ {
382
+ GATE: () => $.legacyMode,
383
+ ALT: () => name = $.CONSUME(T.LegacyPropIdent)
384
+ }
385
+ ]);
386
+ let assign = $.CONSUME(T.Assign);
387
+ let value = $.SUBRULE($.valueList, { ARGS: [ctx] });
388
+ let important;
389
+ $.OPTION(() => {
390
+ important = $.CONSUME(T.Important);
391
+ });
392
+ if (!$.RECORDING_PHASE) {
393
+ let nameNode;
394
+ let nameValue = name.image;
395
+ if (nameValue.includes('@') || nameValue.includes('$')) {
396
+ nameNode = getInterpolated(nameValue, $.getLocationInfo(name), this.context);
397
+ }
398
+ else {
399
+ nameNode = $.wrap(new Any(name.image, { role: 'property' }, $.getLocationInfo(name), this.context), true);
400
+ }
401
+ return [nameNode, assign, value, important];
402
+ }
403
+ }
404
+ },
405
+ {
406
+ ALT: () => {
407
+ let RECORDING_PHASE = $.RECORDING_PHASE;
408
+ let nodes;
409
+ if (!RECORDING_PHASE) {
410
+ nodes = [];
411
+ }
412
+ let name = $.OR3([
413
+ { ALT: () => $.CONSUME(T.InterpolatedCustomProperty) },
414
+ { ALT: () => $.CONSUME(T.CustomProperty) }
415
+ ]);
416
+ let assign = $.CONSUME2(T.Assign);
417
+ $.startRule();
418
+ $.MANY(() => {
419
+ let val = $.SUBRULE($.customValue, { ARGS: [{ ...ctx, inCustomPropertyValue: true }] });
420
+ if (!RECORDING_PHASE) {
421
+ nodes.push(val);
422
+ }
423
+ });
424
+ if (!RECORDING_PHASE) {
425
+ let location = $.endRule();
426
+ let nameNode;
427
+ let nameValue = name.image;
428
+ if (nameValue.includes('@') || nameValue.includes('$')) {
429
+ nameNode = getInterpolated(nameValue, $.getLocationInfo(name), this.context);
430
+ }
431
+ else {
432
+ nameNode = $.wrap(new Any(name.image, { role: 'property' }, $.getLocationInfo(name), this.context), true);
433
+ }
434
+ let value = new Sequence(nodes, undefined, location, this.context);
435
+ return [nameNode, assign, value];
436
+ }
437
+ }
438
+ }
439
+ ];
440
+ return (ctx = {}) => {
441
+ return cssDeclaration.call(this, T, ruleAlt)(ctx);
442
+ };
443
+ }
444
+ export function mediaInParens(T) {
445
+ const $ = this;
446
+ let isEscaped = isEscapedString.bind(this, T);
447
+ return (ctx = {}) => $.OR([
448
+ /**
449
+ * It's up to the Less author to validate that this will produce
450
+ * valid media queries.
451
+ */
452
+ {
453
+ /** Allow escaped strings */
454
+ GATE: isEscaped,
455
+ ALT: () => $.SUBRULE($.string, { ARGS: [ctx] })
456
+ },
457
+ /**
458
+ * After Less evaluation, should throw an error
459
+ * if the value of `@myvar` is a ruleset
460
+ */
461
+ {
462
+ ALT: () => {
463
+ return $.SUBRULE($.valueReference, { ARGS: [{ ...ctx, requireAccessorsAfterMixinCall: true }] });
464
+ }
465
+ },
466
+ {
467
+ ALT: cssMediaInParens.call(this, T)
468
+ }
469
+ ]);
470
+ }
471
+ export function containerInParens(_T, _alt) {
472
+ const $ = this;
473
+ // Reuse mediaInParens which already handles variables
474
+ return (ctx = {}) => $.SUBRULE($.mediaInParens, { ARGS: [ctx] });
475
+ }
476
+ export function mfValue(_T) {
477
+ const $ = this;
478
+ return (ctx = {}) =>
479
+ /**
480
+ * Like the original Less Parser, we're
481
+ * going to allow any value expression,
482
+ * and it's up to the Less author to know
483
+ * if it's valid.
484
+ */
485
+ (() => {
486
+ const exprCtx = { ...ctx, wrapInExpression: true };
487
+ const node = $.SUBRULE($.expressionSum, { ARGS: [exprCtx] });
488
+ if (!$.RECORDING_PHASE) {
489
+ return wrapOuterExpressionIfNeeded.call($, node, exprCtx);
490
+ }
491
+ return node;
492
+ })();
493
+ }
494
+ export function mfNonIdentifierValue(T, _alt) {
495
+ const $ = this;
496
+ return (ctx = {}) => $.OR([
497
+ {
498
+ GATE: () => {
499
+ const next = $.LA(1);
500
+ return next.tokenType === T.AtKeyword || next.tokenType === T.PropertyReference || next.tokenType === T.NestedReference;
501
+ },
502
+ ALT: () => $.SUBRULE($.valueReference, { ARGS: [{ ...ctx, requireAccessorsAfterMixinCall: true }] })
503
+ },
504
+ {
505
+ ALT: () => {
506
+ $.startRule();
507
+ let num1 = $.CONSUME(T.Number);
508
+ let num2;
509
+ $.OPTION(() => {
510
+ $.CONSUME(T.Slash);
511
+ num2 = $.CONSUME2(T.Number);
512
+ });
513
+ if (!$.RECORDING_PHASE) {
514
+ let location = $.endRule();
515
+ let num1Node = $.wrap($.processValueToken(num1), 'both');
516
+ if (!num2) {
517
+ return num1Node;
518
+ }
519
+ let num2Node = $.wrap($.processValueToken(num2), 'both');
520
+ return new List([num1Node, num2Node], { sep: '/' }, location, this.context);
521
+ }
522
+ }
523
+ },
524
+ {
525
+ ALT: () => {
526
+ let dim = $.CONSUME(T.Dimension);
527
+ if (!$.RECORDING_PHASE) {
528
+ return $.wrap($.processValueToken(dim), 'both');
529
+ }
530
+ }
531
+ }
532
+ ]);
533
+ }
534
+ export function wrappedDeclarationList(T) {
535
+ const $ = this;
536
+ return (ctx = {}) => {
537
+ $.CONSUME(T.LCurly);
538
+ let rules = $.SUBRULE($.declarationList, { ARGS: [ctx] });
539
+ $.CONSUME(T.RCurly);
540
+ return rules;
541
+ };
542
+ }
543
+ export function qualifiedRuleBody(T) {
544
+ const $ = this;
545
+ return (ctx = {}) => {
546
+ let RECORDING_PHASE = $.RECORDING_PHASE;
547
+ let selector;
548
+ let isSelectorList;
549
+ if (!RECORDING_PHASE) {
550
+ selector = ctx.selector;
551
+ isSelectorList = ctx.isSelectorList ?? selector instanceof SelectorList;
552
+ }
553
+ let guard;
554
+ $.OPTION4({
555
+ GATE: () => !isSelectorList,
556
+ DEF: () => {
557
+ guard = $.SUBRULE2($.guard, { ARGS: [ctx] });
558
+ }
559
+ });
560
+ $.CONSUME2(T.LCurly);
561
+ // Save extendNodes before parsing declarationList, so nested rulesets don't inherit them
562
+ // Make a copy of the array (not just a reference) so mutations during nested parsing don't affect it
563
+ let savedExtendNodes = ctx.extendNodes ? [...ctx.extendNodes] : undefined;
564
+ ctx.extendNodes = undefined;
565
+ let rules = $.SUBRULE2($.declarationList, { ARGS: [ctx] });
566
+ let end = $.CONSUME2(T.RCurly);
567
+ // After declarationList, check if new extends were added (e.g., by ampersandExtend)
568
+ // If so, merge them with the saved extends; otherwise restore the saved extends
569
+ const newExtends = ctx.extendNodes;
570
+ if (newExtends && newExtends.length) {
571
+ // New extends were added during declarationList (e.g., &:extend())
572
+ if (savedExtendNodes && savedExtendNodes.length > 0) {
573
+ // Merge with saved extends
574
+ ctx.extendNodes = [...savedExtendNodes, ...newExtends];
575
+ }
576
+ else {
577
+ // Keep the new extends
578
+ ctx.extendNodes = newExtends;
579
+ }
580
+ }
581
+ else {
582
+ // No new extends, restore saved extends
583
+ ctx.extendNodes = savedExtendNodes;
584
+ }
585
+ if (!RECORDING_PHASE) {
586
+ let extend = ctx.extendNodes;
587
+ if (extend?.length) {
588
+ /** If it's not a selector list, then our only extend does not need to be grouped */
589
+ if (!isSelectorList) {
590
+ /** For extends inside rulesets (not bubbled), selector should be undefined
591
+ * so it defaults to ampersand and resolves to the ruleset's selector */
592
+ for (let e of extend) {
593
+ e.value.selector = undefined;
594
+ }
595
+ rules.value = [...extend, ...rules.value];
596
+ ctx.extendNodes = undefined;
597
+ }
598
+ else {
599
+ const selectorList = selector;
600
+ const selectorCount = selectorList.value.length;
601
+ const extendCount = extend.length;
602
+ // Determine if extends should bubble up:
603
+ // 1. If any selectors in the list have extends (extendCount < selectorCount)
604
+ // 2. If all selectors have extends but their "all" flags don't match
605
+ let shouldBubble = false;
606
+ if (extendCount < selectorCount) {
607
+ // Some selectors have extends, some don't - bubble up
608
+ shouldBubble = true;
609
+ }
610
+ else if (extendCount === selectorCount) {
611
+ // All selectors have extends - check if flags match
612
+ let finalExtends = groupExtendsByTargetAndFlag(extend);
613
+ if (finalExtends.length === 1) {
614
+ // All extends have same target and flag - can be inside ruleset
615
+ let extendNodes = finalExtends[0];
616
+ let finalExtend = isArray(extendNodes) ? extendNodes[0] : extendNodes;
617
+ finalExtend.value.selector = undefined;
618
+ rules.value = [finalExtend, ...rules.value];
619
+ ctx.extendNodes = undefined;
620
+ }
621
+ else {
622
+ // Multiple extend groups (different targets/flags) - bubble up
623
+ shouldBubble = true;
624
+ }
625
+ }
626
+ else {
627
+ // extendCount > selectorCount - shouldn't happen, but bubble to be safe
628
+ shouldBubble = true;
629
+ }
630
+ if (shouldBubble) {
631
+ // Keep extends in ctx.extendNodes so they bubble up to qualifiedRule
632
+ // Don't clear ctx.extendNodes - let them bubble
633
+ // The extends will be prepended above the ruleset in qualifiedRule
634
+ }
635
+ }
636
+ }
637
+ let node = new Ruleset({ selector, rules, guard }, undefined, undefined, this.context);
638
+ let [startOffset, startLine, startColumn] = selector.location;
639
+ let { endOffset, endLine, endColumn } = end;
640
+ node._location = [startOffset, startLine, startColumn, endOffset, endLine, endColumn];
641
+ return node;
642
+ }
643
+ };
644
+ }
645
+ export function qualifiedRule(_T, altContext) {
646
+ const $ = this;
647
+ let selectorAlt = altContext ?? ((ctx) => [
648
+ {
649
+ GATE: () => !ctx.inner,
650
+ ALT: () => {
651
+ let initialQualifiedRule = ctx.qualifiedRule;
652
+ ctx.qualifiedRule = true;
653
+ let rule = $.SUBRULE($.selectorList, { ARGS: [ctx] });
654
+ ctx.qualifiedRule = initialQualifiedRule;
655
+ return rule;
656
+ }
657
+ },
658
+ {
659
+ GATE: () => !!ctx.inner,
660
+ ALT: () => {
661
+ let initialQualifiedRule = ctx.qualifiedRule;
662
+ let initialFirstSelector = ctx.firstSelector;
663
+ ctx.firstSelector = true;
664
+ ctx.qualifiedRule = true;
665
+ let rule = $.SUBRULE($.forgivingSelectorList, { ARGS: [ctx] });
666
+ ctx.qualifiedRule = initialQualifiedRule;
667
+ ctx.firstSelector = initialFirstSelector;
668
+ return rule;
669
+ }
670
+ }
671
+ ]);
672
+ // qualifiedRule
673
+ // : selectorList WS* LCURLY declarationList RCURLY
674
+ // ;
675
+ return (ctx = {}) => {
676
+ // Save parent's extendNodes before parsing selector (which may set extendNodes)
677
+ let savedExtendNodes = ctx.extendNodes ? [...ctx.extendNodes] : undefined;
678
+ // Set extendNodes to a fresh empty array upon entry to this qualifiedRule
679
+ // so nested rulesets don't inherit extends from parent rulesets
680
+ ctx.extendNodes = undefined;
681
+ let selector = $.OR(selectorAlt(ctx));
682
+ // Use the same context object so modifications propagate back
683
+ ctx.selector = selector;
684
+ // Now extendNodes may have been set by extend() during selector parsing
685
+ // Save it for this ruleset, then clear it so nested rulesets don't see it
686
+ let thisExtendNodes = ctx.extendNodes ? [...ctx.extendNodes] : undefined;
687
+ ctx.extendNodes = undefined;
688
+ let rule = $.SUBRULE($.qualifiedRuleBody, { ARGS: [ctx] });
689
+ // After qualifiedRuleBody returns, ctx.extendNodes may contain:
690
+ // 1. Extends that should bubble up (from nested rulesets or this ruleset that didn't match)
691
+ // 2. Nothing (if all extends were processed)
692
+ // Restore this ruleset's extendNodes (from selector parsing) to process them
693
+ const bubblingExtends = ctx.extendNodes; // Extends that should bubble up
694
+ ctx.extendNodes = thisExtendNodes;
695
+ // Restore parent's extendNodes after processing this ruleset's extends
696
+ let parentExtendNodes = savedExtendNodes;
697
+ if (ctx.extendNodes) {
698
+ let qRuleset = rule;
699
+ // Set the Extend nodes' selector to the ruleset's selector (not &)
700
+ // This allows the extends to work correctly when evaluated in the wrapper Rules context
701
+ for (const extendNode of ctx.extendNodes) {
702
+ if (extendNode.value.selector === undefined || extendNode.value.selector.type === 'Ampersand') {
703
+ extendNode.value.selector = selector;
704
+ }
705
+ }
706
+ /** Prepend a rules block */
707
+ rule = new Rules([
708
+ ...ctx.extendNodes,
709
+ qRuleset
710
+ ]);
711
+ // Set location from the ruleset (which has proper location info)
712
+ if (qRuleset._location) {
713
+ rule._location = qRuleset._location;
714
+ }
715
+ ctx.extendNodes = undefined;
716
+ }
717
+ // Restore parent's extendNodes and merge with any bubbling extends
718
+ const hasBubblingExtends = bubblingExtends && bubblingExtends.length > 0;
719
+ if (hasBubblingExtends) {
720
+ // Bubble them up to parent
721
+ if (parentExtendNodes && parentExtendNodes.length > 0) {
722
+ ctx.extendNodes = [...parentExtendNodes, ...bubblingExtends];
723
+ }
724
+ else {
725
+ ctx.extendNodes = bubblingExtends;
726
+ }
727
+ }
728
+ else {
729
+ ctx.extendNodes = parentExtendNodes;
730
+ }
731
+ return rule;
732
+ };
733
+ }
734
+ /**
735
+ * In order to not do any backtracking, anything with a class or id selector start
736
+ * will end up here, and everything else will be shunted to the qualified rule.
737
+ */
738
+ export function mixinOrQualifiedRule(T) {
739
+ const $ = this;
740
+ // Helper function to convert Any nodes to VarDeclaration nodes for mixin definition parameters
741
+ const convertArgsForDefinition = (args) => {
742
+ if (!args || !args.value) {
743
+ return;
744
+ }
745
+ for (let i = 0; i < args.value.length; i++) {
746
+ const node = args.value[i];
747
+ const location = node.location && node.location.length > 0 ? node.location : undefined;
748
+ // If it's an Any node with role: 'name', convert it to VarDeclaration for mixin definition parameters
749
+ if (node instanceof Any && node.options?.role === 'name') {
750
+ // Reuse the existing Any node but change its role to 'property' for the name
751
+ node.options.role = 'property';
752
+ args.value[i] = new VarDeclaration({
753
+ name: node,
754
+ value: new Nil(undefined, undefined, location, this.context)
755
+ }, { paramVar: true }, location, this.context);
756
+ }
757
+ // Rest nodes with string values can stay as-is for mixin definitions
758
+ }
759
+ };
760
+ // Helper function to convert Any nodes to Reference nodes for mixin call arguments
761
+ const convertArgsForCall = (args) => {
762
+ if (!args || !args.value) {
763
+ return;
764
+ }
765
+ for (let i = 0; i < args.value.length; i++) {
766
+ const node = args.value[i];
767
+ const location = node.location && node.location.length > 0 ? node.location : undefined;
768
+ // If it's an Any node with role: 'name', convert it to Reference for mixin call arguments
769
+ if (node instanceof Any && node.options?.role === 'name') {
770
+ args.value[i] = new Reference({ key: node.value }, { type: 'variable' }, location, this.context);
771
+ }
772
+ else if (node instanceof Rest && typeof node.value === 'string') {
773
+ // If it's a Rest node with a string value, convert it to Rest with Reference for mixin call arguments
774
+ args.value[i] = new Rest(new Reference({ key: node.value }, { type: 'variable' }, location, this.context), undefined, location, this.context);
775
+ }
776
+ }
777
+ };
778
+ // qualifiedRule
779
+ // : selectorList WS* LCURLY declarationList RCURLY
780
+ // ;
781
+ return (ctx = {}) => {
782
+ let RECORDING_PHASE = $.RECORDING_PHASE;
783
+ $.startRule();
784
+ let selector = $.OR([
785
+ {
786
+ GATE: () => !ctx.inner,
787
+ ALT: () => {
788
+ let initialQualifiedRule = ctx.qualifiedRule;
789
+ ctx.qualifiedRule = true;
790
+ let rule = $.SUBRULE($.selectorList, { ARGS: [ctx] });
791
+ ctx.qualifiedRule = initialQualifiedRule;
792
+ return rule;
793
+ }
794
+ },
795
+ {
796
+ GATE: () => !!ctx.inner,
797
+ ALT: () => {
798
+ let initialQualifiedRule = ctx.qualifiedRule;
799
+ let initialFirstSelector = ctx.firstSelector;
800
+ ctx.firstSelector = true;
801
+ ctx.qualifiedRule = true;
802
+ let rule = $.SUBRULE($.forgivingSelectorList, { ARGS: [ctx] });
803
+ ctx.qualifiedRule = initialQualifiedRule;
804
+ ctx.firstSelector = initialFirstSelector;
805
+ return rule;
806
+ }
807
+ }
808
+ ]);
809
+ let isSelectorList = selector instanceof SelectorList;
810
+ let guard;
811
+ let args;
812
+ let important;
813
+ const createMixinCall = (location) => {
814
+ /** Okay, treat the call as a recursive reference */
815
+ if (RECORDING_PHASE) {
816
+ return;
817
+ }
818
+ let leftNode;
819
+ // If selector is a CompoundSelector, ComplexSelector, or single BasicSelector (but not SelectorList),
820
+ // create a single Reference with the selector instance as the key instead of nested references.
821
+ // This handles cases like .foo.bar() or .foo > .bar() as a single call.
822
+ // Note: .foo().bar() still creates nested calls because .foo() is parsed separately.
823
+ if (!isSelectorList && (selector instanceof CompoundSelector
824
+ || selector instanceof ComplexSelector
825
+ || selector instanceof BasicSelector)) {
826
+ // Create a single Reference with the selector instance as the key
827
+ leftNode = new Reference({ key: selector }, { type: 'mixin-ruleset', role: 'name' }, undefined, this.context);
828
+ }
829
+ else {
830
+ // For other cases (like SelectorList or when we need nested references),
831
+ // iterate through selector nodes and create nested references
832
+ for (let s of selector.nodes()) {
833
+ if (s instanceof BasicSelector) {
834
+ leftNode = new Reference({ target: leftNode, key: s.valueOf() }, { type: 'mixin-ruleset', role: 'name' }, undefined, this.context);
835
+ }
836
+ }
837
+ }
838
+ /** Finally, pass this reference into a call */
839
+ leftNode = new Call({ name: leftNode, args }, { markImportant: !!important }, location, this.context);
840
+ return leftNode;
841
+ };
842
+ let isPossibleMixinDefinition = (selector instanceof BasicSelector && (selector.isClass || selector.isId))
843
+ || (selector instanceof InterpolatedSelector && (selector.isClass || selector.isId));
844
+ let isPossibleMixinCall = true;
845
+ if (!isSelectorList && !isPossibleMixinDefinition && !RECORDING_PHASE) {
846
+ for (let s of selector.nodes()) {
847
+ /** Keep going until we get to basic selectors. */
848
+ if (s instanceof ComplexSelector || s instanceof CompoundSelector) {
849
+ continue;
850
+ }
851
+ if ((s instanceof BasicSelector && (s.isClass || s.isId))
852
+ || (s instanceof InterpolatedSelector && (s.isClass || s.isId))
853
+ || (s instanceof Combinator && (s.value === '>' || s.value === ' '))) {
854
+ continue;
855
+ }
856
+ isPossibleMixinCall = false;
857
+ break;
858
+ }
859
+ }
860
+ return $.OR2([
861
+ {
862
+ GATE: () => isPossibleMixinDefinition || isPossibleMixinCall,
863
+ ALT: () => {
864
+ args = $.SUBRULE($.mixinArgs, { ARGS: [ctx] });
865
+ let next = $.LA(1).tokenType;
866
+ if (next === T.LCurly || next === T.When) {
867
+ isPossibleMixinCall = false;
868
+ }
869
+ return $.OR3([
870
+ {
871
+ GATE: () => isPossibleMixinDefinition,
872
+ /** Mixin definition */
873
+ ALT: () => {
874
+ $.OPTION(() => {
875
+ guard = $.SUBRULE($.guard, { ARGS: [ctx] });
876
+ });
877
+ $.CONSUME(T.LCurly);
878
+ let rules = $.SUBRULE($.declarationList, { ARGS: [ctx] });
879
+ $.CONSUME(T.RCurly);
880
+ if (!RECORDING_PHASE) {
881
+ // Convert Any nodes to VarDeclaration nodes for mixin definition parameters
882
+ convertArgsForDefinition(args);
883
+ const guardText = String(guard?.toString?.() ?? '');
884
+ const hasDefault = Boolean(ctx.hasDefault) || guardContainsDefaultCall(guard) || guardText.includes('??()');
885
+ const node = new Mixin({ name: selector.valueOf(), params: args, rules, guard }, hasDefault ? { hasDefault: true } : undefined, $.endRule(), this.context);
886
+ ctx.hasDefault = false;
887
+ return node;
888
+ }
889
+ }
890
+ },
891
+ {
892
+ GATE: () => isPossibleMixinCall,
893
+ /** Mixin call */
894
+ ALT: () => {
895
+ $.OPTION2(() => {
896
+ important = $.CONSUME(T.Important);
897
+ });
898
+ let location;
899
+ if (!RECORDING_PHASE) {
900
+ location = $.endRule();
901
+ // Convert Any nodes to Reference nodes for mixin call arguments
902
+ convertArgsForCall(args);
903
+ }
904
+ let result = $.OPTION3({
905
+ /** in Less legacy mode, mixin calls can happen without a space. */
906
+ GATE: () => {
907
+ let noSpace = $.noSep();
908
+ let next = $.LA(1).tokenType;
909
+ return (noSpace && next === T.LSquare) || ((noSpace || $.looseMode) && next === T.LParen);
910
+ },
911
+ DEF: () => $.SUBRULE($.lookupOrCall, { ARGS: [{ ...ctx, node: createMixinCall(location) }] })
912
+ });
913
+ // Note: Mixin calls without parentheses are handled in the semicolon-terminated ALT below
914
+ return result ?? createMixinCall(location);
915
+ }
916
+ }
917
+ ]);
918
+ }
919
+ },
920
+ {
921
+ /** Parse as qualified rule */
922
+ ALT: () => {
923
+ if (!RECORDING_PHASE) {
924
+ $.endRule();
925
+ }
926
+ let initialSelector = ctx.selector;
927
+ let initialIsSelectorList = ctx.isSelectorList;
928
+ ctx.selector = selector;
929
+ ctx.isSelectorList = isSelectorList;
930
+ let rule = $.SUBRULE($.qualifiedRuleBody, { ARGS: [ctx] });
931
+ ctx.selector = initialSelector;
932
+ ctx.isSelectorList = initialIsSelectorList;
933
+ if (ctx.extendNodes) {
934
+ /** Prepend a rules block */
935
+ let qRule = rule;
936
+ // Set the Extend nodes' selector to the ruleset's selector (not &)
937
+ // This allows the extends to work correctly when evaluated in the wrapper Rules context
938
+ for (const extendNode of ctx.extendNodes) {
939
+ if (extendNode.value.selector === undefined || extendNode.value.selector.type === 'Ampersand') {
940
+ extendNode.value.selector = selector;
941
+ }
942
+ }
943
+ rule = new Rules([
944
+ ...ctx.extendNodes,
945
+ qRule
946
+ ]);
947
+ rule._location = qRule._location;
948
+ ctx.extendNodes = undefined;
949
+ }
950
+ return rule;
951
+ }
952
+ },
953
+ {
954
+ GATE: () => isPossibleMixinCall,
955
+ ALT: () => {
956
+ // Call terminated by a semi-colon and not parens, deprecated
957
+ const semi = $.CONSUME(T.Semi);
958
+ if (!RECORDING_PHASE) {
959
+ const location = $.endRule();
960
+ // Mixin call without parentheses - deprecated
961
+ $.warnDeprecation('Calling a mixin without parentheses is deprecated', semi, 'mixin-call-no-parens');
962
+ return createMixinCall(location);
963
+ }
964
+ }
965
+ }
966
+ ]);
967
+ };
968
+ }
969
+ /**
970
+ * We need to now handle a returned `Extend` node from the complexSelector rule
971
+ */
972
+ export function relativeSelector(T) {
973
+ const $ = this;
974
+ return (ctx = {}) => {
975
+ return $.OR([
976
+ {
977
+ ALT: () => {
978
+ let co = $.CONSUME(T.Combinator);
979
+ let node = $.SUBRULE($.complexSelector, { ARGS: [ctx] });
980
+ if (!$.RECORDING_PHASE) {
981
+ let combinator = new Combinator(co.image, undefined, $.getLocationInfo(co), this.context);
982
+ let targetNode = node instanceof Extend
983
+ ? node.value.selector
984
+ : node;
985
+ if (targetNode instanceof ComplexSelector) {
986
+ targetNode.value.unshift(combinator);
987
+ targetNode._location = $.getLocationFromNodes(targetNode.value);
988
+ }
989
+ else {
990
+ let nodes = [combinator, targetNode];
991
+ let complex = new ComplexSelector(nodes, undefined, $.getLocationFromNodes(nodes), this.context);
992
+ if (node instanceof Extend) {
993
+ node.value.selector = complex;
994
+ let location = node.location;
995
+ location[0] = co.startOffset;
996
+ location[1] = co.startLine;
997
+ location[2] = co.startColumn;
998
+ }
999
+ else {
1000
+ node = complex;
1001
+ }
1002
+ }
1003
+ }
1004
+ return node;
1005
+ }
1006
+ },
1007
+ {
1008
+ ALT: () => $.SUBRULE2($.complexSelector, { ARGS: [ctx] })
1009
+ }
1010
+ ]);
1011
+ };
1012
+ }
1013
+ export function compoundSelector(T) {
1014
+ const $ = this;
1015
+ /**
1016
+ A sequence of simple selectors that are not separated by
1017
+ a combinator.
1018
+ .e.g. `a#selected`
1019
+ */
1020
+ // compoundSelector
1021
+ // : simpleSelector+
1022
+ // ;
1023
+ return (ctx = {}) => {
1024
+ let RECORDING_PHASE = $.RECORDING_PHASE;
1025
+ let selectors;
1026
+ if (!RECORDING_PHASE) {
1027
+ selectors = [];
1028
+ }
1029
+ let sel = $.SUBRULE($.simpleSelector, { ARGS: [ctx] });
1030
+ if (!RECORDING_PHASE) {
1031
+ selectors.push(sel);
1032
+ }
1033
+ $.MANY({
1034
+ /** Make sure we don't ignore space combinators */
1035
+ GATE: () => !$.hasWS() && !(ctx.inExtend && $.LA(1).tokenType === T.All),
1036
+ DEF: () => {
1037
+ let sel = $.SUBRULE2($.simpleSelector, { ARGS: [ctx] });
1038
+ if (!RECORDING_PHASE) {
1039
+ /** Make sure we don't add implicit whitespace */
1040
+ sel.pre = 0;
1041
+ selectors.push(sel);
1042
+ }
1043
+ }
1044
+ });
1045
+ if (!RECORDING_PHASE) {
1046
+ if (selectors.length === 1) {
1047
+ return selectors[0];
1048
+ }
1049
+ return new CompoundSelector(selectors, undefined, $.getLocationFromNodes(selectors), this.context);
1050
+ }
1051
+ };
1052
+ }
1053
+ /**
1054
+ * Extended with :extend
1055
+ */
1056
+ export function complexSelector(T) {
1057
+ const $ = this;
1058
+ let originalComplexRule = cssComplexSelector.call(this, T, (ctx) => () => !ctx.inExtend || $.LA(1).tokenType !== T.All);
1059
+ return (ctx = {}) => {
1060
+ let selector = originalComplexRule(ctx);
1061
+ let isQualifiedRule = !!ctx.qualifiedRule;
1062
+ let flag;
1063
+ $.OR([
1064
+ {
1065
+ /** When we're inside the :extend(...), we can capture the "all" keyword */
1066
+ GATE: () => !!ctx.inExtend,
1067
+ ALT: () => flag = $.CONSUME(T.All)
1068
+ },
1069
+ {
1070
+ GATE: () => isQualifiedRule && !ctx.inExtend,
1071
+ ALT: () => {
1072
+ ctx.selector = selector;
1073
+ $.SUBRULE($.extend, { ARGS: [ctx] });
1074
+ ctx.selector = undefined;
1075
+ }
1076
+ },
1077
+ {
1078
+ ALT: EMPTY_ALT()
1079
+ }
1080
+ ]);
1081
+ if (!$.RECORDING_PHASE && ctx.inExtend) {
1082
+ (ctx.extendTargets ??= []).push({ selector: ctx.selector, target: selector, flag });
1083
+ }
1084
+ return selector;
1085
+ };
1086
+ }
1087
+ const { isArray } = Array;
1088
+ /**
1089
+ * Groups extends by target (using valueOf()) and flag.
1090
+ * Returns an array of grouped Extend nodes where extends with the same target and flag
1091
+ * are combined into a single Extend node with a SelectorList of all matching selectors.
1092
+ *
1093
+ * @todo Group complex selectors into selector lists
1094
+ */
1095
+ function groupExtendsByTargetAndFlag(extendNodes) {
1096
+ // Group extends by target and flag
1097
+ const groups = new Map();
1098
+ for (const ext of extendNodes) {
1099
+ let target = ext.value.target;
1100
+ let flag = ext.value.flag ?? 1; // ExtendFlag.Exact = 1
1101
+ // Create a key from target valueOf() and flag
1102
+ const key = `${target.valueOf()}|${flag}`;
1103
+ let group = groups.get(key);
1104
+ if (!group) {
1105
+ groups.set(key, ext);
1106
+ }
1107
+ else if (isArray(group)) {
1108
+ group.push(ext);
1109
+ }
1110
+ else {
1111
+ groups.set(key, [group, ext]);
1112
+ }
1113
+ }
1114
+ return Array.from(groups.values());
1115
+ }
1116
+ function mergeExtends(selector, extendTargets, location, context, flag) {
1117
+ let extendNodes;
1118
+ let currentTarget = extendTargets[0].target;
1119
+ let currentFlag = (extendTargets[0].flag ?? flag) ? 0 : 1; // ExtendFlag.All = 0, ExtendFlag.Exact = 1
1120
+ let currentNode = new Extend({
1121
+ selector,
1122
+ target: currentTarget,
1123
+ flag: currentFlag
1124
+ }, undefined, location, context);
1125
+ for (let i = 1; i < extendTargets.length; i++) {
1126
+ let ext = extendTargets[i];
1127
+ let thisFlag = (ext.flag ?? flag) ? 0 : 1;
1128
+ /**
1129
+ * Merge extends. We do this instead of merging earlier so that
1130
+ * selector lists with different flags are not merged.
1131
+ */
1132
+ if (thisFlag === currentFlag) {
1133
+ let target = currentNode.value.target;
1134
+ if (!(target instanceof SelectorList)) {
1135
+ currentNode.value.target = new SelectorList([target, ext.target], undefined, location, context);
1136
+ }
1137
+ else {
1138
+ target.value.push(ext.target);
1139
+ }
1140
+ }
1141
+ else {
1142
+ if (!extendNodes || !extendNodes.includes(currentNode)) {
1143
+ (extendNodes ??= []).push(currentNode);
1144
+ }
1145
+ currentFlag = thisFlag;
1146
+ currentTarget = ext.target;
1147
+ currentNode = new Extend({
1148
+ selector,
1149
+ target: currentTarget,
1150
+ flag: currentFlag
1151
+ }, undefined, location, context);
1152
+ extendNodes.push(currentNode);
1153
+ }
1154
+ }
1155
+ ;
1156
+ if (!extendNodes) {
1157
+ return currentNode;
1158
+ }
1159
+ if (extendNodes.length === 1) {
1160
+ return extendNodes[0];
1161
+ }
1162
+ return extendNodes;
1163
+ }
1164
+ /**
1165
+ * &:extend(...) statement ending with a semicolon.
1166
+ * This is the only valid standalone extend statement in Less.
1167
+ */
1168
+ export function ampersandExtend(T) {
1169
+ const $ = this;
1170
+ return (ctx = {}) => {
1171
+ let RECORDING_PHASE = $.RECORDING_PHASE;
1172
+ $.startRule();
1173
+ $.CONSUME(T.AmpersandExtend);
1174
+ ctx.inExtend = true;
1175
+ $.SUBRULE($.selectorList, { ARGS: [ctx] });
1176
+ ctx.inExtend = false;
1177
+ let extendTargets = ctx.extendTargets;
1178
+ let flag = $.OPTION(() => $.CONSUME(T.AllFlag));
1179
+ $.CONSUME(T.RParen);
1180
+ $.CONSUME(T.Semi);
1181
+ if (!RECORDING_PHASE) {
1182
+ let location = $.endRule();
1183
+ let result = mergeExtends(undefined, extendTargets, location, this.context, flag);
1184
+ /** We've converted these extend targets to nodes, so we can reset extend targets */
1185
+ ctx.extendTargets = undefined;
1186
+ if (ctx.extendNodes) {
1187
+ if (isArray(result)) {
1188
+ ctx.extendNodes = [...ctx.extendNodes, ...result];
1189
+ }
1190
+ else {
1191
+ ctx.extendNodes.push(result);
1192
+ }
1193
+ }
1194
+ else {
1195
+ if (isArray(result)) {
1196
+ ctx.extendNodes = result;
1197
+ }
1198
+ else {
1199
+ ctx.extendNodes = [result];
1200
+ }
1201
+ }
1202
+ // Return Nil instead of the extend node - extends are handled via ctx.extendNodes
1203
+ // pathway to avoid duplicates (cssMain collects returned nodes into rules.value).
1204
+ // Nil is needed because undefined confuses cssMain (treats it as semicolon).
1205
+ return new Nil(undefined, undefined, location, this.context);
1206
+ }
1207
+ };
1208
+ }
1209
+ export function extend(T) {
1210
+ const $ = this;
1211
+ return (ctx = {}) => {
1212
+ $.startRule();
1213
+ $.CONSUME(T.Extend);
1214
+ ctx.inExtend = true;
1215
+ $.SUBRULE($.selectorList, { ARGS: [ctx] });
1216
+ let extendTargets = ctx.extendTargets;
1217
+ ctx.inExtend = false;
1218
+ let selector = ctx.selector;
1219
+ let flag = $.OPTION(() => $.CONSUME(T.AllFlag));
1220
+ $.CONSUME(T.RParen);
1221
+ if (!$.RECORDING_PHASE) {
1222
+ let location = $.endRule();
1223
+ // When .c:extend(...) is parsed, selector is .c
1224
+ // The extend will be processed in qualifiedRuleBody where selector: undefined is set
1225
+ // for extends that stay inside the ruleset (not bubbled)
1226
+ // Bubbled extends keep their selector and get it set correctly in qualifiedRule
1227
+ let merged = mergeExtends(selector, extendTargets, location, this.context, flag);
1228
+ /**
1229
+ * If we don't have as many extends as we have selectors, we need a way to signal
1230
+ * that these should be bumped above the ruleset.
1231
+ */
1232
+ /** We've converted these extend targets to nodes, so we can reset extend targets */
1233
+ ctx.extendTargets = undefined;
1234
+ if (ctx.extendNodes) {
1235
+ if (isArray(merged)) {
1236
+ ctx.extendNodes = [...ctx.extendNodes, ...merged];
1237
+ }
1238
+ else {
1239
+ ctx.extendNodes.push(merged);
1240
+ }
1241
+ }
1242
+ else {
1243
+ if (isArray(merged)) {
1244
+ ctx.extendNodes = merged;
1245
+ }
1246
+ else {
1247
+ ctx.extendNodes = [merged];
1248
+ }
1249
+ }
1250
+ }
1251
+ };
1252
+ }
1253
+ function getAmpersandTemplateValue(image) {
1254
+ if (image === '&') {
1255
+ return undefined;
1256
+ }
1257
+ if (image.startsWith('&(') && image.endsWith(')')) {
1258
+ return image.slice(2, -1);
1259
+ }
1260
+ if (image.startsWith('&')) {
1261
+ return image.slice(1) || undefined;
1262
+ }
1263
+ if (image.includes('&')) {
1264
+ return image;
1265
+ }
1266
+ return undefined;
1267
+ }
1268
+ export function simpleSelector(T) {
1269
+ const $ = this;
1270
+ let selectorAlt = (ctx) => [
1271
+ {
1272
+ GATE: () => ((!ctx.inExtend || $.LA(1).tokenType !== T.All)
1273
+ && $.LA(1).tokenType !== T.InterpolatedIdent),
1274
+ /**
1275
+ * In Less/Sass (and now CSS), the first inner selector can be an identifier
1276
+ */
1277
+ ALT: () => $.CONSUME(T.Ident)
1278
+ },
1279
+ {
1280
+ /**
1281
+ * Unlike CSS Nesting, Less allows outer qualified rules
1282
+ * to have `&`, and it is just silently absorbed if there
1283
+ * is no parent selector.
1284
+ */
1285
+ ALT: () => {
1286
+ let amp = $.CONSUME(T.Ampersand);
1287
+ if (!$.RECORDING_PHASE) {
1288
+ const value = getAmpersandTemplateValue(amp.image);
1289
+ return new Ampersand(value || undefined, undefined, $.getLocationInfo(amp), this.context);
1290
+ }
1291
+ }
1292
+ },
1293
+ { ALT: () => $.CONSUME(T.InterpolatedIdent) },
1294
+ { ALT: () => $.CONSUME(T.InterpolatedSelector) },
1295
+ { ALT: () => $.SUBRULE($.classSelector) },
1296
+ { ALT: () => $.SUBRULE($.idSelector) },
1297
+ { ALT: () => $.CONSUME(T.Star) },
1298
+ { ALT: () => {
1299
+ let initialIsQualifiedRule = ctx.qualifiedRule;
1300
+ ctx.qualifiedRule = false;
1301
+ /** Make sure we prevent things like :extend() inside pseudo-selectors */
1302
+ let pseudo = $.SUBRULE($.pseudoSelector, { ARGS: [ctx] });
1303
+ ctx.qualifiedRule = initialIsQualifiedRule;
1304
+ return pseudo;
1305
+ } },
1306
+ { ALT: () => $.SUBRULE($.attributeSelector) },
1307
+ /** Supports keyframes selectors */
1308
+ { ALT: () => $.CONSUME(T.DimensionInt) },
1309
+ { ALT: () => $.CONSUME(T.DimensionNum) }
1310
+ ];
1311
+ return (ctx = {}) => {
1312
+ let selector = $.OR(selectorAlt(ctx));
1313
+ if (!$.RECORDING_PHASE) {
1314
+ if ($.isToken(selector)) {
1315
+ if (selector.tokenType.name === 'Ampersand') {
1316
+ const value = getAmpersandTemplateValue(selector.image);
1317
+ return new Ampersand(value || undefined, undefined, $.getLocationInfo(selector), this.context);
1318
+ }
1319
+ if (selector.tokenType.name === 'InterpolatedSelector'
1320
+ || selector.tokenType.name === 'InterpolatedIdent') {
1321
+ // Create an InterpolatedSelector wrapper for interpolated selectors
1322
+ let nameValue = selector.image;
1323
+ let interpolatedNode = getInterpolated(nameValue, $.getLocationInfo(selector), this.context);
1324
+ return new InterpolatedSelector(interpolatedNode, undefined, $.getLocationInfo(selector), this.context);
1325
+ }
1326
+ return new BasicSelector(selector.image, undefined, $.getLocationInfo(selector), this.context);
1327
+ }
1328
+ return selector;
1329
+ }
1330
+ };
1331
+ }
1332
+ export function anonymousMixinDefinition(T) {
1333
+ const $ = this;
1334
+ return (ctx = {}) => {
1335
+ $.startRule();
1336
+ let params;
1337
+ let anonToken;
1338
+ $.OPTION(() => {
1339
+ anonToken = $.CONSUME(T.AnonMixinStart);
1340
+ params = $.SUBRULE($.mixinArgList, { ARGS: [{ ...ctx, isDefinition: true }] });
1341
+ $.CONSUME(T.RParen);
1342
+ });
1343
+ let rules = $.SUBRULE($.wrappedDeclarationList, { ARGS: [ctx] });
1344
+ if (!$.RECORDING_PHASE) {
1345
+ // Set rulesVisibility for detached rulesets based on leakyRules
1346
+ // Less, for whatever reason, has slightly different lookup rules for
1347
+ // "detached rulesets".
1348
+ // Parse as Anonymous mixin
1349
+ if (!rules.options.rulesVisibility) {
1350
+ rules.options.rulesVisibility = {};
1351
+ }
1352
+ if (this.leakyRules) {
1353
+ rules.options.rulesVisibility.Mixin = 'public';
1354
+ rules.options.rulesVisibility.VarDeclaration = 'private';
1355
+ }
1356
+ else {
1357
+ rules.options.rulesVisibility.Mixin = 'private';
1358
+ rules.options.rulesVisibility.VarDeclaration = 'private';
1359
+ }
1360
+ if (!anonToken) {
1361
+ /** To Less, this is a "detached ruleset" */
1362
+ // Check if this should be parsed as Collection or Rules
1363
+ const shouldBeCollection = (() => {
1364
+ let properties = [];
1365
+ for (const node of rules.value) {
1366
+ if (node.type === 'Declaration') {
1367
+ properties.push(node);
1368
+ }
1369
+ else if (node.type === 'Comment' || node.type === 'VarDeclaration') {
1370
+ continue;
1371
+ }
1372
+ else {
1373
+ /** Not a valid collection, parse as anonymous mixin */
1374
+ return false;
1375
+ }
1376
+ }
1377
+ if (properties.length === 0) {
1378
+ /** If just var declarations and/or comments, parse as collection */
1379
+ return true;
1380
+ }
1381
+ const validPropertyCount = properties.filter((decl) => {
1382
+ const name = decl.value.name;
1383
+ const propName = typeof name === 'string' ? name : name.valueOf();
1384
+ // Skip custom properties (--*)
1385
+ if (propName.startsWith('--')) {
1386
+ return true; // Custom properties are always valid
1387
+ }
1388
+ return all.includes(propName);
1389
+ }).length;
1390
+ // Majority means more than 50%
1391
+ const majorityValid = validPropertyCount > properties.length / 2;
1392
+ /** If this looks like mostly CSS properties, parse as mixin instead */
1393
+ return !majorityValid;
1394
+ })();
1395
+ const usage = ctx.detachedRulesetUsage ?? 'none';
1396
+ const forceMixinForDynamicUsage = usage === 'function-arg'
1397
+ || usage === 'mixin-arg'
1398
+ || usage === 'default-param';
1399
+ const shouldBeCollectionFinal = shouldBeCollection && !forceMixinForDynamicUsage;
1400
+ if (shouldBeCollectionFinal) {
1401
+ return new Collection(rules.value, rules.options, $.endRule(), this.context);
1402
+ }
1403
+ }
1404
+ // If anonToken exists, it's an anonymous mixin with (optional) parameters, return as Mixin
1405
+ return new Mixin({ params, rules }, undefined, $.endRule(), this.context);
1406
+ }
1407
+ };
1408
+ }
1409
+ /**
1410
+ * Mostly copied from css importAtRule, but it maps
1411
+ * differently to Jess nodes depending on if it's meant
1412
+ * to be a Jess-style import or just an at-rule
1413
+ */
1414
+ export function importAtRule(T) {
1415
+ const $ = this;
1416
+ const isCssUrl = (url, options) => {
1417
+ if (options.includes('inline')) {
1418
+ return false;
1419
+ }
1420
+ const lower = url.toLowerCase();
1421
+ const forcedLess = options.includes('less');
1422
+ if (forcedLess) {
1423
+ return false;
1424
+ }
1425
+ if (options.includes('css')) {
1426
+ return true;
1427
+ }
1428
+ if (/\.css([?#].*)?$/.test(lower)) {
1429
+ return true;
1430
+ }
1431
+ if (/\.less([?#].*)?$/.test(lower)) {
1432
+ return false;
1433
+ }
1434
+ // Remote imports default to CSS.
1435
+ if (lower.startsWith('http://') || lower.startsWith('https://') || lower.startsWith('//')) {
1436
+ return true;
1437
+ }
1438
+ return false;
1439
+ };
1440
+ return (ctx = {}) => {
1441
+ let RECORDING_PHASE = $.RECORDING_PHASE;
1442
+ $.startRule();
1443
+ let name = $.CONSUME(T.AtImport);
1444
+ let options;
1445
+ if (!RECORDING_PHASE) {
1446
+ options = [];
1447
+ }
1448
+ $.OPTION(() => {
1449
+ $.CONSUME(T.LParen);
1450
+ $.AT_LEAST_ONE_SEP({
1451
+ SEP: T.Comma,
1452
+ DEF: () => {
1453
+ let opt = $.CONSUME(T.PlainIdent);
1454
+ if (!RECORDING_PHASE) {
1455
+ options.push(opt.image);
1456
+ }
1457
+ }
1458
+ });
1459
+ $.CONSUME(T.RParen);
1460
+ });
1461
+ let urlNode = $.OR([
1462
+ { ALT: () => $.SUBRULE($.urlFunction, { ARGS: [ctx] }) },
1463
+ { ALT: () => $.SUBRULE($.string, { ARGS: [ctx] }) }
1464
+ ]);
1465
+ let isAtRule;
1466
+ let postludeNode;
1467
+ if (!RECORDING_PHASE) {
1468
+ let url = urlNode.valueOf();
1469
+ isAtRule = isCssUrl(url, options);
1470
+ }
1471
+ let preludeNodes;
1472
+ if (!RECORDING_PHASE) {
1473
+ preludeNodes = [$.wrap(urlNode)];
1474
+ }
1475
+ let extraNodes;
1476
+ $.OPTION2(() => {
1477
+ extraNodes = $.SUBRULE($.importPostlude);
1478
+ });
1479
+ if (!RECORDING_PHASE && extraNodes && extraNodes.length) {
1480
+ if (isAtRule) {
1481
+ isAtRule = true;
1482
+ for (const n of extraNodes) {
1483
+ preludeNodes.push(n);
1484
+ }
1485
+ }
1486
+ else {
1487
+ // Less-style imports with media/query/layer postludes should evaluate
1488
+ // the target and then wrap output (for both inline and non-inline forms).
1489
+ // Keep this on import options so StyleImport.evalNode can apply wrappers.
1490
+ const postludeLoc = $.getLocationFromNodes(extraNodes);
1491
+ postludeNode = new Sequence(extraNodes, undefined, postludeLoc, this.context);
1492
+ }
1493
+ }
1494
+ $.CONSUME(T.Semi);
1495
+ if (!RECORDING_PHASE) {
1496
+ let location = $.endRule();
1497
+ if (isAtRule) {
1498
+ const prelude = new Sequence(preludeNodes, undefined, $.getLocationFromNodes(preludeNodes), this.context);
1499
+ const atRule = new AtRule({
1500
+ name: $.wrap(new Any(name.image, { role: 'atkeyword' }, $.getLocationInfo(name), this.context), true),
1501
+ prelude: prelude
1502
+ }, undefined, location, this.context);
1503
+ return atRule;
1504
+ }
1505
+ return new StyleImport({
1506
+ path: urlNode
1507
+ }, {
1508
+ type: 'import',
1509
+ importOptions: {
1510
+ type: options.includes('less') ? 'less' : undefined,
1511
+ reference: options.includes('reference'),
1512
+ once: !options.includes('multiple'),
1513
+ multiple: options.includes('multiple'),
1514
+ optional: options.includes('optional'),
1515
+ inline: options.includes('inline'),
1516
+ postlude: postludeNode
1517
+ }
1518
+ }, location, this.context);
1519
+ }
1520
+ };
1521
+ }
1522
+ /** Less variables */
1523
+ export function varDeclarationOrCall(T) {
1524
+ const $ = this;
1525
+ /**
1526
+ * Less doesn't allow variable variables anymore? It used to. Not sure
1527
+ * when that changed.
1528
+ */
1529
+ // let nameAlt = [
1530
+ // { ALT: () => $.SUBRULE($.varName) },
1531
+ // { ALT: () => $.CONSUME(T.NestedReference) }
1532
+ // ];
1533
+ return (ctx = {}) => {
1534
+ $.startRule();
1535
+ let name = $.SUBRULE($.varName, { ARGS: [ctx] });
1536
+ let value;
1537
+ let args;
1538
+ let important;
1539
+ $.OR([
1540
+ {
1541
+ /**
1542
+ * This is a variable declaration
1543
+ * Disallows `@atrule :foo;` because it resembles a pseudo-selector
1544
+ */
1545
+ ALT: () => {
1546
+ $.CONSUME(T.Colon);
1547
+ return $.OR2([
1548
+ /**
1549
+ * This needs to be gated early, even though it is
1550
+ * gated again in the valueList production, because
1551
+ * chevrotain-allstar needs to pick a path first.
1552
+ */
1553
+ {
1554
+ GATE: () => {
1555
+ let type = $.LA(1).tokenType;
1556
+ return type === T.AnonMixinStart || type === T.LCurly;
1557
+ },
1558
+ ALT: () => {
1559
+ value = $.SUBRULE($.anonymousMixinDefinition, { ARGS: [ctx] });
1560
+ $.OPTION2(() => $.CONSUME2(T.Semi));
1561
+ return value;
1562
+ }
1563
+ },
1564
+ {
1565
+ GATE: () => {
1566
+ let type = $.LA(1).tokenType;
1567
+ return type !== T.AnonMixinStart && type !== T.LCurly;
1568
+ },
1569
+ ALT: () => {
1570
+ value = $.SUBRULE($.valueList, { ARGS: [{ ...ctx, allowMixinCallWithoutAccessor: true }] });
1571
+ $.OPTION(() => {
1572
+ important = $.CONSUME(T.Important);
1573
+ });
1574
+ return value;
1575
+ }
1576
+ }
1577
+ ]);
1578
+ }
1579
+ },
1580
+ /** This is a variable call. Allow optional whitespace between name and (. */
1581
+ {
1582
+ GATE: () => $.LA(1).tokenType === T.LParen,
1583
+ /**
1584
+ * This is a change from Less 1.x-4.x
1585
+ * e.g.
1586
+ * ```
1587
+ * @dr: #(@var1, @var2) {
1588
+ * // ...
1589
+ * }
1590
+ * @dr(arg1, arg2);
1591
+ */
1592
+ ALT: () => {
1593
+ args = $.SUBRULE($.mixinArgs, { ARGS: [ctx] });
1594
+ return args;
1595
+ }
1596
+ }
1597
+ ]);
1598
+ if (!$.RECORDING_PHASE) {
1599
+ let location = $.endRule();
1600
+ let nameVal = getInterpolatedOrString(name.image);
1601
+ let nameNode;
1602
+ if (!(nameVal instanceof Interpolated)) {
1603
+ nameNode = new Any(nameVal, { role: 'ident' }, $.getLocationInfo(name), this.context);
1604
+ }
1605
+ else {
1606
+ nameNode = nameVal;
1607
+ }
1608
+ /** An anonymous mixin call */
1609
+ if (!value) {
1610
+ // When @variable() is called, look up the variable first
1611
+ // The variable's value (which may be a Call node) will be executed
1612
+ const nameRef = nameNode instanceof Interpolated
1613
+ ? new Reference({ key: nameNode }, { type: 'variable', role: 'name' })
1614
+ : new Reference({ key: nameNode }, { type: 'variable', role: 'name' });
1615
+ // Pass markImportant in options if !important is present
1616
+ const callOptions = important ? { markImportant: true } : undefined;
1617
+ const callNode = new Call({ name: nameRef, args }, callOptions, location, this.context);
1618
+ // Clear important since it's now on the Call
1619
+ if (important) {
1620
+ important = undefined;
1621
+ }
1622
+ // Variable calls are expressions at the outermost level (but not parenthesized).
1623
+ // e.g. `$media()`, NOT `$(media())`
1624
+ return new Expression(callNode, undefined, location, this.context);
1625
+ }
1626
+ // If the value is a Call node and we have !important, set markImportant on the Call
1627
+ // instead of on the VarDeclaration (mixin call semantics)
1628
+ if (important && value instanceof Call) {
1629
+ value.options = value.options || {};
1630
+ value.options.markImportant = true;
1631
+ important = undefined;
1632
+ }
1633
+ if (value && isLegacySelectorLikeValue(value)) {
1634
+ const varName = String(nameNode.valueOf());
1635
+ $._errors.push(new NoViableAltException(`Unquoted selector capture in '${varName}' is no longer supported. Use '*[ ... ]' (e.g. ${varName}: *[.a, .b]).`, $.LA(1), $.LA(0)));
1636
+ }
1637
+ return new VarDeclaration({
1638
+ name: $.wrap(nameNode, true),
1639
+ value: $.wrap(value, true),
1640
+ important: important ? $.wrap(new Any(important.image, { role: 'flag' }, $.getLocationInfo(important), this.context), true) : undefined
1641
+ }, undefined, location, this.context);
1642
+ }
1643
+ };
1644
+ }
1645
+ /** True for a node that could be one item in the old unquoted selector list (e.g. .a or #id). */
1646
+ function isSelectorLikeListItem(node) {
1647
+ if (node.type === 'SelectorCapture' || isNode(node, 'Call')) {
1648
+ return false;
1649
+ }
1650
+ if (isNode(node, 'Reference')) {
1651
+ return node.options.type === 'mixin-ruleset';
1652
+ }
1653
+ if (isNode(node, 'List') || isNode(node, 'Sequence')) {
1654
+ return node.value.length > 0 && node.value.every(isSelectorLikeListItem);
1655
+ }
1656
+ return false;
1657
+ }
1658
+ /** True only for the legacy unquoted selector-list form (e.g. @var: .a, .b, .c), not @var: .a; */
1659
+ function isLegacySelectorLikeValue(node) {
1660
+ if (node.type === 'SelectorCapture' || isNode(node, 'Call')) {
1661
+ return false;
1662
+ }
1663
+ if (isNode(node, 'Reference')) {
1664
+ return false; // Single mixin reference is valid.
1665
+ }
1666
+ if (isNode(node, 'List') || isNode(node, 'Sequence')) {
1667
+ return node.value.length > 1 && node.value.every(isSelectorLikeListItem);
1668
+ }
1669
+ return false;
1670
+ }
1671
+ export function selectorCapture(T) {
1672
+ const $ = this;
1673
+ return (ctx = {}) => {
1674
+ $.startRule();
1675
+ $.CONSUME(T.Star);
1676
+ // Use $.OR with a gate for a positive assertion
1677
+ const selector = $.OR([
1678
+ {
1679
+ GATE: $.noSep,
1680
+ ALT: () => {
1681
+ $.CONSUME(T.LSquare);
1682
+ const selector = $.SUBRULE($.forgivingSelectorList, { ARGS: [{ ...ctx, inner: true }] });
1683
+ $.CONSUME(T.RSquare);
1684
+ return selector;
1685
+ }
1686
+ }
1687
+ ]);
1688
+ if (!$.RECORDING_PHASE) {
1689
+ const location = $.endRule();
1690
+ return new SelectorCapture($.wrap(selector, true), undefined, location, this.context);
1691
+ }
1692
+ };
1693
+ }
1694
+ export function valueSequence(_T) {
1695
+ const $ = this;
1696
+ return (ctx = {}) => {
1697
+ let RECORDING_PHASE = $.RECORDING_PHASE;
1698
+ $.startRule();
1699
+ let nodes;
1700
+ if (!RECORDING_PHASE) {
1701
+ nodes = [];
1702
+ }
1703
+ $.OR([
1704
+ {
1705
+ GATE: () => $.looseMode,
1706
+ ALT: () => {
1707
+ $.MANY(() => {
1708
+ const exprCtx = { ...ctx, wrapInExpression: true };
1709
+ let value = $.SUBRULE($.expressionSum, { ARGS: [exprCtx] });
1710
+ if (!RECORDING_PHASE) {
1711
+ value = wrapOuterExpressionIfNeeded.call($, value, exprCtx);
1712
+ nodes.push(value);
1713
+ }
1714
+ });
1715
+ }
1716
+ },
1717
+ {
1718
+ GATE: () => !$.looseMode,
1719
+ /** @todo - create warning if there isn't a value */
1720
+ ALT: () => {
1721
+ $.AT_LEAST_ONE(() => {
1722
+ const exprCtx = { ...ctx, wrapInExpression: true };
1723
+ let value = $.SUBRULE2($.expressionSum, { ARGS: [exprCtx] });
1724
+ if (!RECORDING_PHASE) {
1725
+ value = wrapOuterExpressionIfNeeded.call($, value, exprCtx);
1726
+ nodes.push(value);
1727
+ }
1728
+ });
1729
+ }
1730
+ }
1731
+ ]);
1732
+ if (!RECORDING_PHASE) {
1733
+ let location = $.endRule();
1734
+ if (nodes.length === 1) {
1735
+ const single = nodes[0];
1736
+ return single;
1737
+ }
1738
+ const seq = new Sequence(nodes, undefined, location, this.context);
1739
+ return seq;
1740
+ }
1741
+ };
1742
+ }
1743
+ export function squareValue(T) {
1744
+ const $ = this;
1745
+ return (ctx = {}) => {
1746
+ $.startRule();
1747
+ let RECORDING_PHASE = $.RECORDING_PHASE;
1748
+ $.CONSUME(T.LSquare);
1749
+ let node = $.OR([
1750
+ {
1751
+ GATE: () => !$.looseMode,
1752
+ ALT: () => {
1753
+ let ident = $.CONSUME(T.Ident);
1754
+ if (!RECORDING_PHASE) {
1755
+ return new Any(ident.image, { role: 'ident' }, $.getLocationInfo(ident), this.context);
1756
+ }
1757
+ }
1758
+ },
1759
+ {
1760
+ GATE: () => !!$.looseMode,
1761
+ ALT: () => {
1762
+ let nodes;
1763
+ if (!RECORDING_PHASE) {
1764
+ nodes = [];
1765
+ }
1766
+ $.MANY(() => {
1767
+ let node = $.SUBRULE($.anyInnerValue, { ARGS: [ctx] });
1768
+ if (!RECORDING_PHASE) {
1769
+ const wrapped = $.wrap(node);
1770
+ nodes.push(wrapped);
1771
+ }
1772
+ });
1773
+ if (!RECORDING_PHASE) {
1774
+ const seq = new Sequence(nodes, undefined, $.getLocationFromNodes(nodes), this.context);
1775
+ return seq;
1776
+ }
1777
+ }
1778
+ }
1779
+ ]);
1780
+ $.CONSUME(T.RSquare);
1781
+ if (!$.RECORDING_PHASE) {
1782
+ let location = $.endRule();
1783
+ const blk = new Block(node, { type: 'square' }, location, this.context);
1784
+ return blk;
1785
+ }
1786
+ };
1787
+ }
1788
+ /**
1789
+ * In CSS, would be a single value.
1790
+ * In Less, these are math expressions which
1791
+ * represent a single value. During AST construction,
1792
+ * these will be grouped by order of operations.
1793
+ */
1794
+ export function expressionSum(T) {
1795
+ const $ = this;
1796
+ return (ctx = {}) => {
1797
+ let RECORDING_PHASE = $.RECORDING_PHASE;
1798
+ $.startRule();
1799
+ let left = $.SUBRULE($.expressionProduct, { ARGS: [ctx] });
1800
+ $.MANY({
1801
+ /**
1802
+ * What this GATE does. We need to dis-ambiguate
1803
+ * 1 -1 (a value sequence) from 1-1 (a Less expression),
1804
+ * so Less is white-space sensitive here.
1805
+ */
1806
+ GATE: () => {
1807
+ const next = $.LA(1);
1808
+ const nextType = next.tokenType;
1809
+ return (nextType === T.Plus
1810
+ || nextType === T.Minus
1811
+ || ($.noSep() && tokenMatcher(next, T.Signed)));
1812
+ },
1813
+ DEF: () => {
1814
+ let op;
1815
+ let right;
1816
+ $.OR([
1817
+ {
1818
+ ALT: () => {
1819
+ let opToken = $.OR2([
1820
+ { ALT: () => $.CONSUME(T.Plus) },
1821
+ { ALT: () => $.CONSUME(T.Minus) }
1822
+ ]);
1823
+ if (!RECORDING_PHASE) {
1824
+ op = opToken.image;
1825
+ }
1826
+ right = $.SUBRULE2($.expressionProduct, { ARGS: [ctx] });
1827
+ }
1828
+ },
1829
+ /** This will be interpreted by Less as a complete expression */
1830
+ {
1831
+ ALT: () => {
1832
+ // Consume a signed literal and convert it without rewinding
1833
+ const tok = $.CONSUME(T.Signed);
1834
+ if (!RECORDING_PHASE) {
1835
+ const str = tok.image;
1836
+ op = str[0];
1837
+ // Build a literal node from the signed token directly
1838
+ // Prefer dimension if payload exists, else number, else ident fallback
1839
+ if (tok.payload && tok.payload[1]) {
1840
+ const dim = { number: parseFloat(tok.payload[0]), unit: tok.payload[1] };
1841
+ right = new Dimension(dim, undefined, $.getLocationInfo(tok), this.context);
1842
+ }
1843
+ else {
1844
+ const num = parseFloat(str);
1845
+ if (!Number.isNaN(num)) {
1846
+ right = new Num(num, undefined, $.getLocationInfo(tok), this.context);
1847
+ }
1848
+ else {
1849
+ right = $.processValueToken(tok);
1850
+ }
1851
+ }
1852
+ }
1853
+ }
1854
+ }
1855
+ ]);
1856
+ if (!RECORDING_PHASE) {
1857
+ const operation = new Operation([$.wrap(left, true), op, $.wrap(right)], undefined, $.getLocationFromNodes([left, right]), this.context);
1858
+ left = operation;
1859
+ return left;
1860
+ }
1861
+ }
1862
+ });
1863
+ if (!RECORDING_PHASE) {
1864
+ $.endRule();
1865
+ return left;
1866
+ }
1867
+ };
1868
+ }
1869
+ export function expressionProduct(T) {
1870
+ const $ = this;
1871
+ let opAlt = [
1872
+ { ALT: () => $.CONSUME(T.Star) },
1873
+ { ALT: () => $.CONSUME(T.Slash) },
1874
+ { ALT: () => $.CONSUME(T.Percent) }
1875
+ ];
1876
+ return (ctx = {}) => {
1877
+ let RECORDING_PHASE = $.RECORDING_PHASE;
1878
+ $.startRule();
1879
+ let left = $.SUBRULE($.expressionValue, { ARGS: [ctx] });
1880
+ $.MANY(() => {
1881
+ let op = $.OR(opAlt);
1882
+ // Check for deprecated ./ operator
1883
+ if (!RECORDING_PHASE && op.image === './') {
1884
+ $.warnDeprecation('./ operator is deprecated', op, 'dot-slash-operator');
1885
+ }
1886
+ let right = $.SUBRULE2($.expressionValue, { ARGS: [ctx] });
1887
+ if (!RECORDING_PHASE) {
1888
+ const operation = new Operation([$.wrap(left, true), op.image, $.wrap(right)], undefined, $.getLocationFromNodes([left, right]), this.context);
1889
+ left = operation;
1890
+ }
1891
+ });
1892
+ if (!RECORDING_PHASE) {
1893
+ $.endRule();
1894
+ return left;
1895
+ }
1896
+ };
1897
+ }
1898
+ export function expressionValue(T) {
1899
+ const $ = this;
1900
+ return (ctx = {}) => {
1901
+ let RECORDING_PHASE = $.RECORDING_PHASE;
1902
+ $.startRule();
1903
+ /** Can create a negative expression */
1904
+ let minus = $.OPTION(() => $.CONSUME(T.Minus));
1905
+ let node = $.OR([
1906
+ {
1907
+ ALT: () => {
1908
+ $.startRule();
1909
+ let escape;
1910
+ $.OPTION2(() => {
1911
+ escape = $.CONSUME(T.Tilde);
1912
+ });
1913
+ $.CONSUME(T.LParen);
1914
+ const innerCtx = {
1915
+ ...ctx,
1916
+ inner: true,
1917
+ allowComma: true,
1918
+ // Parentheses in Less enable "math in parens" semantics
1919
+ parenFrames: [...getParenFrames(ctx), true]
1920
+ };
1921
+ let node = $.SUBRULE($.valueList, { ARGS: [innerCtx] });
1922
+ $.CONSUME(T.RParen);
1923
+ if (!RECORDING_PHASE) {
1924
+ let location = $.endRule();
1925
+ node = $.wrap(node, 'both');
1926
+ return new Paren(node, { escaped: !!escape }, location, this.context);
1927
+ }
1928
+ }
1929
+ },
1930
+ { ALT: () => $.SUBRULE($.value, { ARGS: [ctx] }) }
1931
+ ]);
1932
+ if (!RECORDING_PHASE) {
1933
+ let location = $.endRule();
1934
+ if (minus) {
1935
+ return new Negative(node, undefined, location, this.context);
1936
+ }
1937
+ return node;
1938
+ }
1939
+ };
1940
+ }
1941
+ /**
1942
+ * Add interpolation
1943
+ */
1944
+ export function nthValue(T) {
1945
+ const $ = this;
1946
+ let nthValueAlt = (ctx = {}) => [
1947
+ { ALT: () => $.CONSUME(T.InterpolatedIdent) },
1948
+ { ALT: () => $.CONSUME(T.NthOdd) },
1949
+ { ALT: () => $.CONSUME(T.NthEven) },
1950
+ { ALT: () => $.CONSUME(T.Integer) },
1951
+ {
1952
+ ALT: () => {
1953
+ $.OR2([
1954
+ { ALT: () => $.CONSUME(T.NthSignedDimension) },
1955
+ { ALT: () => $.CONSUME(T.NthUnsignedDimension) },
1956
+ { ALT: () => $.CONSUME(T.NthSignedPlus) },
1957
+ { ALT: () => $.CONSUME(T.NthIdent) }
1958
+ ]);
1959
+ $.OPTION(() => {
1960
+ $.OR3([
1961
+ { ALT: () => $.CONSUME(T.SignedInt) },
1962
+ {
1963
+ ALT: () => {
1964
+ $.CONSUME(T.Minus);
1965
+ $.CONSUME(T.UnsignedInt);
1966
+ }
1967
+ }
1968
+ ]);
1969
+ });
1970
+ $.OPTION2(() => {
1971
+ $.CONSUME(T.Of);
1972
+ $.SUBRULE($.complexSelector, { ARGS: [ctx] });
1973
+ });
1974
+ }
1975
+ }
1976
+ ];
1977
+ return cssNthValue.call(this, T, nthValueAlt);
1978
+ }
1979
+ export function knownFunctions(T) {
1980
+ const $ = this;
1981
+ let functions = (ctx = {}) => [
1982
+ { ALT: () => $.SUBRULE($.urlFunction, { ARGS: [ctx] }) },
1983
+ { ALT: () => $.SUBRULE($.varFunction, { ARGS: [ctx] }) },
1984
+ { ALT: () => $.SUBRULE($.calcFunction, { ARGS: [ctx] }) },
1985
+ // colorFunction is already in cssKnownFunctions default, so we don't need to add it here
1986
+ { ALT: () => $.SUBRULE($.ifFunction, { ARGS: [ctx] }) },
1987
+ { ALT: () => $.SUBRULE($.booleanFunction, { ARGS: [ctx] }) }
1988
+ ];
1989
+ return cssKnownFunctions.call(this, T, functions);
1990
+ }
1991
+ /**
1992
+ * Override CSS calc() parsing so we can maintain parse-time `calcFrames`.
1993
+ * This is the parse-time analogue of `Call.evalNode`'s calcFrames++/--.
1994
+ */
1995
+ export function calcFunction(T) {
1996
+ const $ = this;
1997
+ return (ctx = {}) => {
1998
+ $.startRule();
1999
+ $.CONSUME(T.Calc);
2000
+ const innerCtx = withCalcFrame(ctx, 1);
2001
+ const args = $.SUBRULE($.mathSum, { ARGS: [innerCtx] });
2002
+ $.CONSUME2(T.RParen);
2003
+ if (!$.RECORDING_PHASE) {
2004
+ const location = $.endRule();
2005
+ return new Call({
2006
+ name: 'calc',
2007
+ args: new List([args])
2008
+ }, undefined, location, this.context);
2009
+ }
2010
+ };
2011
+ }
2012
+ export function ifFunction(T) {
2013
+ const $ = this;
2014
+ return (ctx = {}) => {
2015
+ let RECORDING_PHASE = $.RECORDING_PHASE;
2016
+ $.startRule();
2017
+ let name = $.CONSUME(T.IfFunction);
2018
+ let args = new List([]);
2019
+ let isCssBranch = false;
2020
+ $.OR([
2021
+ {
2022
+ ALT: () => {
2023
+ isCssBranch = true;
2024
+ const cssArgs = $.SUBRULE($.ifFunctionArgs, { ARGS: [{ ...ctx, inner: true }] });
2025
+ $.CONSUME(T.RParen);
2026
+ if (!RECORDING_PHASE) {
2027
+ args = new List([cssArgs]);
2028
+ }
2029
+ }
2030
+ },
2031
+ {
2032
+ ALT: () => {
2033
+ isCssBranch = false;
2034
+ let node = $.SUBRULE($.guardInner, { ARGS: [{ ...ctx, inValueList: true }] });
2035
+ if (!RECORDING_PHASE) {
2036
+ const condNode = node instanceof Paren && node.value instanceof Node ? node.value : node;
2037
+ args = new List([condNode]);
2038
+ }
2039
+ $.OR2([
2040
+ {
2041
+ ALT: () => {
2042
+ $.CONSUME(T.Semi);
2043
+ node = $.SUBRULE($.valueList, { ARGS: [{ ...ctx, allowAnonymousMixins: true }] });
2044
+ if (!RECORDING_PHASE) {
2045
+ args.value.push(node);
2046
+ }
2047
+ $.OPTION(() => {
2048
+ $.CONSUME2(T.Semi);
2049
+ node = $.SUBRULE2($.valueList, { ARGS: [{ ...ctx, allowAnonymousMixins: true }] });
2050
+ if (!RECORDING_PHASE) {
2051
+ args.value.push(node);
2052
+ }
2053
+ });
2054
+ }
2055
+ },
2056
+ {
2057
+ ALT: () => {
2058
+ $.CONSUME(T.Comma);
2059
+ node = $.SUBRULE($.callArgument, { ARGS: [{ ...ctx, allowAnonymousMixins: true }] });
2060
+ if (!RECORDING_PHASE) {
2061
+ args.value.push(node);
2062
+ }
2063
+ $.OPTION2(() => {
2064
+ $.CONSUME2(T.Comma);
2065
+ node = $.SUBRULE2($.callArgument, { ARGS: [{ ...ctx, allowAnonymousMixins: true }] });
2066
+ if (!RECORDING_PHASE) {
2067
+ args.value.push(node);
2068
+ }
2069
+ });
2070
+ }
2071
+ }
2072
+ ]);
2073
+ $.CONSUME2(T.RParen);
2074
+ }
2075
+ }
2076
+ ]);
2077
+ if (!RECORDING_PHASE) {
2078
+ let location = $.endRule();
2079
+ let nameNode = new Reference('if', {
2080
+ type: 'function',
2081
+ fallbackValue: isCssBranch ? true : undefined
2082
+ }, $.getLocationInfo(name), this.context);
2083
+ const callNode = new Call({ name: nameNode, args }, undefined, location, this.context);
2084
+ return callNode;
2085
+ }
2086
+ };
2087
+ }
2088
+ export function booleanFunction(T) {
2089
+ const $ = this;
2090
+ return (ctx = {}) => {
2091
+ $.startRule();
2092
+ $.CONSUME(T.BooleanFunction);
2093
+ let arg = $.SUBRULE($.guardInner, { ARGS: [{ ...ctx, inValueList: true }] });
2094
+ $.CONSUME(T.RParen);
2095
+ if (!$.RECORDING_PHASE) {
2096
+ let location = $.endRule();
2097
+ const conditionNode = arg instanceof Paren && arg.value instanceof Node ? arg.value : arg;
2098
+ const exprNode = new Expression(conditionNode, { parens: true }, location, this.context);
2099
+ return exprNode;
2100
+ }
2101
+ };
2102
+ }
2103
+ /** At AST time, join comma-lists together if separated by semis */
2104
+ // $.RULE('functionValueList', (ctx: RuleContext = {}) => {
2105
+ // ctx.allowAnonymousMixins = true
2106
+ // $.SUBRULE($.valueSequence, { ARGS: [ctx] })
2107
+ // $.MANY(() => {
2108
+ // $.OR([
2109
+ // { ALT: () => $.CONSUME(T.Comma) },
2110
+ // { ALT: () => $.CONSUME(T.Semi) }
2111
+ // ])
2112
+ // $.SUBRULE2($.valueSequence, { ARGS: [ctx] })
2113
+ // })
2114
+ // })
2115
+ export function varReference(T) {
2116
+ const $ = this;
2117
+ return (ctx = {}) => {
2118
+ let RECORDING_PHASE = $.RECORDING_PHASE;
2119
+ let node = $.OR([
2120
+ {
2121
+ ALT: () => {
2122
+ let token = $.CONSUME(T.PropertyReference);
2123
+ if (!RECORDING_PHASE) {
2124
+ // Warn about $ident in custom property values - it's treated as literal text, not a property reference
2125
+ if (ctx.inCustomPropertyValue) {
2126
+ const atName = token.image;
2127
+ const ident = token.image.slice(1);
2128
+ $.warnDeprecation(`${atName} in custom property values is treated as literal text, not a property reference. Use \${${ident}} if you want it to be evaluated.`, token, 'property-in-unknown-value');
2129
+ return new Reference({ key: token.image.slice(1) }, { type: 'property', role: 'ident' }, $.getLocationInfo(token), this.context);
2130
+ }
2131
+ return new Reference(token.image.slice(1), { type: 'property' }, $.getLocationInfo(token), this.context);
2132
+ }
2133
+ }
2134
+ },
2135
+ {
2136
+ ALT: () => {
2137
+ let token = $.CONSUME(T.NestedReference);
2138
+ if (!RECORDING_PHASE) {
2139
+ const raw = token.image;
2140
+ const type = raw.startsWith('@') ? 'variable' : 'property';
2141
+ const key = getInterpolatedOrString(raw);
2142
+ if (ctx.inCustomPropertyValue && typeof key === 'string') {
2143
+ return new Reference({ key }, { type: 'variable', role: 'ident' }, $.getLocationInfo(token), this.context);
2144
+ }
2145
+ if (typeof key === 'string') {
2146
+ return new Reference(key, { type }, $.getLocationInfo(token), this.context);
2147
+ }
2148
+ return new Reference({ key }, { type }, $.getLocationInfo(token), this.context);
2149
+ }
2150
+ }
2151
+ },
2152
+ {
2153
+ ALT: () => {
2154
+ let token = $.SUBRULE($.varName, { ARGS: [ctx] });
2155
+ if (!RECORDING_PHASE) {
2156
+ // Warn about @ident in custom property values - it's treated as literal text, not a variable reference
2157
+ if (ctx.inCustomPropertyValue) {
2158
+ const atName = token.image;
2159
+ const ident = token.image.slice(1);
2160
+ $.warnDeprecation(`${atName} in custom property values is treated as literal text, not a variable reference. Use @{${ident}} if you want it to be evaluated.`, token, 'variable-in-unknown-value');
2161
+ return new Reference({ key: token.image.slice(1) }, { type: 'variable', role: 'ident' }, $.getLocationInfo(token), this.context);
2162
+ }
2163
+ return new Reference(token.image.slice(1), { type: 'variable' }, $.getLocationInfo(token), this.context);
2164
+ }
2165
+ }
2166
+ }
2167
+ ]);
2168
+ $.OR2([
2169
+ {
2170
+ ALT: () => {
2171
+ /** This spreads a (list) value within a containing list when evaluated */
2172
+ let token = $.CONSUME(T.Ellipsis);
2173
+ if (!RECORDING_PHASE) {
2174
+ node = new Rest(node, undefined, $.getLocationFromNodes([node, token]), this.context);
2175
+ }
2176
+ }
2177
+ },
2178
+ {
2179
+ /** Only variables can have accessors */
2180
+ GATE: () => {
2181
+ if (node?.options?.type !== 'variable') {
2182
+ return false;
2183
+ }
2184
+ let next = $.LA(1).tokenType;
2185
+ if (next !== T.LSquare && next !== T.LParen) {
2186
+ return false;
2187
+ }
2188
+ if (!$.noSep()) {
2189
+ return false;
2190
+ }
2191
+ return true;
2192
+ },
2193
+ ALT: () => {
2194
+ $.AT_LEAST_ONE({
2195
+ GATE: () => {
2196
+ let next = $.LA(1).tokenType;
2197
+ if (next !== T.LSquare && next !== T.LParen) {
2198
+ return false;
2199
+ }
2200
+ if (!$.noSep()) {
2201
+ return false;
2202
+ }
2203
+ return true;
2204
+ },
2205
+ DEF: () => {
2206
+ node = $.SUBRULE($.lookupOrCall, { ARGS: [{ ...ctx, node: node }] });
2207
+ }
2208
+ });
2209
+ $.OPTION(() => {
2210
+ $.OPTION2(() => $.CONSUME(T.Gt));
2211
+ node = $.SUBRULE($.mixinReference, { ARGS: [{ ...ctx, node: node }] });
2212
+ });
2213
+ }
2214
+ },
2215
+ { ALT: EMPTY_ALT() }
2216
+ ]);
2217
+ if (!RECORDING_PHASE) {
2218
+ return $.wrap(node);
2219
+ }
2220
+ };
2221
+ }
2222
+ export function valueReference(_T) {
2223
+ const $ = this;
2224
+ return (ctx = {}) => {
2225
+ return $.OR([
2226
+ { ALT: () => $.SUBRULE($.varReference, { ARGS: [ctx] }) },
2227
+ { ALT: () => $.SUBRULE($.mixinReference, { ARGS: [ctx] }) }
2228
+ ]);
2229
+ };
2230
+ }
2231
+ export function functionCall(T) {
2232
+ const $ = this;
2233
+ const modernColorFunctions = new Set(['rgb', 'rgba', 'hsl', 'hsla']);
2234
+ const isModernColorCall = (name, args) => {
2235
+ if (!modernColorFunctions.has(name.toLowerCase())) {
2236
+ return false;
2237
+ }
2238
+ if (!args || args.value.length !== 1) {
2239
+ return false;
2240
+ }
2241
+ const firstArg = args.value[0];
2242
+ return Boolean(isNode(firstArg, 'Sequence') && firstArg.value.length >= 2);
2243
+ };
2244
+ let funcAlt = (ctx = {}) => [
2245
+ {
2246
+ // Disambiguate known functions by their dedicated tokens
2247
+ GATE: () => {
2248
+ let tokenType = $.LA(1).tokenType;
2249
+ return tokenType === T.UrlStart
2250
+ || tokenType === T.Var
2251
+ || tokenType === T.Calc
2252
+ || tokenType === T.IfFunction
2253
+ || tokenType === T.BooleanFunction;
2254
+ },
2255
+ ALT: () => $.SUBRULE($.knownFunctions, { ARGS: [ctx] })
2256
+ },
2257
+ {
2258
+ // Generic function via FunctionStart token
2259
+ GATE: () => {
2260
+ let tokenType = $.LA(1).tokenType;
2261
+ return tokenType !== T.UrlStart
2262
+ && tokenType !== T.Var
2263
+ && tokenType !== T.Calc
2264
+ && tokenType !== T.IfFunction
2265
+ && tokenType !== T.BooleanFunction;
2266
+ },
2267
+ ALT: () => {
2268
+ $.startRule();
2269
+ const fnStart = $.CONSUME(T.FunctionStart);
2270
+ const fnNameForCtx = fnStart.image.slice(0, -1);
2271
+ let args;
2272
+ $.OPTION(() => args = $.SUBRULE($.functionCallArgs, { ARGS: [{ ...ctx, currentFunctionName: fnNameForCtx }] }));
2273
+ $.CONSUME(T.RParen);
2274
+ if (!$.RECORDING_PHASE) {
2275
+ const location = $.endRule();
2276
+ const nameValue = fnNameForCtx;
2277
+ if (nameValue === 'unit' && args?.value[1] instanceof Any) {
2278
+ const unitArg = args.value[1];
2279
+ const quotedUnit = new Quoted(unitArg.valueOf(), { quote: '"' }, undefined, this.context);
2280
+ quotedUnit.pre = unitArg.pre;
2281
+ quotedUnit.post = unitArg.post;
2282
+ args.value[1] = quotedUnit;
2283
+ }
2284
+ const nameNode = new Reference(nameValue, { type: 'function', fallbackValue: true }, $.getLocationInfo(fnStart), this.context);
2285
+ /** Less / Sass functions we try to call that throw just get turned into calls. */
2286
+ const modernSyntax = isModernColorCall(nameValue, args);
2287
+ return new Call({ name: nameNode, args }, { silentFail: true, ...(modernSyntax ? { modernSyntax: true } : {}) }, location, this.context);
2288
+ }
2289
+ }
2290
+ }
2291
+ ];
2292
+ return (ctx = {}) => $.OR(funcAlt(ctx));
2293
+ }
2294
+ export function functionCallArgs(T) {
2295
+ const $ = this;
2296
+ return (ctx = {}) => {
2297
+ let RECORDING_PHASE = $.RECORDING_PHASE;
2298
+ $.startRule();
2299
+ // Inside function arguments, allow inner tokens like ':'
2300
+ const prevInner = ctx.inner;
2301
+ ctx.inner = true;
2302
+ // Calls intentionally push a `false` paren frame (matches `Call.evalNode`)
2303
+ const argCtx = {
2304
+ ...ctx,
2305
+ allowComma: false,
2306
+ parenFrames: [...getParenFrames(ctx), false],
2307
+ detachedRulesetUsage: 'function-arg'
2308
+ };
2309
+ let node = $.SUBRULE($.callArgument, { ARGS: [argCtx] });
2310
+ let commaNodes;
2311
+ let semiNodes;
2312
+ if (!RECORDING_PHASE) {
2313
+ commaNodes = [$.wrap(node, true)];
2314
+ semiNodes = [];
2315
+ }
2316
+ let isSemiList = false;
2317
+ // First, consume any comma-separated arguments
2318
+ $.MANY(() => {
2319
+ $.CONSUME(T.Comma);
2320
+ node = $.SUBRULE2($.callArgument, { ARGS: [argCtx] });
2321
+ if (!RECORDING_PHASE) {
2322
+ commaNodes.push($.wrap(node, true));
2323
+ }
2324
+ });
2325
+ // Then, optionally switch to semicolon-separated list and continue with semicolons
2326
+ $.OPTION(() => {
2327
+ isSemiList = true;
2328
+ $.CONSUME(T.Semi);
2329
+ if (!RECORDING_PHASE) {
2330
+ // Aggregate the previous set of comma-nodes as the first semi item
2331
+ if (commaNodes.length > 1) {
2332
+ semiNodes.push(new List(commaNodes, undefined, $.getLocationFromNodes(commaNodes), this.context));
2333
+ }
2334
+ else {
2335
+ semiNodes.push(commaNodes[0]);
2336
+ }
2337
+ }
2338
+ node = $.SUBRULE3($.callArgument, { ARGS: [{ ...argCtx, allowComma: true }] });
2339
+ if (!RECORDING_PHASE) {
2340
+ semiNodes.push($.wrap(node, true));
2341
+ }
2342
+ $.MANY2(() => {
2343
+ $.CONSUME2(T.Semi);
2344
+ node = $.SUBRULE4($.callArgument, { ARGS: [{ ...argCtx, allowComma: true }] });
2345
+ if (!RECORDING_PHASE) {
2346
+ semiNodes.push($.wrap(node, true));
2347
+ }
2348
+ });
2349
+ });
2350
+ if (!RECORDING_PHASE) {
2351
+ ctx.inner = prevInner;
2352
+ $.endRule();
2353
+ const nodes = isSemiList ? semiNodes : commaNodes;
2354
+ return new List(nodes, isSemiList ? { sep: ';' } : undefined);
2355
+ }
2356
+ };
2357
+ }
2358
+ export function value(T) {
2359
+ const $ = this;
2360
+ return (ctx = {}) => {
2361
+ if ($.LA(1).tokenType === T.Percent) {
2362
+ }
2363
+ // eslint-disable-next-line @typescript-eslint/naming-convention
2364
+ let _isMixinReference = undefined;
2365
+ const isMixinReference = () => {
2366
+ if (_isMixinReference === undefined) {
2367
+ let tt1 = $.LA(1).tokenType;
2368
+ let tt2 = $.LA(2).tokenType;
2369
+ /**
2370
+ * We'll allow a few "bare" mixin references without parens
2371
+ * or square brackets, but not if they'll conflict with
2372
+ * other syntax.
2373
+ */
2374
+ _isMixinReference =
2375
+ tt1 === T.DotName
2376
+ || tt1 === T.HashName
2377
+ || tt1 === T.InterpolatedSelector
2378
+ || ((tt1 === T.ColorIdentStart
2379
+ || tt1 === T.InterpolatedSelector) && (tt2 === T.Gt
2380
+ || tt2 === T.DotName
2381
+ || tt2 === T.HashName
2382
+ || tt2 === T.InterpolatedSelector
2383
+ || ($.noSep(1)
2384
+ && (tt2 === T.LParen
2385
+ || tt2 === T.LSquare
2386
+ || tt2 === T.HashName
2387
+ || tt2 === T.DotName))));
2388
+ }
2389
+ return _isMixinReference;
2390
+ };
2391
+ let node = $.OR([
2392
+ { ALT: () => $.SUBRULE($.functionCall, { ARGS: [ctx] }) },
2393
+ {
2394
+ GATE: () => $.LA(1).tokenType === T.Star && $.LA(2).tokenType === T.LSquare,
2395
+ ALT: () => $.SUBRULE($.selectorCapture, { ARGS: [ctx] })
2396
+ },
2397
+ {
2398
+ GATE: isMixinReference,
2399
+ ALT: () => $.SUBRULE($.mixinReference, { ARGS: [ctx] })
2400
+ },
2401
+ {
2402
+ GATE: () => !isMixinReference(),
2403
+ ALT: () => $.CONSUME(T.Color)
2404
+ },
2405
+ {
2406
+ GATE: () => !isMixinReference(),
2407
+ ALT: () => $.CONSUME(T.Ident)
2408
+ },
2409
+ { ALT: () => $.SUBRULE($.varReference, { ARGS: [ctx] }) },
2410
+ { ALT: () => $.CONSUME(T.DefaultGuardFunc) },
2411
+ { ALT: () => $.CONSUME(T.Dimension) },
2412
+ { ALT: () => $.CONSUME(T.Number) },
2413
+ {
2414
+ GATE: () => ctx.currentFunctionName === 'unit',
2415
+ ALT: () => $.CONSUME(T.Percent)
2416
+ },
2417
+ { ALT: () => $.CONSUME(T.UnicodeRange) },
2418
+ { ALT: () => $.SUBRULE($.string, { ARGS: [ctx] }) },
2419
+ { ALT: () => $.CONSUME(T.JavaScript) },
2420
+ /** Explicitly not marked as an ident */
2421
+ { ALT: () => $.CONSUME(T.When) },
2422
+ { ALT: () => $.SUBRULE($.squareValue, { ARGS: [ctx] }) },
2423
+ {
2424
+ GATE: () => $.looseMode && !!ctx.inner,
2425
+ ALT: () => $.CONSUME(T.Colon)
2426
+ },
2427
+ {
2428
+ GATE: () => $.looseMode,
2429
+ ALT: () => $.CONSUME(T.Unknown)
2430
+ },
2431
+ {
2432
+ /** e.g. progid:DXImageTransform.Microsoft.Blur(pixelradius=2) */
2433
+ GATE: () => $.legacyMode,
2434
+ ALT: () => $.CONSUME(T.LegacyMSFilter)
2435
+ }
2436
+ ]);
2437
+ if (!$.RECORDING_PHASE) {
2438
+ if (!(node instanceof Node)) {
2439
+ node = $.processValueToken(node);
2440
+ }
2441
+ return $.wrap(node);
2442
+ }
2443
+ };
2444
+ }
2445
+ export function string(T) {
2446
+ const $ = this;
2447
+ let stringAlt = [
2448
+ {
2449
+ ALT: () => {
2450
+ $.startRule();
2451
+ let quote = $.CONSUME(T.SingleQuoteStart);
2452
+ let contents;
2453
+ $.OPTION(() => contents = $.CONSUME(T.SingleQuoteStringContents));
2454
+ $.CONSUME(T.SingleQuoteEnd);
2455
+ if (!$.RECORDING_PHASE) {
2456
+ let quoteImg = quote.image;
2457
+ let escaped = false;
2458
+ if (quoteImg.startsWith('~')) {
2459
+ escaped = true;
2460
+ quoteImg = quoteImg.slice(1);
2461
+ }
2462
+ let location = $.endRule();
2463
+ let value = contents?.image;
2464
+ if (escaped && value) {
2465
+ value = value.replace(/\\(?:\r\n?|\n|\f)/g, '\n');
2466
+ }
2467
+ // Handle interpolation in string contents
2468
+ if (value && (value.includes('@{') || value.includes('${'))) {
2469
+ return new Quoted(processStringInterpolation(value, location, this.context), { quote: quoteImg, escaped }, location, this.context);
2470
+ }
2471
+ return new Quoted(new Any(value ?? '', { role: 'any' }), { quote: quoteImg, escaped }, location, this.context);
2472
+ }
2473
+ }
2474
+ },
2475
+ {
2476
+ ALT: () => {
2477
+ $.startRule();
2478
+ let quote = $.CONSUME(T.DoubleQuoteStart);
2479
+ let contents;
2480
+ $.OPTION2(() => contents = $.CONSUME(T.DoubleQuoteStringContents));
2481
+ $.CONSUME(T.DoubleQuoteEnd);
2482
+ if (!$.RECORDING_PHASE) {
2483
+ let quoteImg = quote.image;
2484
+ let escaped = false;
2485
+ if (quoteImg.startsWith('~')) {
2486
+ escaped = true;
2487
+ quoteImg = quoteImg.slice(1);
2488
+ }
2489
+ let location = $.endRule();
2490
+ let value = contents?.image;
2491
+ if (escaped && value) {
2492
+ value = value.replace(/\\(?:\r\n?|\n|\f)/g, '\n');
2493
+ }
2494
+ // Handle interpolation in string contents
2495
+ if (value && (value.includes('@{') || value.includes('${'))) {
2496
+ return new Quoted(processStringInterpolation(value, location, this.context), { quote: quoteImg, escaped }, location, this.context);
2497
+ }
2498
+ return new Quoted(new Any(value ?? '', { role: 'any' }), { quote: quoteImg, escaped }, location, this.context);
2499
+ }
2500
+ }
2501
+ }
2502
+ ];
2503
+ return (_ctx = {}) => $.OR(stringAlt);
2504
+ }
2505
+ /**
2506
+ * Find interpolation patterns like @{...} or ${...}, handling nested braces.
2507
+ * Returns an array of { start, end, prefix, content } for each match.
2508
+ */
2509
+ function findInterpolations(value) {
2510
+ const matches = [];
2511
+ let i = 0;
2512
+ while (i < value.length) {
2513
+ // Look for @{ or ${
2514
+ if ((value[i] === '@' || value[i] === '$') && value[i + 1] === '{') {
2515
+ const prefix = value[i];
2516
+ const start = i;
2517
+ i += 2; // Skip @{ or ${
2518
+ let braceCount = 1;
2519
+ const contentStart = i;
2520
+ // Find matching closing brace, counting nested braces
2521
+ while (i < value.length && braceCount > 0) {
2522
+ if (value[i] === '{') {
2523
+ braceCount++;
2524
+ }
2525
+ else if (value[i] === '}') {
2526
+ braceCount--;
2527
+ }
2528
+ i++;
2529
+ }
2530
+ if (braceCount === 0) {
2531
+ const content = value.slice(contentStart, i - 1);
2532
+ matches.push({ start, end: i, prefix, content });
2533
+ }
2534
+ }
2535
+ else {
2536
+ i++;
2537
+ }
2538
+ }
2539
+ return matches;
2540
+ }
2541
+ // Helper function to process string interpolation (handles nested @{...} patterns)
2542
+ function processStringInterpolation(value, location, context) {
2543
+ const matches = findInterpolations(value);
2544
+ if (matches.length === 0) {
2545
+ return new Any(value, { role: 'any' }, location, context);
2546
+ }
2547
+ const replacements = [];
2548
+ let source = value;
2549
+ let offset = 0;
2550
+ for (const match of matches) {
2551
+ const adjustedStart = match.start - offset;
2552
+ const adjustedEnd = match.end - offset;
2553
+ const before = source.slice(0, adjustedStart);
2554
+ const after = source.slice(adjustedEnd);
2555
+ source = before + INTERPOLATION_PLACEHOLDER + after;
2556
+ offset += (match.end - match.start) - INTERPOLATION_PLACEHOLDER.length;
2557
+ // Recursively process the content in case it has nested interpolation
2558
+ const innerResult = processStringInterpolation(match.content, location, context);
2559
+ if (innerResult instanceof Interpolated) {
2560
+ // Nested interpolation in string contexts still resolves through a reference,
2561
+ // but must remain expression-wrapped in Jess output.
2562
+ const nestedRef = new Reference({ key: innerResult }, { type: 'variable', role: 'ident' }, location, context);
2563
+ replacements.push(new Expression(nestedRef, undefined, location, context));
2564
+ }
2565
+ else {
2566
+ // Simple interpolation reference
2567
+ replacements.push(createInterpolatedReference(match.prefix, match.content, location, context));
2568
+ }
2569
+ }
2570
+ return new Interpolated({ source, replacements }, { role: 'ident' }, location, context);
2571
+ }
2572
+ export function mathValue(T) {
2573
+ const $ = this;
2574
+ let valueAlt = (ctx = {}) => [
2575
+ { ALT: () => $.CONSUME(T.AtKeyword) },
2576
+ { ALT: () => $.CONSUME(T.Number) },
2577
+ { ALT: () => $.CONSUME(T.Dimension) },
2578
+ // Allow identifiers like channel names in color space calcs (e.g., calc(l - 0.1))
2579
+ { ALT: () => $.CONSUME(T.Ident) },
2580
+ { ALT: () => $.SUBRULE($.functionCall, { ARGS: [ctx] }) },
2581
+ {
2582
+ /** Only allow escaped strings in calc */
2583
+ GATE: () => $.LA(1).image.startsWith('~'),
2584
+ ALT: () => $.SUBRULE($.string, { ARGS: [ctx] })
2585
+ },
2586
+ {
2587
+ /** For some reason, e() goes here instead of $.function */
2588
+ GATE: () => $.LA(2).tokenType !== T.LParen,
2589
+ ALT: () => $.CONSUME(T.MathConstant)
2590
+ },
2591
+ { ALT: () => $.SUBRULE($.mathParen, { ARGS: [ctx] }) }
2592
+ ];
2593
+ return cssMathValue.call(this, T, valueAlt);
2594
+ }
2595
+ /** @todo - add interpolation */
2596
+ // $.OVERRIDE_RULE('string', () => {
2597
+ // $.OR([
2598
+ // {
2599
+ // ALT: () => {
2600
+ // $.CONSUME(T.SingleQuoteStart)
2601
+ // $.OPTION(() => $.CONSUME(T.SingleQuoteStringContents))
2602
+ // $.CONSUME(T.SingleQuoteEnd)
2603
+ // }
2604
+ // },
2605
+ // {
2606
+ // ALT: () => {
2607
+ // $.CONSUME(T.DoubleQuoteStart)
2608
+ // $.OPTION2(() => $.CONSUME(T.DoubleQuoteStringContents))
2609
+ // $.CONSUME(T.DoubleQuoteEnd)
2610
+ // }
2611
+ // }
2612
+ // ])
2613
+ // })
2614
+ export function guard(T) {
2615
+ const $ = this;
2616
+ return (ctx = {}) => {
2617
+ $.CONSUME(T.When);
2618
+ return $.OR2([
2619
+ {
2620
+ GATE: () => !!ctx.inValueList,
2621
+ ALT: () => $.SUBRULE($.comparison, { ARGS: [ctx] })
2622
+ },
2623
+ {
2624
+ ALT: () => {
2625
+ ctx.allowComma = true;
2626
+ const node = $.SUBRULE($.guardOr, { ARGS: [ctx] });
2627
+ return node;
2628
+ }
2629
+ }
2630
+ ]);
2631
+ };
2632
+ }
2633
+ /**
2634
+ * 'or' expression
2635
+ * Allows an (outer) comma like historical media queries
2636
+ */
2637
+ export function guardOr(T) {
2638
+ const $ = this;
2639
+ return (ctx = {}) => {
2640
+ let RECORDING_PHASE = $.RECORDING_PHASE;
2641
+ $.startRule();
2642
+ let left = $.SUBRULE($.guardAnd, { ARGS: [ctx] });
2643
+ let right;
2644
+ $.MANY({
2645
+ GATE: () => {
2646
+ const next = $.LA(1).tokenType;
2647
+ return (ctx.allowComma && next === T.Comma) || next === T.Or;
2648
+ },
2649
+ DEF: () => {
2650
+ /**
2651
+ * Nest expressions within expressions for correct
2652
+ * order of operations.
2653
+ */
2654
+ $.OR3([
2655
+ { ALT: () => $.CONSUME(T.Comma) },
2656
+ { ALT: () => $.CONSUME(T.Or) }
2657
+ ]);
2658
+ right = $.SUBRULE2($.guardAnd, { ARGS: [ctx] });
2659
+ if (!RECORDING_PHASE) {
2660
+ let location = $.endRule();
2661
+ $.startRule();
2662
+ left = new Condition([$.wrap(left, true), 'or', $.wrap(right)], undefined, location, this.context);
2663
+ }
2664
+ }
2665
+ });
2666
+ if (!RECORDING_PHASE) {
2667
+ $.endRule();
2668
+ }
2669
+ return left;
2670
+ };
2671
+ }
2672
+ export function guardDefault(T) {
2673
+ const $ = this;
2674
+ let guardAlt = [
2675
+ { ALT: () => $.CONSUME(T.DefaultGuardIdent) },
2676
+ { ALT: () => $.CONSUME(T.DefaultGuardFunc) }
2677
+ ];
2678
+ return (ctx = {}) => {
2679
+ let guard = $.OR(guardAlt);
2680
+ ctx.hasDefault = true;
2681
+ if (!$.RECORDING_PHASE) {
2682
+ return new DefaultGuard(guard.image, undefined, $.getLocationInfo(guard), this.context);
2683
+ }
2684
+ };
2685
+ }
2686
+ /**
2687
+ * 'and' and 'or' expressions
2688
+ *
2689
+ * In Media queries level 4, you cannot have
2690
+ * `([expr]) or ([expr]) and ([expr])` because
2691
+ * of evaluation order ambiguity.
2692
+ * However, Less allows it.
2693
+ */
2694
+ export function guardAnd(T) {
2695
+ const $ = this;
2696
+ return (ctx = {}) => {
2697
+ let left;
2698
+ let RECORDING_PHASE = $.RECORDING_PHASE;
2699
+ $.MANY_SEP({
2700
+ SEP: T.And,
2701
+ DEF: () => {
2702
+ let not;
2703
+ $.OPTION(() => not = $.CONSUME(T.Not));
2704
+ let allowComma = ctx.allowComma;
2705
+ ctx.allowComma = false;
2706
+ let right = $.OR([
2707
+ { ALT: () => $.SUBRULE($.guardInParens, { ARGS: [ctx] }) },
2708
+ {
2709
+ GATE: () => {
2710
+ const tokenType = $.LA(1).tokenType;
2711
+ return tokenType !== T.Not
2712
+ && tokenType !== T.DefaultGuardFunc
2713
+ && tokenType !== T.DefaultGuardIdent;
2714
+ },
2715
+ ALT: () => $.SUBRULE($.value, { ARGS: [ctx] })
2716
+ }
2717
+ ]);
2718
+ if (!RECORDING_PHASE && isDefaultGuardCall(right)) {
2719
+ ctx.hasDefault = true;
2720
+ const location = Array.isArray(right.location) && right.location.length === 6
2721
+ ? right.location
2722
+ : undefined;
2723
+ right = new DefaultGuard('default()', undefined, location, this.context);
2724
+ }
2725
+ ctx.allowComma = allowComma;
2726
+ if (!RECORDING_PHASE && not) {
2727
+ let [, , , endOffset, endLine, endColumn] = right.location;
2728
+ let [startOffset, startLine, startColumn] = $.getLocationInfo(not);
2729
+ right = new Condition([$.wrap(right, true)], { negate: true }, [startOffset, startLine, startColumn, endOffset, endLine, endColumn], this.context);
2730
+ }
2731
+ if (!left) {
2732
+ left = right;
2733
+ return;
2734
+ }
2735
+ if (!RECORDING_PHASE) {
2736
+ left = new Condition([$.wrap(left, true), 'and', $.wrap(right)], undefined, $.getLocationFromNodes([left, right]), this.context);
2737
+ }
2738
+ }
2739
+ });
2740
+ return left;
2741
+ };
2742
+ }
2743
+ export function guardInParens(T) {
2744
+ const $ = this;
2745
+ return (ctx) => {
2746
+ $.startRule();
2747
+ let node = $.OR([
2748
+ { ALT: () => $.SUBRULE($.guardDefault, { ARGS: [ctx] }) },
2749
+ {
2750
+ ALT: () => {
2751
+ $.CONSUME(T.LParen);
2752
+ let node = $.SUBRULE($.guardInner, { ARGS: [ctx] });
2753
+ $.CONSUME(T.RParen);
2754
+ return node;
2755
+ }
2756
+ }
2757
+ ]);
2758
+ if (!$.RECORDING_PHASE) {
2759
+ if (isDefaultGuardCall(node)) {
2760
+ ctx.hasDefault = true;
2761
+ const location = Array.isArray(node.location) && node.location.length === 6
2762
+ ? node.location
2763
+ : undefined;
2764
+ node = new DefaultGuard('default()', undefined, location, this.context);
2765
+ }
2766
+ node = $.wrap(node, 'both');
2767
+ return new Paren(node, undefined, $.endRule(), this.context);
2768
+ }
2769
+ };
2770
+ }
2771
+ // The inner content of a guard inside parentheses
2772
+ export function guardInner(_T) {
2773
+ const $ = this;
2774
+ return (ctx = {}) => $.OR([
2775
+ { ALT: () => $.SUBRULE($.comparison, { ARGS: [ctx] }) },
2776
+ {
2777
+ ALT: () => $.SUBRULE($.guardOr, { ARGS: [ctx] })
2778
+ }
2779
+ ]);
2780
+ }
2781
+ export function guardWithConditionValue(T) {
2782
+ const $ = this;
2783
+ return (ctx = {}) => $.OR([
2784
+ {
2785
+ ALT: () => {
2786
+ $.OR2([
2787
+ { ALT: () => $.CONSUME(T.DefaultGuardIdent) },
2788
+ { ALT: () => $.CONSUME(T.DefaultGuardFunc) }
2789
+ ]);
2790
+ }
2791
+ },
2792
+ { ALT: () => $.SUBRULE($.guardInParens, { ARGS: [ctx] }) }
2793
+ ]);
2794
+ }
2795
+ export function guardWithCondition(T) {
2796
+ const $ = this;
2797
+ return (ctx = {}) => {
2798
+ $.SUBRULE($.guardWithConditionValue, { ARGS: [ctx] });
2799
+ $.AT_LEAST_ONE(() => {
2800
+ $.OR([
2801
+ { ALT: () => $.CONSUME(T.Or) },
2802
+ { ALT: () => $.CONSUME(T.And) },
2803
+ { ALT: () => $.CONSUME(T.Comma) }
2804
+ ]);
2805
+ $.SUBRULE2($.guardWithConditionValue, { ARGS: [ctx] });
2806
+ });
2807
+ };
2808
+ }
2809
+ /**
2810
+ * Currently, Less only allows a single comparison expression,
2811
+ * unlike Media Queries Level 4, which allows a left and right
2812
+ * comparison.
2813
+ */
2814
+ export function comparison(T) {
2815
+ const $ = this;
2816
+ let opAlt = [
2817
+ { ALT: () => $.CONSUME(T.Eq) },
2818
+ { ALT: () => $.CONSUME(T.Gt) },
2819
+ { ALT: () => $.CONSUME(T.GtEq) },
2820
+ { ALT: () => $.CONSUME(T.GtEqAlias) },
2821
+ { ALT: () => $.CONSUME(T.Lt) },
2822
+ { ALT: () => $.CONSUME(T.LtEq) },
2823
+ { ALT: () => $.CONSUME(T.LtEqAlias) }
2824
+ ];
2825
+ return (ctx = {}) => {
2826
+ let left = $.SUBRULE($.valueList, { ARGS: [ctx] });
2827
+ // $.OPTION(() => {
2828
+ let op = $.OR(opAlt);
2829
+ let right = $.SUBRULE2($.valueList, { ARGS: [ctx] });
2830
+ if (!$.RECORDING_PHASE) {
2831
+ let opStr = op.image;
2832
+ if (opStr === '=>') {
2833
+ opStr = '>=';
2834
+ }
2835
+ else if (opStr === '=<') {
2836
+ opStr = '<=';
2837
+ }
2838
+ left = new Condition([$.wrap(left, true), opStr, $.wrap(right)], undefined, $.getLocationFromNodes([left, right]), this.context);
2839
+ }
2840
+ // })
2841
+ return left;
2842
+ };
2843
+ }
2844
+ /**
2845
+ * Less (perhaps unwisely) allows bubling of normally document-root
2846
+ * at-rules, so we need to override CSS here.
2847
+ */
2848
+ export function innerAtRule(T) {
2849
+ const $ = this;
2850
+ let ruleAlt = (ctx = {}) => [
2851
+ { ALT: () => $.SUBRULE($.mediaAtRule, { ARGS: [{ ...ctx, inner: true }] }) },
2852
+ { ALT: () => $.SUBRULE($.supportsAtRule, { ARGS: [{ ...ctx, inner: true }] }) },
2853
+ { ALT: () => $.SUBRULE($.layerAtRule, { ARGS: [{ ...ctx, inner: true }] }) },
2854
+ { ALT: () => $.SUBRULE($.containerAtRule, { ARGS: [{ ...ctx, inner: true }] }) },
2855
+ { ALT: () => $.SUBRULE($.keyframesAtRule, { ARGS: [{ ...ctx, inner: true }] }) },
2856
+ { ALT: () => $.SUBRULE($.documentAtRule, { ARGS: [{ ...ctx, inner: true }] }) },
2857
+ { ALT: () => $.SUBRULE($.importAtRule, { ARGS: [ctx] }) },
2858
+ { ALT: () => $.SUBRULE($.pageAtRule, { ARGS: [ctx] }) },
2859
+ { ALT: () => $.SUBRULE($.fontFaceAtRule, { ARGS: [ctx] }) },
2860
+ { ALT: () => $.SUBRULE($.nestedAtRule, { ARGS: [ctx] }) },
2861
+ { ALT: () => $.SUBRULE($.nonNestedAtRule, { ARGS: [ctx] }) },
2862
+ { ALT: () => $.SUBRULE($.unknownAtRule, { ARGS: [{ ...ctx, inner: true }] }) }
2863
+ ];
2864
+ return cssInnerAtRule.call(this, T, ruleAlt);
2865
+ }
2866
+ /**
2867
+ * Less override: allow variable reference as the first segment of a layer-name
2868
+ * CSS: <ident> ('.' <ident>)*
2869
+ * Less: (<var-ref> | <ident>) ('.' <ident>)*
2870
+ */
2871
+ export function layerName(T) {
2872
+ const $ = this;
2873
+ return (ctx = {}) => {
2874
+ const RECORDING_PHASE = $.RECORDING_PHASE;
2875
+ $.startRule();
2876
+ const nodes = RECORDING_PHASE ? [] : [];
2877
+ // First segment: variable reference or plain ident
2878
+ const first = $.OR([
2879
+ { ALT: () => $.SUBRULE($.valueReference, { ARGS: [ctx] }) },
2880
+ { ALT: () => $.CONSUME(T.Ident) }
2881
+ ]);
2882
+ if (!RECORDING_PHASE) {
2883
+ if (first instanceof Node) {
2884
+ nodes.push($.wrap(first));
2885
+ }
2886
+ else {
2887
+ nodes.push($.wrap($.processValueToken(first)));
2888
+ }
2889
+ }
2890
+ // Remaining segments: dot + ident (same as CSS)
2891
+ $.MANY({
2892
+ GATE: $.noSep,
2893
+ DEF: () => {
2894
+ const seg = $.CONSUME(T.DotName);
2895
+ if (!RECORDING_PHASE) {
2896
+ nodes.push($.wrap($.processValueToken(seg)));
2897
+ }
2898
+ }
2899
+ });
2900
+ if (!RECORDING_PHASE) {
2901
+ const loc = $.endRule();
2902
+ return new Sequence(nodes, undefined, loc, this.context);
2903
+ }
2904
+ };
2905
+ }
2906
+ /**
2907
+ * Less override: allow variable reference for @keyframes name
2908
+ * CSS: Ident | String
2909
+ * Less: valueReference | Ident | String
2910
+ */
2911
+ // Less override: allow variable reference in keyframes name by overriding keyframesName only
2912
+ export function keyframesName(T) {
2913
+ const $ = this;
2914
+ return (ctx = {}) => {
2915
+ const RECORDING_PHASE = $.RECORDING_PHASE;
2916
+ let node;
2917
+ $.OR({
2918
+ DEF: [
2919
+ { ALT: () => node = $.SUBRULE($.valueReference, { ARGS: [ctx] }) },
2920
+ { ALT: () => {
2921
+ const tok = $.CONSUME(T.Ident);
2922
+ if (!RECORDING_PHASE) {
2923
+ node = $.wrap($.processValueToken(tok));
2924
+ }
2925
+ } },
2926
+ { ALT: () => node = $.SUBRULE($.string) }
2927
+ ]
2928
+ });
2929
+ return node;
2930
+ };
2931
+ }
2932
+ /**
2933
+ * One of the rare rules that returns a token, because
2934
+ * other rules will transform it differently.
2935
+ */
2936
+ export function mixinName(T) {
2937
+ const $ = this;
2938
+ let nameAlt = [
2939
+ { ALT: () => $.CONSUME(T.HashName) },
2940
+ { ALT: () => $.CONSUME(T.ColorIdentStart) },
2941
+ { ALT: () => $.CONSUME(T.DotName) },
2942
+ { ALT: () => $.CONSUME(T.InterpolatedIdent) },
2943
+ { ALT: () => $.CONSUME(T.InterpolatedSelector) }
2944
+ ];
2945
+ /** e.g. .mixin, #mixin */
2946
+ return (ctx = {}) => {
2947
+ let name = $.OR(nameAlt);
2948
+ if (!$.RECORDING_PHASE) {
2949
+ const asReference = ctx.asReference;
2950
+ let nameNode;
2951
+ let nameValue = name.image;
2952
+ let location = $.getLocationInfo(name);
2953
+ if (nameValue.includes('@') || nameValue.includes('$')) {
2954
+ nameNode = getInterpolated(nameValue, location, this.context);
2955
+ if (asReference) {
2956
+ // For interpolated keys, we can't merge into array easily, so keep nested structure
2957
+ // But we still check type to ensure consistency
2958
+ if (isNode(ctx.node, 'Reference') && ctx.node.options.type === 'mixin-ruleset') {
2959
+ // Keep nested structure for interpolated keys
2960
+ nameNode = new Reference({ target: ctx.node, key: nameNode }, { type: 'mixin-ruleset', role: 'name' }, location, this.context);
2961
+ }
2962
+ else {
2963
+ nameNode = new Reference({ target: ctx.node, key: nameNode }, { type: 'mixin-ruleset', role: 'name' }, location, this.context);
2964
+ }
2965
+ }
2966
+ }
2967
+ else {
2968
+ if (asReference) {
2969
+ // If target is a Reference with matching type, merge keys instead of nesting
2970
+ if (isNode(ctx.node, 'Reference') && ctx.node.options.type === 'mixin-ruleset') {
2971
+ const existingKey = ctx.node.value.key;
2972
+ let mergedKeys;
2973
+ if (Array.isArray(existingKey)) {
2974
+ mergedKeys = [...existingKey];
2975
+ }
2976
+ else {
2977
+ mergedKeys = [existingKey];
2978
+ }
2979
+ mergedKeys.push(nameValue);
2980
+ // Create a single Reference with merged keys (no target)
2981
+ nameNode = new Reference({ key: mergedKeys.length === 1 ? mergedKeys[0] : mergedKeys }, { type: 'mixin-ruleset', role: 'name' }, location, this.context);
2982
+ }
2983
+ else {
2984
+ // Target is Call, Reference with different type, or undefined - create Reference with target
2985
+ nameNode = new Reference({ target: ctx.node, key: nameValue }, { type: 'mixin-ruleset', role: 'name' }, location, this.context);
2986
+ }
2987
+ }
2988
+ else {
2989
+ nameNode = $.wrap(new Any(nameValue, { role: 'name' }, $.getLocationInfo(name), this.context), true);
2990
+ }
2991
+ }
2992
+ return nameNode;
2993
+ }
2994
+ };
2995
+ }
2996
+ /**
2997
+ * Used within a value. These can be
2998
+ * chained more recursively, unlike
2999
+ * Less 1.x-4.x
3000
+ * e.g. .mixin1() > .mixin2[@val1].ns() > .sub-mixin[@val2]
3001
+ *
3002
+ * This production intelligently decides whether to produce a Call or Reference
3003
+ * based on whether there are parentheses at the end:
3004
+ * - foo: #id; // Reference
3005
+ * - foo: .class; // Reference
3006
+ * - foo: #id > .scoped; // Reference
3007
+ * - foo: #id > .scoped(); // Call
3008
+ * - foo: #id[]; // Reference with accessor
3009
+ * - foo: #id > .scoped[foo]; // Reference with accessor
3010
+ * - foo: #id > .scoped[@ref](); // Call with accessor
3011
+ */
3012
+ export function mixinReference(T) {
3013
+ const $ = this;
3014
+ return (ctx = {}) => {
3015
+ let leftNode = $.SUBRULE($.mixinName, { ARGS: [{ ...ctx, asReference: true }] });
3016
+ $.MANY({
3017
+ GATE: () => {
3018
+ let next = $.LA(1).tokenType;
3019
+ return $.noSep() && (next === T.LParen || next === T.LSquare);
3020
+ },
3021
+ DEF: () => {
3022
+ leftNode = $.SUBRULE($.lookupOrCall, { ARGS: [{ ...ctx, node: leftNode }] });
3023
+ }
3024
+ });
3025
+ $.OPTION(() => {
3026
+ $.OPTION2(() => $.CONSUME(T.Gt));
3027
+ leftNode = $.SUBRULE2($.mixinReference, { ARGS: [{ ...ctx, node: leftNode }] });
3028
+ });
3029
+ return leftNode;
3030
+ };
3031
+ }
3032
+ export function mixinArgs(T) {
3033
+ const $ = this;
3034
+ return (ctx = {}) => {
3035
+ let args;
3036
+ // Check for whitespace before the opening paren (before consuming)
3037
+ const hasWhitespace = !$.RECORDING_PHASE && !$.noSep();
3038
+ const openingParenToken = hasWhitespace ? $.LA(1) : undefined;
3039
+ $.CONSUME(T.LParen);
3040
+ // Clear ctx.node when parsing arguments - arguments should start fresh, not inherit the parent node
3041
+ // Calls intentionally push a `false` paren frame (matches `Call.evalNode`)
3042
+ const argCtx = {
3043
+ ...ctx,
3044
+ node: undefined,
3045
+ allowComma: false,
3046
+ parenFrames: [...getParenFrames(ctx), false],
3047
+ detachedRulesetUsage: ctx.isDefinition ? 'default-param' : 'mixin-arg'
3048
+ };
3049
+ $.OPTION(() => {
3050
+ args = $.SUBRULE($.mixinArgList, { ARGS: [argCtx] });
3051
+ });
3052
+ $.CONSUME(T.RParen);
3053
+ // Check for whitespace warning AFTER consuming closing paren
3054
+ // Now we can check what comes next to determine if it's actually a definition
3055
+ if (!$.RECORDING_PHASE && hasWhitespace && openingParenToken) {
3056
+ const nextAfterParens = $.LA(1).tokenType;
3057
+ const isActuallyDefinition = nextAfterParens === T.LCurly || nextAfterParens === T.When;
3058
+ // Only warn if it's NOT a definition (i.e., it's a mixin call)
3059
+ if (!isActuallyDefinition) {
3060
+ $.warnDeprecation('Whitespace between a mixin name and parentheses for a mixin call is deprecated', openingParenToken, 'mixin-call-whitespace');
3061
+ }
3062
+ }
3063
+ return args;
3064
+ };
3065
+ }
3066
+ export function lookupOrCall(T) {
3067
+ const $ = this;
3068
+ let keyAlt = [
3069
+ { ALT: () => $.CONSUME(T.NestedReference) },
3070
+ { ALT: () => $.CONSUME(T.AtKeyword) },
3071
+ { ALT: () => $.CONSUME(T.PropertyReference) },
3072
+ { ALT: () => $.CONSUME(T.InterpolatedIdent) },
3073
+ { ALT: () => $.CONSUME(T.Ident) }
3074
+ ];
3075
+ return (ctx = {}) => {
3076
+ $.startRule();
3077
+ let RECORDING_PHASE = $.RECORDING_PHASE;
3078
+ return $.OR([
3079
+ {
3080
+ ALT: () => {
3081
+ let keyToken;
3082
+ $.CONSUME(T.LSquare);
3083
+ $.OPTION(() => keyToken = $.OR2(keyAlt));
3084
+ $.CONSUME(T.RSquare);
3085
+ if (!RECORDING_PHASE) {
3086
+ let ref;
3087
+ let target = ctx.node;
3088
+ if (keyToken) {
3089
+ let tokenStr = keyToken.image;
3090
+ let type = tokenStr.startsWith('@') ? 'variable' : 'property';
3091
+ // Handle all token types consistently
3092
+ if (keyToken.tokenType === T.NestedReference) {
3093
+ // For NestedReference, add $ prefix if not present
3094
+ let tokenStr = keyToken.image;
3095
+ if (!tokenStr.startsWith('$') && !tokenStr.startsWith('@')) {
3096
+ tokenStr = '$' + tokenStr;
3097
+ }
3098
+ }
3099
+ let result = getInterpolatedOrString(tokenStr, $.getLocationInfo(keyToken), this.context);
3100
+ // Only merge keys for mixin, mixin-ruleset, or ruleset types
3101
+ // For variable and property types, keep them nested (target.key structure)
3102
+ const targetType = isNode(target, 'Reference') ? target.options.type : undefined;
3103
+ const shouldMergeKeys = targetType === 'mixin' || targetType === 'mixin-ruleset' || targetType === 'ruleset';
3104
+ if (isNode(target, 'Reference') && target.options.type === type && typeof result === 'string' && shouldMergeKeys) {
3105
+ const existingKey = target.value.key;
3106
+ let mergedKeys;
3107
+ if (Array.isArray(existingKey)) {
3108
+ mergedKeys = [...existingKey];
3109
+ }
3110
+ else {
3111
+ mergedKeys = [existingKey];
3112
+ }
3113
+ mergedKeys.push(result);
3114
+ ref = new Reference({ key: mergedKeys.length === 1 ? mergedKeys[0] : mergedKeys }, { type }, $.endRule(), this.context);
3115
+ }
3116
+ else {
3117
+ ref = new Reference({ target, key: result }, { type }, $.endRule(), this.context);
3118
+ }
3119
+ }
3120
+ else {
3121
+ ref = new Reference({ target, key: -1 }, { type: 'index' }, $.endRule(), this.context);
3122
+ }
3123
+ /** Reference targets will technically precede the reference, so we need to update the location to the target start location */
3124
+ if (target) {
3125
+ let [targetStartOffset, targetStartLine, targetStartColumn] = target.location;
3126
+ ref.location[0] = targetStartOffset;
3127
+ ref.location[1] = targetStartLine;
3128
+ ref.location[2] = targetStartColumn;
3129
+ }
3130
+ return ref;
3131
+ }
3132
+ }
3133
+ },
3134
+ {
3135
+ ALT: () => {
3136
+ let args = $.SUBRULE($.mixinArgs, { ARGS: [ctx] });
3137
+ if (!RECORDING_PHASE) {
3138
+ return new Call({ name: ctx.node, args }, undefined, $.endRule(), this.context);
3139
+ }
3140
+ }
3141
+ }
3142
+ ]);
3143
+ };
3144
+ }
3145
+ // export function accessors(this: P, T: TokenMap) {
3146
+ // const $ = this;
3147
+ // let keyAlt = [
3148
+ // { ALT: () => $.CONSUME(T.NestedReference) },
3149
+ // { ALT: () => $.CONSUME(T.AtKeyword) },
3150
+ // { ALT: () => $.CONSUME(T.PropertyReference) },
3151
+ // { ALT: () => $.CONSUME(T.InterpolatedIdent) },
3152
+ // { ALT: () => $.CONSUME(T.Ident) }
3153
+ // ];
3154
+ // /** The node passed in is what we're looking up on */
3155
+ // return (ctx: RuleContext = {}) => {
3156
+ // let nodeContext = ctx.node! as Reference;
3157
+ // let RECORDING_PHASE = $.RECORDING_PHASE;
3158
+ // $.startRule();
3159
+ // let keyToken: IToken | undefined;
3160
+ // let key: string | number | Reference | Interpolated;
3161
+ // let returnNode: Node;
3162
+ // $.CONSUME(T.LSquare);
3163
+ // $.OPTION(() => keyToken = $.OR(keyAlt));
3164
+ // $.CONSUME(T.RSquare);
3165
+ // if (!RECORDING_PHASE) {
3166
+ // const location = $.endRule();
3167
+ // if (keyToken) {
3168
+ // let tokenStr = keyToken.image;
3169
+ // let type: 'variable' | 'property' = tokenStr.startsWith('@') ? 'variable' : 'property';
3170
+ // // Handle all token types consistently
3171
+ // if (keyToken.tokenType === T.NestedReference) {
3172
+ // // For NestedReference, add $ prefix if not present
3173
+ // let tokenStr = keyToken.image;
3174
+ // if (!tokenStr.startsWith('$') && !tokenStr.startsWith('@')) {
3175
+ // tokenStr = '$' + tokenStr;
3176
+ // }
3177
+ // }
3178
+ // let result = getInterpolatedOrString(tokenStr, $.getLocationInfo(keyToken), this.context);
3179
+ // returnNode = new Reference({ target: nodeContext, key: result }, { type }, location, this.context);
3180
+ // } else {
3181
+ // key = -1;
3182
+ // returnNode = new Reference({ target: nodeContext, key }, { type: 'index' }, location, this.context);
3183
+ // }
3184
+ // }
3185
+ // /**
3186
+ // * Allows chaining of lookups / calls
3187
+ // * @note - In Less, an additional call or accessor implies
3188
+ // * that the previous accessor is a mixin call, therefore
3189
+ // * it should be returned as a Call node.
3190
+ // */
3191
+ // $.OPTION2(() => {
3192
+ // $.OR2([
3193
+ // {
3194
+ // ALT: () => {
3195
+ // let args = $.SUBRULE($.mixinArgs, { ARGS: [ctx] });
3196
+ // if (!RECORDING_PHASE) {
3197
+ // let [startOffset, startLine, startColumn] = returnNode.location;
3198
+ // let { endOffset, endLine, endColumn } = $.LA(0);
3199
+ // returnNode = new Call({ name: returnNode, args }, undefined, [startOffset!, startLine!, startColumn!, endOffset!, endLine!, endColumn!], this.context);
3200
+ // }
3201
+ // }
3202
+ // },
3203
+ // {
3204
+ // ALT: () => {
3205
+ // returnNode = $.SUBRULE($.inlineMixinCall, { ARGS: [{ ...ctx, node: returnNode }] });
3206
+ // return returnNode;
3207
+ // }
3208
+ // },
3209
+ // {
3210
+ // ALT: () => {
3211
+ // returnNode = $.SUBRULE($.accessors, { ARGS: [{ ...ctx, node: returnNode }] });
3212
+ // return returnNode;
3213
+ // }
3214
+ // }
3215
+ // ]);
3216
+ // });
3217
+ // return returnNode!;
3218
+ // };
3219
+ // }
3220
+ /**
3221
+ * @see https://lesscss.org/features/#mixins-feature-mixins-parametric-feature
3222
+ *
3223
+ * This rule is recursive to allow chevrotain-allstar (hopefully) to lookahead
3224
+ * and find semi-colon separators vs. commas.
3225
+ */
3226
+ export function mixinArgList(T) {
3227
+ const $ = this;
3228
+ return (ctx = {}) => {
3229
+ let RECORDING_PHASE = $.RECORDING_PHASE;
3230
+ $.startRule();
3231
+ let node = $.SUBRULE($.mixinArg, { ARGS: [ctx] });
3232
+ let commaNodes;
3233
+ let semiNodes;
3234
+ if (!RECORDING_PHASE) {
3235
+ commaNodes = [$.wrap(node, true)];
3236
+ semiNodes = [];
3237
+ }
3238
+ let isSemiList = false;
3239
+ let moreArgs = true;
3240
+ $.MANY({
3241
+ GATE: () => moreArgs,
3242
+ DEF: () => {
3243
+ $.OR([
3244
+ {
3245
+ GATE: () => !isSemiList,
3246
+ ALT: () => {
3247
+ $.CONSUME(T.Comma);
3248
+ let node = $.SUBRULE2($.mixinArg, { ARGS: [ctx] });
3249
+ if (!RECORDING_PHASE) {
3250
+ commaNodes.push($.wrap(node, true));
3251
+ }
3252
+ }
3253
+ },
3254
+ {
3255
+ ALT: () => {
3256
+ isSemiList = true;
3257
+ let semi = $.CONSUME(T.Semi);
3258
+ if (!RECORDING_PHASE) {
3259
+ /**
3260
+ * Aggregate the previous set of comma-nodes
3261
+ */
3262
+ if (commaNodes) {
3263
+ if (commaNodes.length > 1) {
3264
+ let [first, ...rest] = commaNodes;
3265
+ let hasDeclarations = false;
3266
+ if (first instanceof VarDeclaration) {
3267
+ const nodes = [first.value.value, ...rest];
3268
+ /**
3269
+ * If we still have declarations, we need to push an error.
3270
+ */
3271
+ hasDeclarations = rest.some(n => n instanceof VarDeclaration);
3272
+ first.value.value = new List(nodes, undefined, $.getLocationFromNodes(nodes), this.context);
3273
+ semiNodes.push(first);
3274
+ }
3275
+ else {
3276
+ hasDeclarations = commaNodes.some(n => n instanceof VarDeclaration);
3277
+ let commaList = new List(commaNodes, undefined, $.getLocationFromNodes(commaNodes), this.context);
3278
+ semiNodes.push(commaList);
3279
+ }
3280
+ if (hasDeclarations) {
3281
+ let indexOfSemi = $.originalInput.indexOf(semi);
3282
+ let previousToken = $.originalInput[indexOfSemi - 1];
3283
+ $._errors.push(new NoViableAltException('Cannot mix ; and , as delimiter types', semi, previousToken));
3284
+ }
3285
+ }
3286
+ else {
3287
+ semiNodes.push(commaNodes[0]);
3288
+ }
3289
+ commaNodes = undefined;
3290
+ }
3291
+ }
3292
+ $.OR2([
3293
+ {
3294
+ GATE: () => $.LA(1).tokenType !== T.RParen,
3295
+ ALT: () => {
3296
+ const prevAllow = ctx.allowComma;
3297
+ ctx.allowComma = true;
3298
+ node = $.SUBRULE3($.mixinArg, { ARGS: [ctx] });
3299
+ ctx.allowComma = prevAllow;
3300
+ if (!RECORDING_PHASE) {
3301
+ semiNodes.push($.wrap(node, true));
3302
+ }
3303
+ }
3304
+ },
3305
+ {
3306
+ ALT: () => {
3307
+ moreArgs = false;
3308
+ EMPTY_ALT();
3309
+ }
3310
+ }
3311
+ ]);
3312
+ }
3313
+ }
3314
+ ]);
3315
+ }
3316
+ });
3317
+ if (!RECORDING_PHASE) {
3318
+ let location = $.endRule();
3319
+ let nodes = isSemiList ? semiNodes : commaNodes;
3320
+ let sep = isSemiList ? ';' : ',';
3321
+ return $.wrap(new List(nodes, { sep }, location, this.context), 'both');
3322
+ }
3323
+ };
3324
+ }
3325
+ /**
3326
+ * Less is more lenient about at-keywords. See lessTokens.ts for more details.
3327
+ */
3328
+ export function varName(T) {
3329
+ const $ = this;
3330
+ // AtKeywordLessExtension is categorized as AtName in lessTokens.ts, so consuming
3331
+ // AtName alone preserves behavior while avoiding OR ambiguity warnings.
3332
+ return () => $.CONSUME(T.AtName);
3333
+ }
3334
+ /**
3335
+ * Originally, we were creating alternatives for mixin calls and mixin definitions
3336
+ * that could mostly overlap, which led to longer parsing. Instead, we parse
3337
+ * as if it could be either, and then we disambiguate at the end.
3338
+ */
3339
+ export function mixinArg(T) {
3340
+ const $ = this;
3341
+ return (ctx = {}) => {
3342
+ let RECORDING_PHASE = $.RECORDING_PHASE;
3343
+ let firstToken = $.LA(1);
3344
+ let atStart = (firstToken.tokenType === T.AtKeyword
3345
+ || firstToken.tokenType === T.AtKeywordLessExtension);
3346
+ let isDeclaration = atStart && $.LA(2).tokenType === T.Colon;
3347
+ return $.OR([
3348
+ {
3349
+ GATE: () => !isDeclaration && atStart && $.LA(2).tokenType === T.Ellipsis,
3350
+ ALT: () => {
3351
+ $.startRule();
3352
+ let name = $.SUBRULE2($.varName, { ARGS: [ctx] });
3353
+ let ellipsis;
3354
+ /**
3355
+ * Mixin definitions can have a spread parameter, which
3356
+ * means it will match a variable number of elements
3357
+ * at the end.
3358
+ *
3359
+ * However, mixin calls can have a spread argument,
3360
+ * which means it will expand a variable representing
3361
+ * a list, which, to my knowledge, is an undocumented
3362
+ * feature of Less (and only exists in mixin calls?)
3363
+ *
3364
+ * @todo - Intuitively, shouldn't this be available
3365
+ * elsewhere in the language? Or would there be no
3366
+ * reason?
3367
+ */
3368
+ $.OPTION(() => ellipsis = $.CONSUME(T.Ellipsis));
3369
+ if (!RECORDING_PHASE) {
3370
+ let varName = name.image.slice(1);
3371
+ if (ellipsis) {
3372
+ // For rest parameters, use string which can be converted to Reference later if needed
3373
+ return new Rest(varName, undefined, $.endRule(), this.context);
3374
+ }
3375
+ else {
3376
+ return new Any(varName, { role: 'name' }, $.endRule(), this.context);
3377
+ }
3378
+ }
3379
+ }
3380
+ },
3381
+ {
3382
+ GATE: () => !isDeclaration && !atStart,
3383
+ ALT: () => {
3384
+ return $.SUBRULE($.callArgument, { ARGS: [ctx] });
3385
+ }
3386
+ },
3387
+ {
3388
+ GATE: () => !isDeclaration && atStart && $.LA(2).tokenType !== T.Ellipsis && $.LA(2).tokenType !== T.RParen && $.LA(2).tokenType !== T.Comma && $.LA(2).tokenType !== T.Semi,
3389
+ ALT: () => {
3390
+ return $.SUBRULE3($.callArgument, { ARGS: [ctx] });
3391
+ }
3392
+ },
3393
+ {
3394
+ GATE: () => !isDeclaration && atStart && $.LA(2).tokenType !== T.Ellipsis && ($.LA(2).tokenType === T.RParen || $.LA(2).tokenType === T.Comma || $.LA(2).tokenType === T.Semi),
3395
+ ALT: () => {
3396
+ $.startRule();
3397
+ let name = $.SUBRULE3($.varName, { ARGS: [ctx] });
3398
+ if (!RECORDING_PHASE) {
3399
+ let varName = name.image.slice(1);
3400
+ return new Any(varName, { role: 'name' }, $.endRule(), this.context);
3401
+ }
3402
+ }
3403
+ },
3404
+ {
3405
+ GATE: () => isDeclaration,
3406
+ ALT: () => {
3407
+ $.startRule();
3408
+ let name = $.SUBRULE4($.varName, { ARGS: [ctx] });
3409
+ $.CONSUME(T.Colon);
3410
+ if (!RECORDING_PHASE && typeof name.image === 'string' && name.image.startsWith('@')) {
3411
+ }
3412
+ /** Default value */
3413
+ let value = $.SUBRULE2($.callArgument, { ARGS: [{ ...ctx, allowComma: false, detachedRulesetUsage: 'default-param' }] });
3414
+ if (!RECORDING_PHASE) {
3415
+ let location = $.endRule();
3416
+ return new VarDeclaration({
3417
+ name: new Any(name.image.slice(1), { role: 'property' }, $.getLocationInfo(name), this.context),
3418
+ value
3419
+ }, { paramVar: true }, location, this.context);
3420
+ }
3421
+ }
3422
+ },
3423
+ {
3424
+ ALT: () => {
3425
+ let ellipsis = $.CONSUME2(T.Ellipsis);
3426
+ return new Rest(undefined, undefined, $.getLocationInfo(ellipsis), this.context);
3427
+ }
3428
+ }
3429
+ ]);
3430
+ };
3431
+ }
3432
+ export function callArgument(T) {
3433
+ const $ = this;
3434
+ return (ctx = {}) => {
3435
+ return $.OR([
3436
+ {
3437
+ GATE: () => $.LA(1).tokenType === T.AnonMixinStart || $.LA(1).tokenType === T.LCurly,
3438
+ ALT: () => $.SUBRULE($.anonymousMixinDefinition, { ARGS: [ctx] })
3439
+ },
3440
+ {
3441
+ GATE: () => !ctx.allowComma,
3442
+ ALT: () => $.SUBRULE($.valueSequence, { ARGS: [ctx] })
3443
+ },
3444
+ {
3445
+ GATE: () => !!ctx.allowComma,
3446
+ ALT: () => $.SUBRULE($.valueList, { ARGS: [ctx] })
3447
+ }
3448
+ ]);
3449
+ };
3450
+ }
3451
+ /**
3452
+ * Override unknownAtRule to handle @-export for stylesheet forwarding.
3453
+ * @-export is like @-compose but with forward semantics and no `with` support.
3454
+ */
3455
+ export function unknownAtRule(T) {
3456
+ const $ = this;
3457
+ const baseUnknown = cssProductions.unknownAtRule.call(this, T);
3458
+ return (ctx = {}) => {
3459
+ const img = $.LA(1).image;
3460
+ if (img === '@-export') {
3461
+ return $.SUBRULE($.exportAtRule, { ARGS: [ctx] });
3462
+ }
3463
+ return baseUnknown(ctx);
3464
+ };
3465
+ }
3466
+ /**
3467
+ * Parse @-export './foo.jess' [as <namespace>]
3468
+ *
3469
+ * Creates a StyleImport with forward semantics (members not visible locally but transitive).
3470
+ * Does NOT support `with` (unlike @-compose).
3471
+ * Participates in evaldTrees caching like @-compose.
3472
+ */
3473
+ export function exportAtRule(T) {
3474
+ const $ = this;
3475
+ return (ctx = {}) => {
3476
+ const RECORDING_PHASE = $.RECORDING_PHASE;
3477
+ $.startRule();
3478
+ $.CONSUME(T.AtKeyword); // '@-export'
3479
+ // Parse the path (string or url)
3480
+ const pathNode = $.OR([
3481
+ { ALT: () => $.SUBRULE($.urlFunction, { ARGS: [ctx] }) },
3482
+ { ALT: () => $.SUBRULE($.string, { ARGS: [ctx] }) }
3483
+ ]);
3484
+ // Optional "as <namespace>"
3485
+ let namespace;
3486
+ $.OPTION({
3487
+ GATE: () => {
3488
+ const la = $.LA(1);
3489
+ return (la.tokenType === T.PlainIdent || la.tokenType === T.Ident) && la.image === 'as';
3490
+ },
3491
+ DEF: () => {
3492
+ // Consume "as"
3493
+ if ($.LA(1).tokenType === T.Ident) {
3494
+ $.CONSUME(T.Ident);
3495
+ }
3496
+ else {
3497
+ $.CONSUME(T.PlainIdent);
3498
+ }
3499
+ // Consume namespace identifier
3500
+ const nsTok = ($.LA(1).tokenType === T.Ident)
3501
+ ? $.CONSUME2(T.Ident)
3502
+ : $.CONSUME2(T.PlainIdent);
3503
+ if (!RECORDING_PHASE) {
3504
+ namespace = nsTok.image;
3505
+ }
3506
+ }
3507
+ });
3508
+ $.CONSUME(T.Semi);
3509
+ if (!RECORDING_PHASE) {
3510
+ const loc = $.endRule();
3511
+ return new StyleImport({ path: pathNode }, {
3512
+ type: 'compose',
3513
+ namespace,
3514
+ importOptions: {
3515
+ forward: true
3516
+ }
3517
+ }, loc, $.context);
3518
+ }
3519
+ };
3520
+ }
3521
+ //# sourceMappingURL=productions.js.map