postcss-nested 6.0.1 → 7.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 (3) hide show
  1. package/README.md +26 -27
  2. package/index.js +107 -79
  3. package/package.json +13 -7
package/README.md CHANGED
@@ -8,25 +8,25 @@
8
8
 
9
9
  ```css
10
10
  .phone {
11
- &_title {
12
- width: 500px;
13
- @media (max-width: 500px) {
14
- width: auto;
15
- }
16
- body.is_dark & {
17
- color: white;
18
- }
11
+ &_title {
12
+ width: 500px;
13
+ @media (max-width: 500px) {
14
+ width: auto;
19
15
  }
20
- img {
21
- display: block;
16
+ body.is_dark & {
17
+ color: white;
22
18
  }
19
+ }
20
+ img {
21
+ display: block;
22
+ }
23
23
  }
24
24
 
25
25
  .title {
26
26
  font-size: var(--font);
27
27
 
28
28
  @at-root html {
29
- --font: 16px
29
+ --font: 16px;
30
30
  }
31
31
  }
32
32
  ```
@@ -35,39 +35,39 @@ will be processed to:
35
35
 
36
36
  ```css
37
37
  .phone_title {
38
- width: 500px;
38
+ width: 500px;
39
39
  }
40
40
  @media (max-width: 500px) {
41
- .phone_title {
42
- width: auto;
43
- }
41
+ .phone_title {
42
+ width: auto;
43
+ }
44
44
  }
45
45
  body.is_dark .phone_title {
46
- color: white;
46
+ color: white;
47
47
  }
48
48
  .phone img {
49
- display: block;
49
+ display: block;
50
50
  }
51
51
 
52
52
  .title {
53
53
  font-size: var(--font);
54
54
  }
55
55
  html {
56
- --font: 16px
56
+ --font: 16px;
57
57
  }
58
58
  ```
59
59
 
60
60
  Related plugins:
61
61
 
62
- * Use [`postcss-current-selector`] **after** this plugin if you want
62
+ - Use [`postcss-current-selector`] **after** this plugin if you want
63
63
  to use current selector in properties or variables values.
64
- * Use [`postcss-nested-ancestors`] **before** this plugin if you want
64
+ - Use [`postcss-nested-ancestors`] **before** this plugin if you want
65
65
  to reference any ancestor element directly in your selectors with `^&`.
66
66
 
67
67
  Alternatives:
68
68
 
69
- * See also [`postcss-nesting`], which implements [CSSWG draft].
70
- * [`postcss-nested-props`] for nested properties like `font-size`.
69
+ - See also [`postcss-nesting`], which implements [CSSWG draft].
70
+ - [`postcss-nested-props`] for nested properties like `font-size`.
71
71
 
72
72
  <a href="https://evilmartians.com/?utm_source=postcss-nested">
73
73
  <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg"
@@ -76,11 +76,10 @@ Alternatives:
76
76
 
77
77
  [`postcss-current-selector`]: https://github.com/komlev/postcss-current-selector
78
78
  [`postcss-nested-ancestors`]: https://github.com/toomuchdesign/postcss-nested-ancestors
79
- [`postcss-nested-props`]: https://github.com/jedmao/postcss-nested-props
80
- [`postcss-nesting`]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-nesting
81
- [CSSWG draft]: https://drafts.csswg.org/css-nesting-1/
82
- [PostCSS]: https://github.com/postcss/postcss
83
-
79
+ [`postcss-nested-props`]: https://github.com/jedmao/postcss-nested-props
80
+ [`postcss-nesting`]: https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-nesting
81
+ [CSSWG draft]: https://drafts.csswg.org/css-nesting-1/
82
+ [PostCSS]: https://github.com/postcss/postcss
84
83
 
85
84
  ## Docs
