Skip to content

Commit 3d0ade0

Browse files
committed
refactor: adjust class attribute indentation based on AST nesting depth
1 parent 3bb9d83 commit 3d0ade0

8 files changed

Lines changed: 144 additions & 113 deletions

File tree

‎src/index.ts‎

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -675,25 +675,68 @@ function canCollapseWhitespaceIn(path: Path<import('@babel/types').Node, any>) {
675675
function transformJavaScript(ast: import('@babel/types').Node, { env }: TransformerContext) {
676676
let { matcher } = env
677677

678-
function sortInside(ast: import('@babel/types').Node, column?: number) {
678+
function getNestingDepth(path: any): number {
679+
let depth = 0
680+
let current = path
681+
while (current) {
682+
const node = current.node
683+
if (node) {
684+
if (
685+
node.type === 'JSXElement' ||
686+
node.type === 'JSXFragment' ||
687+
node.type === 'BlockStatement' ||
688+
node.type === 'ObjectExpression' ||
689+
node.type === 'ArrayExpression' ||
690+
node.type === 'ClassBody' ||
691+
node.type === 'SwitchCase'
692+
) {
693+
depth++
694+
} else if (
695+
node.type === 'ArrowFunctionExpression' &&
696+
node.body.type !== 'BlockStatement'
697+
) {
698+
// Implicit return arrow function body
699+
depth++
700+
} else if (
701+
node.type === 'ConditionalExpression' ||
702+
node.type === 'LogicalExpression'
703+
) {
704+
// Nested expressions
705+
depth++
706+
} else if (node.type === 'ReturnStatement') {
707+
depth++
708+
}
709+
}
710+
current = current.parentPath
711+
}
712+
return depth
713+
}
714+
715+
function sortInside(ast: import('@babel/types').Node, depth?: number) {
679716
visit(ast, (node, path) => {
680717
let collapseWhitespace = canCollapseWhitespaceIn(path)
681-
let nodeColumn = column ?? (node as any).loc?.start?.column
718+
// If depth is not provided, calculate it.
719+
// If we are recursing inside (depth is provided), we might need to adjust it based on current node type?
720+
// Actually, sortInside is called for children. The 'depth' arg represents the base depth of the container.
721+
// But we are visiting nodes inside. It's safer to re-calculate depth for each node to be precise,
722+
// OR pass the depth of the ExpressionContainer.
723+
// Let's rely on getNestingDepth(path) for absolute correctness relative to root.
724+
let nodeDepth = depth ?? getNestingDepth(path)
682725

683726
if (isStringLiteral(node)) {
684-
sortStringLiteral(node, { env, collapseWhitespace, column: nodeColumn })
727+
sortStringLiteral(node, { env, collapseWhitespace, column: nodeDepth })
685728
} else if (node.type === 'TemplateLiteral') {
686-
sortTemplateLiteral(node, { env, collapseWhitespace, column: nodeColumn })
729+
sortTemplateLiteral(node, { env, collapseWhitespace, column: nodeDepth })
687730
} else if (node.type === 'TaggedTemplateExpression') {
688731
if (isSortableTemplateExpression(node, matcher)) {
689-
sortTemplateLiteral(node.quasi, { env, collapseWhitespace, column: nodeColumn })
732+
sortTemplateLiteral(node.quasi, { env, collapseWhitespace, column: nodeDepth })
690733
}
691734
}
692735
})
693736
}
694737

695738
visit(ast, {
696-
JSXAttribute(node) {
739+
JSXAttribute(node, path) {
697740
node = node as import('@babel/types').JSXAttribute
698741

699742
if (!node.value) {
@@ -710,21 +753,30 @@ function transformJavaScript(ast: import('@babel/types').Node, { env }: Transfor
710753
return
711754
}
712755

756+
// Logic:
757+
// JSXElement (level N) -> Attributes are at N+1.
758+
// Multiline content inside attribute usually starts at N+2 (relative to element start).
759+
// Or simply: indent = (depth + 1) * tabWidth.
760+
// getNestingDepth includes the JSXElement itself. So depth is N+1 (if we count 1 for Element).
761+
// If we use base depth of JSXElement, then attribute is +1 level deep.
762+
const depth = getNestingDepth(path) + 1
763+
713764
if (isStringLiteral(node.value)) {
714-
sortStringLiteral(node.value, { env, column: node.loc?.start?.column })
765+
sortStringLiteral(node.value, { env, column: depth })
715766
} else if (node.value.type === 'JSXExpressionContainer') {
716-
sortInside(node.value, node.loc?.start?.column)
767+
sortInside(node.value, depth)
717768
}
718769
},
719770

720-
CallExpression(node) {
771+
CallExpression(node, path) {
721772
node = node as import('@babel/types').CallExpression
722773

723774
if (!isSortableCallExpression(node, matcher)) {
724775
return
725776
}
726777

727-
node.arguments.forEach((arg) => sortInside(arg, node.loc?.start?.column))
778+
const depth = getNestingDepth(path) + 1
779+
node.arguments.forEach((arg) => sortInside(arg, depth))
728780
},
729781

730782
TaggedTemplateExpression(node, path) {
@@ -735,11 +787,12 @@ function transformJavaScript(ast: import('@babel/types').Node, { env }: Transfor
735787
}
736788

737789
let collapseWhitespace = canCollapseWhitespaceIn(path)
790+
const depth = getNestingDepth(path) + 1
738791

739792
sortTemplateLiteral(node.quasi, {
740793
env,
741794
collapseWhitespace,
742-
column: node.loc?.start?.column,
795+
column: depth,
743796
})
744797
},
745798
})

‎src/sorting.ts‎

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -76,25 +76,13 @@ export function sortClasses(
7676
let closingIndent: string | undefined
7777

7878
if (column !== undefined && column >= 0) {
79-
let baseIndentUnits = column
79+
const level = column
8080
if (useTabs) {
81-
// If column is multiple of tabWidth, it likely comes from space-counting parsers
82-
if (column >= tabWidth && column % tabWidth === 0) {
83-
baseIndentUnits = column / tabWidth
84-
}
85-
} else {
86-
// If column is small, it likely comes from tab-counting parsers (1 tab = 1 unit)
87-
if (column > 0 && column < tabWidth) {
88-
baseIndentUnits = column * tabWidth
89-
}
90-
}
91-
92-
if (useTabs) {
93-
indent = '\t'.repeat(baseIndentUnits + 1)
94-
closingIndent = useClosingIndent ? '\t'.repeat(baseIndentUnits) : undefined
81+
indent = '\t'.repeat(level + 1)
82+
closingIndent = useClosingIndent ? '\t'.repeat(level) : undefined
9583
} else {
96-
indent = ' '.repeat(baseIndentUnits + tabWidth)
97-
closingIndent = useClosingIndent ? ' '.repeat(baseIndentUnits) : undefined
84+
indent = ' '.repeat((level + 1) * tabWidth)
85+
closingIndent = useClosingIndent ? ' '.repeat(level * tabWidth) : undefined
9886
}
9987
} else {
10088
const baseIndent = useTabs ? '\t' : ' '.repeat(tabWidth)
@@ -135,23 +123,13 @@ export function sortClasses(
135123
let closingIndent: string | undefined
136124

137125
if (column !== undefined && column >= 0) {
138-
let baseIndentUnits = column
139-
if (useTabs) {
140-
if (column >= tabWidth && column % tabWidth === 0) {
141-
baseIndentUnits = column / tabWidth
142-
}
143-
} else {
144-
if (column > 0 && column < tabWidth) {
145-
baseIndentUnits = column * tabWidth
146-
}
147-
}
148-
126+
const level = column
149127
if (useTabs) {
150-
indent = '\t'.repeat(baseIndentUnits + 1)
151-
closingIndent = useClosingIndent ? '\t'.repeat(baseIndentUnits) : undefined
128+
indent = '\t'.repeat(level + 1)
129+
closingIndent = useClosingIndent ? '\t'.repeat(level) : undefined
152130
} else {
153-
indent = ' '.repeat(baseIndentUnits + tabWidth)
154-
closingIndent = useClosingIndent ? ' '.repeat(baseIndentUnits) : undefined
131+
indent = ' '.repeat((level + 1) * tabWidth)
132+
closingIndent = useClosingIndent ? ' '.repeat(level * tabWidth) : undefined
155133
}
156134
} else {
157135
const indentMatch = classStr.match(/\n([ \t]+)/)

‎tests/categories/complex-jsx/complex-jsx.expected.tsx‎

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,27 @@ const Index = (props: IProps) => {
2020
return (
2121
<div
2222
className='
23-
flex flex-col
24-
items-center justify-center
25-
w-screen h-screen
26-
gap-4
27-
'
23+
flex flex-col
24+
items-center justify-center
25+
w-screen h-screen
26+
gap-4
27+
'
2828
>
2929
<AirplaneTiltIcon className='size-20'></AirplaneTiltIcon>
3030
<span>Permissions required</span>
3131
<div className='flex flex-col gap-3'>
3232
{keys.map((item) => (
3333
<button
3434
className='
35-
relative
36-
flex
37-
items-center justify-center
38-
w-72 h-10
39-
px-4
40-
rounded-3xl
41-
bg-std-100
42-
clickable capitalize
43-
'
35+
relative
36+
flex
37+
items-center justify-center
38+
w-72 h-10
39+
px-4
40+
rounded-3xl
41+
bg-std-100
42+
clickable capitalize
43+
'
4444
key={item}
4545
onClick={() => onItem(item)}
4646
>

‎tests/categories/custom-categories/custom-categories.expected.tsx‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ function Component() {
22
return (
33
<div
44
className="
5-
custom-btn
6-
primary-color
7-
large-size
8-
hover-effect shadow-md
9-
rounded-lg p-4 m-2
10-
"
5+
custom-btn
6+
primary-color
7+
large-size
8+
hover-effect shadow-md
9+
rounded-lg p-4 m-2
10+
"
1111
>
1212
Custom Categories Content
1313
</div>

‎tests/categories/dynamic-wrapping/dynamic-wrapping.expected.tsx‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ function Component() {
22
return (
33
<div
44
className="
5-
opacity-0 opacity-5 opacity-10 opacity-20 opacity-25 opacity-30 opacity-40 opacity-50 opacity-60 opacity-70 opacity-75 opacity-80 opacity-90 opacity-95 opacity-100
6-
transition-none transition-all transition-colors transition-opacity transition-shadow transition-transform
7-
duration-75 duration-100 duration-150 duration-200 duration-300 duration-500 duration-700 duration-1000
8-
ease-linear ease-in ease-out ease-in-out
9-
"
5+
opacity-0 opacity-5 opacity-10 opacity-20 opacity-25 opacity-30 opacity-40 opacity-50 opacity-60 opacity-70 opacity-75 opacity-80 opacity-90 opacity-95 opacity-100
6+
transition-none transition-all transition-colors transition-opacity transition-shadow transition-transform
7+
duration-75 duration-100 duration-150 duration-200 duration-300 duration-500 duration-700 duration-1000
8+
ease-linear ease-in ease-out ease-in-out
9+
"
1010
>
1111
Dynamic wrapping test
1212
</div>

‎tests/categories/fn-in-class/fn-in-class.expected.tsx‎

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,26 @@ function Component() {
33
<div
44
className={cx(
55
"
6-
p-4
7-
m-2
8-
rounded-lg
9-
shadow-md
10-
custom-btn primary-color large-size hover-effect
11-
",
6+
p-4
7+
m-2
8+
rounded-lg
9+
shadow-md
10+
custom-btn primary-color large-size hover-effect
11+
",
1212
"
13-
flex
14-
items-center justify-between
15-
w-full h-64
16-
p-4
17-
m-2
18-
rounded-lg
19-
text-xl font-bold
20-
bg-blue-500
21-
from-blue-500
22-
border-2
23-
shadow-lg
24-
opacity-75
25-
"
13+
flex
14+
items-center justify-between
15+
w-full h-64
16+
p-4
17+
m-2
18+
rounded-lg
19+
text-xl font-bold
20+
bg-blue-500
21+
from-blue-500
22+
border-2
23+
shadow-lg
24+
opacity-75
25+
"
2626
)}
2727
>
2828
Custom Categories Content

‎tests/categories/multiple-categories/multiple-categories.expected.tsx‎

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@ function Component() {
22
return (
33
<div
44
className="
5-
flex
6-
items-center justify-between
7-
w-full h-64
8-
p-4
9-
m-2
10-
rounded-lg
11-
text-xl font-bold
12-
bg-blue-500
13-
from-blue-500
14-
border-2
15-
shadow-lg
16-
opacity-75
17-
"
5+
flex
6+
items-center justify-between
7+
w-full h-64
8+
p-4
9+
m-2
10+
rounded-lg
11+
text-xl font-bold
12+
bg-blue-500
13+
from-blue-500
14+
border-2
15+
shadow-lg
16+
opacity-75
17+
"
1818
>
1919
Multi-category Content
2020
</div>

‎tests/categories/template-string/template-string.expected.tsx‎

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@ function Component() {
33
return (
44
<div
55
className={`
6-
flex
7-
items-center justify-between
8-
w-full h-64
9-
p-4
10-
m-2
11-
rounded-lg
12-
text-xl font-bold
13-
bg-blue-500
14-
from-blue-500
15-
border-2
16-
shadow-lg
17-
opacity-75
18-
${dynamicClass}
6+
flex
7+
items-center justify-between
8+
w-full h-64
9+
p-4
10+
m-2
11+
rounded-lg
12+
text-xl font-bold
13+
bg-blue-500
14+
from-blue-500
15+
border-2
16+
shadow-lg
17+
opacity-75
18+
${dynamicClass}
1919
`}
2020
>
2121
Multi-category Content

0 commit comments

Comments
 (0)