tailwindcss 0.0.0-insiders.7c337f2 → 0.0.0-insiders.7d8eb21

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 (161) hide show
  1. package/CHANGELOG.md +246 -6
  2. package/README.md +13 -9
  3. package/defaultTheme.d.ts +2 -1
  4. package/lib/cli/build/deps.js +54 -0
  5. package/lib/cli/build/index.js +48 -0
  6. package/lib/cli/build/plugin.js +362 -0
  7. package/lib/cli/build/utils.js +78 -0
  8. package/lib/cli/build/watching.js +178 -0
  9. package/lib/cli/help/index.js +71 -0
  10. package/lib/cli/index.js +18 -0
  11. package/lib/cli/init/index.js +46 -0
  12. package/lib/cli/shared.js +12 -0
  13. package/lib/cli-peer-dependencies.js +18 -5
  14. package/lib/cli.js +62 -592
  15. package/lib/constants.js +19 -12
  16. package/lib/corePluginList.js +5 -3
  17. package/lib/corePlugins.js +619 -347
  18. package/lib/css/preflight.css +7 -0
  19. package/lib/featureFlags.js +34 -22
  20. package/lib/index.js +16 -9
  21. package/lib/lib/cacheInvalidation.js +42 -21
  22. package/lib/lib/collapseAdjacentRules.js +16 -14
  23. package/lib/lib/collapseDuplicateDeclarations.js +13 -10
  24. package/lib/lib/content.js +177 -0
  25. package/lib/lib/defaultExtractor.js +230 -33
  26. package/lib/lib/detectNesting.js +13 -4
  27. package/lib/lib/evaluateTailwindFunctions.js +105 -42
  28. package/lib/lib/expandApplyAtRules.js +151 -92
  29. package/lib/lib/expandTailwindAtRules.js +134 -149
  30. package/lib/lib/findAtConfigPath.js +44 -0
  31. package/lib/lib/generateRules.js +404 -185
  32. package/lib/lib/getModuleDependencies.js +21 -18
  33. package/lib/lib/normalizeTailwindDirectives.js +11 -8
  34. package/lib/lib/offsets.js +285 -0
  35. package/lib/lib/partitionApplyAtRules.js +14 -11
  36. package/lib/lib/regex.js +60 -0
  37. package/lib/lib/remap-bitfield.js +87 -0
  38. package/lib/lib/resolveDefaultsAtRules.js +97 -79
  39. package/lib/lib/setupContextUtils.js +567 -242
  40. package/lib/lib/setupTrackingContext.js +77 -106
  41. package/lib/lib/sharedState.js +18 -10
  42. package/lib/lib/substituteScreenAtRules.js +9 -7
  43. package/lib/postcss-plugins/nesting/index.js +7 -5
  44. package/lib/postcss-plugins/nesting/plugin.js +10 -8
  45. package/lib/processTailwindFeatures.js +34 -31
  46. package/lib/public/colors.js +7 -5
  47. package/lib/public/create-plugin.js +6 -4
  48. package/lib/public/default-config.js +7 -5
  49. package/lib/public/default-theme.js +7 -5
  50. package/lib/public/resolve-config.js +13 -10
  51. package/lib/util/bigSign.js +4 -1
  52. package/lib/util/buildMediaQuery.js +10 -5
  53. package/lib/util/cloneDeep.js +6 -5
  54. package/lib/util/cloneNodes.js +9 -2
  55. package/lib/util/color.js +20 -17
  56. package/lib/util/configurePlugins.js +5 -2
  57. package/lib/util/createPlugin.js +6 -5
  58. package/lib/util/createUtilityPlugin.js +15 -13
  59. package/lib/util/dataTypes.js +75 -51
  60. package/lib/util/defaults.js +8 -5
  61. package/lib/util/escapeClassName.js +13 -10
  62. package/lib/util/escapeCommas.js +4 -1
  63. package/lib/util/flattenColorPalette.js +7 -8
  64. package/lib/util/formatVariantSelector.js +270 -103
  65. package/lib/util/getAllConfigs.js +34 -15
  66. package/lib/util/hashConfig.js +10 -7
  67. package/lib/util/isKeyframeRule.js +4 -1
  68. package/lib/util/isPlainObject.js +4 -1
  69. package/lib/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +34 -26
  70. package/lib/util/log.js +14 -8
  71. package/lib/util/nameClass.js +19 -8
  72. package/lib/util/negateValue.js +20 -5
  73. package/lib/util/normalizeConfig.js +94 -53
  74. package/lib/util/normalizeScreens.js +115 -4
  75. package/lib/util/parseAnimationValue.js +43 -41
  76. package/lib/util/parseBoxShadowValue.js +12 -45
  77. package/lib/util/parseDependency.js +37 -55
  78. package/lib/util/parseGlob.js +34 -0
  79. package/lib/util/parseObjectStyles.js +15 -12
  80. package/lib/util/pluginUtils.js +146 -42
  81. package/lib/util/prefixSelector.js +26 -9
  82. package/lib/util/removeAlphaVariables.js +29 -0
  83. package/lib/util/resolveConfig.js +104 -105
  84. package/lib/util/resolveConfigPath.js +20 -17
  85. package/lib/util/responsive.js +12 -9
  86. package/lib/util/splitAtTopLevelOnly.js +43 -0
  87. package/lib/util/tap.js +4 -1
  88. package/lib/util/toColorValue.js +4 -1
  89. package/lib/util/toPath.js +18 -2
  90. package/lib/util/transformThemeValue.js +23 -11
  91. package/lib/util/validateConfig.js +24 -0
  92. package/lib/util/validateFormalSyntax.js +24 -0
  93. package/lib/util/withAlphaVariable.js +32 -24
  94. package/package.json +32 -29
  95. package/peers/index.js +6693 -3036
  96. package/plugin.d.ts +10 -5
  97. package/resolveConfig.d.ts +12 -0
  98. package/scripts/generate-types.js +53 -0
  99. package/scripts/release-channel.js +18 -0
  100. package/scripts/release-notes.js +21 -0
  101. package/scripts/type-utils.js +27 -0
  102. package/src/cli/build/deps.js +56 -0
  103. package/src/cli/build/index.js +49 -0
  104. package/src/cli/build/plugin.js +434 -0
  105. package/src/cli/build/utils.js +76 -0
  106. package/src/cli/build/watching.js +227 -0
  107. package/src/cli/help/index.js +70 -0
  108. package/src/cli/index.js +3 -0
  109. package/src/cli/init/index.js +50 -0
  110. package/src/cli/shared.js +5 -0
  111. package/src/cli-peer-dependencies.js +7 -1
  112. package/src/cli.js +46 -659
  113. package/src/corePlugins.js +346 -79
  114. package/src/css/preflight.css +7 -0
  115. package/src/featureFlags.js +12 -2
  116. package/src/index.js +5 -0
  117. package/src/lib/content.js +213 -0
  118. package/src/lib/defaultExtractor.js +202 -35
  119. package/src/lib/detectNesting.js +9 -1
  120. package/src/lib/evaluateTailwindFunctions.js +78 -7
  121. package/src/lib/expandApplyAtRules.js +129 -34
  122. package/src/lib/expandTailwindAtRules.js +12 -50
  123. package/src/lib/findAtConfigPath.js +48 -0
  124. package/src/lib/generateRules.js +379 -126
  125. package/src/lib/offsets.js +351 -0
  126. package/src/lib/regex.js +74 -0
  127. package/src/lib/remap-bitfield.js +82 -0
  128. package/src/lib/resolveDefaultsAtRules.js +53 -36
  129. package/src/lib/setupContextUtils.js +510 -120
  130. package/src/lib/setupTrackingContext.js +8 -45
  131. package/src/lib/sharedState.js +2 -0
  132. package/src/util/buildMediaQuery.js +5 -3
  133. package/src/util/cloneNodes.js +5 -1
  134. package/src/util/dataTypes.js +51 -26
  135. package/src/util/formatVariantSelector.js +317 -101
  136. package/src/util/getAllConfigs.js +19 -0
  137. package/src/util/{isValidArbitraryValue.js → isSyntacticallyValidPropertyValue.js} +1 -1
  138. package/src/util/log.js +1 -1
  139. package/src/util/nameClass.js +4 -0
  140. package/src/util/negateValue.js +10 -2
  141. package/src/util/normalizeConfig.js +40 -10
  142. package/src/util/normalizeScreens.js +99 -4
  143. package/src/util/parseBoxShadowValue.js +3 -50
  144. package/src/util/parseDependency.js +37 -42
  145. package/src/util/parseGlob.js +24 -0
  146. package/src/util/pluginUtils.js +132 -10
  147. package/src/util/prefixSelector.js +28 -10
  148. package/src/util/removeAlphaVariables.js +24 -0
  149. package/src/util/resolveConfig.js +70 -62
  150. package/src/util/splitAtTopLevelOnly.js +45 -0
  151. package/src/util/toPath.js +1 -1
  152. package/src/util/transformThemeValue.js +13 -3
  153. package/src/util/validateConfig.js +13 -0
  154. package/src/util/validateFormalSyntax.js +34 -0
  155. package/src/util/withAlphaVariable.js +1 -1
  156. package/stubs/defaultConfig.stub.js +165 -164
  157. package/stubs/simpleConfig.stub.js +1 -0
  158. package/types/config.d.ts +77 -37
  159. package/types/generated/default-theme.d.ts +342 -0
  160. package/types/index.d.ts +7 -1
  161. package/types.d.ts +0 -1
