@jesscss/less-parser 2.0.0-alpha.8 → 2.0.0-alpha.9

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 (58) hide show
  1. package/README.md +27 -10
  2. package/lib/ast/grammar.d.ts +2 -0
  3. package/lib/ast/grammar.d.ts.map +1 -0
  4. package/lib/grammar.cjs +39855 -31054
  5. package/lib/grammar.d.ts.map +1 -1
  6. package/lib/grammar.js +39855 -31054
  7. package/lib/index.cjs +49609 -15
  8. package/lib/index.d.ts +4 -9
  9. package/lib/index.d.ts.map +1 -1
  10. package/lib/index.js +49607 -5
  11. package/lib/parse-error.d.ts +15 -0
  12. package/lib/parse-error.d.ts.map +1 -0
  13. package/package.json +9 -24
  14. package/lib/builders.d.ts +0 -381
  15. package/lib/builders.d.ts.map +0 -1
  16. package/lib/functional-parser.cjs +0 -2654
  17. package/lib/functional-parser.d.ts +0 -18
  18. package/lib/functional-parser.d.ts.map +0 -1
  19. package/lib/functional-parser.js +0 -2589
  20. package/lib/jess.cjs +0 -3968
  21. package/lib/jess.d.ts +0 -7
  22. package/lib/jess.d.ts.map +0 -1
  23. package/lib/jess.js +0 -3962
  24. package/lib/lessParser.d.ts +0 -38
  25. package/lib/lessParser.d.ts.map +0 -1
  26. package/lib/lessRecursiveParser.d.ts +0 -99
  27. package/lib/lessRecursiveParser.d.ts.map +0 -1
  28. package/lib/lessTokens.d.ts +0 -23
  29. package/lib/lessTokens.d.ts.map +0 -1
  30. package/lib/productions/guards.d.ts +0 -113
  31. package/lib/productions/guards.d.ts.map +0 -1
  32. package/lib/productions/index.d.ts +0 -5
  33. package/lib/productions/index.d.ts.map +0 -1
  34. package/lib/productions/root.d.ts +0 -38
  35. package/lib/productions/root.d.ts.map +0 -1
  36. package/lib/productions/selectors.d.ts +0 -41
  37. package/lib/productions/selectors.d.ts.map +0 -1
  38. package/lib/productions/values.d.ts +0 -35
  39. package/lib/productions/values.d.ts.map +0 -1
  40. package/lib/utils.d.ts +0 -9
  41. package/lib/utils.d.ts.map +0 -1
  42. package/src/__tests__/debug-log.ts +0 -35
  43. package/src/__tests__/wall5-parse.test.ts +0 -67
  44. package/src/builders.ts +0 -3190
  45. package/src/cst.ts +0 -25
  46. package/src/functional-parser.ts +0 -162
  47. package/src/grammar.ts +0 -870
  48. package/src/index.ts +0 -19
  49. package/src/jess.ts +0 -6
  50. package/src/lessParser.ts +0 -120
  51. package/src/lessRecursiveParser.ts +0 -279
  52. package/src/lessTokens.ts +0 -350
  53. package/src/productions/guards.ts +0 -1066
  54. package/src/productions/index.ts +0 -29
  55. package/src/productions/root.ts +0 -1613
  56. package/src/productions/selectors.ts +0 -1309
  57. package/src/productions/values.ts +0 -1449
  58. package/src/utils.ts +0 -178