86
85
  Read full docs **[here](https://github.com/postcss/postcss-nested#readme)**.
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- const { Rule, AtRule } = require('postcss')
1
+ const { AtRule, Rule } = require('postcss')
2
2
  let parser = require('postcss-selector-parser')
3
3
 
4
4
  /**
@@ -53,12 +53,12 @@ function interpolateAmpInSelector(nodes, parent) {
53
53
  */
54
54
  function mergeSelectors(parent, child) {
55
55
  let merged = []
56
- parent.selectors.forEach(sel => {
56
+ for (let sel of parent.selectors) {
57
57
  let parentNode = parse(sel, parent)
58
58
 
59
- child.selectors.forEach(selector => {
59
+ for (let selector of child.selectors) {
60
60
  if (!selector) {
61
- return
61
+ continue
62
62
  }
63
63
  let node = parse(selector, child)
64
64
  let replaced = interpolateAmpInSelector(node, parentNode)
@@ -67,22 +67,43 @@ function mergeSelectors(parent, child) {
67
67
  node.prepend(parentNode.clone({}))
68
68
  }
69
69
  merged.push(node.toString())
70
- })
71
- })
70
+ }
71
+ }
72
72
  return merged
73
73
  }
74
74
 
75
75
  /**
76
- * Move a child and its preceeding comment(s) to after "after"
76
+ * Move a child and its preceding comment(s) to after "after"
77
+ * ! It is necessary to clarify the comment
77
78
  */
78
79
  function breakOut(child, after) {
79
- let prev = child.prev()
80
- after.after(child)
81
- while (prev && prev.type === 'comment') {
82
- let nextPrev = prev.prev()
83
- after.after(prev)
84
- prev = nextPrev
80
+ let changeParent = true
81
+
82
+ for (let node of after.nodes) {
83
+ if (!node.nodes) continue
84
+
85
+ let prevNode = node.prev()
86
+ if (prevNode?.type !== 'comment') continue
87
+
88
+ let parentRule = after.toString()
89
+
90
+ /* Checking that the comment "describes" the rule following. Like this:
91
+ /* comment about the rule below /*
92
+ .rule {}
93
+ */
94
+ let regexp = new RegExp(`${prevNode.toString()} *\n *${node.toString()}`)
95
+
96
+ if (parentRule.match(regexp)) {
97
+ changeParent = false
98
+ after.after(node).after(prevNode)
99
+ }
85
100
  }
101
+
102
+ // It is necessary if the above child has never been moved
103
+ if (changeParent) {
104
+ after.after(child)
105
+ }
106
+
86
107
  return child
87
108
  }
88
109
 
@@ -104,37 +125,42 @@ function createFnAtruleChilds(bubble) {
104
125
  children.push(child)
105
126
  }
106
127
  })
107
- if (bubbling) {
108
- if (children.length) {
109
- let clone = rule.clone({ nodes: [] })
110
- for (let child of children) {
111
- clone.append(child)
112
- }
113
- atrule.prepend(clone)
128
+ if (bubbling && children.length) {
129
+ let clone = rule.clone({ nodes: [] })
130
+ for (let child of children) {
131
+ clone.append(child)
114
132
  }
133
+ atrule.prepend(clone)
115
134
  }
116
135
  }
117
136
  }
118
137
 
119
138
  function pickDeclarations(selector, declarations, after) {
120
139
  let parent = new Rule({
121
- selector,
122
- nodes: []
140
+ nodes: [],
141
+ selector
123
142
  })
124
143
  parent.append(declarations)
125
144
  after.after(parent)
126
145
  return parent
127
146
  }
