@forumone/postcss-advanced-variables 0.0.0-stage → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/LICENSE.md +108 -0
  2. package/README.md +575 -2
  3. package/index.js +730 -0
  4. package/index.mjs +728 -0
  5. package/package.json +55 -4
package/index.js ADDED
@@ -0,0 +1,730 @@
1
+ 'use strict';
2
+
3
+ var postcss = require('postcss');
4
+ var path = require('path');
5
+ var resolve = require('@csstools/sass-import-resolve');
6
+
7
+ // return the closest variable from a node
8
+ function getClosestVariable(name, node, opts) {
9
+ const variables = getVariables(node);
10
+
11
+ let variable = variables[name];
12
+
13
+ if (requiresAncestorVariable(variable, node)) {
14
+ variable = getClosestVariable(name, node.parent, opts);
15
+ }
16
+
17
+ if (requiresFnVariable(variable, opts)) {
18
+ variable = getFnVariable(name, node, opts.variables);
19
+ }
20
+
21
+ return variable;
22
+ }
23
+
24
+ // return the variables object of a node
25
+ const getVariables = node => Object(Object(node).variables);
26
+
27
+ // return whether the variable should be replaced using an ancestor variable
28
+ const requiresAncestorVariable = (variable, node) => undefined === variable && node && node.parent;
29
+
30
+ // return whether variable should be replaced using a variables function
31
+ const requiresFnVariable = (value, opts) => value === undefined && Object(opts).variables === Object(Object(opts).variables);
32
+
33
+ // return whether variable should be replaced using a variables function
34
+ const getFnVariable = (name, node, variables) => 'function' === typeof variables
35
+ ? variables(name, node)
36
+ : variables[name];
37
+
38
+ function manageUnresolved(node, opts, word, message) {
39
+ if ('warn' === opts.unresolved) {
40
+ node.warn(opts.result, message, { word });
41
+ } else if ('ignore' !== opts.unresolved) {
42
+ throw node.error(message, { word });
43
+ }
44
+ }
45
+
46
+ // tooling
47
+
48
+ // return content with its variables replaced by the corresponding values of a node
49
+ function getReplacedString(string, node, opts) {
50
+ const replacedString = string.replace(
51
+ matchVariables,
52
+ (match, before, name1, name2, name3) => {
53
+ // conditionally return an (unescaped) match
54
+ if (before === '\\') {
55
+ return match.slice(1);
56
+ }
57
+
58
+ // the first matching variable name
59
+ const name = name1 || name2 || name3;
60
+
61
+ // the closest variable value
62
+ const value = getClosestVariable(name, node.parent, opts);
63
+
64
+ // if a variable has not been resolved
65
+ if (undefined === value) {
66
+ manageUnresolved(node, opts, name, `Could not resolve the variable "$${name}" within "${string}"`);
67
+
68
+ return match;
69
+ }
70
+
71
+ // the stringified value
72
+ const stringifiedValue = `${before}${stringify(value)}`;
73
+
74
+ return stringifiedValue;
75
+ }
76
+ );
77
+
78
+ return replacedString;
79
+ }
80
+
81
+ // match all $name, $(name), and #{$name} variables (and catch the character before it)
82
+ const matchVariables = /(.?)(?:\$([a-zA-Z][\w-]*)|\$\(([a-zA-Z][\w-]*)\)|#\{\$([a-zA-Z][\w-]*)\})/g;
83
+
84
+ // return a sass stringified variable
85
+ const stringify = object => Array.isArray(object)
86
+ ? `(${object.map(stringify).join(',')})`
87
+ : Object(object) === object
88
+ ? `(${Object.keys(object).map(
89
+ key => `${key}:${stringify(object[key])}`
90
+ ).join(',')})`
91
+ : String(object);
92
+
93
+ // tooling
94
+
95
+ // set a variable on a node
96
+ function setVariable(node, name, value, opts) {
97
+ // if the value is not a default with a value already defined
98
+ if (!matchDefault.test(value) || getClosestVariable(name, node, opts) === undefined) {
99
+ // the value without a default suffix
100
+ const undefaultedValue = matchDefault.test(value)
101
+ ? value.replace(matchDefault, '')
102
+ : value;
103
+
104
+ // ensure the node has a variables object
105
+ node.variables = node.variables || {};
106
+
107
+ // set the variable
108
+ node.variables[name] = undefaultedValue;
109
+ }
110
+ }
111
+
112
+ // match anything ending with a valid !default
113
+ const matchDefault = /\s+!default$/;
114
+
115
+ // tooling
116
+
117
+ // transform declarations
118
+ function transformDecl(decl, opts) {
119
+ // update the declaration value with its variables replaced by their corresponding values
120
+ decl.value = getReplacedString(decl.value, decl, opts);
121
+
122
+
123
+ // if the declaration is a variable declaration
124
+ if (isVariableDeclaration(decl)) {
125
+ // set the variable on the parent of the declaration
126
+ setVariable(decl.parent, decl.prop.slice(1), decl.value, opts);
127
+
128
+ // remove the declaration
129
+ decl.remove();
130
+ } else {
131
+ decl.prop = getReplacedString(decl.prop, decl, opts);
132
+ }
133
+ }
134
+
135
+ // return whether the declaration property is a variable declaration
136
+ const isVariableDeclaration = decl => matchVariable.test(decl.prop);
137
+
138
+ // match a variable ($any-name)
139
+ const matchVariable = /^\$[\w-]+$/;
140
+
141
+ // tooling
142
+
143
+ // transform generic at-rules
144
+ function transformAtrule(rule, opts) {
145
+ // update the at-rule params with its variables replaced by their corresponding values
146
+ rule.params = getReplacedString(rule.params, rule, opts);
147
+ }
148
+
149
+ // tooling
150
+
151
+ // return sass-like arrays as literal arrays ('(hello), (goodbye)' to [[hello], [goodbye]])
152
+ function getValueAsObject(value) {
153
+ const hasWrappingParens = matchWrappingParens.test(value);
154
+ const unwrappedValue = String(hasWrappingParens ? value.replace(matchWrappingParens, '$1') : value).replace(matchTrailingComma, '');
155
+ const separatedValue = postcss.list.comma(unwrappedValue);
156
+ const firstValue = separatedValue[0];
157
+
158
+ if (firstValue === value) {
159
+ return value;
160
+ } else {
161
+ const objectValue = {};
162
+ const arrayValue = [];
163
+
164
+ separatedValue.forEach(
165
+ (subvalue, index) => {
166
+ const [ match, key, keyvalue ] = subvalue.match(matchDeclaration) || [];
167
+
168
+ if (match) {
169
+ objectValue[key] = getValueAsObject(keyvalue);
170
+ } else {
171
+ arrayValue[index] = getValueAsObject(subvalue);
172
+ }
173
+ }
174
+ );
175
+
176
+ const transformedValue = Object.keys(objectValue).length > 0
177
+ ? Object.assign(objectValue, arrayValue)
178
+ : arrayValue;
179
+
180
+ return transformedValue;
181
+ }
182
+ }
183
+
184
+ // match wrapping parentheses ((), (anything), (anything (anything)))
185
+ const matchWrappingParens = /^\(([\W\w]*)\)$/g;
186
+
187
+ // match a property name (any-possible_name)
188
+ const matchDeclaration = /^([\w-]+)\s*:\s*([\W\w]+)\s*$/;
189
+
190
+ // match a trailing comma
191
+ const matchTrailingComma = /\s*,\s*$/;
192
+
193
+ var waterfall = (items, asyncFunction) => items.reduce(
194
+ (lastPromise, item) => lastPromise.then(
195
+ () => asyncFunction(item)
196
+ ),
197
+ Promise.resolve()
198
+ );
199
+
200
+ // tooling
201
+
202
+ // transform @each at-rules
203
+ function transformEachAtrule(rule, opts) {
204
+ // if @each is supported
205
+ if (opts.transform.includes('@each')) {
206
+ // @each options
207
+ const { varname, incname, list } = getEachOpts(rule, opts);
208
+ const replacements = [];
209
+ const ruleClones = [];
210
+
211
+ Object.keys(list).forEach(
212
+ key => {
213
+ // set the current variable
214
+ setVariable(rule, varname, list[key], opts);
215
+
216
+ // conditionally set the incremenator variable
217
+ if (incname) {
218
+ setVariable(rule, incname, key, opts);
219
+ }
220
+
221
+ // clone the @each at-rule
222
+ const clone = rule.clone({
223
+ parent: rule.parent,
224
+ variables: Object.assign({}, rule.variables)
225
+ });
226
+
227
+ ruleClones.push(clone);
228
+ }
229
+ );
230
+
231
+ return waterfall(
232
+ ruleClones,
233
+ clone => transformNode(clone, opts).then(
234
+ () => {
235
+ replacements.push(...clone.nodes);
236
+ }
237
+ )
238
+ ).then(
239
+ () => {
240
+ // replace the @each at-rule with the replacements
241
+ rule.parent.insertBefore(rule, replacements);
242
+
243
+ rule.remove();
244
+ }
245
+ )
246
+ }
247
+ }
248
+
249
+ // return the @each statement options (@each NAME in LIST, @each NAME ITERATOR in LIST)
250
+ const getEachOpts = (node, opts) => {
251
+ const params = node.params.split(matchInOperator);
252
+ const args = (params[0] || '').trim().split(' ');
253
+ const varname = args[0].trim().slice(1);
254
+ const incname = args.length > 1 && args[1].trim().slice(1);
255
+ const rawlist = getValueAsObject(
256
+ getReplacedString(
257
+ params.slice(1).join(matchInOperator),
258
+ node,
259
+ opts
260
+ )
261
+ );
262
+ const list = 'string' === typeof rawlist ? [rawlist] : rawlist;
263
+
264
+ return { varname, incname, list };
265
+ };
266
+
267
+ // match the opertor separating the name and iterator from the list
268
+ const matchInOperator = ' in ';
269
+
270
+ // tooling
271
+
272
+ // transform @if at-rules
273
+ function transformIfAtrule(rule, opts) {
274
+ // @if options
275
+ const isTruthy = isIfTruthy(rule, opts);
276
+ const next = rule.next();
277
+
278
+ const transformAndInsertBeforeParent = node => transformNode(node, opts).then(
279
+ () => node.parent.insertBefore(node, node.nodes)
280
+ );
281
+
282
+ return ifPromise(
283
+ opts.transform.includes('@if'),
284
+ () => ifPromise(
285
+ isTruthy,
286
+ () => transformAndInsertBeforeParent(rule)
287
+ ).then(() => {
288
+ rule.remove();
289
+ })
290
+ ).then(() => ifPromise(
291
+ opts.transform.includes('@else') && isElseRule(next),
292
+ () => ifPromise(
293
+ !isTruthy,
294
+ () => transformAndInsertBeforeParent(next)
295
+ ).then(() => {
296
+ next.remove();
297
+ })
298
+ ))
299
+ }
300
+
301
+ const ifPromise = (condition, trueFunction) => Promise.resolve(condition && trueFunction());
302
+
303
+ // return whether the @if at-rule is truthy
304
+ const isIfTruthy = (node, opts) => {
305
+ // @if statement options (@if EXPRESSION, @if LEFT OPERATOR RIGHT)
306
+ const params = postcss.list.space(node.params);
307
+ const left = getInterprettedString(getReplacedString(params[0] || '', node, opts));
308
+ const operator = params[1];
309
+ const right = getInterprettedString(getReplacedString(params[2] || '', node, opts));
310
+
311
+ // evaluate the expression
312
+ const isTruthy = !operator && left ||
313
+ operator === '==' && left === right ||
314
+ operator === '!=' && left !== right ||
315
+ operator === '<' && left < right ||
316
+ operator === '<=' && left <= right ||
317
+ operator === '>' && left > right ||
318
+ operator === '>=' && left >= right;
319
+
320
+ return isTruthy;
321
+ };
322
+
323
+ // return the value as a boolean, number, or string
324
+ const getInterprettedString = value => 'true' === value
325
+ ? true
326
+ : 'false' === value
327
+ ? false
328
+ : isNaN(value)
329
+ ? value
330
+ : Number(value);
331
+
332
+ // return whether the node is an else at-rule
333
+ const isElseRule = node => Object(node) === node && 'atrule' === node.type && 'else' === node.name;
334
+
335
+ // tooling
336
+
337
+ // transform @import at-rules
338
+ function transformImportAtrule(rule, opts) {
339
+ // if @import is supported
340
+ if (opts.transform.includes('@import')) {
341
+ // @import options
342
+ const { id, media, cwf, cwd } = getImportOpts(rule, opts);
343
+
344
+ // PostCSS options
345
+ const options = opts.result.opts;
346
+ const parser = options.parser || options.syntax && options.syntax.parse || null;
347
+
348
+ if (
349
+ opts.importFilter instanceof Function && opts.importFilter(id, media) ||
350
+ opts.importFilter instanceof RegExp && opts.importFilter.test(id)
351
+ ) {
352
+ const cwds = [cwd].concat(opts.importPaths);
353
+
354
+ // promise the resolved file and its contents using the file resolver
355
+ const importPromise = cwds.reduce(
356
+ (promise, thiscwd) => promise.catch(
357
+ () => opts.importResolve(id, thiscwd, opts)
358
+ ),
359
+ Promise.reject()
360
+ );
361
+
362
+ return importPromise.then(
363
+ // promise the processed file
364
+ ({ file, contents }) => processor.process(contents, { from: file, parser: parser }).then(
365
+ ({ root }) => {
366
+ // push a dependency message
367
+ opts.result.messages.push({ type: 'dependency', file: file, parent: cwf });
368
+
369
+ // imported nodes
370
+ const nodes = root.nodes.slice(0);
371
+
372
+ // if media params were detected
373
+ if (media) {
374
+ // create a new media rule
375
+ const mediaRule = postcss.atRule({
376
+ name: 'media',
377
+ params: media,
378
+ source: rule.source
379
+ });
380
+
381
+ // append with the imported nodes
382
+ mediaRule.append(nodes);
383
+
384
+ // replace the @import at-rule with the @media at-rule
385
+ rule.replaceWith(mediaRule);
386
+ } else {
387
+ // replace the @import at-rule with the imported nodes
388
+ rule.replaceWith(nodes);
389
+ }
390
+
391
+ // transform all nodes from the import
392
+ return transformNode({ nodes }, opts);
393
+ }
394
+ ),
395
+ () => {
396
+ // otherwise, if the @import could not be found
397
+ manageUnresolved(rule, opts, '@import', `Could not resolve the @import for "${id}"`);
398
+ }
399
+ )
400
+ }
401
+ }
402
+ }
403
+
404
+ const noopPlugin = () => {
405
+ return { postcssPlugin: 'noop-plugin', Once() {} };
406
+ };
407
+
408
+ noopPlugin.postcss = true;
409
+
410
+ const processor = postcss([noopPlugin()]);
411
+
412
+ // return the @import statement options (@import ID, @import ID MEDIA)
413
+ const getImportOpts = (node, opts) => {
414
+ const [ rawid, ...medias ] = postcss.list.space(node.params);
415
+ const id = getReplacedString(trimWrappingURL(rawid), node, opts);
416
+ const media = medias.join(' ');
417
+
418
+ // current working file and directory
419
+ const cwf = node.source && node.source.input && node.source.input.file || opts.result.from;
420
+ const cwd = cwf ? path.dirname(cwf) : opts.importRoot;
421
+
422
+ return { id, media, cwf, cwd };
423
+ };
424
+
425
+ // return a string with the wrapping url() and quotes trimmed
426
+ const trimWrappingURL = string => trimWrappingQuotes(string.replace(/^url\(([\W\w]*)\)$/, '$1'));
427
+
428
+ // return a string with the wrapping quotes trimmed
429
+ const trimWrappingQuotes = string => string.replace(/^("|')([\W\w]*)\1$/, '$2');
430
+
431
+ // tooling
432
+
433
+ // transform @include at-rules
434
+ function transformIncludeAtrule(rule, opts) {
435
+ // if @include is supported
436
+ if (opts.transform.includes('@include')) {
437
+ // @include options
438
+ const { name, args } = getIncludeOpts(rule);
439
+
440
+ // the closest @mixin variable
441
+ const mixin = getClosestVariable(`@mixin ${name}`, rule.parent, opts);
442
+
443
+ // if the @mixin variable exists
444
+ if (mixin) {
445
+ // set @mixin variables on the @include at-rule
446
+ mixin.params.forEach(
447
+ (param, index) => {
448
+ const arg = index in args
449
+ ? getReplacedString(args[index], rule, opts)
450
+ : param.value;
451
+
452
+ setVariable(rule, param.name, arg, opts);
453
+ }
454
+ );
455
+
456
+ // clone the @mixin at-rule
457
+ const clone = mixin.rule.clone({
458
+ original: rule,
459
+ parent: rule.parent,
460
+ variables: rule.variables
461
+ });
462
+
463
+ // transform the clone children
464
+ return transformNode(clone, opts).then(() => {
465
+ // replace the @include at-rule with the clone children
466
+ rule.parent.insertBefore(rule, clone.nodes);
467
+
468
+ rule.remove();
469
+ })
470
+ } else {
471
+ // otherwise, if the @mixin variable does not exist
472
+ manageUnresolved(rule, opts, name, `Could not resolve the mixin for "${name}"`);
473
+ }
474
+ }
475
+ }
476
+
477
+ // return the @include statement options (@include NAME, @include NAME(ARGS))
478
+ const getIncludeOpts = node => {
479
+ // @include name and args
480
+ const [ name, sourceArgs ] = node.params.split(matchOpeningParen$1, 2);
481
+ const args = sourceArgs
482
+ ? postcss.list.comma(sourceArgs.slice(0, -1))
483
+ : [];
484
+
485
+ return { name, args };
486
+ };
487
+
488
+ // match an opening parenthesis
489
+ const matchOpeningParen$1 = /(?<!var)\(/;
490
+
491
+ // tooling
492
+
493
+ // transform @for at-rules
494
+ function transformForAtrule(rule, opts) {
495
+ // if @for is supported
496
+ if (opts.transform.includes('@for')) {
497
+ // @for options
498
+ const { varname, start, end, increment } = getForOpts(rule, opts);
499
+ const direction = start <= end ? 1 : -1;
500
+ const replacements = [];
501
+ const ruleClones = [];
502
+
503
+ // for each iteration
504
+ for (let incrementor = start; incrementor * direction <= end * direction; incrementor += increment * direction) {
505
+ // set the current variable
506
+ setVariable(rule, varname, incrementor, opts);
507
+
508
+ // clone the @for at-rule
509
+ const clone = rule.clone({
510
+ parent: rule.parent,
511
+ variables: Object.assign({}, rule.variables)
512
+ });
513
+
514
+ ruleClones.push(clone);
515
+ }
516
+
517
+ return waterfall(
518
+ ruleClones,
519
+ clone => transformNode(clone, opts).then(
520
+ () => {
521
+ replacements.push(...clone.nodes);
522
+ }
523
+ )
524
+ ).then(
525
+ () => {
526
+ // replace the @for at-rule with the replacements
527
+ rule.parent.insertBefore(rule, replacements);
528
+
529
+ rule.remove();
530
+ }
531
+ )
532
+ }
533
+ }
534
+
535
+ // return the @for statement options (@for NAME from START through END, @for NAME from START through END by INCREMENT)
536
+ const getForOpts = (node, opts) => {
537
+ const params = postcss.list.space(node.params);
538
+ const varname = params[0].trim().slice(1);
539
+ const start = Number(getReplacedString(params[2], node, opts));
540
+ const end = Number(getReplacedString(params[4], node, opts));
541
+ const increment = 6 in params && Number(getReplacedString(params[6], node, opts)) || 1;
542
+
543
+ return { varname, start, end, increment };
544
+ };
545
+
546
+ // tooling
547
+
548
+ // transform @mixin at-rules
549
+ function transformMixinAtrule(rule, opts) {
550
+ // if @mixin is supported
551
+ if (opts.transform.includes('@mixin')) {
552
+ // @mixin options
553
+ const { name, params } = getMixinOpts(rule, opts);
554
+
555
+ // set the mixin as a variable on the parent of the @mixin at-rule
556
+ setVariable(rule.parent, `@mixin ${name}`, { params, rule }, opts);
557
+
558
+ // remove the @mixin at-rule
559
+ rule.remove();
560
+ }
561
+ }
562
+
563
+ // return the @mixin statement options (@mixin NAME, @mixin NAME(PARAMS))
564
+ const getMixinOpts = (node, opts) => {
565
+ // @mixin name and default params ([{ name, value }, ...])
566
+ const [ name, sourceParams ] = node.params.split(matchOpeningParen, 2);
567
+ const params = sourceParams && sourceParams.slice(0, -1).trim()
568
+ ? postcss.list.comma(sourceParams.slice(0, -1).trim()).map(
569
+ param => {
570
+ const parts = postcss.list.split(param, ':');
571
+ const paramName = parts[0].slice(1);
572
+ const paramValue = parts.length > 1
573
+ ? getReplacedString(parts.slice(1).join(':'), node, opts)
574
+ : undefined;
575
+
576
+ return { name: paramName, value: paramValue };
577
+ }
578
+ )
579
+ : [];
580
+
581
+ return { name, params };
582
+ };
583
+
584
+ // match an opening parenthesis
585
+ const matchOpeningParen = /(?<!var)\(/;
586
+
587
+ // tooling
588
+
589
+ // transform rule nodes
590
+ function transformRule(rule, opts) {
591
+ // update the rule selector with its variables replaced by their corresponding values
592
+ rule.selector = getReplacedString(rule.selector, rule, opts);
593
+ }
594
+
595
+ // tooling
596
+
597
+ // transform @content at-rules
598
+ function transformContentAtrule(rule, opts) {
599
+ // if @content is supported
600
+ if (opts.transform.includes('@content')) {
601
+ // the closest @mixin at-rule
602
+ const mixin = getClosestMixin(rule);
603
+
604
+ // if the @mixin at-rule exists
605
+ if (mixin) {
606
+ // clone the @mixin at-rule
607
+ const clone = mixin.original.clone({
608
+ parent: rule.parent,
609
+ variables: rule.variables
610
+ });
611
+
612
+ // transform the clone children
613
+ return transformNode(clone, opts).then(() => {
614
+ // replace the @content at-rule with the clone children
615
+ rule.parent.insertBefore(rule, clone.nodes);
616
+
617
+ rule.remove();
618
+ })
619
+
620
+ } else {
621
+ // otherwise, if the @mixin at-rule does not exist
622
+ manageUnresolved(rule, opts, '@content', 'Could not resolve the mixin for @content');
623
+ }
624
+ }
625
+ }
626
+
627
+ // return the closest @mixin at-rule
628
+ const getClosestMixin = node => 'atrule' === node.type && 'mixin' === node.name
629
+ ? node
630
+ : node.parent && getClosestMixin(node.parent);
631
+
632
+ // tooling
633
+
634
+ function transformNode(node, opts) {
635
+ return waterfall(
636
+ getNodesArray(node),
637
+ child => transformRuleOrDecl(child, opts).then(() => {
638
+ // conditionally walk the children of the attached child
639
+ if (child.parent) {
640
+ return transformNode(child, opts);
641
+ }
642
+ })
643
+ );
644
+ }
645
+
646
+ function transformRuleOrDecl(child, opts) {
647
+ return Promise.resolve().then(() => {
648
+ const type = child.type;
649
+
650
+ if ('atrule' === type) {
651
+ const name = (child.name || '').toLowerCase();
652
+
653
+ if ('content' === name) {
654
+ // transform @content at-rules
655
+ return transformContentAtrule(child, opts);
656
+ } else if ('each' === name) {
657
+ // transform @each at-rules
658
+ return transformEachAtrule(child, opts);
659
+ } else if ('if' === name) {
660
+ // transform @if at-rules
661
+ return transformIfAtrule(child, opts);
662
+ } else if ('import' === name) {
663
+ return transformImportAtrule(child, opts);
664
+ } else if ('include' === name) {
665
+ // transform @include at-rules
666
+ return transformIncludeAtrule(child, opts);
667
+ } else if ('for' === name) {
668
+ // transform @for at-rules
669
+ return transformForAtrule(child, opts);
670
+ } else if ('mixin' === name) {
671
+ // transform mixin at-rules
672
+ return transformMixinAtrule(child, opts);
673
+ } else {
674
+ // transform all other at-rules
675
+ return transformAtrule(child, opts);
676
+ }
677
+ } else if ('decl' === type) {
678
+ // transform declarations
679
+ return transformDecl(child, opts);
680
+ } else if ('rule' === type) {
681
+ // transform rule
682
+ return transformRule(child, opts);
683
+ }
684
+ })
685
+ }
686
+
687
+ // return the children of a node as an array
688
+ const getNodesArray = node => Array.from(Object(node).nodes || []);
689
+
690
+ // tooling
691
+
692
+ const matchProtocol = /^(?:[a-zA-Z]+:)?\/\//;
693
+
694
+ // plugin
695
+ const plugin = opts => ({
696
+ postcssPlugin: "postcss-advanced-variables",
697
+ Once(root, { result }) {
698
+ // process options
699
+ const transformOpt = ['@content', '@each', '@else', '@if', '@include', '@import', '@for', '@mixin'].filter(
700
+ feature => !String(Object(opts).disable || '').split(/\s*,\s*|\s+,?\s*|\s,?\s+/).includes(feature)
701
+ );
702
+ const unresolvedOpt = String(Object(opts).unresolved || 'throw').toLowerCase();
703
+ const variablesOpt = Object(opts).variables;
704
+ const importCache = Object(Object(opts).importCache);
705
+ const importFilter = Object(opts).importFilter || (id => {
706
+ return !matchProtocol.test(id);
707
+ });
708
+ const importPaths = [].concat(Object(opts).importPaths || []);
709
+ const importResolve = Object(opts).importResolve || (
710
+ (id, cwd) => resolve(id, { cwd, readFile: true, cache: importCache })
711
+ );
712
+ const importRoot = Object(opts).importRoot || process.cwd();
713
+
714
+ return transformNode(root, {
715
+ result,
716
+ importCache,
717
+ importFilter,
718
+ importPaths,
719
+ importResolve,
720
+ importRoot,
721
+ transform: transformOpt,
722
+ unresolved: unresolvedOpt,
723
+ variables: variablesOpt
724
+ });
725
+ },
726
+ });
727
+
728
+ plugin.postcss = true;
729
+
730
+ module.exports = plugin;