Skip to content

Commit ed28cb8

Browse files
committed
feat: fix nested css by swapping css-tree for @eslint/css-tree
1 parent 7d2b58d commit ed28cb8

File tree

4 files changed

+81
-78
lines changed

4 files changed

+81
-78
lines changed

index.js

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
1-
// @ts-expect-error Typing of css-tree is incomplete
2-
import parse from 'css-tree/parser'
1+
import { parse } from '@eslint/css-tree'
32

43
/**
5-
* @typedef {import('css-tree').CssNode} CssNode
6-
* @typedef {import('css-tree').List<CssNode>} List
7-
* @typedef {import('css-tree').CssLocation} CssLocation
8-
* @typedef {import('css-tree').Raw} Raw
9-
* @typedef {import('css-tree').StyleSheet} StyleSheet
10-
* @typedef {import('css-tree').Atrule} Atrule
11-
* @typedef {import('css-tree').AtrulePrelude} AtrulePrelude
12-
* @typedef {import('css-tree').Rule} Rule
13-
* @typedef {import('css-tree').SelectorList} SelectorList
14-
* @typedef {import('css-tree').Selector} Selector
15-
* @typedef {import('css-tree').PseudoClassSelector} PseudoClassSelector
16-
* @typedef {import('css-tree').PseudoElementSelector} PseudoElementSelector
17-
* @typedef {import('css-tree').Block} Block
18-
* @typedef {import('css-tree').Declaration} Declaration
19-
* @typedef {import('css-tree').Value} Value
20-
* @typedef {import('css-tree').Operator} Operator
4+
* @typedef {import('@eslint/css-tree').CssNode} CssNode
5+
* @typedef {import('@eslint/css-tree').List<CssNode>} List
6+
* @typedef {import('@eslint/css-tree').CssLocation} CssLocation
7+
* @typedef {import('@eslint/css-tree').Raw} Raw
8+
* @typedef {import('@eslint/css-tree').StyleSheet} StyleSheet
9+
* @typedef {import('@eslint/css-tree').Atrule} Atrule
10+
* @typedef {import('@eslint/css-tree').AtrulePrelude} AtrulePrelude
11+
* @typedef {import('@eslint/css-tree').Rule} Rule
12+
* @typedef {import('@eslint/css-tree').SelectorList} SelectorList
13+
* @typedef {import('@eslint/css-tree').Selector} Selector
14+
* @typedef {import('@eslint/css-tree').PseudoClassSelector} PseudoClassSelector
15+
* @typedef {import('@eslint/css-tree').PseudoElementSelector} PseudoElementSelector
16+
* @typedef {import('@eslint/css-tree').Block} Block
17+
* @typedef {import('@eslint/css-tree').Declaration} Declaration
18+
* @typedef {import('@eslint/css-tree').Value} Value
19+
* @typedef {import('@eslint/css-tree').Operator} Operator
2120
*/
2221

