Skip to content

Commit 6443185

Browse files
authored
Remove recast/ast-types deps and optimize dynamic JS attribute handling (#440)
1 parent a8a6040 commit 6443185

File tree

2 files changed

+94
-121
lines changed

2 files changed

+94
-121
lines changed

src/index.ts

Lines changed: 93 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
// @ts-ignore
22
import type * as Liquid from '@shopify/prettier-plugin-liquid/dist/types.js'
3-
import * as astTypes from 'ast-types'
4-
// @ts-ignore
5-
import jsesc from 'jsesc'
63
// @ts-ignore
74
import lineColumn from 'line-column'
85
import * as prettierParserAngular from 'prettier/plugins/angular'
96
import * as prettierParserBabel from 'prettier/plugins/babel'
107
import * as prettierParserCss from 'prettier/plugins/postcss'
118
// @ts-ignore
12-
import * as recast from 'recast'
139
import { createPlugin } from './create-plugin.js'
1410
import type { Matcher } from './options.js'
1511
import { sortClasses, sortClassList } from './sorting.js'
@@ -97,115 +93,126 @@ function transformDynamicAngularAttribute(attr: any, env: TransformerEnv) {
9793
function transformDynamicJsAttribute(attr: any, env: TransformerEnv) {
9894
let { matcher } = env
9995

100-
let ast = recast.parse(`let __prettier_temp__ = ${attr.value}`, {
101-
parser: prettierParserBabel.parsers['babel-ts'],
102-
})
96+
let expressionPrefix = 'let __prettier_temp__ = '
97+
let source = `${expressionPrefix}${attr.value}`
98+
let ast = prettierParserBabel.parsers['babel-ts'].parse(source, env.options)
10399

104-
function* ancestors<N, V>(path: import('ast-types/lib/node-path').NodePath<N, V>) {
105-
yield path
100+
let didChange = false
101+
let changes: StringChange[] = []
106102

107-
while (path.parentPath) {
108-
path = path.parentPath
109-
yield path
110-
}
103+
function findConcatEntry(path: Path<any, any>) {
104+
return path.find(
105+
(entry) => entry.parent?.type === 'BinaryExpression' && entry.parent.operator === '+',
106+
)
111107
}
112108

113-
let didChange = false
109+
function addChange(start: number | null | undefined, end: number | null | undefined, after: string) {
110+
if (start == null || end == null) return
114111

115-
astTypes.visit(ast, {
116-
visitLiteral(path) {
117-
let entries = Array.from(ancestors(path))
118-
let concat = entries.find((entry) => {
119-
return (
120-
entry.parent &&
121-
entry.parent.value &&
122-
entry.parent.value.type === 'BinaryExpression' &&
123-
entry.parent.value.operator === '+'
124-
)
125-
})
112+
let offsetStart = start - expressionPrefix.length
113+
let offsetEnd = end - expressionPrefix.length
126114

127-
if (isStringLiteral(path.node)) {
128-
let sorted = sortStringLiteral(path.node, {
129-
env,
130-
collapseWhitespace: {
131-
start: concat?.name !== 'right',
132-
end: concat?.name !== 'left',
133-
},
134-
})
115+
if (offsetStart < 0 || offsetEnd < 0) return
116+
117+
didChange = true
118+
changes.push({
119+
start: offsetStart,
120+
end: offsetEnd,
121+
before: attr.value.slice(offsetStart, offsetEnd),
122+
after,
123+
})
124+
}
135125

136-
if (sorted) {
137-
didChange = true
138-
139-
// https://github.com/benjamn/recast/issues/171#issuecomment-224996336
140-
// @ts-ignore
141-
let quote = path.node.extra.raw[0]
142-
let value = jsesc(path.node.value, {
143-
quotes: quote === "'" ? 'single' : 'double',
144-
})
145-
// @ts-ignore
146-
path.node.value = new String(quote + value + quote)
126+
visit(ast, {
127+
StringLiteral(node, path) {
128+
let concat = findConcatEntry(path)
129+
let sorted = sortStringLiteral(node, {
130+
env,
131+
collapseWhitespace: {
132+
start: concat?.key !== 'right',
133+
end: concat?.key !== 'left',
134+
},
135+
})
136+
137+
if (sorted) {
138+
// @ts-ignore
139+
let raw = node.extra?.raw ?? node.raw
140+
if (typeof raw === 'string') {
141+
addChange(node.start, node.end, raw)
147142
}
148143
}
149-
this.traverse(path)
150144
},
151145

152-
visitTemplateLiteral(path) {
153-
let entries = Array.from(ancestors(path))
154-
let concat = entries.find((entry) => {
155-
return (
156-
entry.parent &&
157-
entry.parent.value &&
158-
entry.parent.value.type === 'BinaryExpression' &&
159-
entry.parent.value.operator === '+'
160-
)
161-
})
146+
Literal(node: any, path) {
147+
if (!isStringLiteral(node)) return
162148

163-
let sorted = sortTemplateLiteral(path.node, {
149+
let concat = findConcatEntry(path)
150+
let sorted = sortStringLiteral(node, {
164151
env,
165152
collapseWhitespace: {
166-
start: concat?.name !== 'right',
167-
end: concat?.name !== 'left',
153+
start: concat?.key !== 'right',
154+
end: concat?.key !== 'left',
168155
},
169156
})
170157

171158
if (sorted) {
172-
didChange = true
159+
// @ts-ignore
160+
let raw = node.extra?.raw ?? node.raw
161+
if (typeof raw === 'string') {
162+
addChange(node.start, node.end, raw)
163+
}
173164
}
174-
175-
this.traverse(path)
176165
},
177166

178-
visitTaggedTemplateExpression(path) {
179-
let entries = Array.from(ancestors(path))
180-
let concat = entries.find((entry) => {
181-
return (
182-
entry.parent &&
183-
entry.parent.value &&
184-
entry.parent.value.type === 'BinaryExpression' &&
185-
entry.parent.value.operator === '+'
186-
)
167+
TemplateLiteral(node, path) {
168+
let concat = findConcatEntry(path)
169+
let originalQuasis = node.quasis.map((quasi) => quasi.value.raw)
170+
let sorted = sortTemplateLiteral(node, {
171+
env,
172+
collapseWhitespace: {
173+
start: concat?.key !== 'right',
174+
end: concat?.key !== 'left',
175+
},
187176
})
188177

189-
if (isSortableTemplateExpression(path.node, matcher)) {
190-
let sorted = sortTemplateLiteral(path.node.quasi, {
191-
env,
192-
collapseWhitespace: {
193-
start: concat?.name !== 'right',
194-
end: concat?.name !== 'left',
195-
},
196-
})
197-
198-
if (sorted) {
199-
didChange = true
178+
if (sorted) {
179+
for (let i = 0; i < node.quasis.length; i++) {
180+
let quasi = node.quasis[i]
181+
if (quasi.value.raw !== originalQuasis[i]) {
182+
addChange(quasi.start, quasi.end, quasi.value.raw)
183+
}
200184
}
201185
}
186+
},
187+
188+
TaggedTemplateExpression(node, path) {
189+
if (!isSortableTemplateExpression(node, matcher)) {
190+
return
191+
}
192+
193+
let concat = findConcatEntry(path)
194+
let originalQuasis = node.quasi.quasis.map((quasi) => quasi.value.raw)
195+
let sorted = sortTemplateLiteral(node.quasi, {
196+
env,
197+
collapseWhitespace: {
198+
start: concat?.key !== 'right',
199+
end: concat?.key !== 'left',
200+
},
201+
})
202202

203-
this.traverse(path)
203+
if (sorted) {
204+
for (let i = 0; i < node.quasi.quasis.length; i++) {
205+
let quasi = node.quasi.quasis[i]
206+
if (quasi.value.raw !== originalQuasis[i]) {
207+
addChange(quasi.start, quasi.end, quasi.value.raw)
208+
}
209+
}
210+
}
204211
},
205212
})
206213

207214
if (didChange) {
208-
attr.value = recast.print(ast.program.body[0].declarations[0].init).code
215+
attr.value = spliceChangesIntoString(attr.value, changes)
209216
}
210217
}
211218

@@ -510,16 +517,14 @@ function sortTemplateLiteral(
510517
}
511518

512519
function isSortableTemplateExpression(
513-
node:
514-
| import('@babel/types').TaggedTemplateExpression
515-
| import('ast-types').namedTypes.TaggedTemplateExpression,
520+
node: import('@babel/types').TaggedTemplateExpression,
516521
matcher: Matcher,
517522
): boolean {
518523
return isSortableExpression(node.tag, matcher)
519524
}
520525

521526
function isSortableCallExpression(
522-
node: import('@babel/types').CallExpression | import('ast-types').namedTypes.CallExpression,
527+
node: import('@babel/types').CallExpression,
523528
matcher: Matcher,
524529
): boolean {
525530
if (!node.arguments?.length) return false
@@ -528,10 +533,7 @@ function isSortableCallExpression(
528533
}
529534

530535
function isSortableExpression(
531-
node:
532-
| import('@babel/types').Expression
533-
| import('@babel/types').V8IntrinsicIdentifier
534-
| import('ast-types').namedTypes.ASTNode,
536+
node: import('@babel/types').Expression | import('@babel/types').V8IntrinsicIdentifier,
535537
matcher: Matcher,
536538
): boolean {
537539
// Traverse property accesses and function calls to find the leading ident

tsdown.config.ts

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,6 @@ import { readFile } from 'node:fs/promises'
22
import * as path from 'node:path'
33
import { defineConfig, Rolldown } from 'tsdown'
44

5-
6-
/**
7-
* Patches recast to fix template literal spacing issues.
8-
* @see https://github.com/benjamn/recast/issues/611
9-
*/
10-
function patchRecast(): Rolldown.Plugin {
11-
return {
12-
name: 'patch-recast',
13-
async load(id) {
14-
if (!/recast[\\/]lib[\\/]patcher\.js$/.test(id)) {
15-
return null
16-
}
17-
18-
let original = await readFile(id, 'utf8')
19-
return {
20-
code: original
21-
.replace(
22-
'var nls = needsLeadingSpace(lines, oldNode.loc, newLines);',
23-
'var nls = oldNode.type !== "TemplateElement" && needsLeadingSpace(lines, oldNode.loc, newLines);',
24-
)
25-
.replace(
26-
'var nts = needsTrailingSpace(lines, oldNode.loc, newLines)',
27-
'var nts = oldNode.type !== "TemplateElement" && needsTrailingSpace(lines, oldNode.loc, newLines)',
28-
),
29-
}
30-
},
31-
}
32-
}
33-
345
/**
356
* Patches jiti to use require for babel import.
367
*/
@@ -103,5 +74,5 @@ export default defineConfig({
10374
fixedExtension: true,
10475
inlineOnly: false,
10576
shims: true,
106-
plugins: [patchRecast(), patchJiti(), inlineCssImports()],
77+
plugins: [patchJiti(), inlineCssImports()],
10778
})

0 commit comments

Comments
 (0)