@@ -2,7 +2,6 @@ import postcss from 'postcss'
2
2
  import parser from 'postcss-selector-parser'
3
3
 
4
4
  import { resolveMatches } from './generateRules'
5
- import bigSign from '../util/bigSign'
6
5
  import escapeClassName from '../util/escapeClassName'
7
6
 
8
7
  /** @typedef {Map<string, [any, import('postcss').Rule[]]>} ApplyCache */
@@ -34,6 +33,15 @@ function extractClasses(node) {
34
33
  return Object.assign(classes, { groups: normalizedGroups })
35
34
  }
36
35
 
36
+ let selectorExtractor = parser()
37
+
38
+ /**
39
+ * @param {string} ruleSelectors
40
+ */
41
+ function extractSelectors(ruleSelectors) {
42
+ return selectorExtractor.astSync(ruleSelectors)
43
+ }
44
+
37
45
  function extractBaseCandidates(candidates, separator) {
38
46
  let baseClasses = new Set()
39
47
 
@@ -140,9 +148,7 @@ function buildLocalApplyCache(root, context) {
140
148
  /** @type {ApplyCache} */
141
149
  let cache = new Map()
142
150
 
143
- let highestOffset = context.layerOrder.user >> 4n
144
-
145
- root.walkRules((rule, idx) => {
151
+ root.walkRules((rule) => {
146
152
  // Ignore rules generated by Tailwind
147
153
  for (let node of pathToRoot(rule)) {
148
154
  if (node.raws.tailwind?.layer !== undefined) {
@@ -152,6 +158,7 @@ function buildLocalApplyCache(root, context) {
152
158
 
153
159
  // Clone what's required to represent this singular rule in the tree
154
160
  let container = nestedClone(rule)
161
+ let sort = context.offsets.create('user')
155
162
 
156
163
  for (let className of extractClasses(rule)) {
157
164
  let list = cache.get(className) || []
@@ -160,7 +167,7 @@ function buildLocalApplyCache(root, context) {
160
167
  list.push([
161
168
  {
162
169
  layer: 'user',
163
- sort: BigInt(idx) + highestOffset,
170
+ sort,
164
171
  important: false,
165
172
  },
166
173
  container,
@@ -290,31 +297,97 @@ function processApply(root, context, localCache) {
290
297
  * What happens in this function is that we prepend a `.` and escape the candidate.
291
298
  * This will result in `.hover\:font-bold`
292
299
  * Which means that we can replace `.hover\:font-bold` with `.abc` in `.hover\:font-bold:hover` resulting in `.abc:hover`
300
+ *
301
+ * @param {string} selector
302
+ * @param {string} utilitySelectors
303
+ * @param {string} candidate
293
304
  */
294
- // TODO: Should we use postcss-selector-parser for this instead?
295
305
  function replaceSelector(selector, utilitySelectors, candidate) {
296
- let needle = `.${escapeClassName(candidate)}`
297
- let needles = [...new Set([needle, needle.replace(/\\2c /g, '\\,')])]
298
- let utilitySelectorsList = utilitySelectors.split(/\s*(?<!\\)\,(?![^(]*\))\s*/g)
299
-
300
- return selector
301
- .split(/\s*(?<!\\)\,(?![^(]*\))\s*/g)
302
- .map((s) => {
303
- let replaced = []
304
-
305
- for (let utilitySelector of utilitySelectorsList) {
306
- let replacedSelector = utilitySelector
307
- for (const needle of needles) {
308
- replacedSelector = replacedSelector.replace(needle, s)
306
+ let selectorList = extractSelectors(selector)
307
+ let utilitySelectorsList = extractSelectors(utilitySelectors)
308
+ let candidateList = extractSelectors(`.${escapeClassName(candidate)}`)
309
+ let candidateClass = candidateList.nodes[0].nodes[0]
310
+
311
+ selectorList.each((sel) => {
312
+ /** @type {Set<import('postcss-selector-parser').Selector>} */
313
+ let replaced = new Set()
314
+
315
+ utilitySelectorsList.each((utilitySelector) => {
316
+ let hasReplaced = false
317
+ utilitySelector = utilitySelector.clone()
318
+
319
+ utilitySelector.walkClasses((node) => {
320
+ if (node.value !== candidateClass.value) {
321
+ return
322
+ }
323
+
324
+ // Don't replace multiple instances of the same class
325
+ // This is theoretically correct but only partially
326
+ // We'd need to generate every possible permutation of the replacement
327
+ // For example with `.foo + .foo { … }` and `section { @apply foo; }`
328
+ // We'd need to generate all of these:
329
+ // - `.foo + .foo`
330
+ // - `.foo + section`
331
+ // - `section + .foo`
332
+ // - `section + section`
333
+ if (hasReplaced) {
334
+ return
309
335
  }
310
- if (replacedSelector === utilitySelector) {
311
- continue
336
+
337
+ // Since you can only `@apply` class names this is sufficient
338
+ // We want to replace the matched class name with the selector the user is using
339
+ // Ex: Replace `.text-blue-500` with `.foo.bar:is(.something-cool)`
340
+ node.replaceWith(...sel.nodes.map((node) => node.clone()))
341
+
342
+ // Record that we did something and we want to use this new selector
343
+ replaced.add(utilitySelector)
344
+
345
+ hasReplaced = true
346
+ })
347
+ })
348
+
349
+ // Sort tag names before class names (but only sort each group (separated by a combinator)
350
+ // separately and not in total)
351
+ // This happens when replacing `.bar` in `.foo.bar` with a tag like `section`
352
+ for (let sel of replaced) {
353
+ let groups = [[]]
354
+ for (let node of sel.nodes) {
355
+ if (node.type === 'combinator') {
356
+ groups.push(node)
357
+ groups.push([])
358
+ } else {
359
+ let last = groups[groups.length - 1]
360
+ last.push(node)
312
361
  }
313
- replaced.push(replacedSelector)
314
362
  }
315
- return replaced.join(', ')
316
- })
317
- .join(', ')
363
+
364
+ sel.nodes = []
365
+
366
+ for (let group of groups) {
367
+ if (Array.isArray(group)) {
368
+ group.sort((a, b) => {
369
+ if (a.type === 'tag' && b.type === 'class') {
370
+ return -1
371
+ } else if (a.type === 'class' && b.type === 'tag') {
372
+ return 1
373
+ } else if (a.type === 'class' && b.type === 'pseudo' && b.value.startsWith('::')) {
374
+ return -1
375
+ } else if (a.type === 'pseudo' && a.value.startsWith('::') && b.type === 'class') {
376
+ return 1
377
+ }
378
+
379
+ return 0
380
+ })
381
+ }
382
+
383
+ sel.nodes = sel.nodes.concat(group)
384
+ }
385
+ }
386
+
387
+ sel.replaceWith(...replaced)
388
+ })
389
+
390
+ return selectorList.toString()
318
391
  }
319
392
 
320
393
  let perParentApplies = new Map()
@@ -329,7 +402,7 @@ function processApply(root, context, localCache) {
329
402
 
330
403
  if (apply.parent.type === 'atrule') {
331
404
  if (apply.parent.name === 'screen') {
332
- const screenType = apply.parent.params
405
+ let screenType = apply.parent.params
333
406
 
334
407
  throw apply.error(
335
408
  `@apply is not supported within nested at-rules like @screen. We suggest you write this as @apply ${applyCandidates
@@ -361,7 +434,7 @@ function processApply(root, context, localCache) {
361
434
  }
362
435
  }
363
436
 
364
- for (const [parent, [candidates, atApplySource]] of perParentApplies) {
437
+ for (let [parent, [candidates, atApplySource]] of perParentApplies) {
365
438
  let siblings = []
366
439
 
367
440
  for (let [applyCandidate, important, rules] of candidates) {
@@ -463,7 +536,27 @@ function processApply(root, context, localCache) {
463
536
  return
464
537
  }
465
538
 
466
- rule.selector = replaceSelector(parent.selector, rule.selector, applyCandidate)
539
+ // Strip the important selector from the parent selector if at the beginning
540
+ let importantSelector =
541
+ typeof context.tailwindConfig.important === 'string'
542
+ ? context.tailwindConfig.important
543
+ : null
544
+
545
+ // We only want to move the "important" selector if this is a Tailwind-generated utility
546
+ // We do *not* want to do this for user CSS that happens to be structured the same
547
+ let isGenerated = parent.raws.tailwind !== undefined
548
+
549
+ let parentSelector =
550
+ isGenerated && importantSelector && parent.selector.indexOf(importantSelector) === 0
551
+ ? parent.selector.slice(importantSelector.length)
552
+ : parent.selector
553
+
554
+ rule.selector = replaceSelector(parentSelector, rule.selector, applyCandidate)
555
+
556
+ // And then re-add it if it was removed
557
+ if (importantSelector && parentSelector !== parent.selector) {
558
+ rule.selector = `${importantSelector} ${rule.selector}`
559
+ }
467
560
 
468
561
  rule.walkDecls((d) => {
469
562
  d.important = meta.important || important
@@ -471,17 +564,19 @@ function processApply(root, context, localCache) {
471
564
  })
472
565
  }
473
566
 
567
+ // It could be that the node we were inserted was removed because the class didn't match
568
+ // If that was the *only* rule in the parent, then we have nothing add so we skip it
569
+ if (!root.nodes[0]) {
570
+ continue
571
+ }
572
+
474
573
  // Insert it
475
- siblings.push([
476
- // Ensure that when we are sorting, that we take the layer order into account
477
- { ...meta, sort: meta.sort | context.layerOrder[meta.layer] },
478
- root.nodes[0],
479
- ])
574
+ siblings.push([meta.sort, root.nodes[0]])
480
575
  }
481
576
  }
482
577
 
483
578
  // Inject the rules, sorted, correctly
484
- let nodes = siblings.sort(([a], [z]) => bigSign(a.sort - z.sort)).map((s) => s[1])
579
+ let nodes = context.offsets.sort(siblings).map((s) => s[1])
485
580
 
486
581
  // `parent` refers to the node at `.abc` in: .abc { @apply mt-2 }
487
582
  parent.after(nodes)
@@ -1,7 +1,6 @@
1
1
  import LRU from 'quick-lru'
2
2
  import * as sharedState from './sharedState'
3
3
  import { generateRules } from './generateRules'
4
- import bigSign from '../util/bigSign'
5
4
  import log from '../util/log'
6
5
  import cloneNodes from '../util/cloneNodes'
7
6
  import { defaultExtractor } from './defaultExtractor'
@@ -17,14 +16,14 @@ const builtInTransformers = {
17
16
  svelte: (content) => content.replace(/(?:^|\s)class:/g, ' '),
18
17
  }
19
18
 
20
- function getExtractor(tailwindConfig, fileExtension) {
21
- let extractors = tailwindConfig.content.extract
19
+ function getExtractor(context, fileExtension) {
20
+ let extractors = context.tailwindConfig.content.extract
22
21
 
23
22
  return (
24
23
  extractors[fileExtension] ||
25
24
  extractors.DEFAULT ||
26
25
  builtInExtractors[fileExtension] ||
27
- builtInExtractors.DEFAULT
26
+ builtInExtractors.DEFAULT(context)
28
27
  )
29
28
  }
30
29
 
@@ -74,8 +73,13 @@ function getClassCandidates(content, extractor, candidates, seen) {
74
73
  }
75
74
  }
76
75
 
76
+ /**
77
+ *
78
+ * @param {[import('./offsets.js').RuleOffset, import('postcss').Node][]} rules
79
+ * @param {*} context
80
+ */
77
81
  function buildStylesheet(rules, context) {
78
- let sortedRules = rules.sort(([a], [z]) => bigSign(a - z))
82
+ let sortedRules = context.offsets.sort(rules)
79
83
 
80
84
  let returnValue = {
81
85
  base: new Set(),
@@ -83,48 +87,10 @@ function buildStylesheet(rules, context) {
83
87
  components: new Set(),
84
88
  utilities: new Set(),
85
89
  variants: new Set(),
86
-
87
- // All the CSS that is not Tailwind related can be put in this bucket. This
88
- // will make it easier to later use this information when we want to
89
- // `@apply` for example. The main reason we do this here is because we
90
- // still need to make sure the order is correct. Last but not least, we
91
- // will make sure to always re-inject this section into the css, even if
92
- // certain rules were not used. This means that it will look like a no-op
93
- // from the user's perspective, but we gathered all the useful information
94
- // we need.
95
- user: new Set(),
96
90
  }
97
91
 
98
92
  for (let [sort, rule] of sortedRules) {
99
- if (sort >= context.minimumScreen) {
100
- returnValue.variants.add(rule)
101
- continue
102
- }
103
-
104
- if (sort & context.layerOrder.base) {
105
- returnValue.base.add(rule)
106
- continue
107
- }
108
-
109
- if (sort & context.layerOrder.defaults) {
110
- returnValue.defaults.add(rule)
111
- continue
112
- }
113
-
114
- if (sort & context.layerOrder.components) {
115
- returnValue.components.add(rule)
116
- continue
117
- }
118
-
119
- if (sort & context.layerOrder.utilities) {
120
- returnValue.utilities.add(rule)
121
- continue
122
- }
123
-
124
- if (sort & context.layerOrder.user) {
125
- returnValue.user.add(rule)
126
- continue
127
- }
93
+ returnValue[sort.layer].add(rule)
128
94
  }
129
95
 
130
96
  return returnValue
@@ -165,7 +131,7 @@ export default function expandTailwindAtRules(context) {
165
131
 
166
132
  for (let { content, extension } of context.changedContent) {
167
133
  let transformer = getTransformer(context.tailwindConfig, extension)
168
- let extractor = getExtractor(context.tailwindConfig, extension)
134
+ let extractor = getExtractor(context, extension)
169
135
  getClassCandidates(transformer(content), extractor, candidates, seen)
170
136
  }
171
137
 
@@ -177,16 +143,12 @@ export default function expandTailwindAtRules(context) {
177
143
  let classCacheCount = context.classCache.size
178
144
 
179
145
  env.DEBUG && console.time('Generate rules')
180
- let rules = generateRules(candidates, context)
146
+ generateRules(candidates, context)
181
147
  env.DEBUG && console.timeEnd('Generate rules')
182
148
 
183
149
  // We only ever add to the classCache, so if it didn't grow, there is nothing new.
184
150
  env.DEBUG && console.time('Build stylesheet')
185
151
  if (context.stylesheetCache === null || context.classCache.size !== classCacheCount) {
186
- for (let rule of rules) {
187
- context.ruleCache.add(rule)
188
- }
189
-
190
152
  context.stylesheetCache = buildStylesheet([...context.ruleCache], context)
191
153
  }
192
154
  env.DEBUG && console.timeEnd('Build stylesheet')
@@ -0,0 +1,48 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+
4
+ /**
5
+ * Find the @config at-rule in the given CSS AST and return the relative path to the config file
6
+ *
7
+ * @param {import('postcss').Root} root
8
+ * @param {import('postcss').Result} result
9
+ */
10
+ export function findAtConfigPath(root, result) {
11
+ let configPath = null
12
+ let relativeTo = null
13
+
14
+ root.walkAtRules('config', (rule) => {
15
+ relativeTo = rule.source?.input.file ?? result.opts.from ?? null
16
+
17
+ if (relativeTo === null) {
18
+ throw rule.error(
19
+ 'The `@config` directive cannot be used without setting `from` in your PostCSS config.'
20
+ )
21
+ }
22
+
23
+ if (configPath) {
24
+ throw rule.error('Only one `@config` directive is allowed per file.')
25
+ }
26
+
27
+ let matches = rule.params.match(/(['"])(.*?)\1/)
28
+ if (!matches) {
29
+ throw rule.error('A path is required when using the `@config` directive.')
30
+ }
31
+
32
+ let inputPath = matches[2]
33
+ if (path.isAbsolute(inputPath)) {
34
+ throw rule.error('The `@config` directive cannot be used with an absolute path.')
35
+ }
36
+
37
+ configPath = path.resolve(path.dirname(relativeTo), inputPath)
38
+ if (!fs.existsSync(configPath)) {
39
+ throw rule.error(
40
+ `The config file at "${inputPath}" does not exist. Make sure the path is correct and the file exists.`
41
+ )
42
+ }
43
+
44
+ rule.remove()
45
+ })
46
+
47
+ return configPath ? configPath : null
48
+ }