147
+ function pickAndClearDeclarations(ruleSelector, declarations, after, clear = true) {
148
+ if (!declarations.length) return [after, declarations]
128
149
 
129
- function atruleNames(defaults, custom) {
130
- let list = {}
131
- for (let name of defaults) {
132
- list[name] = true
150
+ after = pickDeclarations(ruleSelector, declarations, after)
151
+
152
+ if (clear) {
153
+ declarations = []
133
154
  }
134
- if (custom) {
135
- for (let name of custom) {
136
- list[name.replace(/^@/, '')] = true
137
- }
155
+
156
+ return [after, declarations]
157
+ }
158
+
159
+ function atruleNames(defaults, custom = '') {
160
+ let names = defaults.concat(custom)
161
+ let list = {}
162
+ for (let name of names) {
163
+ list[name.replace(/^@/, '')] = true
138
164
  }
139
165
  return list
140
166
  }
@@ -143,7 +169,7 @@ function parseRootRuleParams(params) {
143
169
  params = params.trim()
144
170
  let braceBlock = params.match(/^\((.*)\)$/)
145
171
  if (!braceBlock) {
146
- return { type: 'basic', selector: params }
172
+ return { selector: params, type: 'basic' }
147
173
  }
148
174
  let bits = braceBlock[1].match(/^(with(?:out)?):(.+)$/)
149
175
  if (bits) {
@@ -165,8 +191,8 @@ function parseRootRuleParams(params) {
165
191
  }
166
192
 
167
193
  return {
168
- type: 'withrules',
169
- escapes
194
+ escapes,
195
+ type: 'withrules'
170
196
  }
171
197
  }
172
198
  // Unrecognized brace block
@@ -250,14 +276,14 @@ const rootRuleEscapes = Symbol('rootRuleEscapes')
250
276
 
251
277
  function normalizeRootRule(rule) {
252
278
  let { params } = rule
253
- let { type, selector, escapes } = parseRootRuleParams(params)
279
+ let { escapes, selector, type } = parseRootRuleParams(params)
254
280
  if (type === 'unknown') {
255
281
  throw rule.error(
256
282
  `Unknown @${rule.name} parameter ${JSON.stringify(params)}`
257
283
  )
258
284
  }
259
285
  if (type === 'basic' && selector) {
260
- let selectorBlock = new Rule({ selector, nodes: rule.nodes })
286
+ let selectorBlock = new Rule({ nodes: rule.nodes, selector })
261
287
  rule.removeAll()
262
288
  rule.append(selectorBlock)
263
289
  }
@@ -269,7 +295,7 @@ const hasRootRule = Symbol('hasRootRule')
269
295
 
270
296
  module.exports = (opts = {}) => {
271
297
  let bubble = atruleNames(
272
- ['media', 'supports', 'layer', 'container'],
298
+ ['media', 'supports', 'layer', 'container', 'starting-style'],
273
299
  opts.bubble
274
300
  )
275
301
  let atruleChilds = createFnAtruleChilds(bubble)
@@ -287,8 +313,6 @@ module.exports = (opts = {}) => {
287
313
  let preserveEmpty = opts.preserveEmpty
288
314
 
289
315
  return {
290
- postcssPlugin: 'postcss-nested',
291
-
292
316
  Once(root) {
293
317
  root.walkAtRules(rootRuleName, node => {
294
318
  normalizeRootRule(node)
@@ -296,6 +320,15 @@ module.exports = (opts = {}) => {
296
320
  })
297
321
  },
298
322
 
323
+ postcssPlugin: 'postcss-nested',
324
+
325
+ RootExit(root) {
326
+ if (!root[hasRootRule]) return
327
+
328
+ root.walkAtRules(rootRuleName, unwrapRootRule)
329
+ root[hasRootRule] = false
330
+ },
331
+
299
332
  Rule(rule) {
300
333
  let unwrapped = false
301
334
  let after = rule
@@ -303,58 +336,53 @@ module.exports = (opts = {}) => {
303
336
  let declarations = []
304
337
 
305
338
  rule.each(child => {
306
- if (child.type === 'rule') {
307
- if (declarations.length) {
308
- after = pickDeclarations(rule.selector, declarations, after)
309
- declarations = []
310
- }
311
-
312
- copyDeclarations = true
313
- unwrapped = true
314
- child.selectors = mergeSelectors(rule, child)
315
- after = breakOut(child, after)
316
- } else if (child.type === 'atrule') {
317
- if (declarations.length) {
318
- after = pickDeclarations(rule.selector, declarations, after)
319
- declarations = []
320
- }
321
- if (child.name === rootRuleName) {
322
- unwrapped = true
323
- atruleChilds(rule, child, true, child[rootRuleMergeSel])
324
- after = breakOut(child, after)
325
- } else if (bubble[child.name]) {
326
- copyDeclarations = true
327
- unwrapped = true
328
- atruleChilds(rule, child, true)
329
- after = breakOut(child, after)
330
- } else if (unwrap[child.name]) {
339
+ switch (child.type) {
340
+ case 'atrule':
341
+ [after, declarations] = pickAndClearDeclarations(rule.selector, declarations, after)
342
+
343
+ if (child.name === rootRuleName) {
344
+ unwrapped = true
345
+ atruleChilds(rule, child, true, child[rootRuleMergeSel])
346
+ after = breakOut(child, after)
347
+ } else if (bubble[child.name]) {
348
+ copyDeclarations = true
349
+ unwrapped = true
350
+ atruleChilds(rule, child, true)
351
+ after = breakOut(child, after)
352
+ } else if (unwrap[child.name]) {
353
+ copyDeclarations = true
354
+ unwrapped = true
355
+ atruleChilds(rule, child, false)
356
+ after = breakOut(child, after)
357
+ } else if (copyDeclarations) {
358
+ declarations.push(child)
359
+ }
360
+
361
+ break
362
+ case 'decl':
363
+ if (copyDeclarations) {
364
+ declarations.push(child)
365
+ }
366
+
367
+ break
368
+ case 'rule':
369
+ [after, declarations] = pickAndClearDeclarations(rule.selector, declarations, after)
370
+
331
371
  copyDeclarations = true
332
372
  unwrapped = true
333
- atruleChilds(rule, child, false)
373
+ child.selectors = mergeSelectors(rule, child)
334
374
  after = breakOut(child, after)
335
- } else if (copyDeclarations) {
336
- declarations.push(child)
337
- }
338
- } else if (child.type === 'decl' && copyDeclarations) {
339
- declarations.push(child)
375
+
376
+ break
340
377
  }
341
378
  })
342
379
 
343
- if (declarations.length) {
344
- after = pickDeclarations(rule.selector, declarations, after)
345
- }
380
+ pickAndClearDeclarations(rule.selector, declarations, after, false)
346
381
 
347
382
  if (unwrapped && preserveEmpty !== true) {
348
383
  rule.raws.semicolon = true
349
384
  if (rule.nodes.length === 0) rule.remove()
350
385
  }
351
- },
352
-
353
- RootExit(root) {
354
- if (root[hasRootRule]) {
355
- root.walkAtRules(rootRuleName, unwrapRootRule)
356
- root[hasRootRule] = false
357
- }
358
386
  }
359
387
  }
360
388
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postcss-nested",
3
- "version": "6.0.1",
3
+ "version": "7.0.0",
4
4
  "description": "PostCSS plugin to unwrap nested rules like how Sass does it",
5
5
  "keywords": [
6
6
  "postcss",
@@ -13,16 +13,22 @@
13
13
  "license": "MIT",
14
14
  "repository": "postcss/postcss-nested",
15
15
  "engines": {
16
- "node": ">=12.0"
17
- },
18
- "funding": {
19
- "type": "opencollective",
20
- "url": "https://opencollective.com/postcss/"
16
+ "node": ">=18.0"
21
17
  },
18
+ "funding": [
19
+ {
20
+ "type": "opencollective",
21
+ "url": "https://opencollective.com/postcss/"
22
+ },
23
+ {
24
+ "type": "github",
25
+ "url": "https://github.com/sponsors/ai"
26
+ }
27
+ ],
22
28
  "peerDependencies": {
23
29
  "postcss": "^8.2.14"
24
30
  },
25
31
  "dependencies": {
26
- "postcss-selector-parser": "^6.0.11"
32
+ "postcss-selector-parser": "^7.0.0"
27
33
  }
28
34
  }