Skip to content

Commit aece0f8

Browse files
authored
Rewriter: Change rewrite/rewriteString to be synchronous (#787)
This pull request refactors the rewriter API to make `rewrite` and `rewriteString` synchronous functions, and updates the `tailwindClassSorter` factory to be asynchronous and to automatically initialize the rewriter before use.
1 parent d6f003d commit aece0f8

File tree

4 files changed

+59
-59
lines changed

4 files changed

+59
-59
lines changed

javascript/packages/rewriter/README.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ await Herb.load()
5959
const template = `<div class="text-red-500 p-4 mt-2"></div>`
6060
const parseResult = Herb.parse(template, { track_whitespace: true })
6161

62-
const { output, node } = await rewrite(parseResult.value, [tailwindClassSorter()])
62+
const sorter = await tailwindClassSorter()
63+
const { output, node } = rewrite(parseResult.value, [sorter])
6364
// output: "<div class="mt-2 p-4 text-red-500"></div>"
6465
// node: The rewritten AST node
6566
```
@@ -77,7 +78,8 @@ await Herb.load()
7778

7879
const template = `<div class="text-red-500 p-4 mt-2"></div>`
7980

80-
const output = await rewriteString(Herb, template, [tailwindClassSorter()])
81+
const sorter = await tailwindClassSorter()
82+
const output = rewriteString(Herb, template, [sorter])
8183
// output: "<div class="mt-2 p-4 text-red-500"></div>"
8284
```
8385

@@ -98,7 +100,8 @@ import { tailwindClassSorter } from "@herb-tools/rewriter/loader"
98100
await Herb.load()
99101

100102
const template = `<div class="px-4 bg-blue-500 text-white rounded py-2"></div>`
101-
const output = await rewriteString(Herb, template, [tailwindClassSorter()])
103+
const sorter = await tailwindClassSorter()
104+
const output = rewriteString(Herb, template, [sorter])
102105
// output: "<div class="rounded bg-blue-500 px-4 py-2 text-white"></div>"
103106
```
104107

@@ -211,16 +214,16 @@ Which means you can just reference and configure them in `.herb.yml` using their
211214
Transform an AST node using the provided rewriters.
212215

213216
```typescript
214-
async function rewrite<T extends Node>(
217+
function rewrite<T extends Node>(
215218
node: T,
216219
rewriters: Rewriter[],
217220
options?: RewriteOptions
218-
): Promise<RewriteResult>
221+
): RewriteResult
219222
```
220223

221224
**Parameters:**
222225
- `node`: The AST node to transform
223-
- `rewriters`: Array of rewriter instances to apply
226+
- `rewriters`: Array of rewriter instances to apply (must be already initialized)
224227
- `options`: Optional configuration
225228
- `baseDir`: Base directory for resolving config files (defaults to `process.cwd()`)
226229
- `filePath`: Optional file path for context
@@ -234,18 +237,18 @@ async function rewrite<T extends Node>(
234237
Convenience wrapper around `rewrite()` that parses the template string first and returns just the output string.
235238

236239
```typescript
237-
async function rewriteString(
240+
function rewriteString(
238241
herb: HerbBackend,
239242
template: string,
240243
rewriters: Rewriter[],
241244
options?: RewriteOptions
242-
): Promise<string>
245+
): string
243246
```
244247

245248
**Parameters:**
246249
- `herb`: The Herb backend instance for parsing
247250
- `template`: The HTML+ERB template string to rewrite
248-
- `rewriters`: Array of rewriter instances to apply
251+
- `rewriters`: Array of rewriter instances to apply (must be already initialized)
249252
- `options`: Optional configuration (same as `rewrite()`)
250253

251254
**Returns:** The rewritten template string

javascript/packages/rewriter/src/rewrite.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,23 +49,19 @@ export interface RewriteResult {
4949
*
5050
* const template = '<div class="text-red-500 p-4 mt-2"></div>'
5151
* const parseResult = Herb.parse(template)
52-
* const { output, document } = await rewrite(parseResult.value, [tailwindClassSorter()])
52+
* const { output, node } = rewrite(parseResult.value, [tailwindClassSorter()])
5353
* ```
5454
*
5555
* @param node - The AST Node to rewrite
5656
* @param rewriters - Array of rewriter instances to apply
5757
* @param options - Optional configuration for the rewrite operation
5858
* @returns Object containing the rewritten string and Node
5959
*/
60-
export async function rewrite<T extends Node>(node: T, rewriters: Rewriter[], options: RewriteOptions = {}): Promise<RewriteResult & { node: T }> {
60+
export function rewrite<T extends Node>(node: T, rewriters: Rewriter[], options: RewriteOptions = {}): RewriteResult & { node: T } {
6161
const { baseDir = process.cwd(), filePath } = options
6262

6363
const context: RewriteContext = { baseDir, filePath }
6464

65-
for (const rewriter of rewriters) {
66-
await rewriter.initialize(context)
67-
}
68-
6965
let currentNode = node
7066

7167
const astRewriters = rewriters.filter(rewriter => rewriter instanceof ASTRewriter)
@@ -109,7 +105,7 @@ export async function rewrite<T extends Node>(node: T, rewriters: Rewriter[], op
109105
* await Herb.load()
110106
*
111107
* const template = '<div class="text-red-500 p-4 mt-2"></div>'
112-
* const output = await rewriteString(Herb, template, [tailwindClassSorter()])
108+
* const output = rewriteString(Herb, template, [tailwindClassSorter()])
113109
* // output: '<div class="mt-2 p-4 text-red-500"></div>'
114110
* ```
115111
*
@@ -119,14 +115,14 @@ export async function rewrite<T extends Node>(node: T, rewriters: Rewriter[], op
119115
* @param options - Optional configuration for the rewrite operation
120116
* @returns The rewritten template string
121117
*/
122-
export async function rewriteString(herb: HerbBackend, template: string, rewriters: Rewriter[], options: RewriteOptions = {}): Promise<string> {
118+
export function rewriteString(herb: HerbBackend, template: string, rewriters: Rewriter[], options: RewriteOptions = {}): string {
123119
const parseResult = herb.parse(template, { track_whitespace: true })
124120

125121
if (parseResult.failed) {
126122
return template
127123
}
128124

129-
const { output } = await rewrite(
125+
const { output } = rewrite(
130126
parseResult.value,
131127
rewriters,
132128
options

javascript/packages/rewriter/src/rewriter-factories.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,27 @@ export interface TailwindClassSorterOptions {
1111
/**
1212
* Factory function for creating a Tailwind class sorter rewriter
1313
*
14+
* Automatically initializes the rewriter before returning it.
15+
*
1416
* @example
1517
* ```typescript
1618
* import { rewrite } from '@herb-tools/rewriter'
1719
* import { tailwindClassSorter } from '@herb-tools/rewriter/loader'
1820
*
1921
* const template = '<div class="text-red-500 p-4 mt-2"></div>'
20-
* const result = await rewrite(template, [tailwindClassSorter()])
22+
* const sorter = await tailwindClassSorter()
23+
* const result = rewrite(template, [sorter])
2124
* // Result: '<div class="mt-2 p-4 text-red-500"></div>'
2225
* ```
2326
*
2427
* @param options - Optional configuration for the Tailwind class sorter
25-
* @returns A configured TailwindClassSorterRewriter instance
28+
* @returns A configured and initialized TailwindClassSorterRewriter instance
2629
*/
27-
export function tailwindClassSorter(options: TailwindClassSorterOptions = {}): TailwindClassSorterRewriter {
30+
export async function tailwindClassSorter(options: TailwindClassSorterOptions = {}): Promise<TailwindClassSorterRewriter> {
2831
const rewriter = new TailwindClassSorterRewriter()
32+
const baseDir = options.baseDir || process.cwd()
2933

30-
if (options.baseDir) {
31-
const originalInitialize = rewriter.initialize.bind(rewriter)
32-
33-
rewriter.initialize = async (context) => {
34-
return originalInitialize({ ...context, baseDir: options.baseDir || context.baseDir })
35-
}
36-
}
34+
await rewriter.initialize({ baseDir })
3735

3836
return rewriter
3937
}

javascript/packages/rewriter/test/rewrite.test.ts

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,32 @@ import { Herb } from "@herb-tools/node-wasm"
55
import { rewrite, rewriteString, tailwindClassSorter } from "../src/loader.js"
66

77
describe("rewrite", () => {
8+
let sorter: Awaited<ReturnType<typeof tailwindClassSorter>>
9+
810
beforeAll(async () => {
911
await Herb.load()
12+
sorter = await tailwindClassSorter()
1013
})
1114

1215
describe("basic usage", () => {
13-
test("rewrites Node with tailwindClassSorter", async () => {
16+
test("rewrites Node with tailwindClassSorter", () => {
1417
const template = '<div class="text-red-500 p-4 mt-2"></div>'
1518
const parseResult = Herb.parse(template, { track_whitespace: true })
16-
const { output, node } = await rewrite(parseResult.value, [tailwindClassSorter()])
19+
const { output, node } = rewrite(parseResult.value, [sorter])
1720

1821
expect(output).toBe('<div class="mt-2 p-4 text-red-500"></div>')
1922
expect(node.type).toBe("AST_DOCUMENT_NODE")
2023
})
2124

22-
test("handles empty rewriters array", async () => {
25+
test("handles empty rewriters array", () => {
2326
const template = '<div class="text-red-500 p-4 mt-2"></div>'
2427
const parseResult = Herb.parse(template, { track_whitespace: true })
25-
const { output } = await rewrite(parseResult.value, [])
28+
const { output } = rewrite(parseResult.value, [])
2629

2730
expect(output).toBe(template)
2831
})
2932

30-
test("handles multiple elements", async () => {
33+
test("handles multiple elements", () => {
3134
const template = dedent`
3235
<div class="px-4 bg-blue-500">
3336
<span class="text-white font-bold"></span>
@@ -40,81 +43,81 @@ describe("rewrite", () => {
4043
`
4144

4245
const parseResult = Herb.parse(template, { track_whitespace: true })
43-
const { output } = await rewrite(parseResult.value, [tailwindClassSorter()])
46+
const { output } = rewrite(parseResult.value, [sorter])
4447

4548
expect(output).toBe(expected)
4649
})
4750
})
4851

4952
describe("rewriteString", () => {
50-
test("rewrites string with tailwindClassSorter", async () => {
53+
test("rewrites string with tailwindClassSorter", () => {
5154
const template = '<div class="text-red-500 p-4 mt-2"></div>'
52-
const output = await rewriteString(Herb, template, [tailwindClassSorter()])
55+
const output = rewriteString(Herb, template, [sorter])
5356

5457
expect(output).toBe('<div class="mt-2 p-4 text-red-500"></div>')
5558
})
5659

57-
test("handles empty rewriters array", async () => {
60+
test("handles empty rewriters array", () => {
5861
const template = '<div class="text-red-500 p-4 mt-2"></div>'
59-
const output = await rewriteString(Herb, template, [])
62+
const output = rewriteString(Herb, template, [])
6063

6164
expect(output).toBe(template)
6265
})
6366

64-
test("returns original template on parse failure", async () => {
67+
test("returns original template on parse failure", () => {
6568
const template = '<div class="unclosed'
66-
const output = await rewriteString(Herb, template, [tailwindClassSorter()])
69+
const output = rewriteString(Herb, template, [sorter])
6770

6871
expect(output).toBe(template)
6972
})
7073
})
7174

7275
describe("options", () => {
73-
test("accepts baseDir option", async () => {
76+
test("accepts baseDir option", () => {
7477
const template = '<div class="text-red-500 p-4 mt-2"></div>'
7578
const parseResult = Herb.parse(template, { track_whitespace: true })
76-
const { output } = await rewrite(parseResult.value, [tailwindClassSorter()], {
79+
const { output } = rewrite(parseResult.value, [sorter], {
7780
baseDir: "/custom/path"
7881
})
7982

8083
expect(output).toBe('<div class="mt-2 p-4 text-red-500"></div>')
8184
})
8285

83-
test("accepts filePath option", async () => {
86+
test("accepts filePath option", () => {
8487
const template = '<div class="text-red-500 p-4 mt-2"></div>'
8588
const parseResult = Herb.parse(template, { track_whitespace: true })
86-
const { output } = await rewrite(parseResult.value, [tailwindClassSorter()], {
89+
const { output } = rewrite(parseResult.value, [sorter], {
8790
filePath: "/custom/path/file.html.erb"
8891
})
8992

9093
expect(output).toBe('<div class="mt-2 p-4 text-red-500"></div>')
9194
})
9295

93-
test("uses process.cwd() as default baseDir", async () => {
96+
test("uses process.cwd() as default baseDir", () => {
9497
const template = '<div class="text-red-500 p-4 mt-2"></div>'
9598
const parseResult = Herb.parse(template, { track_whitespace: true })
96-
const { output } = await rewrite(parseResult.value, [tailwindClassSorter()])
99+
const { output } = rewrite(parseResult.value, [sorter])
97100

98101
expect(output).toBe('<div class="mt-2 p-4 text-red-500"></div>')
99102
})
100103
})
101104

102105
describe("complex templates", () => {
103-
test("handles ERB expressions", async () => {
106+
test("handles ERB expressions", () => {
104107
const template = '<div class="px-4 <%= extra_classes %> bg-blue-500"></div>'
105-
const output = await rewriteString(Herb, template, [tailwindClassSorter()])
108+
const output = rewriteString(Herb, template, [sorter])
106109

107110
expect(output).toBe('<div class="bg-blue-500 px-4 <%= extra_classes %>"></div>')
108111
})
109112

110-
test("handles ERB conditionals", async () => {
113+
test("handles ERB conditionals", () => {
111114
const template = '<div class="px-4 <% if admin? %> text-red-500 font-bold <% end %> bg-blue-500"></div>'
112-
const output = await rewriteString(Herb, template, [tailwindClassSorter()])
115+
const output = rewriteString(Herb, template, [sorter])
113116

114117
expect(output).toBe('<div class="bg-blue-500 px-4 <% if admin? %> font-bold text-red-500 <% end %>"></div>')
115118
})
116119

117-
test("handles multiline templates", async () => {
120+
test("handles multiline templates", () => {
118121
const template = dedent`
119122
<div class="px-4
120123
<% if valid? %>
@@ -134,45 +137,45 @@ describe("rewrite", () => {
134137
</div>
135138
`
136139

137-
const output = await rewriteString(Herb, template, [tailwindClassSorter()])
140+
const output = rewriteString(Herb, template, [sorter])
138141

139142
expect(output).toBe(expected)
140143
})
141144
})
142145

143146
describe("error handling", () => {
144-
test("continues processing on rewriter error", async () => {
147+
test("continues processing on rewriter error", () => {
145148
const template = '<div class="text-red-500 p-4"></div>'
146149

147-
const errorRewriter = tailwindClassSorter()
150+
const errorRewriter = sorter
148151
errorRewriter.rewrite.bind(errorRewriter)
149152

150153
errorRewriter.rewrite = () => {
151154
throw new Error("Test error")
152155
}
153156

154157
const parseResult = Herb.parse(template, { track_whitespace: true })
155-
const { output } = await rewrite(parseResult.value, [errorRewriter])
158+
const { output } = rewrite(parseResult.value, [errorRewriter])
156159

157160
expect(output).toBe(template)
158161
})
159162
})
160163

161164
describe("tailwindClassSorter factory", () => {
162165
test("creates rewriter with default options", () => {
163-
const rewriter = tailwindClassSorter()
166+
const rewriter = sorter
164167

165168
expect(rewriter.name).toBe("tailwind-class-sorter")
166169
})
167170

168-
test("creates rewriter with custom baseDir", () => {
169-
const rewriter = tailwindClassSorter({ baseDir: "/custom/path" })
171+
test("creates rewriter with custom baseDir", async () => {
172+
const rewriter = await tailwindClassSorter({ baseDir: "/custom/path" })
170173

171174
expect(rewriter.name).toBe("tailwind-class-sorter")
172175
})
173176

174177
test("rewriter has initialize method", async () => {
175-
const rewriter = tailwindClassSorter()
178+
const rewriter = sorter
176179

177180
await expect(
178181
rewriter.initialize({ baseDir: process.cwd() })

0 commit comments

Comments
 (0)