less 4.6.7 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Less - Leaner CSS v4.6.7
2
+ * Less - Leaner CSS v4.8.0
3
3
  * http://lesscss.org
4
4
  *
5
5
  * Copyright (c) 2009-2026, Alexis Sellier <self@cloudhead.net>
@@ -31,6 +31,7 @@ class SourceMapGeneratorFallback {
31
31
  return null;
32
32
  }
33
33
  }
34
+
34
35
  var environment = {
35
36
  encodeBase64: function encodeBase64(str) {
36
37
  // Avoid Buffer constructor on newer versions of Node.js.
@@ -40,9 +41,9 @@ var environment = {
40
41
  mimeLookup: function (filename) {
41
42
  try {
42
43
  const mimeModule = require$6('mime');
43
- return mimeModule ? mimeModule.lookup(filename) : "application/octet-stream";
44
+ return mimeModule ? mimeModule.lookup(filename) : 'application/octet-stream';
44
45
  } catch (e) {
45
- return "application/octet-stream";
46
+ return 'application/octet-stream';
46
47
  }
47
48
  },
48
49
  charsetLookup: function (mime) {
@@ -4267,12 +4268,23 @@ var getParserInput = () => {
4267
4268
  /**
4268
4269
  * Permissive parsing. Ignores everything except matching {} [] () and quotes
4269
4270
  * until matching token (outside of blocks)
4270
- */
4271
- parserInput.$parseUntil = tok => {
4271
+ *
4272
+ * @param {string|RegExp} tok - stop token
4273
+ * @param {boolean} [detectBareVar] - when set, also record the position of the
4274
+ * first bare `@variable` reference (not `@{interpolation}`) that appears at
4275
+ * PAREN depth 0 — i.e. a structural reference, not a declaration value inside
4276
+ * `(...)`. Reuses this single pass (which already skips strings/comments) so
4277
+ * callers don't re-scan the text. Exposed as `.bareVarIndex` on the returned
4278
+ * group array (or null). `[...]`/`{...}` do NOT shield a reference — only
4279
+ * `(...)` (a declaration-value group) does.
4280
+ */
4281
+ parserInput.$parseUntil = (tok, detectBareVar) => {
4272
4282
  let quote = '';
4273
4283
  let returnVal = null;
4274
4284
  let inComment = false;
4275
4285
  let blockDepth = 0;
4286
+ let parenDepth = 0;
4287
+ let bareVarIndex = null;
4276
4288
  const blockStack = [];
4277
4289
  const parseGroups = [];
4278
4290
  const length = input.length;
@@ -4312,6 +4324,12 @@ var getParserInput = () => {
4312
4324
  i++;
4313
4325
  continue;
4314
4326
  }
4327
+ if (detectBareVar && bareVarIndex === null && nextChar === '@' && parenDepth === 0) {
4328
+ // A bare `@ident` (not `@{interpolation}`) outside any `(...)` —
4329
+ // a structural reference. Strings/comments are already skipped above.
4330
+ const after = input.charAt(i + 1);
4331
+ if (after && /[-\w]/.test(after)) { bareVarIndex = i; }
4332
+ }
4315
4333
  switch (nextChar) {
4316
4334
  case '\\':
4317
4335
  i++;
@@ -4347,6 +4365,7 @@ var getParserInput = () => {
4347
4365
  case '(':
4348
4366
  blockStack.push(')');
4349
4367
  blockDepth++;
4368
+ parenDepth++;
4350
4369
  break;
4351
4370
  case '[':
4352
4371
  blockStack.push(']');
@@ -4358,6 +4377,7 @@ var getParserInput = () => {
4358
4377
  const expected = blockStack.pop();
4359
4378
  if (nextChar === expected) {
4360
4379
  blockDepth--;
4380
+ if (nextChar === ')' && parenDepth > 0) { parenDepth--; }
4361
4381
  } else {
4362
4382
  // move the parser to the error and return expected
4363
4383
  skipWhitespace(i - startPos);
@@ -4373,6 +4393,7 @@ var getParserInput = () => {
4373
4393
  }
4374
4394
  } while (loop);
4375
4395
 
4396
+ if (Array.isArray(returnVal)) { returnVal.bareVarIndex = bareVarIndex; }
4376
4397
  return returnVal ? returnVal : null;
4377
4398
  };
4378
4399
 
@@ -4573,6 +4594,10 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
4573
4594
 
4574
4595
  const deprecationHandler = new DeprecationHandler();
4575
4596
 
4597
+ // Tracks `${deprecationId}@${index}` pairs already warned about, so a source
4598
+ // position that gets re-parsed via parser backtracking only warns once.
4599
+ const warnedDeprecations = new Set();
4600
+
4576
4601
  /**
4577
4602
  * @param {string} msg
4578
4603
  * @param {number} index
@@ -4582,6 +4607,11 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
4582
4607
  function warn(msg, index, type, deprecationId) {
4583
4608
  if (context.quiet) { return; }
4584
4609
  if (deprecationId && context.quietDeprecations) { return; }
4610
+ if (deprecationId) {
4611
+ const key = `${deprecationId}@${index ?? parserInput.i}`;
4612
+ if (warnedDeprecations.has(key)) { return; }
4613
+ warnedDeprecations.add(key);
4614
+ }
4585
4615
  if (deprecationId && !deprecationHandler.shouldWarn(deprecationId)) { return; }
4586
4616
 
4587
4617
  logger.warn(
@@ -4597,6 +4627,75 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
4597
4627
  );
4598
4628
  }
4599
4629
 
4630
+ /**
4631
+ * Warn that a bare `@variable` reference is being used in a non-value
4632
+ * position (an at-rule prelude, name, or identifier), where it still
4633
+ * resolves today but is deprecated in favour of `@{variable}` interpolation.
4634
+ *
4635
+ * @param {number} index - source position of the bare reference
4636
+ */
4637
+ function warnBareAtRuleVariable(index) {
4638
+ warn('A bare @variable in an at-rule prelude is deprecated. Use @{variable} interpolation instead.', index, 'DEPRECATED', 'variable-in-at-rule-prelude');
4639
+ }
4640
+
4641
+ /**
4642
+ * Numeric-leading variable names are a Less extension rather than valid CSS
4643
+ * identifier syntax. Keep accepting them through Less 4, but make the Less 5
4644
+ * migration visible at every actual variable reference or definition.
4645
+ *
4646
+ * @param {string} name - either an @-prefixed name or the name inside @{...}
4647
+ * @param {number} index - source position of the variable token
4648
+ */
4649
+ function warnNumericVariableName(name, index) {
4650
+ if (/^(?:@@?)?[0-9]/.test(name)) {
4651
+ warn('Variable names beginning with a number are deprecated and will be removed in Less 5.x. Rename the variable to start with a valid identifier character.', index, 'DEPRECATED', 'numeric-variable-name');
4652
+ }
4653
+ }
4654
+
4655
+ /**
4656
+ * A lone '-' is not a CSS identifier. Less historically accepts it as a
4657
+ * variable name; retain that in Less 4 only and warn on definitions,
4658
+ * ordinary references, and interpolation references.
4659
+ *
4660
+ * @param {string} name - either @-/@@-, or the name inside @{...}
4661
+ * @param {number} index - source position of the variable token
4662
+ */
4663
+ function warnDashOnlyVariableName(name, index) {
4664
+ const bareName = name.replace(/^@@?/, '');
4665
+ if (bareName === '-') {
4666
+ warn('The dash-only variable names @- and @{-} are deprecated and will be removed in Less 5.x. Rename the variable to use a valid identifier.', index, 'DEPRECATED', 'dash-only-variable-name');
4667
+ }
4668
+ }
4669
+
4670
+ /**
4671
+ * A lone '-' is not a CSS identifier. Less historically accepts it as a mixin
4672
+ * name after '.' or '#'; retain that in Less 4 only.
4673
+ *
4674
+ * @param {string} name
4675
+ * @param {number} index
4676
+ */
4677
+ function warnDashOnlyMixinName(name, index) {
4678
+ if (name === '.-' || name === '#-') {
4679
+ warn('The dash-only mixin names .-() and #-() are deprecated and will be removed in Less 5.x. Rename the mixin to use a valid CSS identifier.', index, 'DEPRECATED', 'dash-only-mixin-name');
4680
+ }
4681
+ }
4682
+
4683
+ /**
4684
+ * CSS @charset is a source-header declaration, not a general dynamic at-rule.
4685
+ * Less 4 keeps its historical interpolation behavior for compatibility, but
4686
+ * makes each dynamic spelling visible before Less 5 rejects it.
4687
+ *
4688
+ * @param {import('../tree/node.js').default} value
4689
+ * @param {number} index - source position of the @charset token
4690
+ */
4691
+ function warnDynamicCharset(value, index) {
4692
+ if (value instanceof tree.Variable ||
4693
+ (value instanceof tree.Quoted &&
4694
+ (value.containsVariables() || value.value.match(value.propRegex)))) {
4695
+ warn('Dynamic @charset interpolation is deprecated and will be removed in Less 5.x. Use a static quoted encoding declaration instead.', index, 'DEPRECATED', 'dynamic-charset');
4696
+ }
4697
+ }
4698
+
4600
4699
  function expect(arg, msg) {
4601
4700
  // some older browsers return typeof 'function' for RegExp
4602
4701
  const result = (arg instanceof Function) ? arg.call(parsers) : parserInput.$re(arg);
@@ -5050,7 +5149,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
5050
5149
  }
5051
5150
 
5052
5151
  function condition() {
5053
- return [expect(parsers.condition, 'expected condition')];
5152
+ return [expect(() => parsers.condition(false, true), 'expected condition')];
5054
5153
  }
5055
5154
  },
5056
5155
 
@@ -5175,6 +5274,8 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
5175
5274
 
5176
5275
  parserInput.save();
5177
5276
  if (parserInput.currentChar() === '@' && (name = parserInput.$re(/^@@?[\w-]+/))) {
5277
+ warnNumericVariableName(name, index);
5278
+ warnDashOnlyVariableName(name, index);
5178
5279
  ch = parserInput.currentChar();
5179
5280
  if ((ch === '(' && !parserInput.prevChar().match(/^\s/))
5180
5281
  || (ch === '[' && !parserInput.prevChar().match(/^\s/))) {
@@ -5197,6 +5298,8 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
5197
5298
  const index = parserInput.i;
5198
5299
 
5199
5300
  if (parserInput.currentChar() === '@' && (curly = parserInput.$re(/^@\{([\w-]+)\}/))) {
5301
+ warnNumericVariableName(curly[1], index);
5302
+ warnDashOnlyVariableName(curly[1], index);
5200
5303
  return new(tree.Variable)(`@${curly[1]}`, index + currentIndex, fileInfo);
5201
5304
  }
5202
5305
  },
@@ -5319,7 +5422,11 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
5319
5422
  variable: function () {
5320
5423
  let name;
5321
5424
 
5322
- if (parserInput.currentChar() === '@' && (name = parserInput.$re(/^(@[\w-]+)\s*:/))) { return name[1]; }
5425
+ if (parserInput.currentChar() === '@' && (name = parserInput.$re(/^(@[\w-]+)\s*:/))) {
5426
+ warnNumericVariableName(name[1], parserInput.i - name[0].length);
5427
+ warnDashOnlyVariableName(name[1], parserInput.i - name[0].length);
5428
+ return name[1];
5429
+ }
5323
5430
  },
5324
5431
 
5325
5432
  //
@@ -5349,6 +5456,8 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
5349
5456
  }
5350
5457
 
5351
5458
  if (!inValue) {
5459
+ warnNumericVariableName(name[1], i);
5460
+ warnDashOnlyVariableName(name[1], i);
5352
5461
  name = name[1];
5353
5462
  }
5354
5463
 
@@ -5505,6 +5614,9 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
5505
5614
  if (inValue || parsers.end()) {
5506
5615
  parserInput.forget();
5507
5616
  const mixin = new(tree.mixin.Call)(elements, args, index + currentIndex, fileInfo, !lookups && important);
5617
+ for (const element of elements) {
5618
+ warnDashOnlyMixinName(element.value, element._index - currentIndex);
5619
+ }
5508
5620
  if (lookups) {
5509
5621
  return new tree.NamespaceValue(mixin, lookups);
5510
5622
  }
@@ -5701,6 +5813,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
5701
5813
  let ruleset;
5702
5814
  let cond;
5703
5815
  let variadic = false;
5816
+ const index = parserInput.i;
5704
5817
  if ((parserInput.currentChar() !== '.' && parserInput.currentChar() !== '#') ||
5705
5818
  parserInput.peek(/^[^{]*\}/)) {
5706
5819
  return;
@@ -5736,6 +5849,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
5736
5849
 
5737
5850
  if (ruleset) {
5738
5851
  parserInput.forget();
5852
+ warnDashOnlyMixinName(name, index);
5739
5853
  return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic);
5740
5854
  } else {
5741
5855
  parserInput.restore();
@@ -6147,7 +6261,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6147
6261
  if (parserInput.$char(';')) {
6148
6262
  value = new Anonymous('');
6149
6263
  } else {
6150
- value = this.permissiveValue(/[;}]/, true);
6264
+ value = this.permissiveValue(/[;}]/);
6151
6265
  }
6152
6266
  }
6153
6267
  // Try to store values as anonymous
@@ -6206,8 +6320,12 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6206
6320
  * math is allowed.
6207
6321
  *
6208
6322
  * @param {RexExp} untilTokens - Characters to stop parsing at
6323
+ * @param {boolean} [deprecateVariables] - when set, this is an at-rule
6324
+ * prelude (non-value position); accept `@{var}` interpolation and warn
6325
+ * on a bare `@var` reference (which resolves today but is deprecated).
6209
6326
  */
6210
- permissiveValue: function (untilTokens) {
6327
+ permissiveValue: function (untilTokens, deprecateVariables) {
6328
+ const entities = this.entities;
6211
6329
  let i;
6212
6330
  let e;
6213
6331
  let done;
@@ -6234,7 +6352,20 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6234
6352
  value.push(e);
6235
6353
  continue;
6236
6354
  }
6237
- e = this.entity();
6355
+ if (deprecateVariables) {
6356
+ // In an at-rule prelude, `@{var}` interpolation is the supported
6357
+ // form; consume it here so its `{` is not mistaken for a block.
6358
+ e = entities.variableCurly();
6359
+ if (!e) {
6360
+ const varIndex = parserInput.i;
6361
+ e = this.entity();
6362
+ if (e && e.type === 'Variable') {
6363
+ warnBareAtRuleVariable(varIndex);
6364
+ }
6365
+ }
6366
+ } else {
6367
+ e = this.entity();
6368
+ }
6238
6369
  if (e) {
6239
6370
  value.push(e);
6240
6371
  }
@@ -6261,7 +6392,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6261
6392
  }
6262
6393
  parserInput.save();
6263
6394
 
6264
- value = parserInput.$parseUntil(tok);
6395
+ value = parserInput.$parseUntil(tok, deprecateVariables);
6265
6396
 
6266
6397
  if (value) {
6267
6398
  if (typeof value === 'string') {
@@ -6271,6 +6402,14 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6271
6402
  parserInput.forget();
6272
6403
  return new tree.Anonymous('', index);
6273
6404
  }
6405
+ // At-rule prelude: `$parseUntil` (deprecateVariables) records the
6406
+ // first bare `@var` it saw outside any `(...)` in its single pass —
6407
+ // a structural reference (`[...]`/`{...}` don't shield it, only a
6408
+ // declaration-value `(...)` does). Warn once here rather than
6409
+ // re-scanning the text.
6410
+ if (deprecateVariables && value.bareVarIndex !== null && value.bareVarIndex !== undefined) {
6411
+ warnBareAtRuleVariable(value.bareVarIndex);
6412
+ }
6274
6413
  /** @type {string} */
6275
6414
  let item;
6276
6415
  for (i = 0; i < value.length; i++) {
@@ -6287,11 +6426,14 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6287
6426
  const quote = new tree.Quoted('\'', item, true, index, fileInfo);
6288
6427
  const variableRegex = /@([\w-]+)/g;
6289
6428
  const propRegex = /\$([\w-]+)/g;
6290
- if (variableRegex.test(item)) {
6291
- warn('@[ident] in unknown values will not be evaluated as variables in the future. Use @{[ident]}', index, 'DEPRECATED', 'variable-in-unknown-value');
6429
+ // At-rule preludes are handled once above via
6430
+ // `value.bareVarIndex`; the `variable-in-unknown-value`
6431
+ // notice is for unknown declaration values only.
6432
+ if (!deprecateVariables && variableRegex.test(item)) {
6433
+ warn('@variable in unknown values will not be evaluated as variables in the future. Use @{variable}', index, 'DEPRECATED', 'variable-in-unknown-value');
6292
6434
  }
6293
6435
  if (propRegex.test(item)) {
6294
- warn('$[ident] in unknown values will not be evaluated as property references in the future. Use ${[ident]}', index, 'DEPRECATED', 'property-in-unknown-value');
6436
+ warn('$property in unknown values will not be evaluated as property references in the future. Use ${property}', index, 'DEPRECATED', 'property-in-unknown-value');
6295
6437
  }
6296
6438
  quote.variableRegex = /@([\w-]+)|@{([\w-]+)}/g;
6297
6439
  quote.propRegex = /\$([\w-]+)|\${([\w-]+)}/g;
@@ -6400,7 +6542,17 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6400
6542
  }
6401
6543
  parserInput.restore();
6402
6544
 
6403
- e = entities.declarationCall.bind(this)() || cssKeyword() || entities.keyword() || entities.variable() || entities.mixinLookup();
6545
+ e = entities.declarationCall.bind(this)() || cssKeyword() || entities.keyword() || entities.variableCurly();
6546
+ if (!e) {
6547
+ const varIndex = parserInput.i;
6548
+ const bareVariable = entities.variable();
6549
+ if (bareVariable) {
6550
+ warnBareAtRuleVariable(varIndex);
6551
+ e = bareVariable;
6552
+ } else {
6553
+ e = entities.mixinLookup();
6554
+ }
6555
+ }
6404
6556
  if (e) {
6405
6557
  nodes.push(e);
6406
6558
  if (e.type === 'Variable' ||
@@ -6411,7 +6563,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6411
6563
  let closed = false;
6412
6564
  p = this.property();
6413
6565
  parserInput.save();
6414
- if (!p && syntaxOptions.queryInParens && parserInput.$re(/^[0-9a-z-]*\s*([<>]=|<=|>=|[<>]|=)/)) {
6566
+ if (!p && syntaxOptions.queryInParens && parserInput.$re(/^(?:[^()]|\([^()]*\))*\s*([<>]=|<=|>=|[<>]|=)/)) {
6415
6567
  parserInput.restore();
6416
6568
  p = this.condition();
6417
6569
 
@@ -6491,7 +6643,17 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6491
6643
  features[features.length - 1].noSpacing = false;
6492
6644
  }
6493
6645
  } else {
6494
- e = entities.variable() || entities.mixinLookup();
6646
+ e = entities.variableCurly();
6647
+ if (!e) {
6648
+ const varIndex = parserInput.i;
6649
+ const bareVariable = entities.variable();
6650
+ if (bareVariable) {
6651
+ warnBareAtRuleVariable(varIndex);
6652
+ e = bareVariable;
6653
+ } else {
6654
+ e = entities.mixinLookup();
6655
+ }
6656
+ }
6495
6657
  if (e) {
6496
6658
  features.push(e);
6497
6659
  if (!parserInput.$char(',')) { break; }
@@ -6605,8 +6767,24 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6605
6767
  return null;
6606
6768
  }
6607
6769
  },
6770
+ /**
6771
+ * An entity in a non-value at-rule position (an at-rule identifier,
6772
+ * name, or keyword-list item — e.g. the name in `@keyframes @foo`).
6773
+ * `@{foo}` interpolation is the supported form; a bare `@foo` still
6774
+ * resolves but is deprecated.
6775
+ */
6776
+ atRuleEntity: function () {
6777
+ const curly = this.entities.variableCurly();
6778
+ if (curly) { return curly; }
6779
+ const index = parserInput.i;
6780
+ const e = this.entity();
6781
+ if (e && e.type === 'Variable') {
6782
+ warnBareAtRuleVariable(index);
6783
+ }
6784
+ return e;
6785
+ },
6608
6786
  atruleUnknown: function (value, name, hasBlock) {
6609
- value = this.permissiveValue(/^[{;]/);
6787
+ value = this.permissiveValue(/^[{;]/, true);
6610
6788
  hasBlock = (parserInput.currentChar() === '{');
6611
6789
  if (!value) {
6612
6790
  if (!hasBlock && parserInput.currentChar() !== ';') {
@@ -6622,16 +6800,16 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6622
6800
  rules = this.blockRuleset();
6623
6801
  parserInput.save();
6624
6802
  if (!rules && !isRooted) {
6625
- value = this.entity();
6803
+ value = this.atRuleEntity();
6626
6804
  rules = this.blockRuleset();
6627
6805
  }
6628
6806
  if (!rules && !isRooted) {
6629
6807
  parserInput.restore();
6630
6808
  var e = [];
6631
- value = this.entity();
6809
+ value = this.atRuleEntity();
6632
6810
  while (parserInput.$char(',')) {
6633
6811
  e.push(value);
6634
- value = this.entity();
6812
+ value = this.atRuleEntity();
6635
6813
  }
6636
6814
  if (value && e.length > 0) {
6637
6815
  e.push(value);
@@ -6716,12 +6894,28 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6716
6894
  parserInput.commentStore.length = 0;
6717
6895
 
6718
6896
  if (hasIdentifier) {
6719
- value = this.entity();
6897
+ value = this.atRuleEntity();
6720
6898
  if (!value) {
6721
6899
  error(`expected ${name} identifier`);
6722
6900
  }
6901
+ if (nonVendorSpecificName === '@charset') {
6902
+ warnDynamicCharset(value, index);
6903
+ }
6723
6904
  } else if (hasExpression) {
6905
+ // `@namespace` may carry an interpolated `@{ns}` prefix (or a
6906
+ // deprecated bare `@ns`). Parse that prefix directly so `@{ns}`
6907
+ // is accepted here without treating value positions as
6908
+ // interpolation contexts, then read the namespace URL.
6909
+ let prefix = this.entities.variableCurly();
6910
+ if (!prefix && parserInput.peek(/^@@?[\w-]/)) {
6911
+ const prefixIndex = parserInput.i;
6912
+ prefix = this.entities.variable();
6913
+ if (prefix) { warnBareAtRuleVariable(prefixIndex); }
6914
+ }
6724
6915
  value = this.expression();
6916
+ if (prefix) {
6917
+ value = value ? new(tree.Expression)([prefix, ...value.value]) : prefix;
6918
+ }
6725
6919
  if (!value) {
6726
6920
  error(`expected ${name} expression`);
6727
6921
  }
@@ -6911,7 +7105,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6911
7105
  return condition || a;
6912
7106
  }
6913
7107
  },
6914
- condition: function (needsParens) {
7108
+ condition: function (needsParens, allowConditionOperands) {
6915
7109
  let result;
6916
7110
  let logical;
6917
7111
  let next;
@@ -6919,13 +7113,13 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6919
7113
  return parserInput.$str('or');
6920
7114
  }
6921
7115
 
6922
- result = this.conditionAnd(needsParens);
7116
+ result = this.conditionAnd(needsParens, allowConditionOperands);
6923
7117
  if (!result) {
6924
7118
  return ;
6925
7119
  }
6926
7120
  logical = or();
6927
7121
  if (logical) {
6928
- next = this.condition(needsParens);
7122
+ next = this.condition(needsParens, allowConditionOperands);
6929
7123
  if (next) {
6930
7124
  result = new(tree.Condition)(logical, result, next);
6931
7125
  } else {
@@ -6934,13 +7128,13 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6934
7128
  }
6935
7129
  return result;
6936
7130
  },
6937
- conditionAnd: function (needsParens) {
7131
+ conditionAnd: function (needsParens, allowConditionOperands) {
6938
7132
  let result;
6939
7133
  let logical;
6940
7134
  let next;
6941
7135
  const self = this;
6942
7136
  function insideCondition() {
6943
- const cond = self.negatedCondition(needsParens) || self.parenthesisCondition(needsParens);
7137
+ const cond = self.negatedCondition(needsParens, allowConditionOperands) || self.parenthesisCondition(needsParens, allowConditionOperands);
6944
7138
  if (!cond && !needsParens) {
6945
7139
  return self.atomicCondition(needsParens);
6946
7140
  }
@@ -6954,9 +7148,12 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6954
7148
  if (!result) {
6955
7149
  return ;
6956
7150
  }
7151
+ if (allowConditionOperands) {
7152
+ result = this.atomicCondition(needsParens, result, allowConditionOperands) || result;
7153
+ }
6957
7154
  logical = and();
6958
7155
  if (logical) {
6959
- next = this.conditionAnd(needsParens);
7156
+ next = this.conditionAnd(needsParens, allowConditionOperands);
6960
7157
  if (next) {
6961
7158
  result = new(tree.Condition)(logical, result, next);
6962
7159
  } else {
@@ -6965,9 +7162,9 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6965
7162
  }
6966
7163
  return result;
6967
7164
  },
6968
- negatedCondition: function (needsParens) {
7165
+ negatedCondition: function (needsParens, allowConditionOperands) {
6969
7166
  if (parserInput.$str('not')) {
6970
- const result = this.parenthesisCondition(needsParens);
7167
+ const result = this.parenthesisCondition(needsParens, allowConditionOperands);
6971
7168
  if (result) {
6972
7169
  result.negate = !result.negate;
6973
7170
  return result;
@@ -6984,11 +7181,11 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
6984
7181
  }
6985
7182
  }
6986
7183
  },
6987
- parenthesisCondition: function (needsParens) {
7184
+ parenthesisCondition: function (needsParens, allowConditionOperands) {
6988
7185
  function tryConditionFollowedByParenthesis(me) {
6989
7186
  let body;
6990
7187
  parserInput.save();
6991
- body = me.condition(needsParens);
7188
+ body = me.condition(needsParens, allowConditionOperands);
6992
7189
  if (!body) {
6993
7190
  parserInput.restore();
6994
7191
  return ;
@@ -7025,7 +7222,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
7025
7222
  parserInput.forget();
7026
7223
  return body;
7027
7224
  },
7028
- atomicCondition: function (needsParens, preparsedCond) {
7225
+ atomicCondition: function (needsParens, preparsedCond, allowConditionOperands) {
7029
7226
  const entities = this.entities;
7030
7227
  const index = parserInput.i;
7031
7228
  let a;
@@ -7034,7 +7231,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
7034
7231
  let op;
7035
7232
 
7036
7233
  const cond = (function() {
7037
- return this.addition() || entities.keyword() || entities.quoted() || entities.mixinLookup();
7234
+ return (allowConditionOperands && this.parenthesisCondition(needsParens)) || this.addition() || entities.keyword() || entities.quoted() || entities.mixinLookup();
7038
7235
  }).bind(this);
7039
7236
 
7040
7237
  if (preparsedCond) {
@@ -7195,6 +7392,11 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
7195
7392
  }
7196
7393
  for (k = 0; k < name.length; k++) {
7197
7394
  s = name[k];
7395
+ if (s.charAt(0) === '@') {
7396
+ const variableName = s.slice(2, -1);
7397
+ warnNumericVariableName(variableName, index[k]);
7398
+ warnDashOnlyVariableName(variableName, index[k]);
7399
+ }
7198
7400
  name[k] = (s.charAt(0) !== '@' && s.charAt(0) !== '$') ?
7199
7401
  new(tree.Keyword)(s) :
7200
7402
  (s.charAt(0) === '@' ?
@@ -8784,16 +8986,16 @@ const NestableAtRulePrototype = {
8784
8986
  self.features = new Value(self.permute(/** @type {Node[][]} */ (/** @type {unknown} */ (path))).map(
8785
8987
  /** @param {Node | Node[]} path */
8786
8988
  path => {
8787
- path = /** @type {Node[]} */ (path).map(
8989
+ path = /** @type {Node[]} */ (path).map(
8788
8990
  /** @param {Node & { toCSS?: Function }} fragment */
8789
- fragment => fragment.toCSS ? fragment : new Anonymous(/** @type {string} */ (/** @type {unknown} */ (fragment))));
8991
+ fragment => fragment.toCSS ? fragment : new Anonymous(/** @type {string} */ (/** @type {unknown} */ (fragment))));
8790
8992
 
8791
- for (i = /** @type {Node[]} */ (path).length - 1; i > 0; i--) {
8993
+ for (i = /** @type {Node[]} */ (path).length - 1; i > 0; i--) {
8792
8994
  /** @type {Node[]} */ (path).splice(i, 0, new Anonymous('and'));
8793
- }
8995
+ }
8794
8996
 
8795
- return new Expression(/** @type {Node[]} */ (path));
8796
- }));
8997
+ return new Expression(/** @type {Node[]} */ (path));
8998
+ }));
8797
8999
  self.setParent(self.features, self);
8798
9000
 
8799
9001
  // Fake a tree-node that doesn't output anything.
@@ -11147,35 +11349,47 @@ class Definition extends Ruleset {
11147
11349
  */
11148
11350
  matchArgs(args, context) {
11149
11351
  const allArgsCnt = (args && args.length) || 0;
11150
- let len;
11151
- const optionalParameters = this.optionalParameters;
11152
- const requiredArgsCnt = !args ? 0 : args.reduce(function (/** @type {number} */ count, /** @type {MixinArg} */ p) {
11153
- if (optionalParameters.indexOf(p.name) < 0) {
11154
- return count + 1;
11155
- } else {
11156
- return count;
11352
+ const evaldArguments = new Array(this.params.length);
11353
+ const positionalArgs = [];
11354
+ let positionalIndex = 0;
11355
+
11356
+ for (let i = 0; i < allArgsCnt; i++) {
11357
+ const arg = /** @type {MixinArg[]} */ (args)[i];
11358
+ if (!arg.name) {
11359
+ positionalArgs.push(arg);
11360
+ continue;
11157
11361
  }
11158
- }, 0);
11159
11362
 
11160
- if (!this.variadic) {
11161
- if (requiredArgsCnt < this.required) {
11363
+ const paramIndex = this.params.findIndex((param, index) => param.name === arg.name && !evaldArguments[index]);
11364
+ if (paramIndex < 0) {
11162
11365
  return false;
11163
11366
  }
11164
- if (allArgsCnt > this.params.length) {
11165
- return false;
11367
+ evaldArguments[paramIndex] = arg;
11368
+ }
11369
+
11370
+ for (let i = 0; i < this.params.length; i++) {
11371
+ if (evaldArguments[i]) {
11372
+ continue;
11166
11373
  }
11167
- } else {
11168
- if (requiredArgsCnt < (this.required - 1)) {
11374
+ if (this.params[i].variadic) {
11375
+ positionalIndex = positionalArgs.length;
11376
+ continue;
11377
+ }
11378
+ if (positionalIndex < positionalArgs.length) {
11379
+ evaldArguments[i] = positionalArgs[positionalIndex++];
11380
+ } else if (!this.params[i].name || !this.params[i].value) {
11169
11381
  return false;
11170
11382
  }
11171
11383
  }
11172
11384
 
11173
- // check patterns
11174
- len = Math.min(requiredArgsCnt, this.arity);
11385
+ if (positionalIndex < positionalArgs.length) {
11386
+ return false;
11387
+ }
11175
11388
 
11176
- for (let i = 0; i < len; i++) {
11389
+ // check patterns
11390
+ for (let i = 0; i < this.arity; i++) {
11177
11391
  if (!this.params[i].name && !this.params[i].variadic) {
11178
- if (/** @type {MixinArg[]} */ (args)[i].value.eval(context).toCSS(/** @type {EvalContext} */ ({})) != /** @type {Node} */ (this.params[i].value).eval(context).toCSS(/** @type {EvalContext} */ ({}))) {
11392
+ if (evaldArguments[i].value.eval(context).toCSS(/** @type {EvalContext} */ ({})) != /** @type {Node} */ (this.params[i].value).eval(context).toCSS(/** @type {EvalContext} */ ({}))) {
11179
11393
  return false;
11180
11394
  }
11181
11395
  }
@@ -14064,23 +14278,41 @@ var imageSize = environment => {
14064
14278
  throw fileSync.error;
14065
14279
  }
14066
14280
 
14067
- const sizeOf = require$1('image-size');
14068
- return sizeOf ? sizeOf(fileSync.filename) : {width: 0, height: 0};
14281
+ let probe;
14282
+ try {
14283
+ probe = require$1('probe-image-size/sync');
14284
+ } catch (_) {
14285
+ return { width: 0, height: 0 };
14286
+ }
14287
+
14288
+ const size = probe(nodeFs.readFileSync(fileSync.filename));
14289
+
14290
+ if (!size) {
14291
+ throw {
14292
+ type: 'File',
14293
+ message: `Unrecognised image format for '${filePath}'`
14294
+ };
14295
+ }
14296
+
14297
+ return {
14298
+ width: size.width,
14299
+ height: size.height
14300
+ };
14069
14301
  }
14070
14302
 
14071
14303
  const imageFunctions = {
14072
- 'image-size': function(filePathNode) {
14304
+ 'image-size': function (filePathNode) {
14073
14305
  const size = imageSize(this, filePathNode);
14074
14306
  return new Expression([
14075
14307
  new Dimension(size.width, 'px'),
14076
14308
  new Dimension(size.height, 'px')
14077
14309
  ]);
14078
14310
  },
14079
- 'image-width': function(filePathNode) {
14311
+ 'image-width': function (filePathNode) {
14080
14312
  const size = imageSize(this, filePathNode);
14081
14313
  return new Dimension(size.width, 'px');
14082
14314
  },
14083
- 'image-height': function(filePathNode) {
14315
+ 'image-height': function (filePathNode) {
14084
14316
  const size = imageSize(this, filePathNode);
14085
14317
  return new Dimension(size.height, 'px');
14086
14318
  }
@@ -14090,7 +14322,7 @@ var imageSize = environment => {
14090
14322
  };
14091
14323
 
14092
14324
  createRequire();
14093
- const version = "4.6.7";
14325
+ const version = "4.8.0";
14094
14326
 
14095
14327
  const less = createFromEnvironment(environment, [new FileManager(), new UrlFileManager()], version);
14096
14328