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.
- package/dist/less-node.cjs +296 -64
- package/dist/less.cjs +271 -58
- package/dist/less.js +271 -58
- package/dist/less.min.js +2 -2
- package/dist/less.min.js.map +1 -1
- package/lib/less/deprecation.js +17 -2
- package/lib/less/parser/parser-input.js +21 -1
- package/lib/less/parser/parser.js +210 -29
- package/lib/less/tree/mixin-definition.js +30 -18
- package/lib/less/tree/nested-at-rule.js +6 -6
- package/lib/less-node/environment.js +3 -3
- package/lib/less-node/image-size.js +25 -6
- package/package.json +2 -2
package/lib/less/deprecation.js
CHANGED
|
@@ -17,10 +17,25 @@ const deprecations = {
|
|
|
17
17
|
description: 'The ./ operator is deprecated.'
|
|
18
18
|
},
|
|
19
19
|
'variable-in-unknown-value': {
|
|
20
|
-
description: '@
|
|
20
|
+
description: '@variable in custom property values is treated as literal text.'
|
|
21
|
+
},
|
|
22
|
+
'variable-in-at-rule-prelude': {
|
|
23
|
+
description: 'A bare @variable in an at-rule prelude (e.g. @media @foo) is deprecated. Use @{variable} interpolation instead.'
|
|
24
|
+
},
|
|
25
|
+
'numeric-variable-name': {
|
|
26
|
+
description: 'Variable names beginning with a number are deprecated and will be removed in Less 5.x.'
|
|
27
|
+
},
|
|
28
|
+
'dash-only-variable-name': {
|
|
29
|
+
description: 'The dash-only variable names @- and @{-} are deprecated and will be removed in Less 5.x.'
|
|
30
|
+
},
|
|
31
|
+
'dash-only-mixin-name': {
|
|
32
|
+
description: 'The dash-only mixin names .-() and #-() are deprecated and will be removed in Less 5.x.'
|
|
33
|
+
},
|
|
34
|
+
'dynamic-charset': {
|
|
35
|
+
description: 'Dynamic @charset interpolation is deprecated and will be removed in Less 5.x.'
|
|
21
36
|
},
|
|
22
37
|
'property-in-unknown-value': {
|
|
23
|
-
description: '$
|
|
38
|
+
description: '$property in custom property values is treated as literal text.'
|
|
24
39
|
},
|
|
25
40
|
'js-eval': {
|
|
26
41
|
description: 'Inline JavaScript evaluation (backtick expressions) is deprecated and will be removed in Less 5.x.'
|
|
@@ -204,12 +204,23 @@ export default () => {
|
|
|
204
204
|
/**
|
|
205
205
|
* Permissive parsing. Ignores everything except matching {} [] () and quotes
|
|
206
206
|
* until matching token (outside of blocks)
|
|
207
|
+
*
|
|
208
|
+
* @param {string|RegExp} tok - stop token
|
|
209
|
+
* @param {boolean} [detectBareVar] - when set, also record the position of the
|
|
210
|
+
* first bare `@variable` reference (not `@{interpolation}`) that appears at
|
|
211
|
+
* PAREN depth 0 — i.e. a structural reference, not a declaration value inside
|
|
212
|
+
* `(...)`. Reuses this single pass (which already skips strings/comments) so
|
|
213
|
+
* callers don't re-scan the text. Exposed as `.bareVarIndex` on the returned
|
|
214
|
+
* group array (or null). `[...]`/`{...}` do NOT shield a reference — only
|
|
215
|
+
* `(...)` (a declaration-value group) does.
|
|
207
216
|
*/
|
|
208
|
-
parserInput.$parseUntil = tok => {
|
|
217
|
+
parserInput.$parseUntil = (tok, detectBareVar) => {
|
|
209
218
|
let quote = '';
|
|
210
219
|
let returnVal = null;
|
|
211
220
|
let inComment = false;
|
|
212
221
|
let blockDepth = 0;
|
|
222
|
+
let parenDepth = 0;
|
|
223
|
+
let bareVarIndex = null;
|
|
213
224
|
const blockStack = [];
|
|
214
225
|
const parseGroups = [];
|
|
215
226
|
const length = input.length;
|
|
@@ -249,6 +260,12 @@ export default () => {
|
|
|
249
260
|
i++;
|
|
250
261
|
continue;
|
|
251
262
|
}
|
|
263
|
+
if (detectBareVar && bareVarIndex === null && nextChar === '@' && parenDepth === 0) {
|
|
264
|
+
// A bare `@ident` (not `@{interpolation}`) outside any `(...)` —
|
|
265
|
+
// a structural reference. Strings/comments are already skipped above.
|
|
266
|
+
const after = input.charAt(i + 1);
|
|
267
|
+
if (after && /[-\w]/.test(after)) { bareVarIndex = i; }
|
|
268
|
+
}
|
|
252
269
|
switch (nextChar) {
|
|
253
270
|
case '\\':
|
|
254
271
|
i++;
|
|
@@ -284,6 +301,7 @@ export default () => {
|
|
|
284
301
|
case '(':
|
|
285
302
|
blockStack.push(')');
|
|
286
303
|
blockDepth++;
|
|
304
|
+
parenDepth++;
|
|
287
305
|
break;
|
|
288
306
|
case '[':
|
|
289
307
|
blockStack.push(']');
|
|
@@ -295,6 +313,7 @@ export default () => {
|
|
|
295
313
|
const expected = blockStack.pop();
|
|
296
314
|
if (nextChar === expected) {
|
|
297
315
|
blockDepth--;
|
|
316
|
+
if (nextChar === ')' && parenDepth > 0) { parenDepth--; }
|
|
298
317
|
} else {
|
|
299
318
|
// move the parser to the error and return expected
|
|
300
319
|
skipWhitespace(i - startPos);
|
|
@@ -310,6 +329,7 @@ export default () => {
|
|
|
310
329
|
}
|
|
311
330
|
} while (loop);
|
|
312
331
|
|
|
332
|
+
if (Array.isArray(returnVal)) { returnVal.bareVarIndex = bareVarIndex; }
|
|
313
333
|
return returnVal ? returnVal : null;
|
|
314
334
|
}
|
|
315
335
|
|
|
@@ -62,6 +62,10 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
62
62
|
|
|
63
63
|
const deprecationHandler = new DeprecationHandler();
|
|
64
64
|
|
|
65
|
+
// Tracks `${deprecationId}@${index}` pairs already warned about, so a source
|
|
66
|
+
// position that gets re-parsed via parser backtracking only warns once.
|
|
67
|
+
const warnedDeprecations = new Set();
|
|
68
|
+
|
|
65
69
|
/**
|
|
66
70
|
* @param {string} msg
|
|
67
71
|
* @param {number} index
|
|
@@ -71,6 +75,11 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
71
75
|
function warn(msg, index, type, deprecationId) {
|
|
72
76
|
if (context.quiet) { return; }
|
|
73
77
|
if (deprecationId && context.quietDeprecations) { return; }
|
|
78
|
+
if (deprecationId) {
|
|
79
|
+
const key = `${deprecationId}@${index ?? parserInput.i}`;
|
|
80
|
+
if (warnedDeprecations.has(key)) { return; }
|
|
81
|
+
warnedDeprecations.add(key);
|
|
82
|
+
}
|
|
74
83
|
if (deprecationId && !deprecationHandler.shouldWarn(deprecationId)) { return; }
|
|
75
84
|
|
|
76
85
|
logger.warn(
|
|
@@ -86,6 +95,75 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
86
95
|
);
|
|
87
96
|
}
|
|
88
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Warn that a bare `@variable` reference is being used in a non-value
|
|
100
|
+
* position (an at-rule prelude, name, or identifier), where it still
|
|
101
|
+
* resolves today but is deprecated in favour of `@{variable}` interpolation.
|
|
102
|
+
*
|
|
103
|
+
* @param {number} index - source position of the bare reference
|
|
104
|
+
*/
|
|
105
|
+
function warnBareAtRuleVariable(index) {
|
|
106
|
+
warn('A bare @variable in an at-rule prelude is deprecated. Use @{variable} interpolation instead.', index, 'DEPRECATED', 'variable-in-at-rule-prelude');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Numeric-leading variable names are a Less extension rather than valid CSS
|
|
111
|
+
* identifier syntax. Keep accepting them through Less 4, but make the Less 5
|
|
112
|
+
* migration visible at every actual variable reference or definition.
|
|
113
|
+
*
|
|
114
|
+
* @param {string} name - either an @-prefixed name or the name inside @{...}
|
|
115
|
+
* @param {number} index - source position of the variable token
|
|
116
|
+
*/
|
|
117
|
+
function warnNumericVariableName(name, index) {
|
|
118
|
+
if (/^(?:@@?)?[0-9]/.test(name)) {
|
|
119
|
+
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');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* A lone '-' is not a CSS identifier. Less historically accepts it as a
|
|
125
|
+
* variable name; retain that in Less 4 only and warn on definitions,
|
|
126
|
+
* ordinary references, and interpolation references.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} name - either @-/@@-, or the name inside @{...}
|
|
129
|
+
* @param {number} index - source position of the variable token
|
|
130
|
+
*/
|
|
131
|
+
function warnDashOnlyVariableName(name, index) {
|
|
132
|
+
const bareName = name.replace(/^@@?/, '');
|
|
133
|
+
if (bareName === '-') {
|
|
134
|
+
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');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A lone '-' is not a CSS identifier. Less historically accepts it as a mixin
|
|
140
|
+
* name after '.' or '#'; retain that in Less 4 only.
|
|
141
|
+
*
|
|
142
|
+
* @param {string} name
|
|
143
|
+
* @param {number} index
|
|
144
|
+
*/
|
|
145
|
+
function warnDashOnlyMixinName(name, index) {
|
|
146
|
+
if (name === '.-' || name === '#-') {
|
|
147
|
+
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');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* CSS @charset is a source-header declaration, not a general dynamic at-rule.
|
|
153
|
+
* Less 4 keeps its historical interpolation behavior for compatibility, but
|
|
154
|
+
* makes each dynamic spelling visible before Less 5 rejects it.
|
|
155
|
+
*
|
|
156
|
+
* @param {import('../tree/node.js').default} value
|
|
157
|
+
* @param {number} index - source position of the @charset token
|
|
158
|
+
*/
|
|
159
|
+
function warnDynamicCharset(value, index) {
|
|
160
|
+
if (value instanceof tree.Variable ||
|
|
161
|
+
(value instanceof tree.Quoted &&
|
|
162
|
+
(value.containsVariables() || value.value.match(value.propRegex)))) {
|
|
163
|
+
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');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
89
167
|
function expect(arg, msg) {
|
|
90
168
|
// some older browsers return typeof 'function' for RegExp
|
|
91
169
|
const result = (arg instanceof Function) ? arg.call(parsers) : parserInput.$re(arg);
|
|
@@ -539,7 +617,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
539
617
|
}
|
|
540
618
|
|
|
541
619
|
function condition() {
|
|
542
|
-
return [expect(parsers.condition, 'expected condition')];
|
|
620
|
+
return [expect(() => parsers.condition(false, true), 'expected condition')];
|
|
543
621
|
}
|
|
544
622
|
},
|
|
545
623
|
|
|
@@ -664,6 +742,8 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
664
742
|
|
|
665
743
|
parserInput.save();
|
|
666
744
|
if (parserInput.currentChar() === '@' && (name = parserInput.$re(/^@@?[\w-]+/))) {
|
|
745
|
+
warnNumericVariableName(name, index);
|
|
746
|
+
warnDashOnlyVariableName(name, index);
|
|
667
747
|
ch = parserInput.currentChar();
|
|
668
748
|
if ((ch === '(' && !parserInput.prevChar().match(/^\s/))
|
|
669
749
|
|| (ch === '[' && !parserInput.prevChar().match(/^\s/))) {
|
|
@@ -686,6 +766,8 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
686
766
|
const index = parserInput.i;
|
|
687
767
|
|
|
688
768
|
if (parserInput.currentChar() === '@' && (curly = parserInput.$re(/^@\{([\w-]+)\}/))) {
|
|
769
|
+
warnNumericVariableName(curly[1], index);
|
|
770
|
+
warnDashOnlyVariableName(curly[1], index);
|
|
689
771
|
return new(tree.Variable)(`@${curly[1]}`, index + currentIndex, fileInfo);
|
|
690
772
|
}
|
|
691
773
|
},
|
|
@@ -808,7 +890,11 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
808
890
|
variable: function () {
|
|
809
891
|
let name;
|
|
810
892
|
|
|
811
|
-
if (parserInput.currentChar() === '@' && (name = parserInput.$re(/^(@[\w-]+)\s*:/))) {
|
|
893
|
+
if (parserInput.currentChar() === '@' && (name = parserInput.$re(/^(@[\w-]+)\s*:/))) {
|
|
894
|
+
warnNumericVariableName(name[1], parserInput.i - name[0].length);
|
|
895
|
+
warnDashOnlyVariableName(name[1], parserInput.i - name[0].length);
|
|
896
|
+
return name[1];
|
|
897
|
+
}
|
|
812
898
|
},
|
|
813
899
|
|
|
814
900
|
//
|
|
@@ -838,6 +924,8 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
838
924
|
}
|
|
839
925
|
|
|
840
926
|
if (!inValue) {
|
|
927
|
+
warnNumericVariableName(name[1], i);
|
|
928
|
+
warnDashOnlyVariableName(name[1], i);
|
|
841
929
|
name = name[1];
|
|
842
930
|
}
|
|
843
931
|
|
|
@@ -994,6 +1082,9 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
994
1082
|
if (inValue || parsers.end()) {
|
|
995
1083
|
parserInput.forget();
|
|
996
1084
|
const mixin = new(tree.mixin.Call)(elements, args, index + currentIndex, fileInfo, !lookups && important);
|
|
1085
|
+
for (const element of elements) {
|
|
1086
|
+
warnDashOnlyMixinName(element.value, element._index - currentIndex);
|
|
1087
|
+
}
|
|
997
1088
|
if (lookups) {
|
|
998
1089
|
return new tree.NamespaceValue(mixin, lookups);
|
|
999
1090
|
}
|
|
@@ -1190,6 +1281,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
1190
1281
|
let ruleset;
|
|
1191
1282
|
let cond;
|
|
1192
1283
|
let variadic = false;
|
|
1284
|
+
const index = parserInput.i;
|
|
1193
1285
|
if ((parserInput.currentChar() !== '.' && parserInput.currentChar() !== '#') ||
|
|
1194
1286
|
parserInput.peek(/^[^{]*\}/)) {
|
|
1195
1287
|
return;
|
|
@@ -1225,6 +1317,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
1225
1317
|
|
|
1226
1318
|
if (ruleset) {
|
|
1227
1319
|
parserInput.forget();
|
|
1320
|
+
warnDashOnlyMixinName(name, index);
|
|
1228
1321
|
return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic);
|
|
1229
1322
|
} else {
|
|
1230
1323
|
parserInput.restore();
|
|
@@ -1636,7 +1729,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
1636
1729
|
if (parserInput.$char(';')) {
|
|
1637
1730
|
value = new Anonymous('');
|
|
1638
1731
|
} else {
|
|
1639
|
-
value = this.permissiveValue(/[;}]
|
|
1732
|
+
value = this.permissiveValue(/[;}]/);
|
|
1640
1733
|
}
|
|
1641
1734
|
}
|
|
1642
1735
|
// Try to store values as anonymous
|
|
@@ -1695,8 +1788,12 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
1695
1788
|
* math is allowed.
|
|
1696
1789
|
*
|
|
1697
1790
|
* @param {RexExp} untilTokens - Characters to stop parsing at
|
|
1791
|
+
* @param {boolean} [deprecateVariables] - when set, this is an at-rule
|
|
1792
|
+
* prelude (non-value position); accept `@{var}` interpolation and warn
|
|
1793
|
+
* on a bare `@var` reference (which resolves today but is deprecated).
|
|
1698
1794
|
*/
|
|
1699
|
-
permissiveValue: function (untilTokens) {
|
|
1795
|
+
permissiveValue: function (untilTokens, deprecateVariables) {
|
|
1796
|
+
const entities = this.entities;
|
|
1700
1797
|
let i;
|
|
1701
1798
|
let e;
|
|
1702
1799
|
let done;
|
|
@@ -1723,7 +1820,20 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
1723
1820
|
value.push(e);
|
|
1724
1821
|
continue;
|
|
1725
1822
|
}
|
|
1726
|
-
|
|
1823
|
+
if (deprecateVariables) {
|
|
1824
|
+
// In an at-rule prelude, `@{var}` interpolation is the supported
|
|
1825
|
+
// form; consume it here so its `{` is not mistaken for a block.
|
|
1826
|
+
e = entities.variableCurly();
|
|
1827
|
+
if (!e) {
|
|
1828
|
+
const varIndex = parserInput.i;
|
|
1829
|
+
e = this.entity();
|
|
1830
|
+
if (e && e.type === 'Variable') {
|
|
1831
|
+
warnBareAtRuleVariable(varIndex);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
} else {
|
|
1835
|
+
e = this.entity();
|
|
1836
|
+
}
|
|
1727
1837
|
if (e) {
|
|
1728
1838
|
value.push(e);
|
|
1729
1839
|
}
|
|
@@ -1750,7 +1860,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
1750
1860
|
}
|
|
1751
1861
|
parserInput.save();
|
|
1752
1862
|
|
|
1753
|
-
value = parserInput.$parseUntil(tok);
|
|
1863
|
+
value = parserInput.$parseUntil(tok, deprecateVariables);
|
|
1754
1864
|
|
|
1755
1865
|
if (value) {
|
|
1756
1866
|
if (typeof value === 'string') {
|
|
@@ -1760,6 +1870,14 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
1760
1870
|
parserInput.forget();
|
|
1761
1871
|
return new tree.Anonymous('', index);
|
|
1762
1872
|
}
|
|
1873
|
+
// At-rule prelude: `$parseUntil` (deprecateVariables) records the
|
|
1874
|
+
// first bare `@var` it saw outside any `(...)` in its single pass —
|
|
1875
|
+
// a structural reference (`[...]`/`{...}` don't shield it, only a
|
|
1876
|
+
// declaration-value `(...)` does). Warn once here rather than
|
|
1877
|
+
// re-scanning the text.
|
|
1878
|
+
if (deprecateVariables && value.bareVarIndex !== null && value.bareVarIndex !== undefined) {
|
|
1879
|
+
warnBareAtRuleVariable(value.bareVarIndex);
|
|
1880
|
+
}
|
|
1763
1881
|
/** @type {string} */
|
|
1764
1882
|
let item;
|
|
1765
1883
|
for (i = 0; i < value.length; i++) {
|
|
@@ -1776,11 +1894,14 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
1776
1894
|
const quote = new tree.Quoted('\'', item, true, index, fileInfo);
|
|
1777
1895
|
const variableRegex = /@([\w-]+)/g;
|
|
1778
1896
|
const propRegex = /\$([\w-]+)/g;
|
|
1779
|
-
|
|
1780
|
-
|
|
1897
|
+
// At-rule preludes are handled once above via
|
|
1898
|
+
// `value.bareVarIndex`; the `variable-in-unknown-value`
|
|
1899
|
+
// notice is for unknown declaration values only.
|
|
1900
|
+
if (!deprecateVariables && variableRegex.test(item)) {
|
|
1901
|
+
warn('@variable in unknown values will not be evaluated as variables in the future. Use @{variable}', index, 'DEPRECATED', 'variable-in-unknown-value');
|
|
1781
1902
|
}
|
|
1782
1903
|
if (propRegex.test(item)) {
|
|
1783
|
-
warn('$
|
|
1904
|
+
warn('$property in unknown values will not be evaluated as property references in the future. Use ${property}', index, 'DEPRECATED', 'property-in-unknown-value');
|
|
1784
1905
|
}
|
|
1785
1906
|
quote.variableRegex = /@([\w-]+)|@{([\w-]+)}/g;
|
|
1786
1907
|
quote.propRegex = /\$([\w-]+)|\${([\w-]+)}/g;
|
|
@@ -1889,7 +2010,17 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
1889
2010
|
}
|
|
1890
2011
|
parserInput.restore();
|
|
1891
2012
|
|
|
1892
|
-
e = entities.declarationCall.bind(this)() || cssKeyword() || entities.keyword() || entities.
|
|
2013
|
+
e = entities.declarationCall.bind(this)() || cssKeyword() || entities.keyword() || entities.variableCurly();
|
|
2014
|
+
if (!e) {
|
|
2015
|
+
const varIndex = parserInput.i;
|
|
2016
|
+
const bareVariable = entities.variable();
|
|
2017
|
+
if (bareVariable) {
|
|
2018
|
+
warnBareAtRuleVariable(varIndex);
|
|
2019
|
+
e = bareVariable;
|
|
2020
|
+
} else {
|
|
2021
|
+
e = entities.mixinLookup();
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
1893
2024
|
if (e) {
|
|
1894
2025
|
nodes.push(e);
|
|
1895
2026
|
if (e.type === 'Variable' ||
|
|
@@ -1900,7 +2031,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
1900
2031
|
let closed = false;
|
|
1901
2032
|
p = this.property();
|
|
1902
2033
|
parserInput.save();
|
|
1903
|
-
if (!p && syntaxOptions.queryInParens && parserInput.$re(/^[
|
|
2034
|
+
if (!p && syntaxOptions.queryInParens && parserInput.$re(/^(?:[^()]|\([^()]*\))*\s*([<>]=|<=|>=|[<>]|=)/)) {
|
|
1904
2035
|
parserInput.restore();
|
|
1905
2036
|
p = this.condition();
|
|
1906
2037
|
|
|
@@ -1980,7 +2111,17 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
1980
2111
|
features[features.length - 1].noSpacing = false;
|
|
1981
2112
|
}
|
|
1982
2113
|
} else {
|
|
1983
|
-
e = entities.
|
|
2114
|
+
e = entities.variableCurly();
|
|
2115
|
+
if (!e) {
|
|
2116
|
+
const varIndex = parserInput.i;
|
|
2117
|
+
const bareVariable = entities.variable();
|
|
2118
|
+
if (bareVariable) {
|
|
2119
|
+
warnBareAtRuleVariable(varIndex);
|
|
2120
|
+
e = bareVariable;
|
|
2121
|
+
} else {
|
|
2122
|
+
e = entities.mixinLookup();
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
1984
2125
|
if (e) {
|
|
1985
2126
|
features.push(e);
|
|
1986
2127
|
if (!parserInput.$char(',')) { break; }
|
|
@@ -2094,8 +2235,24 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
2094
2235
|
return null;
|
|
2095
2236
|
}
|
|
2096
2237
|
},
|
|
2238
|
+
/**
|
|
2239
|
+
* An entity in a non-value at-rule position (an at-rule identifier,
|
|
2240
|
+
* name, or keyword-list item — e.g. the name in `@keyframes @foo`).
|
|
2241
|
+
* `@{foo}` interpolation is the supported form; a bare `@foo` still
|
|
2242
|
+
* resolves but is deprecated.
|
|
2243
|
+
*/
|
|
2244
|
+
atRuleEntity: function () {
|
|
2245
|
+
const curly = this.entities.variableCurly();
|
|
2246
|
+
if (curly) { return curly; }
|
|
2247
|
+
const index = parserInput.i;
|
|
2248
|
+
const e = this.entity();
|
|
2249
|
+
if (e && e.type === 'Variable') {
|
|
2250
|
+
warnBareAtRuleVariable(index);
|
|
2251
|
+
}
|
|
2252
|
+
return e;
|
|
2253
|
+
},
|
|
2097
2254
|
atruleUnknown: function (value, name, hasBlock) {
|
|
2098
|
-
value = this.permissiveValue(/^[{;]
|
|
2255
|
+
value = this.permissiveValue(/^[{;]/, true);
|
|
2099
2256
|
hasBlock = (parserInput.currentChar() === '{');
|
|
2100
2257
|
if (!value) {
|
|
2101
2258
|
if (!hasBlock && parserInput.currentChar() !== ';') {
|
|
@@ -2111,16 +2268,16 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
2111
2268
|
rules = this.blockRuleset();
|
|
2112
2269
|
parserInput.save();
|
|
2113
2270
|
if (!rules && !isRooted) {
|
|
2114
|
-
value = this.
|
|
2271
|
+
value = this.atRuleEntity();
|
|
2115
2272
|
rules = this.blockRuleset();
|
|
2116
2273
|
}
|
|
2117
2274
|
if (!rules && !isRooted) {
|
|
2118
2275
|
parserInput.restore();
|
|
2119
2276
|
var e = [];
|
|
2120
|
-
value = this.
|
|
2277
|
+
value = this.atRuleEntity();
|
|
2121
2278
|
while (parserInput.$char(',')) {
|
|
2122
2279
|
e.push(value);
|
|
2123
|
-
value = this.
|
|
2280
|
+
value = this.atRuleEntity();
|
|
2124
2281
|
}
|
|
2125
2282
|
if (value && e.length > 0) {
|
|
2126
2283
|
e.push(value);
|
|
@@ -2205,12 +2362,28 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
2205
2362
|
parserInput.commentStore.length = 0;
|
|
2206
2363
|
|
|
2207
2364
|
if (hasIdentifier) {
|
|
2208
|
-
value = this.
|
|
2365
|
+
value = this.atRuleEntity();
|
|
2209
2366
|
if (!value) {
|
|
2210
2367
|
error(`expected ${name} identifier`);
|
|
2211
2368
|
}
|
|
2369
|
+
if (nonVendorSpecificName === '@charset') {
|
|
2370
|
+
warnDynamicCharset(value, index);
|
|
2371
|
+
}
|
|
2212
2372
|
} else if (hasExpression) {
|
|
2373
|
+
// `@namespace` may carry an interpolated `@{ns}` prefix (or a
|
|
2374
|
+
// deprecated bare `@ns`). Parse that prefix directly so `@{ns}`
|
|
2375
|
+
// is accepted here without treating value positions as
|
|
2376
|
+
// interpolation contexts, then read the namespace URL.
|
|
2377
|
+
let prefix = this.entities.variableCurly();
|
|
2378
|
+
if (!prefix && parserInput.peek(/^@@?[\w-]/)) {
|
|
2379
|
+
const prefixIndex = parserInput.i;
|
|
2380
|
+
prefix = this.entities.variable();
|
|
2381
|
+
if (prefix) { warnBareAtRuleVariable(prefixIndex); }
|
|
2382
|
+
}
|
|
2213
2383
|
value = this.expression();
|
|
2384
|
+
if (prefix) {
|
|
2385
|
+
value = value ? new(tree.Expression)([prefix, ...value.value]) : prefix;
|
|
2386
|
+
}
|
|
2214
2387
|
if (!value) {
|
|
2215
2388
|
error(`expected ${name} expression`);
|
|
2216
2389
|
}
|
|
@@ -2400,7 +2573,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
2400
2573
|
return condition || a;
|
|
2401
2574
|
}
|
|
2402
2575
|
},
|
|
2403
|
-
condition: function (needsParens) {
|
|
2576
|
+
condition: function (needsParens, allowConditionOperands) {
|
|
2404
2577
|
let result;
|
|
2405
2578
|
let logical;
|
|
2406
2579
|
let next;
|
|
@@ -2408,13 +2581,13 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
2408
2581
|
return parserInput.$str('or');
|
|
2409
2582
|
}
|
|
2410
2583
|
|
|
2411
|
-
result = this.conditionAnd(needsParens);
|
|
2584
|
+
result = this.conditionAnd(needsParens, allowConditionOperands);
|
|
2412
2585
|
if (!result) {
|
|
2413
2586
|
return ;
|
|
2414
2587
|
}
|
|
2415
2588
|
logical = or();
|
|
2416
2589
|
if (logical) {
|
|
2417
|
-
next = this.condition(needsParens);
|
|
2590
|
+
next = this.condition(needsParens, allowConditionOperands);
|
|
2418
2591
|
if (next) {
|
|
2419
2592
|
result = new(tree.Condition)(logical, result, next);
|
|
2420
2593
|
} else {
|
|
@@ -2423,13 +2596,13 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
2423
2596
|
}
|
|
2424
2597
|
return result;
|
|
2425
2598
|
},
|
|
2426
|
-
conditionAnd: function (needsParens) {
|
|
2599
|
+
conditionAnd: function (needsParens, allowConditionOperands) {
|
|
2427
2600
|
let result;
|
|
2428
2601
|
let logical;
|
|
2429
2602
|
let next;
|
|
2430
2603
|
const self = this;
|
|
2431
2604
|
function insideCondition() {
|
|
2432
|
-
const cond = self.negatedCondition(needsParens) || self.parenthesisCondition(needsParens);
|
|
2605
|
+
const cond = self.negatedCondition(needsParens, allowConditionOperands) || self.parenthesisCondition(needsParens, allowConditionOperands);
|
|
2433
2606
|
if (!cond && !needsParens) {
|
|
2434
2607
|
return self.atomicCondition(needsParens);
|
|
2435
2608
|
}
|
|
@@ -2443,9 +2616,12 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
2443
2616
|
if (!result) {
|
|
2444
2617
|
return ;
|
|
2445
2618
|
}
|
|
2619
|
+
if (allowConditionOperands) {
|
|
2620
|
+
result = this.atomicCondition(needsParens, result, allowConditionOperands) || result;
|
|
2621
|
+
}
|
|
2446
2622
|
logical = and();
|
|
2447
2623
|
if (logical) {
|
|
2448
|
-
next = this.conditionAnd(needsParens);
|
|
2624
|
+
next = this.conditionAnd(needsParens, allowConditionOperands);
|
|
2449
2625
|
if (next) {
|
|
2450
2626
|
result = new(tree.Condition)(logical, result, next);
|
|
2451
2627
|
} else {
|
|
@@ -2454,9 +2630,9 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
2454
2630
|
}
|
|
2455
2631
|
return result;
|
|
2456
2632
|
},
|
|
2457
|
-
negatedCondition: function (needsParens) {
|
|
2633
|
+
negatedCondition: function (needsParens, allowConditionOperands) {
|
|
2458
2634
|
if (parserInput.$str('not')) {
|
|
2459
|
-
const result = this.parenthesisCondition(needsParens);
|
|
2635
|
+
const result = this.parenthesisCondition(needsParens, allowConditionOperands);
|
|
2460
2636
|
if (result) {
|
|
2461
2637
|
result.negate = !result.negate;
|
|
2462
2638
|
return result;
|
|
@@ -2473,11 +2649,11 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
2473
2649
|
}
|
|
2474
2650
|
}
|
|
2475
2651
|
},
|
|
2476
|
-
parenthesisCondition: function (needsParens) {
|
|
2652
|
+
parenthesisCondition: function (needsParens, allowConditionOperands) {
|
|
2477
2653
|
function tryConditionFollowedByParenthesis(me) {
|
|
2478
2654
|
let body;
|
|
2479
2655
|
parserInput.save();
|
|
2480
|
-
body = me.condition(needsParens);
|
|
2656
|
+
body = me.condition(needsParens, allowConditionOperands);
|
|
2481
2657
|
if (!body) {
|
|
2482
2658
|
parserInput.restore();
|
|
2483
2659
|
return ;
|
|
@@ -2514,7 +2690,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
2514
2690
|
parserInput.forget();
|
|
2515
2691
|
return body;
|
|
2516
2692
|
},
|
|
2517
|
-
atomicCondition: function (needsParens, preparsedCond) {
|
|
2693
|
+
atomicCondition: function (needsParens, preparsedCond, allowConditionOperands) {
|
|
2518
2694
|
const entities = this.entities;
|
|
2519
2695
|
const index = parserInput.i;
|
|
2520
2696
|
let a;
|
|
@@ -2523,7 +2699,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
2523
2699
|
let op;
|
|
2524
2700
|
|
|
2525
2701
|
const cond = (function() {
|
|
2526
|
-
return this.addition() || entities.keyword() || entities.quoted() || entities.mixinLookup();
|
|
2702
|
+
return (allowConditionOperands && this.parenthesisCondition(needsParens)) || this.addition() || entities.keyword() || entities.quoted() || entities.mixinLookup();
|
|
2527
2703
|
}).bind(this)
|
|
2528
2704
|
|
|
2529
2705
|
if (preparsedCond) {
|
|
@@ -2684,6 +2860,11 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
|
2684
2860
|
}
|
|
2685
2861
|
for (k = 0; k < name.length; k++) {
|
|
2686
2862
|
s = name[k];
|
|
2863
|
+
if (s.charAt(0) === '@') {
|
|
2864
|
+
const variableName = s.slice(2, -1);
|
|
2865
|
+
warnNumericVariableName(variableName, index[k]);
|
|
2866
|
+
warnDashOnlyVariableName(variableName, index[k]);
|
|
2867
|
+
}
|
|
2687
2868
|
name[k] = (s.charAt(0) !== '@' && s.charAt(0) !== '$') ?
|
|
2688
2869
|
new(tree.Keyword)(s) :
|
|
2689
2870
|
(s.charAt(0) === '@' ?
|
|
@@ -267,35 +267,47 @@ class Definition extends Ruleset {
|
|
|
267
267
|
*/
|
|
268
268
|
matchArgs(args, context) {
|
|
269
269
|
const allArgsCnt = (args && args.length) || 0;
|
|
270
|
-
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
270
|
+
const evaldArguments = new Array(this.params.length);
|
|
271
|
+
const positionalArgs = [];
|
|
272
|
+
let positionalIndex = 0;
|
|
273
|
+
|
|
274
|
+
for (let i = 0; i < allArgsCnt; i++) {
|
|
275
|
+
const arg = /** @type {MixinArg[]} */ (args)[i];
|
|
276
|
+
if (!arg.name) {
|
|
277
|
+
positionalArgs.push(arg);
|
|
278
|
+
continue;
|
|
277
279
|
}
|
|
278
|
-
}, 0);
|
|
279
280
|
|
|
280
|
-
|
|
281
|
-
if (
|
|
281
|
+
const paramIndex = this.params.findIndex((param, index) => param.name === arg.name && !evaldArguments[index]);
|
|
282
|
+
if (paramIndex < 0) {
|
|
282
283
|
return false;
|
|
283
284
|
}
|
|
284
|
-
|
|
285
|
-
|
|
285
|
+
evaldArguments[paramIndex] = arg;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
for (let i = 0; i < this.params.length; i++) {
|
|
289
|
+
if (evaldArguments[i]) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (this.params[i].variadic) {
|
|
293
|
+
positionalIndex = positionalArgs.length;
|
|
294
|
+
continue;
|
|
286
295
|
}
|
|
287
|
-
|
|
288
|
-
|
|
296
|
+
if (positionalIndex < positionalArgs.length) {
|
|
297
|
+
evaldArguments[i] = positionalArgs[positionalIndex++];
|
|
298
|
+
} else if (!this.params[i].name || !this.params[i].value) {
|
|
289
299
|
return false;
|
|
290
300
|
}
|
|
291
301
|
}
|
|
292
302
|
|
|
293
|
-
|
|
294
|
-
|
|
303
|
+
if (positionalIndex < positionalArgs.length) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
295
306
|
|
|
296
|
-
|
|
307
|
+
// check patterns
|
|
308
|
+
for (let i = 0; i < this.arity; i++) {
|
|
297
309
|
if (!this.params[i].name && !this.params[i].variadic) {
|
|
298
|
-
if (
|
|
310
|
+
if (evaldArguments[i].value.eval(context).toCSS(/** @type {EvalContext} */ ({})) != /** @type {Node} */ (this.params[i].value).eval(context).toCSS(/** @type {EvalContext} */ ({}))) {
|
|
299
311
|
return false;
|
|
300
312
|
}
|
|
301
313
|
}
|
|
@@ -145,16 +145,16 @@ const NestableAtRulePrototype = {
|
|
|
145
145
|
self.features = new Value(self.permute(/** @type {Node[][]} */ (/** @type {unknown} */ (path))).map(
|
|
146
146
|
/** @param {Node | Node[]} path */
|
|
147
147
|
path => {
|
|
148
|
-
|
|
148
|
+
path = /** @type {Node[]} */ (path).map(
|
|
149
149
|
/** @param {Node & { toCSS?: Function }} fragment */
|
|
150
|
-
|
|
150
|
+
fragment => fragment.toCSS ? fragment : new Anonymous(/** @type {string} */ (/** @type {unknown} */ (fragment))));
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
for (i = /** @type {Node[]} */ (path).length - 1; i > 0; i--) {
|
|
153
153
|
/** @type {Node[]} */ (path).splice(i, 0, new Anonymous('and'));
|
|
154
|
-
|
|
154
|
+
}
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
return new Expression(/** @type {Node[]} */ (path));
|
|
157
|
+
}));
|
|
158
158
|
self.setParent(self.features, self);
|
|
159
159
|
|
|
160
160
|
// Fake a tree-node that doesn't output anything.
|