less 4.9.0 → 5.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -5
- package/bin/lessc +190 -630
- package/dist/less-node.cjs +399 -14570
- package/index.cjs +5 -2
- package/lib/index.js +164 -0
- package/lib/lessc-helper.js +52 -0
- package/lib/logger.js +76 -0
- package/lib/options.d.ts +46 -0
- package/lib/options.js +145 -0
- package/lib/types.js +20 -0
- package/lib/version.js +19 -0
- package/package.json +32 -39
- package/dist/less.cjs +0 -15215
- package/dist/less.js +0 -15215
- package/dist/less.min.js +0 -11
- package/dist/less.min.js.map +0 -1
- package/lib/less/constants.js +0 -13
- package/lib/less/contexts.js +0 -165
- package/lib/less/data/colors.js +0 -150
- package/lib/less/data/index.js +0 -4
- package/lib/less/data/unit-conversions.js +0 -21
- package/lib/less/default-options.js +0 -89
- package/lib/less/deprecation.js +0 -82
- package/lib/less/environment/abstract-file-manager.js +0 -140
- package/lib/less/environment/abstract-plugin-loader.js +0 -185
- package/lib/less/environment/environment-api.ts +0 -21
- package/lib/less/environment/environment.js +0 -59
- package/lib/less/environment/file-manager-api.ts +0 -77
- package/lib/less/functions/boolean.js +0 -29
- package/lib/less/functions/color-blending.js +0 -85
- package/lib/less/functions/color.js +0 -452
- package/lib/less/functions/data-uri.js +0 -74
- package/lib/less/functions/default.js +0 -26
- package/lib/less/functions/function-caller.js +0 -55
- package/lib/less/functions/function-registry.js +0 -36
- package/lib/less/functions/index.js +0 -35
- package/lib/less/functions/list.js +0 -158
- package/lib/less/functions/math-helper.js +0 -22
- package/lib/less/functions/math.js +0 -30
- package/lib/less/functions/number.js +0 -95
- package/lib/less/functions/string.js +0 -39
- package/lib/less/functions/style.js +0 -29
- package/lib/less/functions/svg.js +0 -87
- package/lib/less/functions/types.js +0 -70
- package/lib/less/import-manager.js +0 -183
- package/lib/less/index.js +0 -95
- package/lib/less/less-error.js +0 -166
- package/lib/less/logger.js +0 -34
- package/lib/less/parse-tree.js +0 -124
- package/lib/less/parse.js +0 -87
- package/lib/less/parser/lookup-pattern.js +0 -26
- package/lib/less/parser/parser-input.js +0 -400
- package/lib/less/parser/parser.js +0 -2998
- package/lib/less/plugin-manager.js +0 -168
- package/lib/less/render.js +0 -41
- package/lib/less/source-map-builder.js +0 -82
- package/lib/less/source-map-output.js +0 -151
- package/lib/less/transform-tree.js +0 -102
- package/lib/less/tree/anonymous.js +0 -61
- package/lib/less/tree/assignment.js +0 -48
- package/lib/less/tree/atrule-syntax.js +0 -8
- package/lib/less/tree/atrule.js +0 -340
- package/lib/less/tree/attribute.js +0 -63
- package/lib/less/tree/call.js +0 -132
- package/lib/less/tree/color.js +0 -285
- package/lib/less/tree/combinator.js +0 -38
- package/lib/less/tree/comment.js +0 -48
- package/lib/less/tree/condition.js +0 -65
- package/lib/less/tree/container.js +0 -104
- package/lib/less/tree/debug-info.js +0 -80
- package/lib/less/tree/declaration.js +0 -139
- package/lib/less/tree/detached-ruleset.js +0 -45
- package/lib/less/tree/dimension.js +0 -215
- package/lib/less/tree/element.js +0 -93
- package/lib/less/tree/expression.js +0 -100
- package/lib/less/tree/extend.js +0 -88
- package/lib/less/tree/import.js +0 -299
- package/lib/less/tree/index.js +0 -56
- package/lib/less/tree/interpolated-variable.js +0 -106
- package/lib/less/tree/javascript.js +0 -45
- package/lib/less/tree/js-eval-node.js +0 -80
- package/lib/less/tree/keyword.js +0 -28
- package/lib/less/tree/media.js +0 -90
- package/lib/less/tree/merge-rules.js +0 -47
- package/lib/less/tree/mixin-call.js +0 -283
- package/lib/less/tree/mixin-definition.js +0 -319
- package/lib/less/tree/namespace-value.js +0 -95
- package/lib/less/tree/negative.js +0 -37
- package/lib/less/tree/nested-at-rule.js +0 -239
- package/lib/less/tree/node.js +0 -310
- package/lib/less/tree/operation.js +0 -81
- package/lib/less/tree/paren.js +0 -41
- package/lib/less/tree/property.js +0 -96
- package/lib/less/tree/query-in-parens.js +0 -64
- package/lib/less/tree/quoted.js +0 -131
- package/lib/less/tree/ruleset.js +0 -1068
- package/lib/less/tree/selector.js +0 -201
- package/lib/less/tree/unicode-descriptor.js +0 -14
- package/lib/less/tree/unit.js +0 -170
- package/lib/less/tree/url.js +0 -83
- package/lib/less/tree/value.js +0 -60
- package/lib/less/tree/variable-call.js +0 -60
- package/lib/less/tree/variable.js +0 -85
- package/lib/less/utils.js +0 -126
- package/lib/less/visitors/extend-visitor.js +0 -505
- package/lib/less/visitors/import-sequencer.js +0 -56
- package/lib/less/visitors/import-visitor.js +0 -203
- package/lib/less/visitors/index.js +0 -15
- package/lib/less/visitors/join-selector-visitor.js +0 -65
- package/lib/less/visitors/set-tree-visibility-visitor.js +0 -57
- package/lib/less/visitors/to-css-visitor.js +0 -332
- package/lib/less/visitors/visitor.js +0 -165
- package/lib/less-browser/add-default-options.js +0 -53
- package/lib/less-browser/bootstrap.js +0 -67
- package/lib/less-browser/browser.js +0 -65
- package/lib/less-browser/cache.js +0 -43
- package/lib/less-browser/error-reporting.js +0 -170
- package/lib/less-browser/file-manager.js +0 -112
- package/lib/less-browser/image-size.js +0 -28
- package/lib/less-browser/index.js +0 -284
- package/lib/less-browser/log-listener.js +0 -42
- package/lib/less-browser/plugin-loader.js +0 -24
- package/lib/less-browser/utils.js +0 -30
- package/lib/less-node/environment.js +0 -43
- package/lib/less-node/file-manager.js +0 -156
- package/lib/less-node/fs.js +0 -14
- package/lib/less-node/image-size.js +0 -78
- package/lib/less-node/index.js +0 -31
- package/lib/less-node/lessc-helper.js +0 -95
- package/lib/less-node/plugin-loader.js +0 -62
- package/lib/less-node/url-file-manager.js +0 -60
|
@@ -1,2998 +0,0 @@
|
|
|
1
|
-
import LessError from '../less-error.js';
|
|
2
|
-
import tree from '../tree/index.js';
|
|
3
|
-
import visitors from '../visitors/index.js';
|
|
4
|
-
import getParserInput from './parser-input.js';
|
|
5
|
-
import * as utils from '../utils.js';
|
|
6
|
-
import functionRegistry from '../functions/function-registry.js';
|
|
7
|
-
import { ContainerSyntaxOptions, MediaSyntaxOptions } from '../tree/atrule-syntax.js';
|
|
8
|
-
import logger from '../logger.js';
|
|
9
|
-
import { DeprecationHandler } from '../deprecation.js';
|
|
10
|
-
import Selector from '../tree/selector.js';
|
|
11
|
-
import Anonymous from '../tree/anonymous.js';
|
|
12
|
-
import { VARIABLE_WITH_LOOKUPS } from './lookup-pattern.js';
|
|
13
|
-
import {
|
|
14
|
-
splitLookups,
|
|
15
|
-
resolveInterpolatedVariable,
|
|
16
|
-
resolveInterpolatedProperty,
|
|
17
|
-
hasInterpolation,
|
|
18
|
-
VARIABLE_INTERPOLATION,
|
|
19
|
-
PROPERTY_INTERPOLATION
|
|
20
|
-
} from '../tree/interpolated-variable.js';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* One particle of a property name: a literal chunk, an `@{...}` interpolation
|
|
24
|
-
* which may carry a lookup chain, or a `${...}` interpolation which may not.
|
|
25
|
-
*
|
|
26
|
-
* The sigils are spelled out separately rather than sharing `[@$]`: properties
|
|
27
|
-
* have no lookup grammar, so accepting `${name[key]}` here would hand `name[key]`
|
|
28
|
-
* to `resolveInterpolatedProperty` and produce a misleading "undefined property"
|
|
29
|
-
* error for syntax the language does not define.
|
|
30
|
-
*/
|
|
31
|
-
const RULE_PROPERTY_PARTICLE = new RegExp(
|
|
32
|
-
`^((?:[\\w-]+)|(?:@\\{${VARIABLE_WITH_LOOKUPS}\\})|(?:\\$\\{[\\w-]+\\}))`
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
// less.js - parser
|
|
37
|
-
//
|
|
38
|
-
// A relatively straight-forward predictive parser.
|
|
39
|
-
// There is no tokenization/lexing stage, the input is parsed
|
|
40
|
-
// in one sweep.
|
|
41
|
-
//
|
|
42
|
-
// To make the parser fast enough to run in the browser, several
|
|
43
|
-
// optimization had to be made:
|
|
44
|
-
//
|
|
45
|
-
// - Matching and slicing on a huge input is often cause of slowdowns.
|
|
46
|
-
// The solution is to chunkify the input into smaller strings.
|
|
47
|
-
// The chunks are stored in the `chunks` var,
|
|
48
|
-
// `j` holds the current chunk index, and `currentPos` holds
|
|
49
|
-
// the index of the current chunk in relation to `input`.
|
|
50
|
-
// This gives us an almost 4x speed-up.
|
|
51
|
-
//
|
|
52
|
-
// - In many cases, we don't need to match individual tokens;
|
|
53
|
-
// for example, if a value doesn't hold any variables, operations
|
|
54
|
-
// or dynamic references, the parser can effectively 'skip' it,
|
|
55
|
-
// treating it as a literal.
|
|
56
|
-
// An example would be '1px solid #000' - which evaluates to itself,
|
|
57
|
-
// we don't need to know what the individual components are.
|
|
58
|
-
// The drawback, of course is that you don't get the benefits of
|
|
59
|
-
// syntax-checking on the CSS. This gives us a 50% speed-up in the parser,
|
|
60
|
-
// and a smaller speed-up in the code-gen.
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
// Token matching is done with the `$` function, which either takes
|
|
64
|
-
// a terminal string or regexp, or a non-terminal function to call.
|
|
65
|
-
// It also takes care of moving all the indices forwards.
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
const Parser = function Parser(context, imports, fileInfo, currentIndex) {
|
|
69
|
-
currentIndex = currentIndex || 0;
|
|
70
|
-
let parsers;
|
|
71
|
-
const parserInput = getParserInput();
|
|
72
|
-
|
|
73
|
-
function error(msg, type) {
|
|
74
|
-
throw new LessError(
|
|
75
|
-
{
|
|
76
|
-
index: parserInput.i,
|
|
77
|
-
filename: fileInfo.filename,
|
|
78
|
-
type: type || 'Syntax',
|
|
79
|
-
message: msg
|
|
80
|
-
},
|
|
81
|
-
imports
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const deprecationHandler = new DeprecationHandler();
|
|
86
|
-
|
|
87
|
-
// Tracks `${deprecationId}@${index}` pairs already warned about, so a source
|
|
88
|
-
// position that gets re-parsed via parser backtracking only warns once.
|
|
89
|
-
const warnedDeprecations = new Set();
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* @param {string} msg
|
|
93
|
-
* @param {number} index
|
|
94
|
-
* @param {string} type
|
|
95
|
-
* @param {string} [deprecationId] - stable deprecation ID for repetition limiting
|
|
96
|
-
*/
|
|
97
|
-
function warn(msg, index, type, deprecationId) {
|
|
98
|
-
if (context.quiet) { return; }
|
|
99
|
-
if (deprecationId && context.quietDeprecations) { return; }
|
|
100
|
-
if (deprecationId) {
|
|
101
|
-
const key = `${deprecationId}@${index ?? parserInput.i}`;
|
|
102
|
-
if (warnedDeprecations.has(key)) { return; }
|
|
103
|
-
warnedDeprecations.add(key);
|
|
104
|
-
}
|
|
105
|
-
if (deprecationId && !deprecationHandler.shouldWarn(deprecationId)) { return; }
|
|
106
|
-
|
|
107
|
-
logger.warn(
|
|
108
|
-
(new LessError(
|
|
109
|
-
{
|
|
110
|
-
index: index ?? parserInput.i,
|
|
111
|
-
filename: fileInfo.filename,
|
|
112
|
-
type: type ? `${type.toUpperCase()} WARNING` : 'WARNING',
|
|
113
|
-
message: msg
|
|
114
|
-
},
|
|
115
|
-
imports
|
|
116
|
-
)).toString()
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Warn that a bare `@variable` reference is being used in a non-value
|
|
122
|
-
* position (an at-rule prelude, name, or identifier), where it still
|
|
123
|
-
* resolves today but is deprecated in favour of `@{variable}` interpolation.
|
|
124
|
-
*
|
|
125
|
-
* @param {number} index - source position of the bare reference
|
|
126
|
-
*/
|
|
127
|
-
function warnBareAtRuleVariable(index) {
|
|
128
|
-
warn('A bare @variable in an at-rule prelude is deprecated. Use @{variable} interpolation instead.', index, 'DEPRECATED', 'variable-in-at-rule-prelude');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Whether a parsed entity is a bare `@variable` reference in a structural
|
|
133
|
-
* position, and so subject to the interpolation deprecation.
|
|
134
|
-
*
|
|
135
|
-
* A lookup such as `@map[key]` is parsed by `entities.variable()` into a
|
|
136
|
-
* `NamespaceValue` wrapping a `VariableCall`, not a `Variable`. Testing for
|
|
137
|
-
* `Variable` alone silently exempted every bare lookup from the deprecation.
|
|
138
|
-
*
|
|
139
|
-
* @param {{ type?: string } | undefined | null} e
|
|
140
|
-
* @returns {boolean}
|
|
141
|
-
*/
|
|
142
|
-
function isBareVariableReference(e) {
|
|
143
|
-
if (!e) { return false; }
|
|
144
|
-
return e.type === 'Variable' || e.type === 'VariableCall' || e.type === 'NamespaceValue';
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Numeric-leading variable names are a Less extension rather than valid CSS
|
|
149
|
-
* identifier syntax. Keep accepting them through Less 4, but make the Less 5
|
|
150
|
-
* migration visible at every actual variable reference or definition.
|
|
151
|
-
*
|
|
152
|
-
* @param {string} name - either an @-prefixed name or the name inside @{...}
|
|
153
|
-
* @param {number} index - source position of the variable token
|
|
154
|
-
*/
|
|
155
|
-
function warnNumericVariableName(name, index) {
|
|
156
|
-
if (/^(?:@@?)?[0-9]/.test(name)) {
|
|
157
|
-
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');
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* A lone '-' is not a CSS identifier. Less historically accepts it as a
|
|
163
|
-
* variable name; retain that in Less 4 only and warn on definitions,
|
|
164
|
-
* ordinary references, and interpolation references.
|
|
165
|
-
*
|
|
166
|
-
* @param {string} name - either @-/@@-, or the name inside @{...}
|
|
167
|
-
* @param {number} index - source position of the variable token
|
|
168
|
-
*/
|
|
169
|
-
function warnDashOnlyVariableName(name, index) {
|
|
170
|
-
const bareName = name.replace(/^@@?/, '');
|
|
171
|
-
if (bareName === '-') {
|
|
172
|
-
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');
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* A lone '-' is not a CSS identifier. Less historically accepts it as a mixin
|
|
178
|
-
* name after '.' or '#'; retain that in Less 4 only.
|
|
179
|
-
*
|
|
180
|
-
* @param {string} name
|
|
181
|
-
* @param {number} index
|
|
182
|
-
*/
|
|
183
|
-
function warnDashOnlyMixinName(name, index) {
|
|
184
|
-
if (name === '.-' || name === '#-') {
|
|
185
|
-
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');
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* CSS @charset is a source-header declaration, not a general dynamic at-rule.
|
|
191
|
-
* Less 4 keeps its historical interpolation behavior for compatibility, but
|
|
192
|
-
* makes each dynamic spelling visible before Less 5 rejects it.
|
|
193
|
-
*
|
|
194
|
-
* @param {import('../tree/node.js').default} value
|
|
195
|
-
* @param {number} index - source position of the @charset token
|
|
196
|
-
*/
|
|
197
|
-
function warnDynamicCharset(value, index) {
|
|
198
|
-
if (value instanceof tree.Variable ||
|
|
199
|
-
(value instanceof tree.Quoted &&
|
|
200
|
-
(value.containsVariables() || value.value.match(value.propRegex)))) {
|
|
201
|
-
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');
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function expect(arg, msg) {
|
|
206
|
-
// some older browsers return typeof 'function' for RegExp
|
|
207
|
-
const result = (arg instanceof Function) ? arg.call(parsers) : parserInput.$re(arg);
|
|
208
|
-
if (result) {
|
|
209
|
-
return result;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
error(msg || (typeof arg === 'string'
|
|
213
|
-
? `expected '${arg}' got '${parserInput.currentChar()}'`
|
|
214
|
-
: 'unexpected token'));
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Specialization of expect()
|
|
218
|
-
function expectChar(arg, msg) {
|
|
219
|
-
if (parserInput.$char(arg)) {
|
|
220
|
-
return arg;
|
|
221
|
-
}
|
|
222
|
-
error(msg || `expected '${arg}' got '${parserInput.currentChar()}'`);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function getDebugInfo(index) {
|
|
226
|
-
const filename = fileInfo.filename;
|
|
227
|
-
|
|
228
|
-
return {
|
|
229
|
-
lineNumber: utils.getLocation(index, parserInput.getInput()).line + 1,
|
|
230
|
-
fileName: filename
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Used after initial parsing to create nodes on the fly
|
|
236
|
-
*
|
|
237
|
-
* @param {String} str - string to parse
|
|
238
|
-
* @param {Array} parseList - array of parsers to run input through e.g. ["value", "important"]
|
|
239
|
-
* @param {Number} currentIndex - start number to begin indexing
|
|
240
|
-
* @param {Object} fileInfo - fileInfo to attach to created nodes
|
|
241
|
-
*/
|
|
242
|
-
function parseNode(str, parseList, callback) {
|
|
243
|
-
let result;
|
|
244
|
-
const returnNodes = [];
|
|
245
|
-
const parser = parserInput;
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
parser.start(str);
|
|
249
|
-
for (let x = 0, p; (p = parseList[x]); x++) {
|
|
250
|
-
result = parsers[p]();
|
|
251
|
-
returnNodes.push(result || null);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const endInfo = parser.end();
|
|
255
|
-
if (endInfo.isFinished) {
|
|
256
|
-
callback(null, returnNodes);
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
callback(true, null);
|
|
260
|
-
}
|
|
261
|
-
} catch (e) {
|
|
262
|
-
throw new LessError({
|
|
263
|
-
index: e.index + currentIndex,
|
|
264
|
-
message: e.message
|
|
265
|
-
}, imports, fileInfo.filename);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
//
|
|
270
|
-
// The Parser
|
|
271
|
-
//
|
|
272
|
-
return {
|
|
273
|
-
parserInput,
|
|
274
|
-
imports,
|
|
275
|
-
fileInfo,
|
|
276
|
-
parseNode,
|
|
277
|
-
//
|
|
278
|
-
// Parse an input string into an abstract syntax tree,
|
|
279
|
-
// @param str A string containing 'less' markup
|
|
280
|
-
// @param callback call `callback` when done.
|
|
281
|
-
// @param [additionalData] An optional map which can contains vars - a map (key, value) of variables to apply
|
|
282
|
-
//
|
|
283
|
-
parse: function (str, callback, additionalData) {
|
|
284
|
-
let root;
|
|
285
|
-
let err = null;
|
|
286
|
-
let globalVars;
|
|
287
|
-
let modifyVars;
|
|
288
|
-
let ignored;
|
|
289
|
-
let preText = '';
|
|
290
|
-
|
|
291
|
-
// Optionally disable @plugin parsing
|
|
292
|
-
if (additionalData && additionalData.disablePluginRule) {
|
|
293
|
-
parsers.plugin = function() {
|
|
294
|
-
var dir = parserInput.$re(/^@plugin?\s+/);
|
|
295
|
-
if (dir) {
|
|
296
|
-
error('@plugin statements are not allowed when disablePluginRule is set to true');
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
globalVars = (additionalData && additionalData.globalVars) ? `${Parser.serializeVars(additionalData.globalVars)}\n` : '';
|
|
302
|
-
modifyVars = (additionalData && additionalData.modifyVars) ? `\n${Parser.serializeVars(additionalData.modifyVars)}` : '';
|
|
303
|
-
|
|
304
|
-
if (context.pluginManager) {
|
|
305
|
-
const preProcessors = context.pluginManager.getPreProcessors();
|
|
306
|
-
for (let i = 0; i < preProcessors.length; i++) {
|
|
307
|
-
str = preProcessors[i].process(str, { context, imports, fileInfo });
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (globalVars || (additionalData && additionalData.banner)) {
|
|
312
|
-
preText = ((additionalData && additionalData.banner) ? additionalData.banner : '') + globalVars;
|
|
313
|
-
ignored = imports.contentsIgnoredChars;
|
|
314
|
-
ignored[fileInfo.filename] = ignored[fileInfo.filename] || 0;
|
|
315
|
-
ignored[fileInfo.filename] += preText.length;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
str = str.replace(/\r\n?/g, '\n');
|
|
319
|
-
// Remove potential UTF Byte Order Mark
|
|
320
|
-
str = preText + str.replace(/^\uFEFF/, '') + modifyVars;
|
|
321
|
-
imports.contents[fileInfo.filename] = str;
|
|
322
|
-
|
|
323
|
-
// Start with the primary rule.
|
|
324
|
-
// The whole syntax tree is held under a Ruleset node,
|
|
325
|
-
// with the `root` property set to true, so no `{}` are
|
|
326
|
-
// output. The callback is called when the input is parsed.
|
|
327
|
-
try {
|
|
328
|
-
parserInput.start(str);
|
|
329
|
-
|
|
330
|
-
tree.Node.prototype.parse = this;
|
|
331
|
-
root = new tree.Ruleset(null, this.parsers.primary());
|
|
332
|
-
tree.Node.prototype.rootNode = root;
|
|
333
|
-
root.root = true;
|
|
334
|
-
root.firstRoot = true;
|
|
335
|
-
root.functionRegistry = functionRegistry.inherit();
|
|
336
|
-
|
|
337
|
-
} catch (e) {
|
|
338
|
-
return callback(new LessError(e, imports, fileInfo.filename));
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// If `i` is smaller than the `input.length - 1`,
|
|
342
|
-
// it means the parser wasn't able to parse the whole
|
|
343
|
-
// string, so we've got a parsing error.
|
|
344
|
-
//
|
|
345
|
-
// We try to extract a \n delimited string,
|
|
346
|
-
// showing the line where the parse error occurred.
|
|
347
|
-
// We split it up into two parts (the part which parsed,
|
|
348
|
-
// and the part which didn't), so we can color them differently.
|
|
349
|
-
const endInfo = parserInput.end();
|
|
350
|
-
if (!endInfo.isFinished) {
|
|
351
|
-
|
|
352
|
-
let message = endInfo.furthestPossibleErrorMessage;
|
|
353
|
-
|
|
354
|
-
if (!message) {
|
|
355
|
-
message = 'Unrecognised input';
|
|
356
|
-
if (endInfo.furthestChar === '}') {
|
|
357
|
-
message += '. Possibly missing opening \'{\'';
|
|
358
|
-
} else if (endInfo.furthestChar === ')') {
|
|
359
|
-
message += '. Possibly missing opening \'(\'';
|
|
360
|
-
} else if (endInfo.furthestReachedEnd) {
|
|
361
|
-
message += '. Possibly missing something';
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
err = new LessError({
|
|
366
|
-
type: 'Parse',
|
|
367
|
-
message,
|
|
368
|
-
index: endInfo.furthest,
|
|
369
|
-
filename: fileInfo.filename
|
|
370
|
-
}, imports);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const finish = e => {
|
|
374
|
-
e = err || e || imports.error;
|
|
375
|
-
|
|
376
|
-
if (e) {
|
|
377
|
-
if (!(e instanceof LessError)) {
|
|
378
|
-
e = new LessError(e, imports, fileInfo.filename);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
return callback(e);
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
return callback(null, root);
|
|
385
|
-
}
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
if (context.processImports !== false) {
|
|
389
|
-
new visitors.ImportVisitor(imports, finish)
|
|
390
|
-
.run(root);
|
|
391
|
-
} else {
|
|
392
|
-
return finish();
|
|
393
|
-
}
|
|
394
|
-
},
|
|
395
|
-
|
|
396
|
-
//
|
|
397
|
-
// Here in, the parsing rules/functions
|
|
398
|
-
//
|
|
399
|
-
// The basic structure of the syntax tree generated is as follows:
|
|
400
|
-
//
|
|
401
|
-
// Ruleset -> Declaration -> Value -> Expression -> Entity
|
|
402
|
-
//
|
|
403
|
-
// Here's some Less code:
|
|
404
|
-
//
|
|
405
|
-
// .class {
|
|
406
|
-
// color: #fff;
|
|
407
|
-
// border: 1px solid #000;
|
|
408
|
-
// width: @w + 4px;
|
|
409
|
-
// > .child {...}
|
|
410
|
-
// }
|
|
411
|
-
//
|
|
412
|
-
// And here's what the parse tree might look like:
|
|
413
|
-
//
|
|
414
|
-
// Ruleset (Selector '.class', [
|
|
415
|
-
// Declaration ("color", Value ([Expression [Color #fff]]))
|
|
416
|
-
// Declaration ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]]))
|
|
417
|
-
// Declaration ("width", Value ([Expression [Operation " + " [Variable "@w"][Dimension 4px]]]))
|
|
418
|
-
// Ruleset (Selector [Element '>', '.child'], [...])
|
|
419
|
-
// ])
|
|
420
|
-
//
|
|
421
|
-
// In general, most rules will try to parse a token with the `$re()` function, and if the return
|
|
422
|
-
// value is truly, will return a new node, of the relevant type. Sometimes, we need to check
|
|
423
|
-
// first, before parsing, that's when we use `peek()`.
|
|
424
|
-
//
|
|
425
|
-
parsers: parsers = {
|
|
426
|
-
//
|
|
427
|
-
// The `primary` rule is the *entry* and *exit* point of the parser.
|
|
428
|
-
// The rules here can appear at any level of the parse tree.
|
|
429
|
-
//
|
|
430
|
-
// The recursive nature of the grammar is an interplay between the `block`
|
|
431
|
-
// rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule,
|
|
432
|
-
// as represented by this simplified grammar:
|
|
433
|
-
//
|
|
434
|
-
// primary → (ruleset | declaration)+
|
|
435
|
-
// ruleset → selector+ block
|
|
436
|
-
// block → '{' primary '}'
|
|
437
|
-
//
|
|
438
|
-
// Only at one point is the primary rule not called from the
|
|
439
|
-
// block rule: at the root level.
|
|
440
|
-
//
|
|
441
|
-
primary: function () {
|
|
442
|
-
const mixin = this.mixin;
|
|
443
|
-
let root = [];
|
|
444
|
-
let node;
|
|
445
|
-
|
|
446
|
-
while (true) {
|
|
447
|
-
while (true) {
|
|
448
|
-
node = this.comment();
|
|
449
|
-
if (!node) { break; }
|
|
450
|
-
root.push(node);
|
|
451
|
-
}
|
|
452
|
-
// always process comments before deciding if finished
|
|
453
|
-
if (parserInput.finished) {
|
|
454
|
-
break;
|
|
455
|
-
}
|
|
456
|
-
if (parserInput.peek('}')) {
|
|
457
|
-
break;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
node = this.extendRule();
|
|
461
|
-
if (node) {
|
|
462
|
-
root = root.concat(node);
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
node = mixin.definition() || this.declaration() || mixin.call(false, false) ||
|
|
467
|
-
this.ruleset() || this.variableCall() || this.entities.call() || this.atrule();
|
|
468
|
-
if (node) {
|
|
469
|
-
root.push(node);
|
|
470
|
-
} else {
|
|
471
|
-
let foundSemiColon = false;
|
|
472
|
-
while (parserInput.$char(';')) {
|
|
473
|
-
foundSemiColon = true;
|
|
474
|
-
}
|
|
475
|
-
if (!foundSemiColon) {
|
|
476
|
-
break;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
return root;
|
|
482
|
-
},
|
|
483
|
-
|
|
484
|
-
// comments are collected by the main parsing mechanism and then assigned to nodes
|
|
485
|
-
// where the current structure allows it
|
|
486
|
-
comment: function () {
|
|
487
|
-
if (parserInput.commentStore.length) {
|
|
488
|
-
const comment = parserInput.commentStore.shift();
|
|
489
|
-
return new(tree.Comment)(comment.text, comment.isLineComment, comment.index + currentIndex, fileInfo);
|
|
490
|
-
}
|
|
491
|
-
},
|
|
492
|
-
|
|
493
|
-
//
|
|
494
|
-
// Entities are tokens which can be found inside an Expression
|
|
495
|
-
//
|
|
496
|
-
entities: {
|
|
497
|
-
mixinLookup: function() {
|
|
498
|
-
return parsers.mixin.call(true, true);
|
|
499
|
-
},
|
|
500
|
-
//
|
|
501
|
-
// A string, which supports escaping " and '
|
|
502
|
-
//
|
|
503
|
-
// "milky way" 'he\'s the one!'
|
|
504
|
-
//
|
|
505
|
-
quoted: function (forceEscaped) {
|
|
506
|
-
let str;
|
|
507
|
-
const index = parserInput.i;
|
|
508
|
-
let isEscaped = false;
|
|
509
|
-
|
|
510
|
-
parserInput.save();
|
|
511
|
-
if (parserInput.$char('~')) {
|
|
512
|
-
isEscaped = true;
|
|
513
|
-
} else if (forceEscaped) {
|
|
514
|
-
parserInput.restore();
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
str = parserInput.$quoted();
|
|
519
|
-
if (!str) {
|
|
520
|
-
parserInput.restore();
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
parserInput.forget();
|
|
524
|
-
|
|
525
|
-
return new(tree.Quoted)(str.charAt(0), str.slice(1, -1), isEscaped, index + currentIndex, fileInfo);
|
|
526
|
-
},
|
|
527
|
-
|
|
528
|
-
//
|
|
529
|
-
// A catch-all word, such as:
|
|
530
|
-
//
|
|
531
|
-
// black border-collapse
|
|
532
|
-
//
|
|
533
|
-
keyword: function () {
|
|
534
|
-
const k = parserInput.$char('%') || parserInput.$re(/^\[?(?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+\]?/);
|
|
535
|
-
if (k) {
|
|
536
|
-
return tree.Color.fromKeyword(k) || new(tree.Keyword)(k);
|
|
537
|
-
}
|
|
538
|
-
},
|
|
539
|
-
|
|
540
|
-
//
|
|
541
|
-
// A function call
|
|
542
|
-
//
|
|
543
|
-
// rgb(255, 0, 255)
|
|
544
|
-
//
|
|
545
|
-
// The arguments are parsed with the `entities.arguments` parser.
|
|
546
|
-
//
|
|
547
|
-
call: function () {
|
|
548
|
-
let name;
|
|
549
|
-
let args;
|
|
550
|
-
let func;
|
|
551
|
-
const index = parserInput.i;
|
|
552
|
-
|
|
553
|
-
// http://jsperf.com/case-insensitive-regex-vs-strtolower-then-regex/18
|
|
554
|
-
if (parserInput.peek(/^url\(/i)) {
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
parserInput.save();
|
|
559
|
-
|
|
560
|
-
name = parserInput.$re(/^([\w-]+|%|~|progid:[\w.]+)\(/);
|
|
561
|
-
if (!name) {
|
|
562
|
-
parserInput.forget();
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
name = name[1];
|
|
567
|
-
func = this.customFuncCall(name);
|
|
568
|
-
if (func) {
|
|
569
|
-
args = func.parse();
|
|
570
|
-
if (args && func.stop) {
|
|
571
|
-
parserInput.forget();
|
|
572
|
-
return args;
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
args = this.arguments(args);
|
|
577
|
-
|
|
578
|
-
if (!parserInput.$char(')')) {
|
|
579
|
-
parserInput.restore('Could not parse call arguments or missing \')\'');
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
parserInput.forget();
|
|
584
|
-
|
|
585
|
-
return new(tree.Call)(name, args, index + currentIndex, fileInfo);
|
|
586
|
-
},
|
|
587
|
-
|
|
588
|
-
declarationCall: function () {
|
|
589
|
-
let validCall;
|
|
590
|
-
let args;
|
|
591
|
-
const index = parserInput.i;
|
|
592
|
-
|
|
593
|
-
parserInput.save();
|
|
594
|
-
|
|
595
|
-
validCall = parserInput.$re(/^[\w]+\(/);
|
|
596
|
-
if (!validCall) {
|
|
597
|
-
parserInput.forget();
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
validCall = validCall.substring(0, validCall.length - 1);
|
|
602
|
-
|
|
603
|
-
// CSS at-rule keywords should never be parsed as declaration calls
|
|
604
|
-
if (/^(and|or|not|only|layer)$/i.test(validCall)) {
|
|
605
|
-
parserInput.restore();
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
let rule = this.ruleProperty();
|
|
610
|
-
let value;
|
|
611
|
-
|
|
612
|
-
if (rule) {
|
|
613
|
-
value = this.value();
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
if (rule && value) {
|
|
617
|
-
args = [new (tree.Declaration)(rule, value, null, null, parserInput.i + currentIndex, fileInfo, true)];
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
if (!parserInput.$char(')')) {
|
|
621
|
-
parserInput.restore('Could not parse call arguments or missing \')\'');
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
parserInput.forget();
|
|
626
|
-
|
|
627
|
-
return new(tree.Call)(validCall, args, index + currentIndex, fileInfo);
|
|
628
|
-
},
|
|
629
|
-
|
|
630
|
-
//
|
|
631
|
-
// Parsing rules for functions with non-standard args, e.g.:
|
|
632
|
-
//
|
|
633
|
-
// boolean(not(2 > 1))
|
|
634
|
-
//
|
|
635
|
-
// This is a quick prototype, to be modified/improved when
|
|
636
|
-
// more custom-parsed funcs come (e.g. `selector(...)`)
|
|
637
|
-
//
|
|
638
|
-
|
|
639
|
-
customFuncCall: function (name) {
|
|
640
|
-
/* Ideally the table is to be moved out of here for faster perf.,
|
|
641
|
-
but it's quite tricky since it relies on all these `parsers`
|
|
642
|
-
and `expect` available only here */
|
|
643
|
-
return {
|
|
644
|
-
alpha: f(parsers.ieAlpha, true),
|
|
645
|
-
boolean: f(condition),
|
|
646
|
-
'if': f(condition)
|
|
647
|
-
}[name.toLowerCase()];
|
|
648
|
-
|
|
649
|
-
function f(parse, stop) {
|
|
650
|
-
return {
|
|
651
|
-
parse, // parsing function
|
|
652
|
-
stop // when true - stop after parse() and return its result,
|
|
653
|
-
// otherwise continue for plain args
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
function condition() {
|
|
658
|
-
return [expect(() => parsers.condition(false, true), 'expected condition')];
|
|
659
|
-
}
|
|
660
|
-
},
|
|
661
|
-
|
|
662
|
-
arguments: function (prevArgs) {
|
|
663
|
-
let argsComma = prevArgs || [];
|
|
664
|
-
const argsSemiColon = [];
|
|
665
|
-
let isSemiColonSeparated;
|
|
666
|
-
let value;
|
|
667
|
-
|
|
668
|
-
parserInput.save();
|
|
669
|
-
|
|
670
|
-
while (true) {
|
|
671
|
-
if (prevArgs) {
|
|
672
|
-
prevArgs = false;
|
|
673
|
-
} else {
|
|
674
|
-
value = parsers.detachedRuleset() || this.assignment() || parsers.expression();
|
|
675
|
-
if (!value) {
|
|
676
|
-
break;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
if (value.value && value.value.length == 1) {
|
|
680
|
-
value = value.value[0];
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
argsComma.push(value);
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
if (parserInput.$char(',')) {
|
|
687
|
-
continue;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
if (parserInput.$char(';') || isSemiColonSeparated) {
|
|
691
|
-
isSemiColonSeparated = true;
|
|
692
|
-
value = (argsComma.length < 1) ? argsComma[0]
|
|
693
|
-
: new tree.Value(argsComma);
|
|
694
|
-
argsSemiColon.push(value);
|
|
695
|
-
argsComma = [];
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
parserInput.forget();
|
|
700
|
-
return isSemiColonSeparated ? argsSemiColon : argsComma;
|
|
701
|
-
},
|
|
702
|
-
literal: function () {
|
|
703
|
-
return this.dimension() ||
|
|
704
|
-
this.color() ||
|
|
705
|
-
this.quoted() ||
|
|
706
|
-
this.unicodeDescriptor();
|
|
707
|
-
},
|
|
708
|
-
|
|
709
|
-
// Assignments are argument entities for calls.
|
|
710
|
-
// They are present in ie filter properties as shown below.
|
|
711
|
-
//
|
|
712
|
-
// filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* )
|
|
713
|
-
//
|
|
714
|
-
|
|
715
|
-
assignment: function () {
|
|
716
|
-
let key;
|
|
717
|
-
let value;
|
|
718
|
-
parserInput.save();
|
|
719
|
-
key = parserInput.$re(/^\w+(?=\s?=)/i);
|
|
720
|
-
if (!key) {
|
|
721
|
-
parserInput.restore();
|
|
722
|
-
return;
|
|
723
|
-
}
|
|
724
|
-
if (!parserInput.$char('=')) {
|
|
725
|
-
parserInput.restore();
|
|
726
|
-
return;
|
|
727
|
-
}
|
|
728
|
-
value = parsers.entity();
|
|
729
|
-
if (value) {
|
|
730
|
-
parserInput.forget();
|
|
731
|
-
return new(tree.Assignment)(key, value);
|
|
732
|
-
} else {
|
|
733
|
-
parserInput.restore();
|
|
734
|
-
}
|
|
735
|
-
},
|
|
736
|
-
|
|
737
|
-
//
|
|
738
|
-
// Parse url() tokens
|
|
739
|
-
//
|
|
740
|
-
// We use a specific rule for urls, because they don't really behave like
|
|
741
|
-
// standard function calls. The difference is that the argument doesn't have
|
|
742
|
-
// to be enclosed within a string, so it can't be parsed as an Expression.
|
|
743
|
-
//
|
|
744
|
-
url: function () {
|
|
745
|
-
let value;
|
|
746
|
-
const index = parserInput.i;
|
|
747
|
-
|
|
748
|
-
parserInput.autoCommentAbsorb = false;
|
|
749
|
-
|
|
750
|
-
if (!parserInput.$str('url(')) {
|
|
751
|
-
parserInput.autoCommentAbsorb = true;
|
|
752
|
-
return;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
value = this.quoted() || this.variable() || this.property() ||
|
|
756
|
-
parserInput.$re(/^(?:(?:\\[()'"])|[^()'"])+/) || '';
|
|
757
|
-
|
|
758
|
-
// An unquoted url() body is otherwise raw text wrapped in an
|
|
759
|
-
// Anonymous node, which never substitutes anything — so
|
|
760
|
-
// `url(@{path}/a.png)` used to emit the braces verbatim while the
|
|
761
|
-
// quoted form resolved. Hand text containing interpolation to an
|
|
762
|
-
// escaped Quoted so both spellings resolve identically.
|
|
763
|
-
//
|
|
764
|
-
// The empty quote string matters: `URL.eval` escapes a rewritten
|
|
765
|
-
// rootpath only for unquoted values, so a real quote character here
|
|
766
|
-
// would suppress that and emit `url(a(b)/x.png)` unescaped. Escaped
|
|
767
|
-
// means no quote is written to the output either way.
|
|
768
|
-
if (typeof value === 'string' && hasInterpolation(value)) {
|
|
769
|
-
value = new(tree.Quoted)('', value, true, index + currentIndex, fileInfo);
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
parserInput.autoCommentAbsorb = true;
|
|
773
|
-
|
|
774
|
-
expectChar(')');
|
|
775
|
-
|
|
776
|
-
return new(tree.URL)((value.value !== undefined ||
|
|
777
|
-
value instanceof tree.Variable ||
|
|
778
|
-
value instanceof tree.Property) ?
|
|
779
|
-
value : new(tree.Anonymous)(value, index), index + currentIndex, fileInfo);
|
|
780
|
-
},
|
|
781
|
-
|
|
782
|
-
//
|
|
783
|
-
// A Variable entity, such as `@fink`, in
|
|
784
|
-
//
|
|
785
|
-
// width: @fink + 2px
|
|
786
|
-
//
|
|
787
|
-
// We use a different parser for variable definitions,
|
|
788
|
-
// see `parsers.variable`.
|
|
789
|
-
//
|
|
790
|
-
variable: function () {
|
|
791
|
-
let ch;
|
|
792
|
-
let name;
|
|
793
|
-
const index = parserInput.i;
|
|
794
|
-
|
|
795
|
-
parserInput.save();
|
|
796
|
-
if (parserInput.currentChar() === '@' && (name = parserInput.$re(/^@@?[\w-]+/))) {
|
|
797
|
-
warnNumericVariableName(name, index);
|
|
798
|
-
warnDashOnlyVariableName(name, index);
|
|
799
|
-
ch = parserInput.currentChar();
|
|
800
|
-
if ((ch === '(' && !parserInput.prevChar().match(/^\s/))
|
|
801
|
-
|| (ch === '[' && !parserInput.prevChar().match(/^\s/))) {
|
|
802
|
-
// this may be a VariableCall lookup
|
|
803
|
-
const result = parsers.variableCall(name);
|
|
804
|
-
if (result) {
|
|
805
|
-
parserInput.forget();
|
|
806
|
-
return result;
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
parserInput.forget();
|
|
810
|
-
return new(tree.Variable)(name, index + currentIndex, fileInfo);
|
|
811
|
-
}
|
|
812
|
-
parserInput.restore();
|
|
813
|
-
},
|
|
814
|
-
|
|
815
|
-
// A variable entity using the protective {} e.g. @{var}, optionally
|
|
816
|
-
// followed by a lookup chain e.g. @{map[key]} or @{map[@a][b]}.
|
|
817
|
-
//
|
|
818
|
-
// The chain is consumed by `mixin.ruleLookups()` rather than matched
|
|
819
|
-
// here, so the interpolated form shares one grammar with the bare
|
|
820
|
-
// `@map[key]` form instead of re-implementing it.
|
|
821
|
-
variableCurly: function () {
|
|
822
|
-
let curly;
|
|
823
|
-
const index = parserInput.i;
|
|
824
|
-
|
|
825
|
-
if (parserInput.currentChar() !== '@') {
|
|
826
|
-
return;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
parserInput.save();
|
|
830
|
-
if (!(curly = parserInput.$re(/^@\{([\w-]+)/))) {
|
|
831
|
-
parserInput.restore();
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
const name = curly[1];
|
|
836
|
-
const lookups = parsers.mixin.ruleLookups();
|
|
837
|
-
|
|
838
|
-
if (!parserInput.$char('}')) {
|
|
839
|
-
parserInput.restore();
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
parserInput.forget();
|
|
844
|
-
warnNumericVariableName(name, index);
|
|
845
|
-
warnDashOnlyVariableName(name, index);
|
|
846
|
-
|
|
847
|
-
if (!lookups) {
|
|
848
|
-
return new(tree.Variable)(`@${name}`, index + currentIndex, fileInfo);
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
const call = new(tree.VariableCall)(`@${name}`, index + currentIndex, fileInfo);
|
|
852
|
-
return new(tree.NamespaceValue)(call, lookups, index + currentIndex, fileInfo);
|
|
853
|
-
},
|
|
854
|
-
//
|
|
855
|
-
// A Property accessor, such as `$color`, in
|
|
856
|
-
//
|
|
857
|
-
// background-color: $color
|
|
858
|
-
//
|
|
859
|
-
property: function () {
|
|
860
|
-
let name;
|
|
861
|
-
const index = parserInput.i;
|
|
862
|
-
|
|
863
|
-
if (parserInput.currentChar() === '$' && (name = parserInput.$re(/^\$[\w-]+/))) {
|
|
864
|
-
return new(tree.Property)(name, index + currentIndex, fileInfo);
|
|
865
|
-
}
|
|
866
|
-
},
|
|
867
|
-
|
|
868
|
-
//
|
|
869
|
-
// A Hexadecimal color
|
|
870
|
-
//
|
|
871
|
-
// #4F3C2F
|
|
872
|
-
//
|
|
873
|
-
// `rgb` and `hsl` colors are parsed through the `entities.call` parser.
|
|
874
|
-
//
|
|
875
|
-
color: function () {
|
|
876
|
-
let rgb;
|
|
877
|
-
parserInput.save();
|
|
878
|
-
|
|
879
|
-
if (parserInput.currentChar() === '#' && (rgb = parserInput.$re(/^#([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3,4})([\w.#[])?/))) {
|
|
880
|
-
if (!rgb[2]) {
|
|
881
|
-
parserInput.forget();
|
|
882
|
-
return new(tree.Color)(rgb[1], undefined, rgb[0]);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
parserInput.restore();
|
|
886
|
-
},
|
|
887
|
-
|
|
888
|
-
colorKeyword: function () {
|
|
889
|
-
parserInput.save();
|
|
890
|
-
const autoCommentAbsorb = parserInput.autoCommentAbsorb;
|
|
891
|
-
parserInput.autoCommentAbsorb = false;
|
|
892
|
-
const k = parserInput.$re(/^[_A-Za-z-][_A-Za-z0-9-]+/);
|
|
893
|
-
parserInput.autoCommentAbsorb = autoCommentAbsorb;
|
|
894
|
-
if (!k) {
|
|
895
|
-
parserInput.forget();
|
|
896
|
-
return;
|
|
897
|
-
}
|
|
898
|
-
parserInput.restore();
|
|
899
|
-
const color = tree.Color.fromKeyword(k);
|
|
900
|
-
if (color) {
|
|
901
|
-
parserInput.$str(k);
|
|
902
|
-
return color;
|
|
903
|
-
}
|
|
904
|
-
},
|
|
905
|
-
|
|
906
|
-
//
|
|
907
|
-
// A Dimension, that is, a number and a unit
|
|
908
|
-
//
|
|
909
|
-
// 0.5em 95%
|
|
910
|
-
//
|
|
911
|
-
dimension: function () {
|
|
912
|
-
if (parserInput.peekNotNumeric()) {
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
const value = parserInput.$re(/^([+-]?\d*\.?\d+)(%|[a-z_]+)?/i);
|
|
917
|
-
if (value) {
|
|
918
|
-
return new(tree.Dimension)(value[1], value[2]);
|
|
919
|
-
}
|
|
920
|
-
},
|
|
921
|
-
|
|
922
|
-
//
|
|
923
|
-
// A unicode descriptor, as is used in unicode-range
|
|
924
|
-
//
|
|
925
|
-
// U+0?? or U+00A1-00A9
|
|
926
|
-
//
|
|
927
|
-
unicodeDescriptor: function () {
|
|
928
|
-
let ud;
|
|
929
|
-
|
|
930
|
-
ud = parserInput.$re(/^U\+[0-9a-fA-F?]+(-[0-9a-fA-F?]+)?/);
|
|
931
|
-
if (ud) {
|
|
932
|
-
return new(tree.UnicodeDescriptor)(ud[0]);
|
|
933
|
-
}
|
|
934
|
-
},
|
|
935
|
-
|
|
936
|
-
//
|
|
937
|
-
// JavaScript code to be evaluated
|
|
938
|
-
//
|
|
939
|
-
// `window.location.href`
|
|
940
|
-
//
|
|
941
|
-
javascript: function () {
|
|
942
|
-
let js;
|
|
943
|
-
const index = parserInput.i;
|
|
944
|
-
|
|
945
|
-
parserInput.save();
|
|
946
|
-
|
|
947
|
-
const escape = parserInput.$char('~');
|
|
948
|
-
const jsQuote = parserInput.$char('`');
|
|
949
|
-
|
|
950
|
-
if (!jsQuote) {
|
|
951
|
-
parserInput.restore();
|
|
952
|
-
return;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
js = parserInput.$re(/^[^`]*`/);
|
|
956
|
-
if (js) {
|
|
957
|
-
warn('Inline JavaScript evaluation (backtick expressions) is deprecated and will be removed in Less 5.x. Use Less functions or custom plugins instead.', index, 'DEPRECATED', 'js-eval');
|
|
958
|
-
parserInput.forget();
|
|
959
|
-
return new(tree.JavaScript)(js.slice(0, -1), Boolean(escape), index + currentIndex, fileInfo);
|
|
960
|
-
}
|
|
961
|
-
parserInput.restore('invalid javascript definition');
|
|
962
|
-
}
|
|
963
|
-
},
|
|
964
|
-
|
|
965
|
-
//
|
|
966
|
-
// The variable part of a variable definition. Used in the `rule` parser
|
|
967
|
-
//
|
|
968
|
-
// @fink:
|
|
969
|
-
//
|
|
970
|
-
variable: function () {
|
|
971
|
-
let name;
|
|
972
|
-
|
|
973
|
-
if (parserInput.currentChar() === '@' && (name = parserInput.$re(/^(@[\w-]+)\s*:/))) {
|
|
974
|
-
warnNumericVariableName(name[1], parserInput.i - name[0].length);
|
|
975
|
-
warnDashOnlyVariableName(name[1], parserInput.i - name[0].length);
|
|
976
|
-
return name[1];
|
|
977
|
-
}
|
|
978
|
-
},
|
|
979
|
-
|
|
980
|
-
//
|
|
981
|
-
// Call a variable value to retrieve a detached ruleset
|
|
982
|
-
// or a value from a detached ruleset's rules.
|
|
983
|
-
//
|
|
984
|
-
// @fink();
|
|
985
|
-
// @fink;
|
|
986
|
-
// color: @fink[@color];
|
|
987
|
-
//
|
|
988
|
-
variableCall: function (parsedName) {
|
|
989
|
-
let lookups;
|
|
990
|
-
const i = parserInput.i;
|
|
991
|
-
const inValue = !!parsedName;
|
|
992
|
-
let name = parsedName;
|
|
993
|
-
|
|
994
|
-
parserInput.save();
|
|
995
|
-
|
|
996
|
-
if (name || (parserInput.currentChar() === '@'
|
|
997
|
-
&& (name = parserInput.$re(/^(@[\w-]+)(\(\s*\))?/)))) {
|
|
998
|
-
|
|
999
|
-
lookups = this.mixin.ruleLookups();
|
|
1000
|
-
|
|
1001
|
-
if (!lookups && ((inValue && parserInput.$str('()') !== '()') || (name[2] !== '()'))) {
|
|
1002
|
-
parserInput.restore('Missing \'[...]\' lookup in variable call');
|
|
1003
|
-
return;
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
if (!inValue) {
|
|
1007
|
-
warnNumericVariableName(name[1], i);
|
|
1008
|
-
warnDashOnlyVariableName(name[1], i);
|
|
1009
|
-
name = name[1];
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
const call = new tree.VariableCall(name, i, fileInfo);
|
|
1013
|
-
if (!inValue && parsers.end()) {
|
|
1014
|
-
parserInput.forget();
|
|
1015
|
-
return call;
|
|
1016
|
-
}
|
|
1017
|
-
else {
|
|
1018
|
-
parserInput.forget();
|
|
1019
|
-
return new tree.NamespaceValue(call, lookups, i, fileInfo);
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
parserInput.restore();
|
|
1024
|
-
},
|
|
1025
|
-
|
|
1026
|
-
//
|
|
1027
|
-
// extend syntax - used to extend selectors
|
|
1028
|
-
//
|
|
1029
|
-
extend: function(isRule) {
|
|
1030
|
-
let elements;
|
|
1031
|
-
let e;
|
|
1032
|
-
const index = parserInput.i;
|
|
1033
|
-
let option;
|
|
1034
|
-
let extendList;
|
|
1035
|
-
let extend;
|
|
1036
|
-
|
|
1037
|
-
if (!parserInput.$str(isRule ? '&:extend(' : ':extend(')) {
|
|
1038
|
-
return;
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
do {
|
|
1042
|
-
option = null;
|
|
1043
|
-
elements = null;
|
|
1044
|
-
let first = true;
|
|
1045
|
-
while (!(option = parserInput.$re(/^(!?all)(?=\s*(\)|,))/))) {
|
|
1046
|
-
e = this.element();
|
|
1047
|
-
|
|
1048
|
-
if (!e) {
|
|
1049
|
-
break;
|
|
1050
|
-
}
|
|
1051
|
-
/**
|
|
1052
|
-
* @note - This will not catch selectors in pseudos like :is() and :where() because
|
|
1053
|
-
* they don't currently parse their contents as selectors.
|
|
1054
|
-
*/
|
|
1055
|
-
if (!first && e.combinator.value) {
|
|
1056
|
-
warn('Targeting complex selectors can have unexpected behavior, and this behavior may change in the future.', index)
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
first = false;
|
|
1060
|
-
if (elements) {
|
|
1061
|
-
elements.push(e);
|
|
1062
|
-
} else {
|
|
1063
|
-
elements = [ e ];
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
option = option && option[1];
|
|
1068
|
-
if (!elements) {
|
|
1069
|
-
error('Missing target selector for :extend().');
|
|
1070
|
-
}
|
|
1071
|
-
extend = new(tree.Extend)(new(tree.Selector)(elements), option, index + currentIndex, fileInfo);
|
|
1072
|
-
if (extendList) {
|
|
1073
|
-
extendList.push(extend);
|
|
1074
|
-
} else {
|
|
1075
|
-
extendList = [ extend ];
|
|
1076
|
-
}
|
|
1077
|
-
} while (parserInput.$char(','));
|
|
1078
|
-
|
|
1079
|
-
expect(/^\)/);
|
|
1080
|
-
|
|
1081
|
-
if (isRule) {
|
|
1082
|
-
expect(/^;/);
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
return extendList;
|
|
1086
|
-
},
|
|
1087
|
-
|
|
1088
|
-
//
|
|
1089
|
-
// extendRule - used in a rule to extend all the parent selectors
|
|
1090
|
-
//
|
|
1091
|
-
extendRule: function() {
|
|
1092
|
-
return this.extend(true);
|
|
1093
|
-
},
|
|
1094
|
-
|
|
1095
|
-
//
|
|
1096
|
-
// Mixins
|
|
1097
|
-
//
|
|
1098
|
-
mixin: {
|
|
1099
|
-
//
|
|
1100
|
-
// A Mixin call, with an optional argument list
|
|
1101
|
-
//
|
|
1102
|
-
// #mixins > .square(#fff);
|
|
1103
|
-
// #mixins.square(#fff);
|
|
1104
|
-
// .rounded(4px, black);
|
|
1105
|
-
// .button;
|
|
1106
|
-
//
|
|
1107
|
-
// We can lookup / return a value using the lookup syntax:
|
|
1108
|
-
//
|
|
1109
|
-
// color: #mixin.square(#fff)[@color];
|
|
1110
|
-
//
|
|
1111
|
-
// The `while` loop is there because mixins can be
|
|
1112
|
-
// namespaced, but we only support the child and descendant
|
|
1113
|
-
// selector for now.
|
|
1114
|
-
//
|
|
1115
|
-
call: function (inValue, getLookup) {
|
|
1116
|
-
const s = parserInput.currentChar();
|
|
1117
|
-
let important = false;
|
|
1118
|
-
let lookups;
|
|
1119
|
-
const index = parserInput.i;
|
|
1120
|
-
let elements;
|
|
1121
|
-
let args;
|
|
1122
|
-
let hasParens;
|
|
1123
|
-
let parensIndex;
|
|
1124
|
-
let parensWS = false;
|
|
1125
|
-
|
|
1126
|
-
if (s !== '.' && s !== '#') { return; }
|
|
1127
|
-
|
|
1128
|
-
parserInput.save(); // stop us absorbing part of an invalid selector
|
|
1129
|
-
|
|
1130
|
-
elements = this.elements();
|
|
1131
|
-
|
|
1132
|
-
if (elements) {
|
|
1133
|
-
parensIndex = parserInput.i;
|
|
1134
|
-
parensWS = parserInput.isWhitespace(-1);
|
|
1135
|
-
if (parserInput.$char('(')) {
|
|
1136
|
-
args = this.args(true).args;
|
|
1137
|
-
expectChar(')');
|
|
1138
|
-
hasParens = true;
|
|
1139
|
-
if (parensWS) {
|
|
1140
|
-
warn('Whitespace between a mixin name and parentheses for a mixin call is deprecated', parensIndex, 'DEPRECATED', 'mixin-call-whitespace');
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
if (getLookup !== false) {
|
|
1145
|
-
lookups = this.ruleLookups();
|
|
1146
|
-
}
|
|
1147
|
-
if (getLookup === true && !lookups) {
|
|
1148
|
-
parserInput.restore();
|
|
1149
|
-
return;
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
|
-
if (inValue && !lookups && !hasParens) {
|
|
1153
|
-
// This isn't a valid in-value mixin call
|
|
1154
|
-
parserInput.restore();
|
|
1155
|
-
return;
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
if (!inValue && parsers.important()) {
|
|
1159
|
-
important = true;
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
if (inValue || parsers.end()) {
|
|
1163
|
-
parserInput.forget();
|
|
1164
|
-
const mixin = new(tree.mixin.Call)(elements, args, index + currentIndex, fileInfo, !lookups && important);
|
|
1165
|
-
for (const element of elements) {
|
|
1166
|
-
warnDashOnlyMixinName(element.value, element._index - currentIndex);
|
|
1167
|
-
}
|
|
1168
|
-
if (lookups) {
|
|
1169
|
-
return new tree.NamespaceValue(mixin, lookups);
|
|
1170
|
-
}
|
|
1171
|
-
else {
|
|
1172
|
-
if (!hasParens) {
|
|
1173
|
-
warn('Calling a mixin without parentheses is deprecated', parensIndex, 'DEPRECATED', 'mixin-call-no-parens');
|
|
1174
|
-
}
|
|
1175
|
-
return mixin;
|
|
1176
|
-
}
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
parserInput.restore();
|
|
1181
|
-
},
|
|
1182
|
-
/**
|
|
1183
|
-
* Matching elements for mixins
|
|
1184
|
-
* (Start with . or # and can have > )
|
|
1185
|
-
*/
|
|
1186
|
-
elements: function() {
|
|
1187
|
-
let elements;
|
|
1188
|
-
let e;
|
|
1189
|
-
let c;
|
|
1190
|
-
let elem;
|
|
1191
|
-
let elemIndex;
|
|
1192
|
-
const re = /^[#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/;
|
|
1193
|
-
while (true) {
|
|
1194
|
-
elemIndex = parserInput.i;
|
|
1195
|
-
e = parserInput.$re(re);
|
|
1196
|
-
|
|
1197
|
-
if (!e) {
|
|
1198
|
-
break;
|
|
1199
|
-
}
|
|
1200
|
-
elem = new(tree.Element)(c, e, false, elemIndex + currentIndex, fileInfo);
|
|
1201
|
-
if (elements) {
|
|
1202
|
-
elements.push(elem);
|
|
1203
|
-
} else {
|
|
1204
|
-
elements = [ elem ];
|
|
1205
|
-
}
|
|
1206
|
-
c = parserInput.$char('>');
|
|
1207
|
-
}
|
|
1208
|
-
return elements;
|
|
1209
|
-
},
|
|
1210
|
-
args: function (isCall) {
|
|
1211
|
-
const entities = parsers.entities;
|
|
1212
|
-
const returner = { args:null, variadic: false };
|
|
1213
|
-
let expressions = [];
|
|
1214
|
-
const argsSemiColon = [];
|
|
1215
|
-
const argsComma = [];
|
|
1216
|
-
let isSemiColonSeparated;
|
|
1217
|
-
let expressionContainsNamed;
|
|
1218
|
-
let name;
|
|
1219
|
-
let nameLoop;
|
|
1220
|
-
let value;
|
|
1221
|
-
let arg;
|
|
1222
|
-
let expand;
|
|
1223
|
-
let hasSep = true;
|
|
1224
|
-
|
|
1225
|
-
parserInput.save();
|
|
1226
|
-
|
|
1227
|
-
while (true) {
|
|
1228
|
-
if (isCall) {
|
|
1229
|
-
arg = parsers.detachedRuleset() || parsers.expression();
|
|
1230
|
-
} else {
|
|
1231
|
-
parserInput.commentStore.length = 0;
|
|
1232
|
-
if (parserInput.$str('...')) {
|
|
1233
|
-
returner.variadic = true;
|
|
1234
|
-
if (parserInput.$char(';') && !isSemiColonSeparated) {
|
|
1235
|
-
isSemiColonSeparated = true;
|
|
1236
|
-
}
|
|
1237
|
-
(isSemiColonSeparated ? argsSemiColon : argsComma)
|
|
1238
|
-
.push({ variadic: true });
|
|
1239
|
-
break;
|
|
1240
|
-
}
|
|
1241
|
-
arg = entities.variable() || entities.property() || entities.literal() || entities.keyword() || this.call(true);
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
if (!arg || !hasSep) {
|
|
1245
|
-
break;
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
nameLoop = null;
|
|
1249
|
-
if (arg.throwAwayComments) {
|
|
1250
|
-
arg.throwAwayComments();
|
|
1251
|
-
}
|
|
1252
|
-
value = arg;
|
|
1253
|
-
let val = null;
|
|
1254
|
-
|
|
1255
|
-
if (isCall) {
|
|
1256
|
-
// Variable
|
|
1257
|
-
if (arg.value && arg.value.length == 1) {
|
|
1258
|
-
val = arg.value[0];
|
|
1259
|
-
}
|
|
1260
|
-
} else {
|
|
1261
|
-
val = arg;
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
if (val && (val instanceof tree.Variable || val instanceof tree.Property)) {
|
|
1265
|
-
if (parserInput.$char(':')) {
|
|
1266
|
-
if (expressions.length > 0) {
|
|
1267
|
-
if (isSemiColonSeparated) {
|
|
1268
|
-
error('Cannot mix ; and , as delimiter types');
|
|
1269
|
-
}
|
|
1270
|
-
expressionContainsNamed = true;
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
value = parsers.detachedRuleset() || parsers.expression();
|
|
1274
|
-
|
|
1275
|
-
if (!value) {
|
|
1276
|
-
if (isCall) {
|
|
1277
|
-
error('could not understand value for named argument');
|
|
1278
|
-
} else {
|
|
1279
|
-
parserInput.restore();
|
|
1280
|
-
returner.args = [];
|
|
1281
|
-
return returner;
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
nameLoop = (name = val.name);
|
|
1285
|
-
} else if (parserInput.$str('...')) {
|
|
1286
|
-
if (!isCall) {
|
|
1287
|
-
returner.variadic = true;
|
|
1288
|
-
if (parserInput.$char(';') && !isSemiColonSeparated) {
|
|
1289
|
-
isSemiColonSeparated = true;
|
|
1290
|
-
}
|
|
1291
|
-
(isSemiColonSeparated ? argsSemiColon : argsComma)
|
|
1292
|
-
.push({ name: arg.name, variadic: true });
|
|
1293
|
-
break;
|
|
1294
|
-
} else {
|
|
1295
|
-
expand = true;
|
|
1296
|
-
}
|
|
1297
|
-
} else if (!isCall) {
|
|
1298
|
-
name = nameLoop = val.name;
|
|
1299
|
-
value = null;
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
if (value) {
|
|
1304
|
-
expressions.push(value);
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1307
|
-
argsComma.push({ name:nameLoop, value, expand });
|
|
1308
|
-
|
|
1309
|
-
if (parserInput.$char(',')) {
|
|
1310
|
-
hasSep = true;
|
|
1311
|
-
continue;
|
|
1312
|
-
}
|
|
1313
|
-
hasSep = parserInput.$char(';') === ';';
|
|
1314
|
-
|
|
1315
|
-
if (hasSep || isSemiColonSeparated) {
|
|
1316
|
-
|
|
1317
|
-
if (expressionContainsNamed) {
|
|
1318
|
-
error('Cannot mix ; and , as delimiter types');
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
isSemiColonSeparated = true;
|
|
1322
|
-
|
|
1323
|
-
if (expressions.length > 1) {
|
|
1324
|
-
value = new(tree.Value)(expressions);
|
|
1325
|
-
}
|
|
1326
|
-
argsSemiColon.push({ name, value, expand });
|
|
1327
|
-
|
|
1328
|
-
name = null;
|
|
1329
|
-
expressions = [];
|
|
1330
|
-
expressionContainsNamed = false;
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
parserInput.forget();
|
|
1335
|
-
returner.args = isSemiColonSeparated ? argsSemiColon : argsComma;
|
|
1336
|
-
return returner;
|
|
1337
|
-
},
|
|
1338
|
-
//
|
|
1339
|
-
// A Mixin definition, with a list of parameters
|
|
1340
|
-
//
|
|
1341
|
-
// .rounded (@radius: 2px, @color) {
|
|
1342
|
-
// ...
|
|
1343
|
-
// }
|
|
1344
|
-
//
|
|
1345
|
-
// Until we have a finer grained state-machine, we have to
|
|
1346
|
-
// do a look-ahead, to make sure we don't have a mixin call.
|
|
1347
|
-
// See the `rule` function for more information.
|
|
1348
|
-
//
|
|
1349
|
-
// We start by matching `.rounded (`, and then proceed on to
|
|
1350
|
-
// the argument list, which has optional default values.
|
|
1351
|
-
// We store the parameters in `params`, with a `value` key,
|
|
1352
|
-
// if there is a value, such as in the case of `@radius`.
|
|
1353
|
-
//
|
|
1354
|
-
// Once we've got our params list, and a closing `)`, we parse
|
|
1355
|
-
// the `{...}` block.
|
|
1356
|
-
//
|
|
1357
|
-
definition: function () {
|
|
1358
|
-
let name;
|
|
1359
|
-
let params = [];
|
|
1360
|
-
let match;
|
|
1361
|
-
let ruleset;
|
|
1362
|
-
let cond;
|
|
1363
|
-
let variadic = false;
|
|
1364
|
-
const index = parserInput.i;
|
|
1365
|
-
if ((parserInput.currentChar() !== '.' && parserInput.currentChar() !== '#') ||
|
|
1366
|
-
parserInput.peek(/^[^{]*\}/)) {
|
|
1367
|
-
return;
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
parserInput.save();
|
|
1371
|
-
|
|
1372
|
-
match = parserInput.$re(/^([#.](?:[\w-]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/);
|
|
1373
|
-
if (match) {
|
|
1374
|
-
name = match[1];
|
|
1375
|
-
|
|
1376
|
-
const argInfo = this.args(false);
|
|
1377
|
-
params = argInfo.args;
|
|
1378
|
-
variadic = argInfo.variadic;
|
|
1379
|
-
|
|
1380
|
-
// .mixincall("@{a}");
|
|
1381
|
-
// looks a bit like a mixin definition..
|
|
1382
|
-
// also
|
|
1383
|
-
// .mixincall(@a: {rule: set;});
|
|
1384
|
-
// so we have to be nice and restore
|
|
1385
|
-
if (!parserInput.$char(')')) {
|
|
1386
|
-
parserInput.restore('Missing closing \')\'');
|
|
1387
|
-
return;
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
parserInput.commentStore.length = 0;
|
|
1391
|
-
|
|
1392
|
-
if (parserInput.$str('when')) { // Guard
|
|
1393
|
-
cond = expect(parsers.conditions, 'expected condition');
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
ruleset = parsers.block();
|
|
1397
|
-
|
|
1398
|
-
if (ruleset) {
|
|
1399
|
-
parserInput.forget();
|
|
1400
|
-
warnDashOnlyMixinName(name, index);
|
|
1401
|
-
return new(tree.mixin.Definition)(name, params, ruleset, cond, variadic);
|
|
1402
|
-
} else {
|
|
1403
|
-
parserInput.restore();
|
|
1404
|
-
}
|
|
1405
|
-
} else {
|
|
1406
|
-
parserInput.restore();
|
|
1407
|
-
}
|
|
1408
|
-
},
|
|
1409
|
-
|
|
1410
|
-
ruleLookups: function() {
|
|
1411
|
-
let rule;
|
|
1412
|
-
const lookups = [];
|
|
1413
|
-
|
|
1414
|
-
if (parserInput.currentChar() !== '[') {
|
|
1415
|
-
return;
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
while (true) {
|
|
1419
|
-
parserInput.save();
|
|
1420
|
-
rule = this.lookupValue();
|
|
1421
|
-
if (!rule && rule !== '') {
|
|
1422
|
-
parserInput.restore();
|
|
1423
|
-
break;
|
|
1424
|
-
}
|
|
1425
|
-
lookups.push(rule);
|
|
1426
|
-
parserInput.forget();
|
|
1427
|
-
}
|
|
1428
|
-
if (lookups.length > 0) {
|
|
1429
|
-
return lookups;
|
|
1430
|
-
}
|
|
1431
|
-
},
|
|
1432
|
-
|
|
1433
|
-
lookupValue: function() {
|
|
1434
|
-
parserInput.save();
|
|
1435
|
-
|
|
1436
|
-
if (!parserInput.$char('[')) {
|
|
1437
|
-
parserInput.restore();
|
|
1438
|
-
return;
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
const name = parserInput.$re(/^(?:[@$]{0,2})[_a-zA-Z0-9-]*/);
|
|
1442
|
-
|
|
1443
|
-
if (!parserInput.$char(']')) {
|
|
1444
|
-
parserInput.restore();
|
|
1445
|
-
return;
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
if (name || name === '') {
|
|
1449
|
-
parserInput.forget();
|
|
1450
|
-
return name;
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
parserInput.restore();
|
|
1454
|
-
}
|
|
1455
|
-
},
|
|
1456
|
-
//
|
|
1457
|
-
// Entities are the smallest recognized token,
|
|
1458
|
-
// and can be found inside a rule's value.
|
|
1459
|
-
//
|
|
1460
|
-
entity: function () {
|
|
1461
|
-
const entities = this.entities;
|
|
1462
|
-
|
|
1463
|
-
return this.comment() || entities.literal() || entities.variable() || entities.url() ||
|
|
1464
|
-
entities.property() || entities.call() || entities.keyword() || this.mixin.call(true) ||
|
|
1465
|
-
entities.javascript();
|
|
1466
|
-
},
|
|
1467
|
-
|
|
1468
|
-
//
|
|
1469
|
-
// A Declaration terminator. Note that we use `peek()` to check for '}',
|
|
1470
|
-
// because the `block` rule will be expecting it, but we still need to make sure
|
|
1471
|
-
// it's there, if ';' was omitted.
|
|
1472
|
-
//
|
|
1473
|
-
end: function () {
|
|
1474
|
-
return parserInput.$char(';') || parserInput.peek('}');
|
|
1475
|
-
},
|
|
1476
|
-
|
|
1477
|
-
//
|
|
1478
|
-
// IE's alpha function
|
|
1479
|
-
//
|
|
1480
|
-
// alpha(opacity=88)
|
|
1481
|
-
//
|
|
1482
|
-
ieAlpha: function () {
|
|
1483
|
-
let value;
|
|
1484
|
-
|
|
1485
|
-
// http://jsperf.com/case-insensitive-regex-vs-strtolower-then-regex/18
|
|
1486
|
-
if (!parserInput.$re(/^opacity=/i)) { return; }
|
|
1487
|
-
value = parserInput.$re(/^\d+/);
|
|
1488
|
-
if (!value) {
|
|
1489
|
-
value = expect(parsers.entities.variable, 'Could not parse alpha');
|
|
1490
|
-
value = `@{${value.name.slice(1)}}`;
|
|
1491
|
-
}
|
|
1492
|
-
expectChar(')');
|
|
1493
|
-
return new tree.Quoted('', `alpha(opacity=${value})`);
|
|
1494
|
-
},
|
|
1495
|
-
|
|
1496
|
-
/**
|
|
1497
|
-
* A Selector Element
|
|
1498
|
-
*
|
|
1499
|
-
* div
|
|
1500
|
-
* + h1
|
|
1501
|
-
* #socks
|
|
1502
|
-
* input[type="text"]
|
|
1503
|
-
*
|
|
1504
|
-
* Elements are the building blocks for Selectors,
|
|
1505
|
-
* they are made out of a `Combinator` (see combinator rule),
|
|
1506
|
-
* and an element name, such as a tag a class, or `*`.
|
|
1507
|
-
*/
|
|
1508
|
-
element: function () {
|
|
1509
|
-
let e;
|
|
1510
|
-
let c;
|
|
1511
|
-
let v;
|
|
1512
|
-
const index = parserInput.i;
|
|
1513
|
-
|
|
1514
|
-
c = this.combinator();
|
|
1515
|
-
|
|
1516
|
-
/** This selector parser is quite simplistic and will pass a number of invalid selectors. */
|
|
1517
|
-
e = parserInput.$re(/^(?:\d+\.\d+|\d+)%/) ||
|
|
1518
|
-
// eslint-disable-next-line no-control-regex
|
|
1519
|
-
parserInput.$re(/^(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/) ||
|
|
1520
|
-
parserInput.$char('*') || parserInput.$char('&') || this.attribute() ||
|
|
1521
|
-
parserInput.$re(/^\([^&()@]+\)/) || parserInput.$re(/^[.#:](?=@)/) ||
|
|
1522
|
-
this.entities.variableCurly();
|
|
1523
|
-
|
|
1524
|
-
if (!e) {
|
|
1525
|
-
parserInput.save();
|
|
1526
|
-
if (parserInput.$char('(')) {
|
|
1527
|
-
if ((v = this.selector(false))) {
|
|
1528
|
-
let selectors = [];
|
|
1529
|
-
while (parserInput.$char(',')) {
|
|
1530
|
-
selectors.push(v);
|
|
1531
|
-
selectors.push(new Anonymous(','));
|
|
1532
|
-
v = this.selector(false);
|
|
1533
|
-
}
|
|
1534
|
-
selectors.push(v);
|
|
1535
|
-
|
|
1536
|
-
if (parserInput.$char(')')) {
|
|
1537
|
-
if (selectors.length > 1) {
|
|
1538
|
-
e = new (tree.Paren)(new Selector(selectors));
|
|
1539
|
-
} else {
|
|
1540
|
-
e = new(tree.Paren)(v);
|
|
1541
|
-
}
|
|
1542
|
-
parserInput.forget();
|
|
1543
|
-
} else {
|
|
1544
|
-
parserInput.restore('Missing closing \')\'');
|
|
1545
|
-
}
|
|
1546
|
-
} else {
|
|
1547
|
-
parserInput.restore('Missing closing \')\'');
|
|
1548
|
-
}
|
|
1549
|
-
} else {
|
|
1550
|
-
parserInput.forget();
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
if (e) { return new(tree.Element)(c, e, e instanceof tree.Variable, index + currentIndex, fileInfo); }
|
|
1555
|
-
},
|
|
1556
|
-
|
|
1557
|
-
//
|
|
1558
|
-
// Combinators combine elements together, in a Selector.
|
|
1559
|
-
//
|
|
1560
|
-
// Because our parser isn't white-space sensitive, special care
|
|
1561
|
-
// has to be taken, when parsing the descendant combinator, ` `,
|
|
1562
|
-
// as it's an empty space. We have to check the previous character
|
|
1563
|
-
// in the input, to see if it's a ` ` character. More info on how
|
|
1564
|
-
// we deal with this in *combinator.js*.
|
|
1565
|
-
//
|
|
1566
|
-
combinator: function () {
|
|
1567
|
-
let c = parserInput.currentChar();
|
|
1568
|
-
|
|
1569
|
-
if (c === '/') {
|
|
1570
|
-
parserInput.save();
|
|
1571
|
-
const slashedCombinator = parserInput.$re(/^\/[a-z]+\//i);
|
|
1572
|
-
if (slashedCombinator) {
|
|
1573
|
-
parserInput.forget();
|
|
1574
|
-
return new(tree.Combinator)(slashedCombinator);
|
|
1575
|
-
}
|
|
1576
|
-
parserInput.restore();
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
if (c === '>' || c === '+' || c === '~' || c === '|' || c === '^') {
|
|
1580
|
-
parserInput.i++;
|
|
1581
|
-
if (c === '^' && parserInput.currentChar() === '^') {
|
|
1582
|
-
c = '^^';
|
|
1583
|
-
parserInput.i++;
|
|
1584
|
-
}
|
|
1585
|
-
while (parserInput.isWhitespace()) { parserInput.i++; }
|
|
1586
|
-
return new(tree.Combinator)(c);
|
|
1587
|
-
} else if (parserInput.isWhitespace(-1)) {
|
|
1588
|
-
return new(tree.Combinator)(' ');
|
|
1589
|
-
} else {
|
|
1590
|
-
return new(tree.Combinator)(null);
|
|
1591
|
-
}
|
|
1592
|
-
},
|
|
1593
|
-
//
|
|
1594
|
-
// A CSS Selector
|
|
1595
|
-
// with less extensions e.g. the ability to extend and guard
|
|
1596
|
-
//
|
|
1597
|
-
// .class > div + h1
|
|
1598
|
-
// li a:hover
|
|
1599
|
-
//
|
|
1600
|
-
// Selectors are made out of one or more Elements, see above.
|
|
1601
|
-
//
|
|
1602
|
-
selector: function (isLess) {
|
|
1603
|
-
const index = parserInput.i;
|
|
1604
|
-
let elements;
|
|
1605
|
-
let extendList;
|
|
1606
|
-
let c;
|
|
1607
|
-
let e;
|
|
1608
|
-
let allExtends;
|
|
1609
|
-
let when;
|
|
1610
|
-
let condition;
|
|
1611
|
-
isLess = isLess !== false;
|
|
1612
|
-
while ((isLess && (extendList = this.extend())) || (isLess && (when = parserInput.$str('when'))) || (e = this.element())) {
|
|
1613
|
-
if (when) {
|
|
1614
|
-
condition = expect(this.conditions, 'expected condition');
|
|
1615
|
-
} else if (condition) {
|
|
1616
|
-
error('CSS guard can only be used at the end of selector');
|
|
1617
|
-
} else if (extendList) {
|
|
1618
|
-
if (allExtends) {
|
|
1619
|
-
allExtends = allExtends.concat(extendList);
|
|
1620
|
-
} else {
|
|
1621
|
-
allExtends = extendList;
|
|
1622
|
-
}
|
|
1623
|
-
} else {
|
|
1624
|
-
if (allExtends) { error('Extend can only be used at the end of selector'); }
|
|
1625
|
-
c = parserInput.currentChar();
|
|
1626
|
-
if (Array.isArray(e)){
|
|
1627
|
-
e.forEach(ele => elements.push(ele));
|
|
1628
|
-
} if (elements) {
|
|
1629
|
-
elements.push(e);
|
|
1630
|
-
} else {
|
|
1631
|
-
elements = [ e ];
|
|
1632
|
-
}
|
|
1633
|
-
e = null;
|
|
1634
|
-
}
|
|
1635
|
-
if (c === '{' || c === '}' || c === ';' || c === ',' || c === ')') {
|
|
1636
|
-
break;
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
if (elements) { return new(tree.Selector)(elements, allExtends, condition, index + currentIndex, fileInfo); }
|
|
1641
|
-
if (allExtends) { error('Extend must be used to extend a selector, it cannot be used on its own'); }
|
|
1642
|
-
},
|
|
1643
|
-
selectors: function () {
|
|
1644
|
-
let s;
|
|
1645
|
-
let selectors;
|
|
1646
|
-
while (true) {
|
|
1647
|
-
s = this.selector();
|
|
1648
|
-
if (!s) {
|
|
1649
|
-
break;
|
|
1650
|
-
}
|
|
1651
|
-
if (selectors) {
|
|
1652
|
-
selectors.push(s);
|
|
1653
|
-
} else {
|
|
1654
|
-
selectors = [ s ];
|
|
1655
|
-
}
|
|
1656
|
-
parserInput.commentStore.length = 0;
|
|
1657
|
-
if (s.condition && selectors.length > 1) {
|
|
1658
|
-
error('Guards are only currently allowed on a single selector.');
|
|
1659
|
-
}
|
|
1660
|
-
if (!parserInput.$char(',')) { break; }
|
|
1661
|
-
if (s.condition) {
|
|
1662
|
-
error('Guards are only currently allowed on a single selector.');
|
|
1663
|
-
}
|
|
1664
|
-
parserInput.commentStore.length = 0;
|
|
1665
|
-
}
|
|
1666
|
-
return selectors;
|
|
1667
|
-
},
|
|
1668
|
-
attribute: function () {
|
|
1669
|
-
if (!parserInput.$char('[')) { return; }
|
|
1670
|
-
|
|
1671
|
-
const entities = this.entities;
|
|
1672
|
-
let key;
|
|
1673
|
-
let val;
|
|
1674
|
-
let op;
|
|
1675
|
-
//
|
|
1676
|
-
// case-insensitive flag
|
|
1677
|
-
// e.g. [attr operator value i]
|
|
1678
|
-
//
|
|
1679
|
-
let cif;
|
|
1680
|
-
|
|
1681
|
-
if (!(key = entities.variableCurly())) {
|
|
1682
|
-
key = expect(/^(?:[_A-Za-z0-9-*]*\|)?(?:[_A-Za-z0-9-]|\\.)+/);
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
op = parserInput.$re(/^[|~*$^]?=/);
|
|
1686
|
-
if (op) {
|
|
1687
|
-
val = entities.quoted() || parserInput.$re(/^[0-9]+%/) || parserInput.$re(/^[\w-]+/) || entities.variableCurly();
|
|
1688
|
-
if (val) {
|
|
1689
|
-
cif = parserInput.$re(/^[iIsS]/);
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
expectChar(']');
|
|
1694
|
-
|
|
1695
|
-
return new(tree.Attribute)(key, op, val, cif);
|
|
1696
|
-
},
|
|
1697
|
-
|
|
1698
|
-
//
|
|
1699
|
-
// The `block` rule is used by `ruleset` and `mixin.definition`.
|
|
1700
|
-
// It's a wrapper around the `primary` rule, with added `{}`.
|
|
1701
|
-
//
|
|
1702
|
-
block: function () {
|
|
1703
|
-
let content;
|
|
1704
|
-
if (parserInput.$char('{') && (content = this.primary()) && parserInput.$char('}')) {
|
|
1705
|
-
return content;
|
|
1706
|
-
}
|
|
1707
|
-
},
|
|
1708
|
-
|
|
1709
|
-
blockRuleset: function() {
|
|
1710
|
-
let block = this.block();
|
|
1711
|
-
if (block) {
|
|
1712
|
-
return new tree.Ruleset(null, block);
|
|
1713
|
-
}
|
|
1714
|
-
},
|
|
1715
|
-
|
|
1716
|
-
detachedRuleset: function() {
|
|
1717
|
-
let argInfo;
|
|
1718
|
-
let params;
|
|
1719
|
-
let variadic;
|
|
1720
|
-
|
|
1721
|
-
parserInput.save();
|
|
1722
|
-
if (parserInput.$re(/^[.#]\(/)) {
|
|
1723
|
-
/**
|
|
1724
|
-
* DR args currently only implemented for each() function, and not
|
|
1725
|
-
* yet settable as `@dr: #(@arg) {}`
|
|
1726
|
-
* This should be done when DRs are merged with mixins.
|
|
1727
|
-
* See: https://github.com/less/less-meta/issues/16
|
|
1728
|
-
*/
|
|
1729
|
-
argInfo = this.mixin.args(false);
|
|
1730
|
-
params = argInfo.args;
|
|
1731
|
-
variadic = argInfo.variadic;
|
|
1732
|
-
if (!parserInput.$char(')')) {
|
|
1733
|
-
parserInput.restore();
|
|
1734
|
-
return;
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
const blockRuleset = this.blockRuleset();
|
|
1738
|
-
if (blockRuleset) {
|
|
1739
|
-
parserInput.forget();
|
|
1740
|
-
if (params) {
|
|
1741
|
-
return new tree.mixin.Definition(null, params, blockRuleset, null, variadic);
|
|
1742
|
-
}
|
|
1743
|
-
return new tree.DetachedRuleset(blockRuleset);
|
|
1744
|
-
}
|
|
1745
|
-
parserInput.restore();
|
|
1746
|
-
},
|
|
1747
|
-
|
|
1748
|
-
//
|
|
1749
|
-
// div, .class, body > p {...}
|
|
1750
|
-
//
|
|
1751
|
-
ruleset: function () {
|
|
1752
|
-
let selectors;
|
|
1753
|
-
let rules;
|
|
1754
|
-
let debugInfo;
|
|
1755
|
-
|
|
1756
|
-
parserInput.save();
|
|
1757
|
-
|
|
1758
|
-
if (context.dumpLineNumbers) {
|
|
1759
|
-
debugInfo = getDebugInfo(parserInput.i);
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
selectors = this.selectors();
|
|
1763
|
-
|
|
1764
|
-
if (selectors && (rules = this.block())) {
|
|
1765
|
-
parserInput.forget();
|
|
1766
|
-
const ruleset = new(tree.Ruleset)(selectors, rules, context.strictImports);
|
|
1767
|
-
if (context.dumpLineNumbers) {
|
|
1768
|
-
ruleset.debugInfo = debugInfo;
|
|
1769
|
-
}
|
|
1770
|
-
return ruleset;
|
|
1771
|
-
} else {
|
|
1772
|
-
parserInput.restore();
|
|
1773
|
-
}
|
|
1774
|
-
},
|
|
1775
|
-
declaration: function () {
|
|
1776
|
-
let name;
|
|
1777
|
-
let value;
|
|
1778
|
-
const index = parserInput.i;
|
|
1779
|
-
let hasDR;
|
|
1780
|
-
const c = parserInput.currentChar();
|
|
1781
|
-
let important;
|
|
1782
|
-
let merge;
|
|
1783
|
-
let isVariable;
|
|
1784
|
-
|
|
1785
|
-
if (c === '.' || c === '#' || c === '&' || c === ':') { return; }
|
|
1786
|
-
|
|
1787
|
-
parserInput.save();
|
|
1788
|
-
|
|
1789
|
-
name = this.variable() || this.ruleProperty();
|
|
1790
|
-
if (name) {
|
|
1791
|
-
isVariable = typeof name === 'string';
|
|
1792
|
-
|
|
1793
|
-
if (isVariable) {
|
|
1794
|
-
value = this.detachedRuleset();
|
|
1795
|
-
if (value) {
|
|
1796
|
-
hasDR = true;
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
parserInput.commentStore.length = 0;
|
|
1801
|
-
if (!value) {
|
|
1802
|
-
// a name returned by this.ruleProperty() is always an array of the form:
|
|
1803
|
-
// [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"]
|
|
1804
|
-
// where each item is a tree.Keyword or tree.Variable
|
|
1805
|
-
merge = !isVariable && name.length > 1 && name.pop().value;
|
|
1806
|
-
|
|
1807
|
-
// Custom property values get permissive parsing
|
|
1808
|
-
// A lookup particle (`NamespaceValue`) carries a node in `value`
|
|
1809
|
-
// rather than a string, and can never spell a `--` prefix.
|
|
1810
|
-
if (typeof name[0].value === 'string' && name[0].value.slice(0, 2) === '--') {
|
|
1811
|
-
if (parserInput.$char(';')) {
|
|
1812
|
-
value = new Anonymous('');
|
|
1813
|
-
} else {
|
|
1814
|
-
value = this.permissiveValue(/[;}]/);
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
// Try to store values as anonymous
|
|
1818
|
-
// If we need the value later we'll re-parse it in ruleset.parseValue
|
|
1819
|
-
else {
|
|
1820
|
-
value = this.anonymousValue();
|
|
1821
|
-
}
|
|
1822
|
-
if (value) {
|
|
1823
|
-
parserInput.forget();
|
|
1824
|
-
// anonymous values absorb the end ';' which is required for them to work
|
|
1825
|
-
return new(tree.Declaration)(name, value, false, merge, index + currentIndex, fileInfo);
|
|
1826
|
-
}
|
|
1827
|
-
|
|
1828
|
-
if (!value) {
|
|
1829
|
-
value = this.value();
|
|
1830
|
-
}
|
|
1831
|
-
|
|
1832
|
-
if (value) {
|
|
1833
|
-
important = this.important();
|
|
1834
|
-
} else if (isVariable) {
|
|
1835
|
-
/**
|
|
1836
|
-
* As a last resort, try permissiveValue
|
|
1837
|
-
*
|
|
1838
|
-
* @todo - This has created some knock-on problems of not
|
|
1839
|
-
* flagging incorrect syntax or detecting user intent.
|
|
1840
|
-
*/
|
|
1841
|
-
value = this.permissiveValue();
|
|
1842
|
-
}
|
|
1843
|
-
}
|
|
1844
|
-
|
|
1845
|
-
if (value && (this.end() || hasDR)) {
|
|
1846
|
-
parserInput.forget();
|
|
1847
|
-
return new(tree.Declaration)(name, value, important, merge, index + currentIndex, fileInfo);
|
|
1848
|
-
}
|
|
1849
|
-
else {
|
|
1850
|
-
parserInput.restore();
|
|
1851
|
-
}
|
|
1852
|
-
} else {
|
|
1853
|
-
parserInput.restore();
|
|
1854
|
-
}
|
|
1855
|
-
},
|
|
1856
|
-
anonymousValue: function () {
|
|
1857
|
-
const index = parserInput.i;
|
|
1858
|
-
const match = parserInput.$re(/^([^.#@$+/'"*`(;{}-]*);/);
|
|
1859
|
-
if (match) {
|
|
1860
|
-
return new(tree.Anonymous)(match[1], index + currentIndex);
|
|
1861
|
-
}
|
|
1862
|
-
},
|
|
1863
|
-
/**
|
|
1864
|
-
* Used for custom properties, at-rules, and variables (as fallback)
|
|
1865
|
-
* Parses almost anything inside of {} [] () "" blocks
|
|
1866
|
-
* until it reaches outer-most tokens.
|
|
1867
|
-
*
|
|
1868
|
-
* First, it will try to parse comments and entities to reach
|
|
1869
|
-
* the end. This is mostly like the Expression parser except no
|
|
1870
|
-
* math is allowed.
|
|
1871
|
-
*
|
|
1872
|
-
* @param {RexExp} untilTokens - Characters to stop parsing at
|
|
1873
|
-
* @param {boolean} [deprecateVariables] - when set, this is an at-rule
|
|
1874
|
-
* prelude (non-value position); accept `@{var}` interpolation and warn
|
|
1875
|
-
* on a bare `@var` reference (which resolves today but is deprecated).
|
|
1876
|
-
*/
|
|
1877
|
-
permissiveValue: function (untilTokens, deprecateVariables) {
|
|
1878
|
-
const entities = this.entities;
|
|
1879
|
-
let i;
|
|
1880
|
-
let e;
|
|
1881
|
-
let done;
|
|
1882
|
-
let value;
|
|
1883
|
-
const tok = untilTokens || ';';
|
|
1884
|
-
const index = parserInput.i;
|
|
1885
|
-
const result = [];
|
|
1886
|
-
|
|
1887
|
-
function testCurrentChar() {
|
|
1888
|
-
const char = parserInput.currentChar();
|
|
1889
|
-
if (typeof tok === 'string') {
|
|
1890
|
-
return char === tok;
|
|
1891
|
-
} else {
|
|
1892
|
-
return tok.test(char);
|
|
1893
|
-
}
|
|
1894
|
-
}
|
|
1895
|
-
if (testCurrentChar()) {
|
|
1896
|
-
return;
|
|
1897
|
-
}
|
|
1898
|
-
value = [];
|
|
1899
|
-
do {
|
|
1900
|
-
e = this.comment();
|
|
1901
|
-
if (e) {
|
|
1902
|
-
value.push(e);
|
|
1903
|
-
continue;
|
|
1904
|
-
}
|
|
1905
|
-
if (deprecateVariables) {
|
|
1906
|
-
// In an at-rule prelude, `@{var}` interpolation is the supported
|
|
1907
|
-
// form; consume it here so its `{` is not mistaken for a block.
|
|
1908
|
-
e = entities.variableCurly();
|
|
1909
|
-
if (!e) {
|
|
1910
|
-
const varIndex = parserInput.i;
|
|
1911
|
-
e = this.entity();
|
|
1912
|
-
if (isBareVariableReference(e)) {
|
|
1913
|
-
warnBareAtRuleVariable(varIndex);
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
} else {
|
|
1917
|
-
e = this.entity();
|
|
1918
|
-
}
|
|
1919
|
-
if (e) {
|
|
1920
|
-
value.push(e);
|
|
1921
|
-
}
|
|
1922
|
-
if (parserInput.peek(',')) {
|
|
1923
|
-
value.push(new (tree.Anonymous)(',', parserInput.i));
|
|
1924
|
-
parserInput.$char(',');
|
|
1925
|
-
}
|
|
1926
|
-
} while (e);
|
|
1927
|
-
|
|
1928
|
-
done = testCurrentChar();
|
|
1929
|
-
|
|
1930
|
-
if (value.length > 0) {
|
|
1931
|
-
value = new(tree.Expression)(value);
|
|
1932
|
-
if (done) {
|
|
1933
|
-
return value;
|
|
1934
|
-
}
|
|
1935
|
-
else {
|
|
1936
|
-
result.push(value);
|
|
1937
|
-
}
|
|
1938
|
-
// Preserve space before $parseUntil as it will not
|
|
1939
|
-
if (parserInput.prevChar() === ' ') {
|
|
1940
|
-
result.push(new tree.Anonymous(' ', index));
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
parserInput.save();
|
|
1944
|
-
|
|
1945
|
-
value = parserInput.$parseUntil(tok, deprecateVariables);
|
|
1946
|
-
|
|
1947
|
-
if (value) {
|
|
1948
|
-
if (typeof value === 'string') {
|
|
1949
|
-
error(`Expected '${value}'`, 'Parse');
|
|
1950
|
-
}
|
|
1951
|
-
if (value.length === 1 && value[0] === ' ') {
|
|
1952
|
-
parserInput.forget();
|
|
1953
|
-
return new tree.Anonymous('', index);
|
|
1954
|
-
}
|
|
1955
|
-
// At-rule prelude: `$parseUntil` (deprecateVariables) records the
|
|
1956
|
-
// first bare `@var` it saw outside any `(...)` in its single pass —
|
|
1957
|
-
// a structural reference (`[...]`/`{...}` don't shield it, only a
|
|
1958
|
-
// declaration-value `(...)` does). Warn once here rather than
|
|
1959
|
-
// re-scanning the text.
|
|
1960
|
-
if (deprecateVariables && value.bareVarIndex !== null && value.bareVarIndex !== undefined) {
|
|
1961
|
-
warnBareAtRuleVariable(value.bareVarIndex);
|
|
1962
|
-
}
|
|
1963
|
-
/** @type {string} */
|
|
1964
|
-
let item;
|
|
1965
|
-
for (i = 0; i < value.length; i++) {
|
|
1966
|
-
item = value[i];
|
|
1967
|
-
if (Array.isArray(item)) {
|
|
1968
|
-
// Treat actual quotes as normal quoted values
|
|
1969
|
-
result.push(new tree.Quoted(item[0], item[1], true, index, fileInfo));
|
|
1970
|
-
}
|
|
1971
|
-
else {
|
|
1972
|
-
if (i === value.length - 1) {
|
|
1973
|
-
item = item.trim();
|
|
1974
|
-
}
|
|
1975
|
-
// Treat like quoted values, but replace vars like unquoted expressions
|
|
1976
|
-
const quote = new tree.Quoted('\'', item, true, index, fileInfo);
|
|
1977
|
-
const variableRegex = /@([\w-]+)/g;
|
|
1978
|
-
const propRegex = /\$([\w-]+)/g;
|
|
1979
|
-
// These notices are about *bare* references, so test the text
|
|
1980
|
-
// with interpolations removed. A lookup key is itself allowed
|
|
1981
|
-
// to be a variable (`@{map[@key]}`), and without this the
|
|
1982
|
-
// `@key` inside the braces would be misreported as a bare use
|
|
1983
|
-
// of the very syntax the notice tells you to adopt.
|
|
1984
|
-
const bareOnly = item
|
|
1985
|
-
.replace(VARIABLE_INTERPOLATION, '')
|
|
1986
|
-
.replace(PROPERTY_INTERPOLATION, '');
|
|
1987
|
-
// At-rule preludes are handled once above via
|
|
1988
|
-
// `value.bareVarIndex`; the `variable-in-unknown-value`
|
|
1989
|
-
// notice is for unknown declaration values only.
|
|
1990
|
-
if (!deprecateVariables && variableRegex.test(bareOnly)) {
|
|
1991
|
-
warn('@variable in unknown values will not be evaluated as variables in the future. Use @{variable}', index, 'DEPRECATED', 'variable-in-unknown-value');
|
|
1992
|
-
}
|
|
1993
|
-
if (propRegex.test(bareOnly)) {
|
|
1994
|
-
warn('$property in unknown values will not be evaluated as property references in the future. Use ${property}', index, 'DEPRECATED', 'property-in-unknown-value');
|
|
1995
|
-
}
|
|
1996
|
-
// Both alternatives carry the lookup chain. An unknown at-rule
|
|
1997
|
-
// prelude (`@supports`) is scanned as text, so a bare
|
|
1998
|
-
// `@map[key]` reaches this regex rather than being parsed
|
|
1999
|
-
// structurally; matching only `@map` would resolve it to the
|
|
2000
|
-
// whole ruleset and leave `[key]` behind as literal text.
|
|
2001
|
-
quote.variableRegex = new RegExp(
|
|
2002
|
-
`@(${VARIABLE_WITH_LOOKUPS})|@\\{(${VARIABLE_WITH_LOOKUPS})\\}`, 'g'
|
|
2003
|
-
);
|
|
2004
|
-
// Properties stay narrow — they have no lookup grammar.
|
|
2005
|
-
quote.propRegex = /\$([\w-]+)|\$\{([\w-]+)\}/g;
|
|
2006
|
-
result.push(quote);
|
|
2007
|
-
}
|
|
2008
|
-
}
|
|
2009
|
-
parserInput.forget();
|
|
2010
|
-
return new tree.Expression(result, true);
|
|
2011
|
-
}
|
|
2012
|
-
parserInput.restore();
|
|
2013
|
-
},
|
|
2014
|
-
|
|
2015
|
-
//
|
|
2016
|
-
// An @import atrule
|
|
2017
|
-
//
|
|
2018
|
-
// @import "lib";
|
|
2019
|
-
//
|
|
2020
|
-
// Depending on our environment, importing is done differently:
|
|
2021
|
-
// In the browser, it's an XHR request, in Node, it would be a
|
|
2022
|
-
// file-system operation. The function used for importing is
|
|
2023
|
-
// stored in `import`, which we pass to the Import constructor.
|
|
2024
|
-
//
|
|
2025
|
-
'import': function () {
|
|
2026
|
-
let path;
|
|
2027
|
-
let features;
|
|
2028
|
-
const index = parserInput.i;
|
|
2029
|
-
|
|
2030
|
-
const dir = parserInput.$re(/^@import\s+/);
|
|
2031
|
-
|
|
2032
|
-
if (dir) {
|
|
2033
|
-
const options = (dir ? this.importOptions() : null) || {};
|
|
2034
|
-
|
|
2035
|
-
if ((path = this.entities.quoted() || this.entities.url())) {
|
|
2036
|
-
features = this.mediaFeatures({});
|
|
2037
|
-
|
|
2038
|
-
if (!parserInput.$char(';')) {
|
|
2039
|
-
parserInput.i = index;
|
|
2040
|
-
error('missing semi-colon or unrecognised media features on import');
|
|
2041
|
-
}
|
|
2042
|
-
features = features && new(tree.Value)(features);
|
|
2043
|
-
return new(tree.Import)(path, features, options, index + currentIndex, fileInfo);
|
|
2044
|
-
}
|
|
2045
|
-
else {
|
|
2046
|
-
parserInput.i = index;
|
|
2047
|
-
error('malformed import statement');
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2050
|
-
},
|
|
2051
|
-
|
|
2052
|
-
importOptions: function() {
|
|
2053
|
-
let o;
|
|
2054
|
-
const options = {};
|
|
2055
|
-
let optionName;
|
|
2056
|
-
let value;
|
|
2057
|
-
|
|
2058
|
-
// list of options, surrounded by parens
|
|
2059
|
-
if (!parserInput.$char('(')) { return null; }
|
|
2060
|
-
do {
|
|
2061
|
-
o = this.importOption();
|
|
2062
|
-
if (o) {
|
|
2063
|
-
optionName = o;
|
|
2064
|
-
value = true;
|
|
2065
|
-
switch (optionName) {
|
|
2066
|
-
case 'css':
|
|
2067
|
-
optionName = 'less';
|
|
2068
|
-
value = false;
|
|
2069
|
-
break;
|
|
2070
|
-
case 'once':
|
|
2071
|
-
optionName = 'multiple';
|
|
2072
|
-
value = false;
|
|
2073
|
-
break;
|
|
2074
|
-
}
|
|
2075
|
-
options[optionName] = value;
|
|
2076
|
-
if (!parserInput.$char(',')) { break; }
|
|
2077
|
-
}
|
|
2078
|
-
} while (o);
|
|
2079
|
-
expectChar(')');
|
|
2080
|
-
return options;
|
|
2081
|
-
},
|
|
2082
|
-
|
|
2083
|
-
importOption: function() {
|
|
2084
|
-
const opt = parserInput.$re(/^(less|css|multiple|once|inline|reference|optional)/);
|
|
2085
|
-
if (opt) {
|
|
2086
|
-
return opt[1];
|
|
2087
|
-
}
|
|
2088
|
-
},
|
|
2089
|
-
|
|
2090
|
-
mediaFeature: function (syntaxOptions) {
|
|
2091
|
-
const entities = this.entities;
|
|
2092
|
-
const nodes = [];
|
|
2093
|
-
let e;
|
|
2094
|
-
let p;
|
|
2095
|
-
let rangeP;
|
|
2096
|
-
let spacing = false;
|
|
2097
|
-
const cssKeyword = () => {
|
|
2098
|
-
const k = parserInput.$re(/^(?:--|-?(?:[_a-zA-Z0-9]|[^\0-\x7F]|\\[0-9a-fA-F]{1,6}\s?|\\[^\n\r\f0-9a-fA-F]))(?:[-_a-zA-Z0-9]|[^\0-\x7F]|\\[0-9a-fA-F]{1,6}\s?|\\[^\n\r\f0-9a-fA-F])*/);
|
|
2099
|
-
if (k) {
|
|
2100
|
-
return tree.Color.fromKeyword(k) || new(tree.Keyword)(k);
|
|
2101
|
-
}
|
|
2102
|
-
};
|
|
2103
|
-
parserInput.save();
|
|
2104
|
-
do {
|
|
2105
|
-
parserInput.save();
|
|
2106
|
-
if (parserInput.$re(/^(?:--|-?(?:[_a-zA-Z0-9]|[^\0-\x7F]|\\[0-9a-fA-F]{1,6}\s?|\\[^\n\r\f0-9a-fA-F]))(?:[-_a-zA-Z0-9]|[^\0-\x7F]|\\[0-9a-fA-F]{1,6}\s?|\\[^\n\r\f0-9a-fA-F])*\s+\(/)) {
|
|
2107
|
-
spacing = true;
|
|
2108
|
-
}
|
|
2109
|
-
parserInput.restore();
|
|
2110
|
-
|
|
2111
|
-
e = entities.declarationCall.bind(this)() || cssKeyword() || entities.keyword() || entities.variableCurly();
|
|
2112
|
-
if (!e) {
|
|
2113
|
-
const varIndex = parserInput.i;
|
|
2114
|
-
const bareVariable = entities.variable();
|
|
2115
|
-
if (bareVariable) {
|
|
2116
|
-
warnBareAtRuleVariable(varIndex);
|
|
2117
|
-
e = bareVariable;
|
|
2118
|
-
} else {
|
|
2119
|
-
e = entities.mixinLookup();
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
|
-
if (e) {
|
|
2123
|
-
nodes.push(e);
|
|
2124
|
-
if (e.type === 'Variable' ||
|
|
2125
|
-
(e.type === 'Keyword' && /^(and|or|not|only)$/i.test(e.value))) {
|
|
2126
|
-
spacing = true;
|
|
2127
|
-
}
|
|
2128
|
-
} else if (parserInput.$char('(')) {
|
|
2129
|
-
let closed = false;
|
|
2130
|
-
p = this.property();
|
|
2131
|
-
parserInput.save();
|
|
2132
|
-
if (!p && syntaxOptions.queryInParens && parserInput.$re(/^(?:[^()]|\([^()]*\))*\s*([<>]=|<=|>=|[<>]|=)/)) {
|
|
2133
|
-
parserInput.restore();
|
|
2134
|
-
p = this.condition();
|
|
2135
|
-
|
|
2136
|
-
parserInput.save();
|
|
2137
|
-
rangeP = this.atomicCondition(null, p.rvalue);
|
|
2138
|
-
if (!rangeP) {
|
|
2139
|
-
parserInput.restore();
|
|
2140
|
-
}
|
|
2141
|
-
} else {
|
|
2142
|
-
parserInput.restore();
|
|
2143
|
-
parserInput.save();
|
|
2144
|
-
e = this.value();
|
|
2145
|
-
if (e && parserInput.$char(')')) {
|
|
2146
|
-
closed = true;
|
|
2147
|
-
parserInput.forget();
|
|
2148
|
-
} else {
|
|
2149
|
-
parserInput.restore();
|
|
2150
|
-
e = this.mediaFeature(syntaxOptions);
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
if (!closed && parserInput.$char(')')) {
|
|
2154
|
-
closed = true;
|
|
2155
|
-
}
|
|
2156
|
-
if (closed) {
|
|
2157
|
-
if (p && !e) {
|
|
2158
|
-
const paren = new (tree.Paren)(new (tree.QueryInParens)(p.op, p.lvalue, p.rvalue, rangeP ? rangeP.op : null, rangeP ? rangeP.rvalue : null, p._index));
|
|
2159
|
-
if (!spacing) {
|
|
2160
|
-
paren.noSpacing = true;
|
|
2161
|
-
}
|
|
2162
|
-
nodes.push(paren);
|
|
2163
|
-
e = p;
|
|
2164
|
-
} else if (p && e) {
|
|
2165
|
-
nodes.push(new (tree.Paren)(new (tree.Declaration)(p, e, null, null, parserInput.i + currentIndex, fileInfo, true)));
|
|
2166
|
-
if (!spacing) {
|
|
2167
|
-
nodes[nodes.length - 1].noSpacing = true;
|
|
2168
|
-
}
|
|
2169
|
-
spacing = false;
|
|
2170
|
-
} else if (e) {
|
|
2171
|
-
const paren = new(tree.Paren)(e);
|
|
2172
|
-
if (!spacing) {
|
|
2173
|
-
paren.noSpacing = true;
|
|
2174
|
-
}
|
|
2175
|
-
nodes.push(paren);
|
|
2176
|
-
spacing = false;
|
|
2177
|
-
} else {
|
|
2178
|
-
error('badly formed media feature definition');
|
|
2179
|
-
}
|
|
2180
|
-
} else {
|
|
2181
|
-
error('Missing closing \')\'', 'Parse');
|
|
2182
|
-
}
|
|
2183
|
-
}
|
|
2184
|
-
} while (e);
|
|
2185
|
-
|
|
2186
|
-
parserInput.forget();
|
|
2187
|
-
if (nodes.length > 0) {
|
|
2188
|
-
const expression = new(tree.Expression)(nodes);
|
|
2189
|
-
if (nodes.length === 2
|
|
2190
|
-
&& (nodes[0].type === 'Keyword' || nodes[0].type === 'Variable')
|
|
2191
|
-
&& nodes[1].type === 'Paren'
|
|
2192
|
-
&& nodes[1].noSpacing) {
|
|
2193
|
-
expression.noSpacing = true;
|
|
2194
|
-
}
|
|
2195
|
-
return expression;
|
|
2196
|
-
}
|
|
2197
|
-
},
|
|
2198
|
-
|
|
2199
|
-
mediaFeatures: function (syntaxOptions) {
|
|
2200
|
-
const entities = this.entities;
|
|
2201
|
-
const features = [];
|
|
2202
|
-
let e;
|
|
2203
|
-
do {
|
|
2204
|
-
e = this.mediaFeature(syntaxOptions);
|
|
2205
|
-
if (e) {
|
|
2206
|
-
features.push(e);
|
|
2207
|
-
if (!parserInput.$char(',')) { break; }
|
|
2208
|
-
else if (!features[features.length - 1].noSpacing) {
|
|
2209
|
-
features[features.length - 1].noSpacing = false;
|
|
2210
|
-
}
|
|
2211
|
-
} else {
|
|
2212
|
-
e = entities.variableCurly();
|
|
2213
|
-
if (!e) {
|
|
2214
|
-
const varIndex = parserInput.i;
|
|
2215
|
-
const bareVariable = entities.variable();
|
|
2216
|
-
if (bareVariable) {
|
|
2217
|
-
warnBareAtRuleVariable(varIndex);
|
|
2218
|
-
e = bareVariable;
|
|
2219
|
-
} else {
|
|
2220
|
-
e = entities.mixinLookup();
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
if (e) {
|
|
2224
|
-
features.push(e);
|
|
2225
|
-
if (!parserInput.$char(',')) { break; }
|
|
2226
|
-
else if (!features[features.length - 1].noSpacing) {
|
|
2227
|
-
features[features.length - 1].noSpacing = false;
|
|
2228
|
-
}
|
|
2229
|
-
}
|
|
2230
|
-
}
|
|
2231
|
-
} while (e);
|
|
2232
|
-
|
|
2233
|
-
return features.length > 0 ? features : null;
|
|
2234
|
-
},
|
|
2235
|
-
|
|
2236
|
-
prepareAndGetNestableAtRule: function (treeType, index, debugInfo, syntaxOptions) {
|
|
2237
|
-
const features = this.mediaFeatures(syntaxOptions);
|
|
2238
|
-
|
|
2239
|
-
const rules = this.block();
|
|
2240
|
-
|
|
2241
|
-
if (!rules) {
|
|
2242
|
-
error('media definitions require block statements after any features');
|
|
2243
|
-
}
|
|
2244
|
-
|
|
2245
|
-
parserInput.forget();
|
|
2246
|
-
|
|
2247
|
-
const atRule = new (treeType)(rules, features, index + currentIndex, fileInfo);
|
|
2248
|
-
if (context.dumpLineNumbers) {
|
|
2249
|
-
atRule.debugInfo = debugInfo;
|
|
2250
|
-
}
|
|
2251
|
-
|
|
2252
|
-
return atRule;
|
|
2253
|
-
},
|
|
2254
|
-
|
|
2255
|
-
nestableAtRule: function () {
|
|
2256
|
-
let debugInfo;
|
|
2257
|
-
const index = parserInput.i;
|
|
2258
|
-
|
|
2259
|
-
if (context.dumpLineNumbers) {
|
|
2260
|
-
debugInfo = getDebugInfo(index);
|
|
2261
|
-
}
|
|
2262
|
-
parserInput.save();
|
|
2263
|
-
|
|
2264
|
-
if (parserInput.$peekChar('@')) {
|
|
2265
|
-
if (parserInput.$str('@media')) {
|
|
2266
|
-
return this.prepareAndGetNestableAtRule(tree.Media, index, debugInfo, MediaSyntaxOptions);
|
|
2267
|
-
}
|
|
2268
|
-
|
|
2269
|
-
if (parserInput.$str('@container')) {
|
|
2270
|
-
return this.prepareAndGetNestableAtRule(tree.Container, index, debugInfo, ContainerSyntaxOptions);
|
|
2271
|
-
}
|
|
2272
|
-
}
|
|
2273
|
-
|
|
2274
|
-
parserInput.restore();
|
|
2275
|
-
},
|
|
2276
|
-
|
|
2277
|
-
//
|
|
2278
|
-
|
|
2279
|
-
// A @plugin directive, used to import plugins dynamically.
|
|
2280
|
-
//
|
|
2281
|
-
// @plugin (args) "lib";
|
|
2282
|
-
//
|
|
2283
|
-
plugin: function () {
|
|
2284
|
-
let path;
|
|
2285
|
-
let args;
|
|
2286
|
-
let options;
|
|
2287
|
-
const index = parserInput.i;
|
|
2288
|
-
const dir = parserInput.$re(/^@plugin\s+/);
|
|
2289
|
-
|
|
2290
|
-
if (dir) {
|
|
2291
|
-
warn('The @plugin directive is deprecated and will be replaced in Less 5.x. Use --plugin CLI option or the programmatic plugin API instead.', index, 'DEPRECATED', 'at-plugin');
|
|
2292
|
-
args = this.pluginArgs();
|
|
2293
|
-
|
|
2294
|
-
if (args) {
|
|
2295
|
-
options = {
|
|
2296
|
-
pluginArgs: args,
|
|
2297
|
-
isPlugin: true
|
|
2298
|
-
};
|
|
2299
|
-
}
|
|
2300
|
-
else {
|
|
2301
|
-
options = { isPlugin: true };
|
|
2302
|
-
}
|
|
2303
|
-
|
|
2304
|
-
if ((path = this.entities.quoted() || this.entities.url())) {
|
|
2305
|
-
|
|
2306
|
-
if (!parserInput.$char(';')) {
|
|
2307
|
-
parserInput.i = index;
|
|
2308
|
-
error('missing semi-colon on @plugin');
|
|
2309
|
-
}
|
|
2310
|
-
return new(tree.Import)(path, null, options, index + currentIndex, fileInfo);
|
|
2311
|
-
}
|
|
2312
|
-
else {
|
|
2313
|
-
parserInput.i = index;
|
|
2314
|
-
error('malformed @plugin statement');
|
|
2315
|
-
}
|
|
2316
|
-
}
|
|
2317
|
-
},
|
|
2318
|
-
|
|
2319
|
-
pluginArgs: function() {
|
|
2320
|
-
// list of options, surrounded by parens
|
|
2321
|
-
parserInput.save();
|
|
2322
|
-
if (!parserInput.$char('(')) {
|
|
2323
|
-
parserInput.restore();
|
|
2324
|
-
return null;
|
|
2325
|
-
}
|
|
2326
|
-
const args = parserInput.$re(/^\s*([^);]+)\)\s*/);
|
|
2327
|
-
if (args[1]) {
|
|
2328
|
-
parserInput.forget();
|
|
2329
|
-
return args[1].trim();
|
|
2330
|
-
}
|
|
2331
|
-
else {
|
|
2332
|
-
parserInput.restore();
|
|
2333
|
-
return null;
|
|
2334
|
-
}
|
|
2335
|
-
},
|
|
2336
|
-
/**
|
|
2337
|
-
* An entity in a non-value at-rule position (an at-rule identifier,
|
|
2338
|
-
* name, or keyword-list item — e.g. the name in `@keyframes @foo`).
|
|
2339
|
-
* `@{foo}` interpolation is the supported form; a bare `@foo` still
|
|
2340
|
-
* resolves but is deprecated.
|
|
2341
|
-
*/
|
|
2342
|
-
atRuleEntity: function () {
|
|
2343
|
-
const curly = this.entities.variableCurly();
|
|
2344
|
-
if (curly) { return curly; }
|
|
2345
|
-
const index = parserInput.i;
|
|
2346
|
-
const e = this.entity();
|
|
2347
|
-
if (isBareVariableReference(e)) {
|
|
2348
|
-
warnBareAtRuleVariable(index);
|
|
2349
|
-
}
|
|
2350
|
-
return e;
|
|
2351
|
-
},
|
|
2352
|
-
atruleUnknown: function (value, name, hasBlock) {
|
|
2353
|
-
value = this.permissiveValue(/^[{;]/, true);
|
|
2354
|
-
hasBlock = (parserInput.currentChar() === '{');
|
|
2355
|
-
if (!value) {
|
|
2356
|
-
if (!hasBlock && parserInput.currentChar() !== ';') {
|
|
2357
|
-
error(''.concat(name, ' rule is missing block or ending semi-colon'));
|
|
2358
|
-
}
|
|
2359
|
-
}
|
|
2360
|
-
else if (!value.value) {
|
|
2361
|
-
value = null;
|
|
2362
|
-
}
|
|
2363
|
-
return [value, hasBlock];
|
|
2364
|
-
},
|
|
2365
|
-
atruleBlock: function (rules, value, isRooted, isKeywordList) {
|
|
2366
|
-
rules = this.blockRuleset();
|
|
2367
|
-
parserInput.save();
|
|
2368
|
-
if (!rules && !isRooted) {
|
|
2369
|
-
value = this.atRuleEntity();
|
|
2370
|
-
rules = this.blockRuleset();
|
|
2371
|
-
}
|
|
2372
|
-
if (!rules && !isRooted) {
|
|
2373
|
-
parserInput.restore();
|
|
2374
|
-
var e = [];
|
|
2375
|
-
value = this.atRuleEntity();
|
|
2376
|
-
while (parserInput.$char(',')) {
|
|
2377
|
-
e.push(value);
|
|
2378
|
-
value = this.atRuleEntity();
|
|
2379
|
-
}
|
|
2380
|
-
if (value && e.length > 0) {
|
|
2381
|
-
e.push(value);
|
|
2382
|
-
value = e;
|
|
2383
|
-
isKeywordList = true;
|
|
2384
|
-
}
|
|
2385
|
-
else {
|
|
2386
|
-
rules = this.blockRuleset();
|
|
2387
|
-
}
|
|
2388
|
-
}
|
|
2389
|
-
else {
|
|
2390
|
-
parserInput.forget();
|
|
2391
|
-
}
|
|
2392
|
-
|
|
2393
|
-
return [rules, value, isKeywordList];
|
|
2394
|
-
},
|
|
2395
|
-
//
|
|
2396
|
-
// A CSS AtRule
|
|
2397
|
-
//
|
|
2398
|
-
// @charset "utf-8";
|
|
2399
|
-
//
|
|
2400
|
-
atrule: function () {
|
|
2401
|
-
const index = parserInput.i;
|
|
2402
|
-
let name;
|
|
2403
|
-
let value;
|
|
2404
|
-
let rules;
|
|
2405
|
-
let nonVendorSpecificName;
|
|
2406
|
-
let hasIdentifier;
|
|
2407
|
-
let hasExpression;
|
|
2408
|
-
let hasUnknown;
|
|
2409
|
-
let hasBlock = true;
|
|
2410
|
-
let isRooted = true;
|
|
2411
|
-
let isKeywordList = false;
|
|
2412
|
-
|
|
2413
|
-
if (parserInput.currentChar() !== '@') { return; }
|
|
2414
|
-
|
|
2415
|
-
value = this['import']() || this.plugin() || this.nestableAtRule();
|
|
2416
|
-
if (value) {
|
|
2417
|
-
return value;
|
|
2418
|
-
}
|
|
2419
|
-
|
|
2420
|
-
parserInput.save();
|
|
2421
|
-
|
|
2422
|
-
name = parserInput.$re(/^@[a-z-]+/);
|
|
2423
|
-
|
|
2424
|
-
if (!name) { return; }
|
|
2425
|
-
|
|
2426
|
-
nonVendorSpecificName = name;
|
|
2427
|
-
if (name.charAt(1) == '-' && name.indexOf('-', 2) > 0) {
|
|
2428
|
-
nonVendorSpecificName = `@${name.slice(name.indexOf('-', 2) + 1)}`;
|
|
2429
|
-
}
|
|
2430
|
-
|
|
2431
|
-
switch (nonVendorSpecificName) {
|
|
2432
|
-
case '@charset':
|
|
2433
|
-
hasIdentifier = true;
|
|
2434
|
-
hasBlock = false;
|
|
2435
|
-
break;
|
|
2436
|
-
case '@namespace':
|
|
2437
|
-
hasExpression = true;
|
|
2438
|
-
hasBlock = false;
|
|
2439
|
-
break;
|
|
2440
|
-
case '@keyframes':
|
|
2441
|
-
case '@counter-style':
|
|
2442
|
-
hasIdentifier = true;
|
|
2443
|
-
break;
|
|
2444
|
-
case '@document':
|
|
2445
|
-
case '@supports':
|
|
2446
|
-
hasUnknown = true;
|
|
2447
|
-
isRooted = false;
|
|
2448
|
-
break;
|
|
2449
|
-
case '@starting-style':
|
|
2450
|
-
isRooted = false;
|
|
2451
|
-
break;
|
|
2452
|
-
case '@layer':
|
|
2453
|
-
isRooted = false;
|
|
2454
|
-
break;
|
|
2455
|
-
default:
|
|
2456
|
-
hasUnknown = true;
|
|
2457
|
-
break;
|
|
2458
|
-
}
|
|
2459
|
-
|
|
2460
|
-
parserInput.commentStore.length = 0;
|
|
2461
|
-
|
|
2462
|
-
if (hasIdentifier) {
|
|
2463
|
-
value = this.atRuleEntity();
|
|
2464
|
-
if (!value) {
|
|
2465
|
-
error(`expected ${name} identifier`);
|
|
2466
|
-
}
|
|
2467
|
-
if (nonVendorSpecificName === '@charset') {
|
|
2468
|
-
warnDynamicCharset(value, index);
|
|
2469
|
-
}
|
|
2470
|
-
} else if (hasExpression) {
|
|
2471
|
-
// `@namespace` may carry an interpolated `@{ns}` prefix (or a
|
|
2472
|
-
// deprecated bare `@ns`). Parse that prefix directly so `@{ns}`
|
|
2473
|
-
// is accepted here without treating value positions as
|
|
2474
|
-
// interpolation contexts, then read the namespace URL.
|
|
2475
|
-
let prefix = this.entities.variableCurly();
|
|
2476
|
-
if (!prefix && parserInput.peek(/^@@?[\w-]/)) {
|
|
2477
|
-
const prefixIndex = parserInput.i;
|
|
2478
|
-
prefix = this.entities.variable();
|
|
2479
|
-
if (prefix) { warnBareAtRuleVariable(prefixIndex); }
|
|
2480
|
-
}
|
|
2481
|
-
value = this.expression();
|
|
2482
|
-
if (prefix) {
|
|
2483
|
-
value = value ? new(tree.Expression)([prefix, ...value.value]) : prefix;
|
|
2484
|
-
}
|
|
2485
|
-
if (!value) {
|
|
2486
|
-
error(`expected ${name} expression`);
|
|
2487
|
-
}
|
|
2488
|
-
} else if (hasUnknown) {
|
|
2489
|
-
const unknownPackage = this.atruleUnknown(value, name, hasBlock);
|
|
2490
|
-
value = unknownPackage[0];
|
|
2491
|
-
hasBlock = unknownPackage[1];
|
|
2492
|
-
}
|
|
2493
|
-
|
|
2494
|
-
if (hasBlock) {
|
|
2495
|
-
let blockPackage = this.atruleBlock(rules, value, isRooted, isKeywordList);
|
|
2496
|
-
rules = blockPackage[0];
|
|
2497
|
-
value = blockPackage[1];
|
|
2498
|
-
isKeywordList = blockPackage[2];
|
|
2499
|
-
|
|
2500
|
-
if (!rules && !hasUnknown) {
|
|
2501
|
-
parserInput.restore();
|
|
2502
|
-
name = parserInput.$re(/^@[a-z-]+/);
|
|
2503
|
-
const unknownPackage = this.atruleUnknown(value, name, hasBlock);
|
|
2504
|
-
value = unknownPackage[0];
|
|
2505
|
-
hasBlock = unknownPackage[1];
|
|
2506
|
-
if (hasBlock) {
|
|
2507
|
-
blockPackage = this.atruleBlock(rules, value, isRooted, isKeywordList);
|
|
2508
|
-
rules = blockPackage[0];
|
|
2509
|
-
value = blockPackage[1];
|
|
2510
|
-
isKeywordList = blockPackage[2];
|
|
2511
|
-
}
|
|
2512
|
-
}
|
|
2513
|
-
}
|
|
2514
|
-
|
|
2515
|
-
if (rules || isKeywordList || (!hasBlock && value && parserInput.$char(';'))) {
|
|
2516
|
-
parserInput.forget();
|
|
2517
|
-
return new(tree.AtRule)(name, value, rules, index + currentIndex, fileInfo,
|
|
2518
|
-
context.dumpLineNumbers ? getDebugInfo(index) : null,
|
|
2519
|
-
isRooted
|
|
2520
|
-
);
|
|
2521
|
-
}
|
|
2522
|
-
|
|
2523
|
-
parserInput.restore('at-rule options not recognised');
|
|
2524
|
-
},
|
|
2525
|
-
|
|
2526
|
-
//
|
|
2527
|
-
// A Value is a comma-delimited list of Expressions
|
|
2528
|
-
//
|
|
2529
|
-
// font-family: Baskerville, Georgia, serif;
|
|
2530
|
-
//
|
|
2531
|
-
// In a Rule, a Value represents everything after the `:`,
|
|
2532
|
-
// and before the `;`.
|
|
2533
|
-
//
|
|
2534
|
-
value: function () {
|
|
2535
|
-
let e;
|
|
2536
|
-
const expressions = [];
|
|
2537
|
-
const index = parserInput.i;
|
|
2538
|
-
|
|
2539
|
-
do {
|
|
2540
|
-
e = this.expression();
|
|
2541
|
-
if (e) {
|
|
2542
|
-
expressions.push(e);
|
|
2543
|
-
if (!parserInput.$char(',')) { break; }
|
|
2544
|
-
}
|
|
2545
|
-
} while (e);
|
|
2546
|
-
|
|
2547
|
-
if (expressions.length > 0) {
|
|
2548
|
-
return new(tree.Value)(expressions, index + currentIndex);
|
|
2549
|
-
}
|
|
2550
|
-
},
|
|
2551
|
-
important: function () {
|
|
2552
|
-
if (parserInput.currentChar() === '!') {
|
|
2553
|
-
return parserInput.$re(/^! *important/);
|
|
2554
|
-
}
|
|
2555
|
-
},
|
|
2556
|
-
sub: function () {
|
|
2557
|
-
let a;
|
|
2558
|
-
let e;
|
|
2559
|
-
|
|
2560
|
-
parserInput.save();
|
|
2561
|
-
if (parserInput.$char('(')) {
|
|
2562
|
-
a = this.addition();
|
|
2563
|
-
if (a && parserInput.$char(')')) {
|
|
2564
|
-
parserInput.forget();
|
|
2565
|
-
e = new(tree.Expression)([a]);
|
|
2566
|
-
e.parens = true;
|
|
2567
|
-
return e;
|
|
2568
|
-
}
|
|
2569
|
-
parserInput.restore('Expected \')\'');
|
|
2570
|
-
return;
|
|
2571
|
-
}
|
|
2572
|
-
parserInput.restore();
|
|
2573
|
-
},
|
|
2574
|
-
colorOperand: function () {
|
|
2575
|
-
parserInput.save();
|
|
2576
|
-
|
|
2577
|
-
// hsl or rgb or lch operand
|
|
2578
|
-
const match = parserInput.$re(/^([lchrgbs])(?=\s|[,/*)]|$)/);
|
|
2579
|
-
if (match) {
|
|
2580
|
-
parserInput.forget();
|
|
2581
|
-
return new tree.Keyword(match[1]);
|
|
2582
|
-
}
|
|
2583
|
-
|
|
2584
|
-
parserInput.restore();
|
|
2585
|
-
},
|
|
2586
|
-
multiplication: function () {
|
|
2587
|
-
let m;
|
|
2588
|
-
let a;
|
|
2589
|
-
let op;
|
|
2590
|
-
let operation;
|
|
2591
|
-
let isSpaced;
|
|
2592
|
-
m = this.operand();
|
|
2593
|
-
if (m) {
|
|
2594
|
-
isSpaced = parserInput.isWhitespace(-1);
|
|
2595
|
-
while (true) {
|
|
2596
|
-
if (parserInput.peek(/^\/[*/]/)) {
|
|
2597
|
-
break;
|
|
2598
|
-
}
|
|
2599
|
-
|
|
2600
|
-
parserInput.save();
|
|
2601
|
-
|
|
2602
|
-
op = parserInput.$char('/') || parserInput.$char('*');
|
|
2603
|
-
if (!op) {
|
|
2604
|
-
let index = parserInput.i;
|
|
2605
|
-
op = parserInput.$str('./');
|
|
2606
|
-
if (op) {
|
|
2607
|
-
warn('./ operator is deprecated', index, 'DEPRECATED', 'dot-slash-operator');
|
|
2608
|
-
}
|
|
2609
|
-
}
|
|
2610
|
-
|
|
2611
|
-
if (!op) { parserInput.forget(); break; }
|
|
2612
|
-
|
|
2613
|
-
a = this.operand();
|
|
2614
|
-
|
|
2615
|
-
if (!a) { parserInput.restore(); break; }
|
|
2616
|
-
parserInput.forget();
|
|
2617
|
-
|
|
2618
|
-
m.parensInOp = true;
|
|
2619
|
-
a.parensInOp = true;
|
|
2620
|
-
operation = new(tree.Operation)(op, [operation || m, a], isSpaced);
|
|
2621
|
-
isSpaced = parserInput.isWhitespace(-1);
|
|
2622
|
-
}
|
|
2623
|
-
return operation || m;
|
|
2624
|
-
}
|
|
2625
|
-
},
|
|
2626
|
-
addition: function () {
|
|
2627
|
-
let m;
|
|
2628
|
-
let a;
|
|
2629
|
-
let op;
|
|
2630
|
-
let operation;
|
|
2631
|
-
let isSpaced;
|
|
2632
|
-
m = this.multiplication();
|
|
2633
|
-
if (m) {
|
|
2634
|
-
isSpaced = parserInput.isWhitespace(-1);
|
|
2635
|
-
while (true) {
|
|
2636
|
-
op = parserInput.$re(/^[-+]\s+/) || (!isSpaced && (parserInput.$char('+') || parserInput.$char('-')));
|
|
2637
|
-
if (!op) {
|
|
2638
|
-
break;
|
|
2639
|
-
}
|
|
2640
|
-
a = this.multiplication();
|
|
2641
|
-
if (!a) {
|
|
2642
|
-
break;
|
|
2643
|
-
}
|
|
2644
|
-
|
|
2645
|
-
m.parensInOp = true;
|
|
2646
|
-
a.parensInOp = true;
|
|
2647
|
-
operation = new(tree.Operation)(op, [operation || m, a], isSpaced);
|
|
2648
|
-
isSpaced = parserInput.isWhitespace(-1);
|
|
2649
|
-
}
|
|
2650
|
-
return operation || m;
|
|
2651
|
-
}
|
|
2652
|
-
},
|
|
2653
|
-
conditions: function () {
|
|
2654
|
-
let a;
|
|
2655
|
-
let b;
|
|
2656
|
-
const index = parserInput.i;
|
|
2657
|
-
let condition;
|
|
2658
|
-
|
|
2659
|
-
a = this.condition(true);
|
|
2660
|
-
if (a) {
|
|
2661
|
-
while (true) {
|
|
2662
|
-
if (!parserInput.peek(/^,\s*(not\s*)?\(/) || !parserInput.$char(',')) {
|
|
2663
|
-
break;
|
|
2664
|
-
}
|
|
2665
|
-
b = this.condition(true);
|
|
2666
|
-
if (!b) {
|
|
2667
|
-
break;
|
|
2668
|
-
}
|
|
2669
|
-
condition = new(tree.Condition)('or', condition || a, b, index + currentIndex);
|
|
2670
|
-
}
|
|
2671
|
-
return condition || a;
|
|
2672
|
-
}
|
|
2673
|
-
},
|
|
2674
|
-
condition: function (needsParens, allowConditionOperands) {
|
|
2675
|
-
let result;
|
|
2676
|
-
let logical;
|
|
2677
|
-
let next;
|
|
2678
|
-
function or() {
|
|
2679
|
-
return parserInput.$str('or');
|
|
2680
|
-
}
|
|
2681
|
-
|
|
2682
|
-
result = this.conditionAnd(needsParens, allowConditionOperands);
|
|
2683
|
-
if (!result) {
|
|
2684
|
-
return ;
|
|
2685
|
-
}
|
|
2686
|
-
logical = or();
|
|
2687
|
-
if (logical) {
|
|
2688
|
-
next = this.condition(needsParens, allowConditionOperands);
|
|
2689
|
-
if (next) {
|
|
2690
|
-
result = new(tree.Condition)(logical, result, next);
|
|
2691
|
-
} else {
|
|
2692
|
-
return ;
|
|
2693
|
-
}
|
|
2694
|
-
}
|
|
2695
|
-
return result;
|
|
2696
|
-
},
|
|
2697
|
-
conditionAnd: function (needsParens, allowConditionOperands) {
|
|
2698
|
-
let result;
|
|
2699
|
-
let logical;
|
|
2700
|
-
let next;
|
|
2701
|
-
const self = this;
|
|
2702
|
-
function insideCondition() {
|
|
2703
|
-
const cond = self.negatedCondition(needsParens, allowConditionOperands) || self.parenthesisCondition(needsParens, allowConditionOperands);
|
|
2704
|
-
if (!cond && !needsParens) {
|
|
2705
|
-
return self.atomicCondition(needsParens);
|
|
2706
|
-
}
|
|
2707
|
-
return cond;
|
|
2708
|
-
}
|
|
2709
|
-
function and() {
|
|
2710
|
-
return parserInput.$str('and');
|
|
2711
|
-
}
|
|
2712
|
-
|
|
2713
|
-
result = insideCondition();
|
|
2714
|
-
if (!result) {
|
|
2715
|
-
return ;
|
|
2716
|
-
}
|
|
2717
|
-
if (allowConditionOperands) {
|
|
2718
|
-
result = this.atomicCondition(needsParens, result, allowConditionOperands) || result;
|
|
2719
|
-
}
|
|
2720
|
-
logical = and();
|
|
2721
|
-
if (logical) {
|
|
2722
|
-
next = this.conditionAnd(needsParens, allowConditionOperands);
|
|
2723
|
-
if (next) {
|
|
2724
|
-
result = new(tree.Condition)(logical, result, next);
|
|
2725
|
-
} else {
|
|
2726
|
-
return ;
|
|
2727
|
-
}
|
|
2728
|
-
}
|
|
2729
|
-
return result;
|
|
2730
|
-
},
|
|
2731
|
-
negatedCondition: function (needsParens, allowConditionOperands) {
|
|
2732
|
-
if (parserInput.$str('not')) {
|
|
2733
|
-
const result = this.parenthesisCondition(needsParens, allowConditionOperands);
|
|
2734
|
-
if (result) {
|
|
2735
|
-
result.negate = !result.negate;
|
|
2736
|
-
return result;
|
|
2737
|
-
}
|
|
2738
|
-
|
|
2739
|
-
// Allow simple bare values (keyword/variable) without parens,
|
|
2740
|
-
// e.g., `not false` or `not @var`.
|
|
2741
|
-
// Complex conditions (comparisons, function calls) require parentheses.
|
|
2742
|
-
const entities = this.entities;
|
|
2743
|
-
const index = parserInput.i;
|
|
2744
|
-
const a = entities.keyword() || entities.variable() || entities.quoted() || entities.mixinLookup();
|
|
2745
|
-
if (a) {
|
|
2746
|
-
return new(tree.Condition)('=', a, new(tree.Keyword)('true'), index + currentIndex, true);
|
|
2747
|
-
}
|
|
2748
|
-
}
|
|
2749
|
-
},
|
|
2750
|
-
parenthesisCondition: function (needsParens, allowConditionOperands) {
|
|
2751
|
-
function tryConditionFollowedByParenthesis(me) {
|
|
2752
|
-
let body;
|
|
2753
|
-
parserInput.save();
|
|
2754
|
-
body = me.condition(needsParens, allowConditionOperands);
|
|
2755
|
-
if (!body) {
|
|
2756
|
-
parserInput.restore();
|
|
2757
|
-
return ;
|
|
2758
|
-
}
|
|
2759
|
-
if (!parserInput.$char(')')) {
|
|
2760
|
-
parserInput.restore();
|
|
2761
|
-
return ;
|
|
2762
|
-
}
|
|
2763
|
-
parserInput.forget();
|
|
2764
|
-
return body;
|
|
2765
|
-
}
|
|
2766
|
-
|
|
2767
|
-
let body;
|
|
2768
|
-
parserInput.save();
|
|
2769
|
-
if (!parserInput.$str('(')) {
|
|
2770
|
-
parserInput.restore();
|
|
2771
|
-
return ;
|
|
2772
|
-
}
|
|
2773
|
-
body = tryConditionFollowedByParenthesis(this);
|
|
2774
|
-
if (body) {
|
|
2775
|
-
parserInput.forget();
|
|
2776
|
-
return body;
|
|
2777
|
-
}
|
|
2778
|
-
|
|
2779
|
-
body = this.atomicCondition(needsParens);
|
|
2780
|
-
if (!body) {
|
|
2781
|
-
parserInput.restore();
|
|
2782
|
-
return ;
|
|
2783
|
-
}
|
|
2784
|
-
if (!parserInput.$char(')')) {
|
|
2785
|
-
parserInput.restore(`expected ')' got '${parserInput.currentChar()}'`);
|
|
2786
|
-
return ;
|
|
2787
|
-
}
|
|
2788
|
-
parserInput.forget();
|
|
2789
|
-
return body;
|
|
2790
|
-
},
|
|
2791
|
-
atomicCondition: function (needsParens, preparsedCond, allowConditionOperands) {
|
|
2792
|
-
const entities = this.entities;
|
|
2793
|
-
const index = parserInput.i;
|
|
2794
|
-
let a;
|
|
2795
|
-
let b;
|
|
2796
|
-
let c;
|
|
2797
|
-
let op;
|
|
2798
|
-
|
|
2799
|
-
const cond = (function() {
|
|
2800
|
-
return (allowConditionOperands && this.parenthesisCondition(needsParens)) || this.addition() || entities.keyword() || entities.quoted() || entities.mixinLookup();
|
|
2801
|
-
}).bind(this)
|
|
2802
|
-
|
|
2803
|
-
if (preparsedCond) {
|
|
2804
|
-
a = preparsedCond;
|
|
2805
|
-
} else {
|
|
2806
|
-
a = cond();
|
|
2807
|
-
}
|
|
2808
|
-
|
|
2809
|
-
if (a) {
|
|
2810
|
-
if (parserInput.$char('>')) {
|
|
2811
|
-
if (parserInput.$char('=')) {
|
|
2812
|
-
op = '>=';
|
|
2813
|
-
} else {
|
|
2814
|
-
op = '>';
|
|
2815
|
-
}
|
|
2816
|
-
} else
|
|
2817
|
-
if (parserInput.$char('<')) {
|
|
2818
|
-
if (parserInput.$char('=')) {
|
|
2819
|
-
op = '<=';
|
|
2820
|
-
} else {
|
|
2821
|
-
op = '<';
|
|
2822
|
-
}
|
|
2823
|
-
} else
|
|
2824
|
-
if (parserInput.$char('=')) {
|
|
2825
|
-
if (parserInput.$char('>')) {
|
|
2826
|
-
op = '=>';
|
|
2827
|
-
} else if (parserInput.$char('<')) {
|
|
2828
|
-
op = '=<';
|
|
2829
|
-
} else {
|
|
2830
|
-
op = '=';
|
|
2831
|
-
}
|
|
2832
|
-
}
|
|
2833
|
-
if (op) {
|
|
2834
|
-
b = cond();
|
|
2835
|
-
if (b) {
|
|
2836
|
-
c = new(tree.Condition)(op, a, b, index + currentIndex, false);
|
|
2837
|
-
} else {
|
|
2838
|
-
error('expected expression');
|
|
2839
|
-
}
|
|
2840
|
-
} else if (!preparsedCond) {
|
|
2841
|
-
c = new(tree.Condition)('=', a, new(tree.Keyword)('true'), index + currentIndex, false);
|
|
2842
|
-
}
|
|
2843
|
-
return c;
|
|
2844
|
-
}
|
|
2845
|
-
},
|
|
2846
|
-
|
|
2847
|
-
//
|
|
2848
|
-
// An operand is anything that can be part of an operation,
|
|
2849
|
-
// such as a Color, or a Variable
|
|
2850
|
-
//
|
|
2851
|
-
operand: function () {
|
|
2852
|
-
const entities = this.entities;
|
|
2853
|
-
let negate;
|
|
2854
|
-
|
|
2855
|
-
if (parserInput.peek(/^-[@$(]/)) {
|
|
2856
|
-
negate = parserInput.$char('-');
|
|
2857
|
-
}
|
|
2858
|
-
|
|
2859
|
-
let o = this.sub() || entities.dimension() ||
|
|
2860
|
-
entities.color() || entities.variable() ||
|
|
2861
|
-
entities.property() || entities.call() ||
|
|
2862
|
-
entities.quoted(true) || entities.colorKeyword() ||
|
|
2863
|
-
this.colorOperand() || entities.mixinLookup();
|
|
2864
|
-
|
|
2865
|
-
if (negate) {
|
|
2866
|
-
o.parensInOp = true;
|
|
2867
|
-
o = new(tree.Negative)(o);
|
|
2868
|
-
}
|
|
2869
|
-
|
|
2870
|
-
return o;
|
|
2871
|
-
},
|
|
2872
|
-
|
|
2873
|
-
//
|
|
2874
|
-
// Expressions either represent mathematical operations,
|
|
2875
|
-
// or white-space delimited Entities.
|
|
2876
|
-
//
|
|
2877
|
-
// 1px solid black
|
|
2878
|
-
// @var * 2
|
|
2879
|
-
//
|
|
2880
|
-
expression: function () {
|
|
2881
|
-
const entities = [];
|
|
2882
|
-
let e;
|
|
2883
|
-
let delim;
|
|
2884
|
-
const index = parserInput.i;
|
|
2885
|
-
|
|
2886
|
-
do {
|
|
2887
|
-
e = this.comment();
|
|
2888
|
-
if (e && !e.isLineComment) {
|
|
2889
|
-
entities.push(e);
|
|
2890
|
-
continue;
|
|
2891
|
-
}
|
|
2892
|
-
e = this.addition() || this.entity();
|
|
2893
|
-
|
|
2894
|
-
if (e instanceof tree.Comment) {
|
|
2895
|
-
e = null;
|
|
2896
|
-
}
|
|
2897
|
-
|
|
2898
|
-
if (e) {
|
|
2899
|
-
entities.push(e);
|
|
2900
|
-
// operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here
|
|
2901
|
-
if (!parserInput.peek(/^\/[/*]/)) {
|
|
2902
|
-
delim = parserInput.$char('/');
|
|
2903
|
-
if (delim) {
|
|
2904
|
-
entities.push(new(tree.Anonymous)(delim, index + currentIndex));
|
|
2905
|
-
}
|
|
2906
|
-
}
|
|
2907
|
-
}
|
|
2908
|
-
} while (e);
|
|
2909
|
-
if (entities.length > 0) {
|
|
2910
|
-
return new(tree.Expression)(entities);
|
|
2911
|
-
}
|
|
2912
|
-
},
|
|
2913
|
-
property: function () {
|
|
2914
|
-
const name = parserInput.$re(/^(\*?-?[_a-zA-Z0-9-]+)\s*:/);
|
|
2915
|
-
if (name) {
|
|
2916
|
-
return name[1];
|
|
2917
|
-
}
|
|
2918
|
-
},
|
|
2919
|
-
ruleProperty: function () {
|
|
2920
|
-
let name = [];
|
|
2921
|
-
const index = [];
|
|
2922
|
-
let s;
|
|
2923
|
-
let k;
|
|
2924
|
-
|
|
2925
|
-
parserInput.save();
|
|
2926
|
-
|
|
2927
|
-
const simpleProperty = parserInput.$re(/^([_a-zA-Z0-9-]+)\s*:/);
|
|
2928
|
-
if (simpleProperty) {
|
|
2929
|
-
name = [new(tree.Keyword)(simpleProperty[1])];
|
|
2930
|
-
parserInput.forget();
|
|
2931
|
-
return name;
|
|
2932
|
-
}
|
|
2933
|
-
|
|
2934
|
-
function match(re) {
|
|
2935
|
-
const i = parserInput.i;
|
|
2936
|
-
const chunk = parserInput.$re(re);
|
|
2937
|
-
if (chunk) {
|
|
2938
|
-
index.push(i);
|
|
2939
|
-
return name.push(chunk[1]);
|
|
2940
|
-
}
|
|
2941
|
-
}
|
|
2942
|
-
|
|
2943
|
-
match(/^(\*?)/);
|
|
2944
|
-
while (true) {
|
|
2945
|
-
if (!match(RULE_PROPERTY_PARTICLE)) {
|
|
2946
|
-
break;
|
|
2947
|
-
}
|
|
2948
|
-
}
|
|
2949
|
-
|
|
2950
|
-
if ((name.length > 1) && match(/^((?:\+_|\+)?)\s*:/)) {
|
|
2951
|
-
parserInput.forget();
|
|
2952
|
-
|
|
2953
|
-
// at last, we have the complete match now. move forward,
|
|
2954
|
-
// convert name particles to tree objects and return:
|
|
2955
|
-
if (name[0] === '') {
|
|
2956
|
-
name.shift();
|
|
2957
|
-
index.shift();
|
|
2958
|
-
}
|
|
2959
|
-
for (k = 0; k < name.length; k++) {
|
|
2960
|
-
s = name[k];
|
|
2961
|
-
const sigil = s.charAt(0);
|
|
2962
|
-
if (sigil !== '@' && sigil !== '$') {
|
|
2963
|
-
name[k] = new(tree.Keyword)(s);
|
|
2964
|
-
continue;
|
|
2965
|
-
}
|
|
2966
|
-
// `@{name}` / `@{name[key]}` — strip the sigil and braces, then
|
|
2967
|
-
// let the shared resolver decide between a plain reference and
|
|
2968
|
-
// a lookup so this path cannot drift from the others.
|
|
2969
|
-
const raw = s.slice(2, -1);
|
|
2970
|
-
if (sigil === '@') {
|
|
2971
|
-
warnNumericVariableName(splitLookups(raw).name, index[k]);
|
|
2972
|
-
warnDashOnlyVariableName(splitLookups(raw).name, index[k]);
|
|
2973
|
-
name[k] = resolveInterpolatedVariable(raw, index[k] + currentIndex, fileInfo);
|
|
2974
|
-
} else {
|
|
2975
|
-
name[k] = resolveInterpolatedProperty(raw, index[k] + currentIndex, fileInfo);
|
|
2976
|
-
}
|
|
2977
|
-
}
|
|
2978
|
-
return name;
|
|
2979
|
-
}
|
|
2980
|
-
parserInput.restore();
|
|
2981
|
-
}
|
|
2982
|
-
}
|
|
2983
|
-
};
|
|
2984
|
-
};
|
|
2985
|
-
Parser.serializeVars = vars => {
|
|
2986
|
-
let s = '';
|
|
2987
|
-
|
|
2988
|
-
for (const name in vars) {
|
|
2989
|
-
if (Object.hasOwnProperty.call(vars, name)) {
|
|
2990
|
-
const value = vars[name];
|
|
2991
|
-
s += `${((name[0] === '@') ? '' : '@') + name}: ${value}${(String(value).slice(-1) === ';') ? '' : ';'}`;
|
|
2992
|
-
}
|
|
2993
|
-
}
|
|
2994
|
-
|
|
2995
|
-
return s;
|
|
2996
|
-
};
|
|
2997
|
-
|
|
2998
|
-
export default Parser;
|