Skip to content

Commit 648311c

Browse files
bartvenemanclaude
andauthored
perf: replace Wallace css parser/walker with PostCSS native APIs where possible (#90)
## Summary Refactored multiple stylelint rules to use PostCSS native APIs instead of the `@projectwallace/css-parser` library, simplifying the codebase and improving maintainability. ## Key Changes - **no-unused-layers**: Replaced custom parser with `root.walkAtRules('layer')`, simplified position tracking by using AtRule nodes directly, and extracted allowlist logic to `isAllowed()` utility function - **max-selector-complexity**: Replaced custom parser with `root.walkRules()` for iterating style rules, removed manual line offset calculations - **max-average-declarations-per-rule**: Replaced custom parser with `root.walkRules()` and `rule.each()` for traversing declarations, simplified node type checking - **max-important-ratio**: Removed custom declaration parser, now uses PostCSS's native `declaration.important` property instead of parsing declaration source text ## Implementation Details - Removed dependencies on `@projectwallace/css-parser` modules: `parse`, `walk`, `nodes` constants - Eliminated manual line offset calculations (`root.source?.start?.line`) by using PostCSS node objects directly for reporting - Simplified allowlist checking in no-unused-layers by delegating to extracted utility function - All rules now leverage PostCSS's built-in traversal methods and node properties, reducing code complexity and improving consistency with stylelint patterns https://claude.ai/code/session_01VsxyyE2S6uMjaSkPKRej5u Co-authored-by: Claude <[email protected]>
1 parent 54ee7d7 commit 648311c

4 files changed

Lines changed: 18 additions & 79 deletions

File tree

src/rules/max-average-declarations-per-rule/index.ts

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import stylelint from 'stylelint'
22
import type { Root } from 'postcss'
3-
import { STYLE_RULE, AT_RULE, DECLARATION } from '@projectwallace/css-parser/nodes'
4-
import { walk, SKIP } from '@projectwallace/css-parser/walker'
5-
import { parse } from '@projectwallace/css-parser/parse'
63

74
const { createPlugin, utils } = stylelint
85

@@ -28,24 +25,14 @@ const ruleFunction = (primaryOption: number) => {
2825
return
2926
}
3027

31-
const css = root.toString()
32-
const ast = parse(css, { parse_selectors: false, parse_values: false })
3328
let total_declarations = 0
3429
let rule_count = 0
3530

36-
walk(ast, (node) => {
37-
if (node.type !== STYLE_RULE) return
38-
31+
root.walkRules((rule) => {
3932
rule_count++
40-
let decl_count = 0
41-
42-
walk(node, (child, child_depth) => {
43-
if (child_depth === 0) return
44-
if (child.type === DECLARATION) decl_count++
45-
if (child.type === STYLE_RULE || child.type === AT_RULE) return SKIP
33+
rule.each((node) => {
34+
if (node.type === 'decl') total_declarations++
4635
})
47-
48-
total_declarations += decl_count
4936
})
5037

5138
if (rule_count === 0) return

src/rules/max-important-ratio/index.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import stylelint from 'stylelint'
22
import type { Root } from 'postcss'
3-
import { parse_declaration } from '@projectwallace/css-parser/parse-declaration'
43

54
const { createPlugin, utils } = stylelint
65

@@ -31,19 +30,12 @@ const ruleFunction = (primaryOption: number) => {
3130
return
3231
}
3332

34-
const css = root.source!.input.css
3533
let total_declarations = 0
3634
let important_declarations = 0
3735

3836
root.walkDecls((declaration) => {
39-
const decl_source = css.substring(
40-
declaration.source!.start!.offset,
41-
declaration.source!.end!.offset,
42-
)
43-
const parsed = parse_declaration(decl_source)
44-
4537
total_declarations++
46-
if (parsed.is_important) {
38+
if (declaration.important) {
4739
important_declarations++
4840
}
4941
})

src/rules/max-selector-complexity/index.ts

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import stylelint from 'stylelint'
22
import type { Root } from 'postcss'
33
import { parse_selector } from '@projectwallace/css-parser/parse-selector'
4-
import { STYLE_RULE } from '@projectwallace/css-parser/nodes'
5-
import { walk } from '@projectwallace/css-parser/walker'
6-
import { parse } from '@projectwallace/css-parser/parse'
74
import { selectorComplexity } from '@projectwallace/css-analyzer'
85

96
const { createPlugin, utils } = stylelint
@@ -30,17 +27,8 @@ const ruleFunction = (primaryOption: number) => {
3027
return
3128
}
3229

33-
const css = root.toString()
34-
const parsed = parse(css, {
35-
parse_atrule_preludes: false,
36-
parse_values: false,
37-
})
38-
const line_offset = (root.source?.start?.line ?? 1) - 1
39-
40-
walk(parsed, (node) => {
41-
if (node.type !== STYLE_RULE) return
42-
43-
const selector_text = node.prelude?.text ?? ''
30+
root.walkRules((rule) => {
31+
const selector_text = rule.selector
4432
if (!selector_text.trim()) return
4533

4634
const selector_list = parse_selector(selector_text)
@@ -52,9 +40,7 @@ const ruleFunction = (primaryOption: number) => {
5240
if (complexity > primaryOption) {
5341
utils.report({
5442
message: messages.rejected(stringified, complexity, primaryOption),
55-
node: root,
56-
start: { line: node.line + line_offset, column: node.column },
57-
end: { line: node.line + line_offset, column: node.column + node.length },
43+
node: rule,
5844
result,
5945
ruleName: rule_name,
6046
})

src/rules/no-unused-layers/index.ts

Lines changed: 11 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import stylelint from 'stylelint'
2-
import type { Root } from 'postcss'
3-
import { AT_RULE } from '@projectwallace/css-parser/nodes'
4-
import { walk } from '@projectwallace/css-parser/walker'
5-
import { parse } from '@projectwallace/css-parser/parse'
2+
import type { Root, AtRule } from 'postcss'
3+
import { isAllowed } from '../../utils/allow-list.js'
64

75
const { createPlugin, utils } = stylelint
86

@@ -31,63 +29,39 @@ const ruleFunction = (primaryOptions: true, secondaryOptions?: SecondaryOptions)
3129
return
3230
}
3331

34-
const css = root.toString()
35-
const parsed = parse(css, {
36-
parse_selectors: false,
37-
parse_values: false,
38-
})
39-
const line_offset = (root.source?.start?.line ?? 1) - 1
40-
41-
const declared_layers = new Map<string, { line: number; column: number }>()
32+
const declared_layers = new Map<string, AtRule>()
4233
const defined_layers = new Set<string>()
4334

44-
walk(parsed, (node) => {
45-
if (node.type !== AT_RULE) return
46-
if (node.name !== 'layer') return
47-
48-
if (node.has_block) {
35+
root.walkAtRules('layer', (atRule) => {
36+
if (atRule.nodes !== undefined) {
4937
// Block rule: @layer name { ... }
50-
const name = node.prelude?.text.trim()
38+
const name = atRule.params.trim()
5139
if (name) {
5240
defined_layers.add(name)
5341
}
5442
} else {
5543
// Statement: @layer name; or @layer a, b, c;
56-
const prelude_text = node.prelude?.text ?? ''
57-
const names = prelude_text
44+
const names = atRule.params
5845
.split(',')
5946
.map((n) => n.trim())
6047
.filter(Boolean)
6148
for (const name of names) {
6249
if (!declared_layers.has(name)) {
63-
declared_layers.set(name, { line: node.line, column: node.column })
50+
declared_layers.set(name, atRule)
6451
}
6552
}
6653
}
6754
})
6855

69-
for (const [layer, pos] of declared_layers) {
56+
for (const [layer, node] of declared_layers) {
7057
if (defined_layers.has(layer)) continue
71-
72-
if (secondaryOptions?.allowlist) {
73-
const allowed = secondaryOptions.allowlist.some(
74-
(pattern) =>
75-
(typeof pattern === 'string' && pattern === layer) ||
76-
(pattern instanceof RegExp && pattern.test(layer)),
77-
)
78-
if (allowed) continue
79-
}
58+
if (secondaryOptions?.allowlist && isAllowed(layer, secondaryOptions.allowlist)) continue
8059

8160
utils.report({
8261
result,
8362
ruleName: rule_name,
8463
message: messages.rejected(layer),
85-
node: root,
86-
start: { line: pos.line + line_offset, column: pos.column },
87-
end: {
88-
line: pos.line + line_offset,
89-
column: pos.column + '@layer'.length,
90-
},
64+
node,
9165
word: layer,
9266
})
9367
}

0 commit comments

Comments
 (0)