@@ -1,1066 +0,0 @@
1
- // @ts-nocheck — Retired Chevrotain parser. Uses the legacy 6-tuple `.location`
2
- // shape removed from Node in the provenance-side-table refactor; the functional
3
- // Parséman grammar (grammar-rules.ts + builders.ts) is the maintained parser.
4
- // Not type-checked.
5
- import type { RuleContext } from '../lessRecursiveParser.js';
6
- import type { TokenMap } from '../lessRecursiveParser.js';
7
- import type { IToken } from 'chevrotain';
8
- import { NoViableAltException } from 'chevrotain';
9
- import { productions as cssProductions } from '@jesscss/css-parser/jess';
10
- import {
11
- type LocationInfo,
12
- Node,
13
- Any,
14
- AtRuleStatement,
15
- Condition,
16
- type ConditionOperator,
17
- DefaultGuard,
18
- Paren,
19
- List,
20
- Sequence,
21
- Call,
22
- Reference,
23
- Quoted,
24
- Rest,
25
- Nil,
26
- VarDeclaration,
27
- JsImport,
28
- StyleImport,
29
- type Url,
30
- isNode,
31
- N
32
- } from '@jesscss/core';
33
- import { getInterpolatedNode, getInterpolatedOrString } from '../utils.js';
34
-
35
- /** Use `any` for `this` to avoid structural incompatibility between LessRecursiveParser and CssRecursiveParser */
36
- type P = any;
37
- type ProductionRule = (...args: any[]) => any;
38
-
39
- function isScriptUsePath(path: string): boolean {
40
- const filePart = path.replace(/[?#].*$/u, '');
41
- return path === '#less'
42
- || path.startsWith('#less/')
43
- || /\.(?:js|mjs|cjs|ts|mts|cts|json)$/i.test(filePart);
44
- }
45
-
46
- function defaultNamespaceFromPath(path: string): string | undefined {
47
- if (path === '#less') {
48
- return 'less';
49
- }
50
- if (path.startsWith('#less/')) {
51
- return path.split('/').filter(Boolean).pop();
52
- }
53
- const base = path.split('/').filter(Boolean).pop();
54
- if (!base) {
55
- return undefined;
56
- }
57
- const noExt = base.replace(/\.(less|css|jess|js|mjs|cjs|ts|mts|cts|json)$/i, '');
58
- return noExt || undefined;
59
- }
60
-
61
- function getParenFrames(ctx: RuleContext | undefined): boolean[] {
62
- return (ctx?.parenFrames as boolean[] | undefined) ?? [];
63
- }
64
-
65
- function isDefaultGuardCall(node: Node | undefined): node is Call {
66
- if (!node || !isNode(node, N.Call)) {
67
- return false;
68
- }
69
- const callName = node.name;
70
- const callNameStr = String(
71
- (typeof callName === 'object' && callName !== null && 'valueOf' in callName)
72
- ? callName.valueOf()
73
- : callName ?? ''
74
- );
75
- if (callNameStr === 'default' || callNameStr === '??') {
76
- return true;
77
- }
78
- if (callName instanceof Reference) {
79
- const key = callName.key;
80
- const keyStr = String(
81
- (typeof key === 'object' && key !== null && 'valueOf' in key)
82
- ? key.valueOf()
83
- : key ?? ''
84
- );
85
- return keyStr === 'default' || keyStr === '??';
86
- }
87
- return false;
88
- }
89
-
90
- // Save CSS production factory for super calls
91
- const cssUnknownAtRule = cssProductions.unknownAtRule;
92
-
93
- function isGuardComparisonToken(tt: unknown, T: TokenMap) {
94
- return tt === T.CompareOperator
95
- || tt === T.Eq
96
- || tt === T.Gt
97
- || tt === T.GtEq
98
- || tt === T.GtEqAlias
99
- || tt === T.Lt
100
- || tt === T.LtEq
101
- || tt === T.LtEqAlias;
102
- }
103
-
104
- function normalizeComparisonOperator(op: string): ConditionOperator {
105
- switch (op) {
106
- case '=>':
107
- case '>=':
108
- return '>=';
109
- case '=<':
110
- case '<=':
111
- return '<=';
112
- case '=':
113
- return '=';
114
- case '>':
115
- return '>';
116
- case '<':
117
- return '<';
118
- default:
119
- return '=';
120
- }
121
- }
122
-
123
- export function guard(this: P, T: TokenMap) {
124
- const $ = this;
125
- return (ctx: RuleContext = {}) => {
126
- $.CONSUME(T.When);
127
- return $.OR([
128
- {
129
- GATE: () => !!ctx.inValueList,
130
- ALT: () => $.SUBRULE($.comparison, { ARGS: [ctx] })
131
- },
132
- {
133
- ALT: () => {
134
- const node = $.SUBRULE($.guardOr, { ARGS: [{ ...ctx, allowComma: true }] });
135
- return node;
136
- }
137
- }
138
- ]);
139
- };
140
- }
141
-
142
- /**
143
- * 'or' expression
144
- * Allows an (outer) comma like historical media queries
145
- */
146
- export function guardOr(this: P, T: TokenMap) {
147
- const $ = this;
148
- return (ctx: RuleContext = {}) => {
149
- $.startRule();
150
-
151
- let left = $.SUBRULE($.guardAnd, { ARGS: [ctx] });
152
- let right: Node | undefined;
153
- $.MANY({
154
- GATE: () => (ctx.allowComma && $.isType(T.Comma)) || $.isType(T.Or),
155
- DEF: () => {
156
- /**
157
- * Nest expressions within expressions for correct
158
- * order of operations.
159
- */
160
- $.OR([
161
- { ALT: () => $.CONSUME(T.Comma) },
162
- { ALT: () => $.CONSUME(T.Or) }
163
- ]);
164
- right = $.SUBRULE2($.guardAnd, { ARGS: [ctx] });
165
- let location = $.endRule();
166
- $.startRule();
167
- left = new Condition(
168
- [left, 'or', right!],
169
- undefined,
170
- location,
171
- $.context
172
- );
173
- }
174
- });
175
- $.endRule();
176
- return left;
177
- };
178
- }
179
-
180
- export function guardDefault(this: P, T: TokenMap) {
181
- const $ = this;
182
- return (ctx: RuleContext = {}) => {
183
- let guard = $.OR([
184
- { ALT: () => $.CONSUME(T.DefaultGuardIdent) },
185
- { ALT: () => $.CONSUME(T.DefaultGuardFunc) }
186
- ]);
187
- if ($.RECORDING_PHASE) {
188
- return;
189
- }
190
- ctx.hasDefault = true;
191
- return new DefaultGuard(guard.image, undefined, $.getLocationInfo(guard));
192
- };
193
- }
194
-
195
- /**
196
- * 'and' and 'or' expressions
197
- *
198
- * In Media queries level 4, you cannot have
199
- * `([expr]) or ([expr]) and ([expr])` because
200
- * of evaluation order ambiguity.
201
- * However, Less allows it.
202
- */
203
- export function guardAnd(this: P, T: TokenMap): ProductionRule {
204
- const $ = this;
205
- return (ctx: RuleContext = {}) => {
206
- let left: Node;
207
- $.MANY_SEP({
208
- SEP: T.And,
209
- DEF: () => {
210
- let not: IToken | undefined;
211
- $.OPTION(() => not = $.CONSUME(T.Not));
212
- let allowComma = ctx.allowComma;
213
- ctx.allowComma = false;
214
- let right: Node;
215
- try {
216
- right = $.OR([
217
- { ALT: () => $.SUBRULE($.guardInParens, { ARGS: [ctx] }) },
218
- {
219
- GATE: () => {
220
- const tokenType = $.LA(1).tokenType;
221
- return tokenType !== T.Not
222
- && tokenType !== T.DefaultGuardFunc
223
- && tokenType !== T.DefaultGuardIdent;
224
- },
225
- ALT: () => $.SUBRULE($.expressionSum, { ARGS: [ctx] })
226
- }
227
- ]);
228
- $.OPTION2({
229
- GATE: () => isGuardComparisonToken($.LA(1).tokenType, T),
230
- DEF: () => {
231
- const op = $.CONSUME(T.CompareOperator);
232
- const compareRight = $.SUBRULE2($.expressionSum, { ARGS: [ctx] });
233
- if (!$.RECORDING_PHASE) {
234
- right = new Condition(
235
- [
236
- right,
237
- normalizeComparisonOperator(op.image),
238
- compareRight
239
- ],
240
- undefined,
241
- $.getLocationFromNodes([right, compareRight]),
242
- $.context
243
- );
244
- }
245
- }
246
- });
247
- } finally {
248
- ctx.allowComma = allowComma;
249
- }
250
- if (!$.RECORDING_PHASE) {
251
- if (isDefaultGuardCall(right!)) {
252
- ctx.hasDefault = true;
253
- const location = Array.isArray(right!.location) && right!.location.length === 6
254
- ? right!.location as LocationInfo
255
- : undefined;
256
- right = new DefaultGuard('default()', undefined, location);
257
- }
258
- if (not) {
259
- let [,,, endOffset, endLine, endColumn] = right.location!;
260
- let [startOffset, startLine, startColumn] = $.getLocationInfo(not);
261
- right = new Condition(
262
- [right],
263
- { negate: true },
264
- [startOffset!, startLine!, startColumn!, endOffset!, endLine!, endColumn!],
265
- $.context
266
- );
267
- }
268
- if (!left) {
269
- left = right;
270
- return;
271
- }
272
- left = new Condition(
273
- [left, 'and', right],
274
- undefined,
275
- $.getLocationFromNodes([left, right]),
276
- $.context
277
- );
278
- }
279
- }
280
- });
281
- return left!;
282
- };
283
- }
284
-
285
- export function guardInParens(this: P, T: TokenMap) {
286
- const $ = this;
287
- return (ctx: RuleContext) => {
288
- $.startRule();
289
- let node = $.OR([
290
- { ALT: () => $.SUBRULE($.guardDefault, { ARGS: [ctx] }) },
291
- {
292
- ALT: () => {
293
- $.CONSUME(T.LParen);
294
- let node = $.SUBRULE($.guardInner, { ARGS: [ctx] });
295
- $.CONSUME(T.RParen);
296
- return node;
297
- }
298
- }
299
- ]);
300
-
301
- if (isDefaultGuardCall(node)) {
302
- ctx.hasDefault = true;
303
- const location = Array.isArray(node.location) && node.location.length === 6
304
- ? node.location as LocationInfo
305
- : undefined;
306
- node = new DefaultGuard('default()', undefined, location);
307
- }
308
- node = node;
309
- return new Paren(node, undefined, $.endRule(), $.context);
310
- };
311
- }
312
-
313
- // The inner content of a guard inside parentheses
314
- export function guardInner(this: P, _T: TokenMap) {
315
- const $ = this;
316
- return (ctx: RuleContext = {}) => {
317
- return $.SUBRULE($.guardOr, { ARGS: [ctx] });
318
- };
319
- }
320
-
321
- export function guardWithConditionValue(this: P, T: TokenMap) {
322
- const $ = this;
323
- return (ctx: RuleContext = {}) => {
324
- if ($.isType(T.DefaultGuardIdent) || $.isType(T.DefaultGuardFunc)) {
325
- $.OR([
326
- { ALT: () => $.CONSUME(T.DefaultGuardIdent) },
327
- { ALT: () => $.CONSUME(T.DefaultGuardFunc) }
328
- ]);
329
- return;
330
- }
331
- return $.SUBRULE($.guardInParens, { ARGS: [ctx] });
332
- };
333
- }
334
-
335
- export function guardWithCondition(this: P, T: TokenMap) {
336
- const $ = this;
337
- return (ctx: RuleContext = {}) => {
338
- $.SUBRULE($.guardWithConditionValue, { ARGS: [ctx] });
339
- $.AT_LEAST_ONE(() => {
340
- $.OR([
341
- { ALT: () => $.CONSUME(T.Or) },
342
- { ALT: () => $.CONSUME(T.And) },
343
- { ALT: () => $.CONSUME(T.Comma) }
344
- ]);
345
- $.SUBRULE2($.guardWithConditionValue, { ARGS: [ctx] });
346
- });
347
- };
348
- }
349
-
350
- /**
351
- * Currently, Less only allows a single comparison expression,
352
- * unlike Media Queries Level 4, which allows a left and right
353
- * comparison.
354
- */
355
- export function comparison(this: P, T: TokenMap) {
356
- const $ = this;
357
- return (ctx: RuleContext = {}) => {
358
- let left = $.SUBRULE($.expressionSum, { ARGS: [ctx] });
359
- const op = $.CONSUME(T.CompareOperator);
360
- let right = $.SUBRULE2($.expressionSum, { ARGS: [ctx] });
361
- if (isDefaultGuardCall(right)) {
362
- ctx.hasDefault = true;
363
- const location = Array.isArray(right.location) && right.location.length === 6
364
- ? right.location as LocationInfo
365
- : undefined;
366
- right = new DefaultGuard('default()', undefined, location);
367
- }
368
- left = new Condition(
369
- [left, normalizeComparisonOperator(op.image), right],
370
- undefined,
371
- $.getLocationFromNodes([left, right]),
372
- $.context
373
- );
374
- return left;
375
- };
376
- }
377
-
378
- /**
379
- * Less (perhaps unwisely) allows bubbling of normally document-root
380
- * at-rules, so we need to override CSS here.
381
- */
382
- export function innerAtRule(this: P, _T: TokenMap) {
383
- const $ = this;
384
- return (ctx: RuleContext = {}): Node => {
385
- return $.OR([
386
- { ALT: () => $.SUBRULE($.mediaAtRule, { ARGS: [{ ...ctx, inner: true }] }) },
387
- { ALT: () => $.SUBRULE($.supportsAtRule, { ARGS: [{ ...ctx, inner: true }] }) },
388
- { ALT: () => $.SUBRULE($.layerAtRule, { ARGS: [{ ...ctx, inner: true }] }) },
389
- { ALT: () => $.SUBRULE($.containerAtRule, { ARGS: [{ ...ctx, inner: true }] }) },
390
- { ALT: () => $.SUBRULE($.keyframesAtRule, { ARGS: [{ ...ctx, inner: true }] }) },
391
- { ALT: () => $.SUBRULE($.documentAtRule, { ARGS: [{ ...ctx, inner: true }] }) },
392
- { ALT: () => $.SUBRULE($.importAtRule, { ARGS: [ctx] }) },
393
- { ALT: () => $.SUBRULE($.pageAtRule, { ARGS: [ctx] }) },
394
- { ALT: () => $.SUBRULE($.fontFaceAtRule, { ARGS: [ctx] }) },
395
- { ALT: () => $.SUBRULE($.nestedAtRule, { ARGS: [ctx] }) },
396
- { ALT: () => $.SUBRULE($.nonNestedAtRule, { ARGS: [ctx] }) },
397
- { ALT: () => $.SUBRULE($.unknownAtRule, { ARGS: [{ ...ctx, inner: true }] }) }
398
- ]);
399
- };
400
- }
401
-
402
- /**
403
- * Less override: allow variable reference as the first segment of a layer-name
404
- * CSS: <ident> ('.' <ident>)*
405
- * Less: (<var-ref> | <ident>) ('.' <ident>)*
406
- */
407
- export function layerName(this: P, T: TokenMap) {
408
- const $ = this;
409
- return (ctx: RuleContext = {}) => {
410
- const preludeCtx: RuleContext = { ...ctx, atRulePreludeBareVariableAs: 'index' };
411
- $.startRule();
412
- let RECORDING_PHASE = $.RECORDING_PHASE;
413
- let nodes: Node[];
414
- if (!RECORDING_PHASE) {
415
- nodes = [];
416
- }
417
-
418
- // First segment: variable reference or plain ident
419
- const first = $.OR([
420
- { ALT: () => $.SUBRULE($.valueReference, { ARGS: [preludeCtx] }) },
421
- {
422
- GATE: () => $.isType(T.Ident),
423
- ALT: () => $.CONSUME(T.Ident)
424
- }
425
- ]);
426
-
427
- if (!RECORDING_PHASE) {
428
- if (first instanceof Node) {
429
- nodes!.push(first);
430
- } else {
431
- nodes!.push($.processValueToken(first));
432
- }
433
- }
434
-
435
- // Remaining segments: dot + ident (same as CSS)
436
- $.MANY({
437
- GATE: $.noSep.bind($),
438
- DEF: () => {
439
- const seg = $.CONSUME(T.DotName);
440
- if (!RECORDING_PHASE) {
441
- nodes!.push($.processValueToken(seg));
442
- }
443
- }
444
- });
445
-
446
- if (RECORDING_PHASE) {
447
- return;
448
- }
449
- const loc = $.endRule();
450
- return new Sequence(nodes!, undefined, loc, $.context);
451
- };
452
- }
453
-
454
- /**
455
- * Less override: allow variable reference for @keyframes name
456
- * CSS: Ident | String
457
- * Less: valueReference | Ident | String
458
- */
459
- export function keyframesName(this: P, T: TokenMap): ProductionRule {
460
- const $ = this;
461
- return (ctx: RuleContext = {}) => {
462
- const preludeCtx: RuleContext = { ...ctx, atRulePreludeBareVariableAs: 'index' };
463
- let node: Node | undefined;
464
- $.OR([
465
- { ALT: () => node = $.SUBRULE($.valueReference, { ARGS: [preludeCtx] }) },
466
- {
467
- GATE: () => $.isType(T.Ident) && !$.isType(T.InterpolatedIdent),
468
- ALT: () => {
469
- const tok = $.CONSUME(T.Ident);
470
- node = $.processValueToken(tok);
471
- } },
472
- { ALT: () => node = $.SUBRULE($.string, { ARGS: [] }) }
473
- ]);
474
- return node!;
475
- };
476
- }
477
-
478
- /**
479
- * One of the rare rules that returns a token, because
480
- * other rules will transform it differently.
481
- */
482
- export function mixinName(this: P, T: TokenMap): ProductionRule {
483
- const $ = this;
484
- return (ctx: RuleContext = {}) => {
485
- /** e.g. .mixin, #mixin */
486
- let name = $.OR([
487
- { ALT: () => $.CONSUME(T.HashName) },
488
- { ALT: () => $.CONSUME(T.ColorIdentStart) },
489
- { ALT: () => $.CONSUME(T.DotName) },
490
- { ALT: () => $.CONSUME(T.InterpolatedIdent) },
491
- { ALT: () => $.CONSUME(T.InterpolatedSelector) }
492
- ]);
493
- if ($.RECORDING_PHASE) {
494
- return;
495
- }
496
- const asReference = ctx.asReference;
497
- let nameNode: Node;
498
- let nameValue = name.image;
499
- let location = $.getLocationInfo(name);
500
- if (nameValue.includes('@') || nameValue.includes('$')) {
501
- const interpolated = getInterpolatedNode(nameValue, location, $.context);
502
- nameNode = interpolated;
503
- if (asReference) {
504
- if (isNode(ctx.node, N.Reference) && ctx.node.options.type === 'mixin-ruleset') {
505
- nameNode = new Reference({ target: ctx.node, key: interpolated }, { type: 'mixin-ruleset', role: 'name' }, location, $.context);
506
- } else {
507
- const target = ctx.node as Node | undefined;
508
- nameNode = new Reference({ target: target instanceof Call ? target : target instanceof Reference ? target : undefined, key: interpolated }, { type: 'mixin-ruleset', role: 'name' }, location, $.context);
509
- }
510
- }
511
- } else {
512
- if (asReference) {
513
- // If target is a Reference with matching type, merge keys instead of nesting
514
- if (isNode(ctx.node, N.Reference) && ctx.node.options.type === 'mixin-ruleset') {
515
- const existingKey = ctx.node.key;
516
- const existingRawKey = ctx.node.rawKey;
517
- let mergedKeys: string[];
518
- if (Array.isArray(existingKey)) {
519
- mergedKeys = [...existingKey];
520
- } else {
521
- mergedKeys = [String(existingKey)];
522
- }
523
- mergedKeys.push(nameValue);
524
- const rawPrefix = Array.isArray(existingRawKey)
525
- ? existingRawKey.join(' > ')
526
- : typeof existingRawKey === 'string'
527
- ? existingRawKey
528
- : Array.isArray(existingKey)
529
- ? existingKey.join(' > ')
530
- : String(existingKey);
531
- nameNode = new Reference(
532
- {
533
- key: mergedKeys.length === 1 ? mergedKeys[0]! : mergedKeys,
534
- rawKey: `${rawPrefix} > ${nameValue}`
535
- },
536
- { type: 'mixin-ruleset', role: 'name' },
537
- location,
538
- $.context
539
- );
540
- } else {
541
- const target = ctx.node as Node | undefined;
542
- nameNode = new Reference({ target: target instanceof Call ? target : target instanceof Reference ? target : undefined, key: nameValue }, { type: 'mixin-ruleset', role: 'name' }, location, $.context);
543
- }
544
- } else {
545
- nameNode = new Any(nameValue, { role: 'name' }, $.getLocationInfo(name), $.context);
546
- }
547
- }
548
- return nameNode;
549
- };
550
- }
551
-
552
- /**
553
- * Used within a value. These can be
554
- * chained more recursively, unlike
555
- * Less 1.x-4.x
556
- * e.g. .mixin1() > .mixin2[@val1].ns() > .sub-mixin[@val2]
557
- *
558
- * This production intelligently decides whether to produce a Call or Reference
559
- * based on whether there are parentheses at the end:
560
- * - foo: #id; // Reference
561
- * - foo: .class; // Reference
562
- * - foo: #id > .scoped; // Reference
563
- * - foo: #id > .scoped(); // Call
564
- * - foo: #id[]; // Reference with accessor
565
- * - foo: #id > .scoped[foo]; // Reference with accessor
566
- * - foo: #id > .scoped[@ref](); // Call with accessor
567
- */
568
- export function mixinReference(this: P, T: TokenMap) {
569
- const $ = this;
570
- return (ctx: RuleContext = {}) => {
571
- let leftNode = $.SUBRULE($.mixinName, { ARGS: [{ ...ctx, asReference: true }] });
572
-
573
- $.MANY({
574
- GATE: () => {
575
- let next = $.LA(1).tokenType;
576
- return $.noSep() && (next === T.LParen || next === T.LSquare);
577
- },
578
- DEF: () => {
579
- leftNode = $.SUBRULE($.lookupOrCall, { ARGS: [{ ...ctx, node: leftNode }] });
580
- }
581
- });
582
-
583
- $.OPTION(() => {
584
- $.OPTION2(() => $.CONSUME(T.Gt));
585
- leftNode = $.SUBRULE($.mixinReference, { ARGS: [{ ...ctx, node: leftNode }] });
586
- });
587
-
588
- return leftNode;
589
- };
590
- }
591
-
592
- export function mixinArgs(this: P, T: TokenMap): ProductionRule {
593
- const $ = this;
594
- return (ctx: RuleContext = {}) => {
595
- let args: List | undefined;
596
- // Check for whitespace before the opening paren (before consuming)
597
- const hasWhitespace = !$.noSep();
598
- const openingParenToken = hasWhitespace ? $.LA(1) : undefined;
599
-
600
- $.CONSUME(T.LParen);
601
- // Clear ctx.node when parsing arguments - arguments should start fresh, not inherit the parent node
602
- // Calls intentionally push a `false` paren frame (matches `Call.evalNode`)
603
- const argCtx: RuleContext = {
604
- ...ctx,
605
- node: undefined,
606
- allowComma: false,
607
- parenFrames: [...getParenFrames(ctx), false],
608
- detachedRulesetUsage: ctx.isDefinition ? 'default-param' : 'mixin-arg'
609
- };
610
- if (!$.isType(T.RParen)) {
611
- args = $.SUBRULE($.mixinArgList, { ARGS: [argCtx] });
612
- }
613
- $.CONSUME(T.RParen);
614
-
615
- // Check for whitespace warning AFTER consuming closing paren
616
- // Now we can check what comes next to determine if it's actually a definition
617
- if (hasWhitespace && openingParenToken) {
618
- const nextAfterParens = $.LA(1).tokenType;
619
- const isActuallyDefinition = nextAfterParens === T.LCurly || nextAfterParens === T.When;
620
- // Only warn if it's NOT a definition (i.e., it's a mixin call)
621
- if (!isActuallyDefinition) {
622
- $.warnDeprecation(
623
- 'Whitespace between a mixin name and parentheses for a mixin call is deprecated',
624
- openingParenToken,
625
- 'mixin-call-whitespace'
626
- );
627
- }
628
- }
629
-
630
- return args;
631
- };
632
- }
633
-
634
- export function lookupOrCall(this: P, T: TokenMap) {
635
- const $ = this;
636
- return (ctx: RuleContext = {}) => {
637
- $.startRule();
638
- return $.OR([
639
- {
640
- ALT: () => {
641
- let keyToken: IToken | undefined;
642
- $.CONSUME(T.LSquare);
643
- $.OPTION(() => keyToken = $.OR2([
644
- { ALT: () => $.CONSUME(T.NestedReference) },
645
- { ALT: () => $.CONSUME(T.AtKeyword) },
646
- { ALT: () => $.CONSUME(T.PropertyReference) },
647
- { ALT: () => $.CONSUME(T.InterpolatedIdent) },
648
- {
649
- GATE: () => !$.isType(T.NestedReference)
650
- && !$.isType(T.AtKeyword)
651
- && !$.isType(T.PropertyReference)
652
- && !$.isType(T.InterpolatedIdent)
653
- && $.isType(T.Ident),
654
- ALT: () => $.CONSUME(T.Ident)
655
- }
656
- ]));
657
- $.CONSUME(T.RSquare);
658
- if ($.RECORDING_PHASE) {
659
- return;
660
- }
661
- let ref: Reference;
662
- const targetNode = ctx.node;
663
- const target = targetNode instanceof Call ? targetNode : targetNode instanceof Reference ? targetNode : undefined;
664
- if (keyToken) {
665
- let tokenStr = keyToken.image;
666
- let type: 'variable' | 'index' = tokenStr.startsWith('@') ? 'variable' : 'index';
667
- if (keyToken.tokenType === T.NestedReference) {
668
- tokenStr = keyToken.image;
669
- if (!tokenStr.startsWith('$') && !tokenStr.startsWith('@')) {
670
- tokenStr = '$' + tokenStr;
671
- }
672
- }
673
- let rawResult = getInterpolatedOrString(tokenStr, $.getLocationInfo(keyToken), $.context);
674
- let result: typeof rawResult | Quoted = rawResult;
675
- if (type === 'index') {
676
- result = new Quoted(rawResult, { quote: '\'' }, $.getLocationInfo(keyToken), $.context);
677
- }
678
-
679
- const targetType = isNode(target, N.Reference) ? target.options.type : undefined;
680
- const shouldMergeKeys = targetType === 'mixin' || targetType === 'mixin-ruleset';
681
- if (isNode(target, N.Reference) && target.options.type === type && typeof result === 'string' && shouldMergeKeys) {
682
- const existingKey = target.key;
683
- let mergedKeys: string[];
684
- if (Array.isArray(existingKey)) {
685
- mergedKeys = [...existingKey];
686
- } else {
687
- mergedKeys = [String(existingKey)];
688
- }
689
- mergedKeys.push(result);
690
- ref = new Reference(
691
- { key: mergedKeys.length === 1 ? mergedKeys[0]! : mergedKeys },
692
- { type },
693
- $.endRule(),
694
- $.context
695
- );
696
- } else {
697
- ref = new Reference({ target, key: result }, { type }, $.endRule(), $.context);
698
- }
699
- } else {
700
- ref = new Reference({ target, key: -1 }, { type: 'index' }, $.endRule(), $.context);
701
- }
702
- /** Reference targets will technically precede the reference, so we need to update the location to the target start location */
703
- if (target) {
704
- let [targetStartOffset, targetStartLine, targetStartColumn] = target.location!;
705
- ref.location.start = targetStartOffset;
706
- ref.location[1] = targetStartLine;
707
- ref.location[2] = targetStartColumn;
708
- }
709
- return ref;
710
- }
711
- },
712
- {
713
- ALT: () => {
714
- let args = $.SUBRULE($.mixinArgs, { ARGS: [ctx] });
715
- if ($.RECORDING_PHASE) {
716
- return;
717
- }
718
- return new Call({ name: ctx.node!, args }, undefined, $.endRule(), $.context);
719
- }
720
- }
721
- ]);
722
- };
723
- }
724
-
725
- /**
726
- * @see https://lesscss.org/features/#mixins-feature-mixins-parametric-feature
727
- *
728
- * This rule is recursive to allow chevrotain-allstar (hopefully) to lookahead
729
- * and find semi-colon separators vs. commas.
730
- */
731
- export function mixinArgList(this: P, T: TokenMap): ProductionRule {
732
- const $ = this;
733
- return (ctx: RuleContext = {}) => {
734
- $.startRule();
735
- const first = $.SUBRULE($.mixinArg, { ARGS: [ctx] });
736
-
737
- let commaNodes: Node[] | undefined = [first];
738
- const semiNodes: Node[] = [];
739
- let isSemiList = false;
740
-
741
- const collapseCommaNodesIntoSemiNodes = (semi: IToken) => {
742
- if (!commaNodes) {
743
- return;
744
- }
745
- if (commaNodes.length > 1) {
746
- const [head, ...rest] = commaNodes;
747
- let hasDeclarations = false;
748
- if (head instanceof VarDeclaration) {
749
- const headValue = head.value instanceof Node ? head.value : undefined;
750
- const nodes = headValue ? [headValue, ...rest] : [...rest];
751
- hasDeclarations = rest.some(n => n instanceof VarDeclaration);
752
- const value = new List(nodes, undefined, $.getLocationFromNodes(nodes), $.context);
753
- semiNodes.push(new VarDeclaration({
754
- name: head.name,
755
- value,
756
- important: head.important
757
- }, head.options, head.location, $.context));
758
- } else {
759
- hasDeclarations = commaNodes.some(n => n instanceof VarDeclaration);
760
- semiNodes.push(new List(commaNodes, undefined, $.getLocationFromNodes(commaNodes), $.context));
761
- }
762
- if (hasDeclarations) {
763
- const indexOfSemi = $.input.indexOf(semi);
764
- const previousToken = $.input[indexOfSemi - 1]!;
765
- $.SAVE_ERROR(
766
- new NoViableAltException(
767
- 'Cannot mix ; and , as delimiter types',
768
- semi,
769
- previousToken
770
- )
771
- );
772
- }
773
- } else {
774
- semiNodes.push(commaNodes[0]!);
775
- }
776
- commaNodes = undefined;
777
- };
778
-
779
- while ($.isType(T.Comma) || $.isType(T.Semi)) {
780
- if ($.isType(T.Comma)) {
781
- const comma = $.CONSUME(T.Comma);
782
- const node = $.SUBRULE2($.mixinArg, { ARGS: [ctx] });
783
- if (commaNodes) {
784
- commaNodes.push(node);
785
- } else {
786
- $.SAVE_ERROR(
787
- new NoViableAltException(
788
- 'Cannot mix ; and , as delimiter types',
789
- comma,
790
- $.LA(0)
791
- )
792
- );
793
- semiNodes.push(node);
794
- }
795
- continue;
796
- }
797
-
798
- const semi = $.CONSUME(T.Semi);
799
- isSemiList = true;
800
- collapseCommaNodesIntoSemiNodes(semi);
801
-
802
- if ($.isType(T.RParen)) {
803
- break;
804
- }
805
-
806
- const prevAllow = ctx.allowComma;
807
- ctx.allowComma = true;
808
- const node = $.SUBRULE3($.mixinArg, { ARGS: [ctx] });
809
- ctx.allowComma = prevAllow;
810
- semiNodes.push(node);
811
- }
812
-
813
- let location = $.endRule();
814
- let nodes = isSemiList ? semiNodes : commaNodes!;
815
- let sep: ';' | ',' = isSemiList ? ';' : ',';
816
- const result: List = new List(nodes, { sep }, location, $.context);
817
- return result;
818
- };
819
- }
820
-
821
- /**
822
- * Less is more lenient about at-keywords. See lessTokens.ts for more details.
823
- */
824
- export function varName(this: P, T: TokenMap) {
825
- const $ = this;
826
- return () => {
827
- // AtKeywordLessExtension is categorized as AtName in lessTokens.ts, so consuming
828
- // AtName alone preserves behavior while avoiding OR ambiguity warnings.
829
- return $.CONSUME(T.AtName);
830
- };
831
- }
832
-
833
- /**
834
- * Originally, we were creating alternatives for mixin calls and mixin definitions
835
- * that could mostly overlap, which led to longer parsing. Instead, we parse
836
- * as if it could be either, and then we disambiguate at the end.
837
- */
838
- export function mixinArg(this: P, T: TokenMap) {
839
- const $ = this;
840
- return (ctx: RuleContext = {}) => {
841
- const firstToken = $.LA(1);
842
- const atStart = $.matchToken(firstToken, T.AtName);
843
- const tt2 = $.LA(2).tokenType;
844
- const tt3 = $.LA(3).tokenType;
845
- const hasWsAfterName = tt2 === T.WS;
846
- const nextTokenType = hasWsAfterName ? tt3 : tt2;
847
-
848
- if (atStart && nextTokenType === T.Ellipsis) {
849
- $.startRule();
850
- const name = $.CONSUME(T.AtName);
851
- if (hasWsAfterName) {
852
- $.CONSUME(T.WS);
853
- }
854
- $.CONSUME(T.Ellipsis);
855
- if ($.RECORDING_PHASE) {
856
- return;
857
- }
858
- return new Rest(name.image.slice(1), undefined, $.endRule(), $.context);
859
- }
860
-
861
- if (atStart && nextTokenType === T.Colon) {
862
- $.startRule();
863
- const name = $.CONSUME2(T.AtName);
864
- if (hasWsAfterName) {
865
- $.CONSUME2(T.WS);
866
- }
867
- $.CONSUME(T.Colon);
868
- const value = $.SUBRULE3($.callArgument, { ARGS: [{ ...ctx, allowComma: !!ctx.allowComma, detachedRulesetUsage: 'default-param' }] });
869
-
870
- const location = $.endRule();
871
- if ($.RECORDING_PHASE) {
872
- return;
873
- }
874
- return new VarDeclaration({
875
- name: name.image.slice(1),
876
- value
877
- }, { paramVar: true }, location, $.context);
878
- }
879
-
880
- if (atStart && (nextTokenType === T.RParen || nextTokenType === T.Comma || nextTokenType === T.Semi)) {
881
- $.startRule();
882
- const name = $.CONSUME3(T.AtName);
883
- if ($.RECORDING_PHASE) {
884
- return;
885
- }
886
- const location = $.endRule();
887
- if (ctx.isDefinition) {
888
- return new VarDeclaration({
889
- name: name.image.slice(1),
890
- value: new Nil(undefined, undefined, location, $.context)
891
- }, { paramVar: true }, location, $.context);
892
- }
893
- return new Any(name.image.slice(1), { role: 'name' }, location, $.context);
894
- }
895
-
896
- if ($.isType(T.Ellipsis)) {
897
- const ellipsis = $.CONSUME2(T.Ellipsis);
898
- return new Rest(undefined, undefined, $.getLocationInfo(ellipsis), $.context);
899
- }
900
-
901
- return $.SUBRULE($.callArgument, { ARGS: [ctx] });
902
- };
903
- }
904
-
905
- export function callArgument(this: P, T: TokenMap) {
906
- const $ = this;
907
- return (ctx: RuleContext = {}) => {
908
- return $.OR([
909
- {
910
- GATE: () => $.isType(T.AnonMixinStart) || $.isType(T.LCurly),
911
- ALT: () => $.SUBRULE($.anonymousMixinDefinition, { ARGS: [ctx] })
912
- },
913
- {
914
- GATE: () => !ctx.allowComma,
915
- ALT: () => $.SUBRULE($.valueSequence, { ARGS: [ctx] })
916
- },
917
- {
918
- GATE: () => !!ctx.allowComma,
919
- ALT: () => $.SUBRULE($.valueList, { ARGS: [ctx] })
920
- }
921
- ]);
922
- };
923
- }
924
-
925
- /**
926
- * Override unknownAtRule to handle @-export for stylesheet forwarding.
927
- * @-export is like @-compose but with forward semantics and no `with` support.
928
- */
929
- export function unknownAtRule(this: P, T: TokenMap) {
930
- const $ = this;
931
- return (ctx: RuleContext = {}) => {
932
- const img = $.LA(1).image;
933
- if (img === '@use' || img === '@-use') {
934
- return $.SUBRULE($.useAtRule, { ARGS: [ctx] });
935
- }
936
- if (img === '@-export') {
937
- return $.SUBRULE($.exportAtRule, { ARGS: [ctx] });
938
- }
939
- return cssUnknownAtRule.call($, T)(ctx);
940
- };
941
- }
942
-
943
- /**
944
- * Parse Less v5 script-module imports.
945
- *
946
- * Stylesheet composition uses `@compose`; `@use` / `@-use` only become
947
- * JsImport nodes for script-style paths and aliases like `#less/math`.
948
- */
949
- export function useAtRule(this: P, T: TokenMap) {
950
- const $ = this;
951
- return (ctx: RuleContext = {}) => {
952
- $.startRule();
953
- const name = $.CONSUME(T.AtName); // '@use' or '@-use'
954
-
955
- const pathNode: Quoted = $.SUBRULE($.string, { ARGS: [ctx] });
956
-
957
- let namespace: string | undefined;
958
- $.OPTION({
959
- GATE: () =>
960
- $.LA(1).tokenType === T.PlainIdent
961
- && $.LA(1).image === 'as',
962
- DEF: () => {
963
- $.CONSUME(T.PlainIdent);
964
- $.OR2([
965
- { ALT: () => {
966
- namespace = $.CONSUME2(T.PlainIdent).image;
967
- } },
968
- { ALT: () => {
969
- namespace = $.CONSUME(T.Star).image;
970
- } }
971
- ]);
972
- }
973
- });
974
-
975
- $.CONSUME(T.Semi);
976
-
977
- const location = $.endRule();
978
- if ($.RECORDING_PHASE) {
979
- return;
980
- }
981
-
982
- const rawPath = pathNode.valueOf();
983
- if (isScriptUsePath(rawPath)) {
984
- return new JsImport(
985
- { path: pathNode },
986
- { namespace: namespace ?? defaultNamespaceFromPath(rawPath) },
987
- location,
988
- $.context
989
- );
990
- }
991
-
992
- const preludeNodes: Node[] = [pathNode];
993
- if (namespace) {
994
- preludeNodes.push(
995
- new Any('as', { role: 'ident' }, undefined, $.context),
996
- new Any(namespace, { role: namespace === '*' ? 'operator' : 'ident' }, undefined, $.context)
997
- );
998
- }
999
- return new AtRuleStatement(
1000
- {
1001
- name: name.image,
1002
- prelude: new Sequence(preludeNodes, undefined, $.getLocationFromNodes(preludeNodes), $.context)
1003
- },
1004
- undefined,
1005
- location,
1006
- $.context
1007
- );
1008
- };
1009
- }
1010
-
1011
- /**
1012
- * Parse @-export './foo.jess' [as <namespace>]
1013
- *
1014
- * Creates a StyleImport with forward semantics (members not visible locally but transitive).
1015
- * Does NOT support `with` (unlike @-compose).
1016
- * Participates in evaldTrees caching like @-compose.
1017
- */
1018
- export function exportAtRule(this: P, T: TokenMap) {
1019
- const $ = this;
1020
- return (ctx: RuleContext = {}) => {
1021
- $.startRule();
1022
- $.CONSUME(T.AtKeyword); // '@-export'
1023
-
1024
- // Parse the path (string or url)
1025
- const pathNode: Quoted | Url = $.OR([
1026
- { ALT: () => $.SUBRULE($.urlFunction, { ARGS: [ctx] }) },
1027
- { ALT: () => $.SUBRULE($.string, { ARGS: [ctx] }) }
1028
- ]);
1029
-
1030
- // Optional "as <namespace>"
1031
- let namespace: string | undefined;
1032
- $.OPTION(() => {
1033
- const la = $.LA(1);
1034
- if (!((la.tokenType === T.PlainIdent || la.tokenType === T.Ident) && la.image === 'as')) {
1035
- return;
1036
- }
1037
- // Consume "as"
1038
- if ($.isType(T.Ident)) {
1039
- $.CONSUME(T.Ident);
1040
- } else {
1041
- $.CONSUME(T.PlainIdent);
1042
- }
1043
- // Consume namespace identifier
1044
- const nsTok: IToken = $.isType(T.Ident)
1045
- ? $.CONSUME(T.Ident)
1046
- : $.CONSUME(T.PlainIdent);
1047
- namespace = nsTok.image;
1048
- });
1049
-
1050
- $.CONSUME(T.Semi);
1051
-
1052
- const loc = $.endRule();
1053
- return new StyleImport(
1054
- { path: pathNode },
1055
- {
1056
- type: 'compose',
1057
- namespace,
1058
- importOptions: {
1059
- forward: true
1060
- }
1061
- },
1062
- loc,
1063
- $.context
1064
- );
1065
- };
1066
- }