2322
const SPACE = ' '
@@ -61,11 +60,7 @@ function lowercase(str) {
6160
* @param {Options} [options]
6261
* @returns {string} The formatted CSS
6362
*/
64-
export function format(css, {
65-
minify = false,
66-
tab_size = undefined,
67-
} = Object.create(null)) {
68-
63+
export function format(css, { minify = false, tab_size = undefined } = Object.create(null)) {
6964
if (tab_size !== undefined && Number(tab_size) < 1) {
7065
throw new TypeError('tab_size must be a number greater than 0')
7166
}
@@ -75,10 +70,10 @@ export function format(css, {
7570

7671
/**
7772
* @param {string} _ The comment text
78-
* @param {CssLocation} position
73+
* @param {CssLocation} loc
7974
*/
80-
function on_comment(_, position) {
81-
comments.push(position.start.offset, position.end.offset)
75+
function on_comment(_, loc) {
76+
comments.push(loc.start.offset, loc.end.offset)
8277
}
8378

8479
/** @type {StyleSheet} */
@@ -122,13 +117,13 @@ export function format(css, {
122117

123118
/** @param {CssNode} node */
124119
function start_offset(node) {
125-
let loc = /** @type {CssLocation} */(node.loc)
120+
let loc = /** @type {CssLocation} */ (node.loc)
126121
return loc.start.offset
127122
}
128123

129124
/** @param {CssNode} node */
130125
function end_offset(node) {
131-
let loc = /** @type {CssLocation} */(node.loc)
126+
let loc = /** @type {CssLocation} */ (node.loc)
132127
return loc.end.offset
133128
}
134129

@@ -218,7 +213,10 @@ export function format(css, {
218213
}
219214
case 'Combinator': {
220215
// putting spaces around `child.name` (+ > ~ or ' '), unless the combinator is ' '
221-
buffer += SPACE
216+
// and the combinator is not the first in a nested selectorlist
217+
if (child !== children.first) {
218+
buffer += SPACE
219+
}
222220

223221
if (child.name !== ' ') {
224222
buffer += child.name + SPACE
@@ -348,7 +346,7 @@ export function format(css, {
348346

349347
indent_level++
350348

351-
let opening_comment = print_comment(start_offset(node), start_offset(/** @type {CssNode} */(children.first)))
349+
let opening_comment = print_comment(start_offset(node), start_offset(/** @type {CssNode} */ (children.first)))
352350
if (opening_comment) {
353351
buffer += indent(indent_level) + opening_comment + NEWLINE
354352
}
@@ -392,7 +390,7 @@ export function format(css, {
392390
}
393391
})
394392

395-
let closing_comment = print_comment(end_offset(/** @type {CssNode} */(children.last)), end_offset(node))
393+
let closing_comment = print_comment(end_offset(/** @type {CssNode} */ (children.last)), end_offset(node))
396394
if (closing_comment) {
397395
buffer += NEWLINE + indent(indent_level) + closing_comment
398396
}
@@ -447,7 +445,7 @@ export function format(css, {
447445
let space = operator === '+' || operator === '-' ? SPACE : OPTIONAL_SPACE
448446
return `calc(${left.trim()}${space}${operator}${space}${right.trim()})`
449447
})
450-
.replace(/selector|url|supports|layer\(/ig, (match) => lowercase(match)) // lowercase function names
448+
.replace(/selector|url|supports|layer\(/gi, (match) => lowercase(match)) // lowercase function names
451449
}
452450

453451
/** @param {Declaration} node */
@@ -603,7 +601,7 @@ export function format(css, {
603601
}
604602
})
605603

606-
let closing_comment = print_comment(end_offset(/** @type {CssNode} */(children.last)), end_offset(ast))
604+
let closing_comment = print_comment(end_offset(/** @type {CssNode} */ (children.last)), end_offset(ast))
607605
if (closing_comment) {
608606
buffer += NEWLINE + closing_comment
609607
}

package-lock.json

Lines changed: 18 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,13 @@
2828
},
2929
"devDependencies": {
3030
"@codecov/vite-plugin": "^1.9.0",
31-
"@types/css-tree": "^2.3.10",
3231
"c8": "^10.1.3",
3332
"prettier": "^3.5.3",
3433
"typescript": "^5.8.3",
3534
"uvu": "^0.5.6",
3635
"vite": "^6.2.6",
3736
"vite-plugin-dts": "^4.5.3"
3837
},
39-
"dependencies": {
40-
"css-tree": "^3.0.1"
41-
},
4238
"files": [
4339
"dist"
4440
],
@@ -57,5 +53,8 @@
5753
"useTabs": true,
5854
"printWidth": 140,
5955
"singleQuote": true
56+
},
57+
"dependencies": {
58+
"@eslint/css-tree": "^3.6.6"
6059
}
6160
}

test/rules.test.js

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -185,19 +185,21 @@ test('formats unknown stuff in curly braces', () => {
185185
assert.is(actual, expected)
186186
})
187187

188-
test('[check broken test] Relaxed nesting: formats nested rules with a selector with a &', () => {
188+
test('Relaxed nesting: formats nested rules with a selector with a &', () => {
189189
let actual = format(`
190190
selector {
191191
a & { color:red }
192192
}
193193
`)
194194
let expected = `selector {
195-
a & { color:red }
195+
a & {
196+
color: red;
197+
}
196198
}`
197199
assert.equal(actual, expected)
198200
})
199201

200-
test.skip('Relaxed nesting: formats nested rules with a selector with a &', () => {
202+
test('Relaxed nesting: formats nested rules with a selector with a &', () => {
201203
let actual = format(`
202204
selector {
203205
a & { color:red }
@@ -211,19 +213,21 @@ test.skip('Relaxed nesting: formats nested rules with a selector with a &', () =
211213
assert.equal(actual, expected)
212214
})
213215

214-
test('[check broken test] Relaxed nesting: formats nested rules with a selector without a &', () => {
216+
test('Relaxed nesting: formats nested rules with a selector without a &', () => {
215217
let actual = format(`
216218
selector {
217219
a { color:red }
218220
}
219221
`)
220222
let expected = `selector {
221-
a { color:red }
223+
a {
224+
color: red;
225+
}
222226
}`
223227
assert.equal(actual, expected)
224228
})
225229

226-
test.skip('Relaxed nesting: formats nested rules with a selector without a &', () => {
230+
test('Relaxed nesting: formats nested rules with a selector without a &', () => {
227231
let actual = format(`
228232
selector {
229233
a { color:red }
@@ -237,7 +241,7 @@ test.skip('Relaxed nesting: formats nested rules with a selector without a &', (
237241
assert.equal(actual, expected)
238242
})
239243

240-
test('[check broken test] Relaxed nesting: formats nested rules with a selector starting with a selector combinator', () => {
244+
test('Relaxed nesting: formats nested rules with a selector starting with a selector combinator', () => {
241245
let actual = format(`
242246
selector {
243247
> a { color:red }
@@ -246,31 +250,39 @@ test('[check broken test] Relaxed nesting: formats nested rules with a selector
246250
}
247251
`)
248252
let expected = `selector {
249-
> a { color:red }
250-
~ a { color:red }
251-
+ a { color:red }
253+
> a {
254+
color: red;
255+
}
256+
257+
~ a {
258+
color: red;
259+
}
260+
261+
+ a {
262+
color: red;
263+
}
252264
}`
253265
assert.equal(actual, expected)
254266
})
255267

256-
test.skip('Relaxed nesting: formats nested rules with a selector starting with a selector combinator', () => {
268+
test('Relaxed nesting: formats nested rules with a selector starting with a selector combinator and &', () => {
257269
let actual = format(`
258270
selector {
259-
> a { color:red }
260-
~ a { color:red }
261-
+ a { color:red }
271+
& > a { color:red }
272+
& ~ a { color:red }
273+
& + a { color:red }
262274
}
263275
`)
264276
let expected = `selector {
265-
> a {
277+
& > a {
266278
color: red;
267279
}
268280
269-
~ a {
281+
& ~ a {
270282
color: red;
271283
}
272284
273-
+ a {
285+
& + a {
274286
color: red;
275287
}
276288
}`

0 commit comments

Comments
 (0)