@herb-tools/rewriter 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +342 -0
- package/dist/index.cjs +3802 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.esm.js +3793 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/loader.cjs +12480 -0
- package/dist/loader.cjs.map +1 -0
- package/dist/loader.esm.js +12446 -0
- package/dist/loader.esm.js.map +1 -0
- package/dist/types/ast-rewriter.d.ts +64 -0
- package/dist/types/built-ins/index.d.ts +14 -0
- package/dist/types/built-ins/tailwind-class-sorter.d.ts +13 -0
- package/dist/types/context.d.ts +19 -0
- package/dist/types/custom-rewriter-loader.d.ts +67 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/loader.d.ts +6 -0
- package/dist/types/mutable.d.ts +17 -0
- package/dist/types/rewrite.d.ts +77 -0
- package/dist/types/rewriter-factories.d.ts +28 -0
- package/dist/types/string-rewriter.d.ts +54 -0
- package/dist/types/type-guards.d.ts +20 -0
- package/package.json +54 -0
- package/src/ast-rewriter.ts +70 -0
- package/src/built-ins/index.ts +33 -0
- package/src/built-ins/tailwind-class-sorter.ts +278 -0
- package/src/context.ts +21 -0
- package/src/custom-rewriter-loader.ts +187 -0
- package/src/index.ts +13 -0
- package/src/loader.ts +8 -0
- package/src/mutable.ts +22 -0
- package/src/rewrite.ts +132 -0
- package/src/rewriter-factories.ts +37 -0
- package/src/string-rewriter.ts +60 -0
- package/src/type-guards.ts +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# Herb Rewriter
|
|
2
|
+
|
|
3
|
+
**Package:** [`@herb-tools/rewriter`](https://www.npmjs.com/package/@herb-tools/rewriter)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Rewriter system for transforming HTML+ERB AST nodes and formatted strings. Provides base classes and utilities for creating custom rewriters that can modify templates.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
:::code-group
|
|
12
|
+
|
|
13
|
+
```shell [npm]
|
|
14
|
+
npm add @herb-tools/rewriter
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```shell [pnpm]
|
|
18
|
+
pnpm add @herb-tools/rewriter
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```shell [yarn]
|
|
22
|
+
yarn add @herb-tools/rewriter
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```shell [bun]
|
|
26
|
+
bun add @herb-tools/rewriter
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
:::
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## Overview
|
|
33
|
+
|
|
34
|
+
The rewriter package provides a plugin architecture for transforming HTML+ERB templates. Rewriters can be used to transform templates before formatting, implement linter autofixes, or perform any custom AST or string transformations.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
### Rewriter Types
|
|
38
|
+
|
|
39
|
+
- **`ASTRewriter`**: Transform the parsed AST (e.g., sorting Tailwind classes, restructuring HTML)
|
|
40
|
+
- **`StringRewriter`**: Transform formatted strings (e.g., adding trailing newlines, normalizing whitespace)
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
### Quick Start
|
|
45
|
+
|
|
46
|
+
The rewriter package exposes two main functions for applying rewriters to templates:
|
|
47
|
+
|
|
48
|
+
#### `rewrite()` - Transform AST Nodes
|
|
49
|
+
|
|
50
|
+
Use `rewrite()` when you already have a parsed AST node:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { Herb } from "@herb-tools/node-wasm"
|
|
54
|
+
import { rewrite } from "@herb-tools/rewriter"
|
|
55
|
+
import { tailwindClassSorter } from "@herb-tools/rewriter/loader"
|
|
56
|
+
|
|
57
|
+
await Herb.load()
|
|
58
|
+
|
|
59
|
+
const template = `<div class="text-red-500 p-4 mt-2"></div>`
|
|
60
|
+
const parseResult = Herb.parse(template, { track_whitespace: true })
|
|
61
|
+
|
|
62
|
+
const sorter = await tailwindClassSorter()
|
|
63
|
+
const { output, node } = rewrite(parseResult.value, [sorter])
|
|
64
|
+
// output: "<div class="mt-2 p-4 text-red-500"></div>"
|
|
65
|
+
// node: The rewritten AST node
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### `rewriteString()` - Transform Template Strings
|
|
69
|
+
|
|
70
|
+
Use `rewriteString()` as a convenience wrapper when working with template strings:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { Herb } from "@herb-tools/node-wasm"
|
|
74
|
+
import { rewriteString } from "@herb-tools/rewriter"
|
|
75
|
+
import { tailwindClassSorter } from "@herb-tools/rewriter/loader"
|
|
76
|
+
|
|
77
|
+
await Herb.load()
|
|
78
|
+
|
|
79
|
+
const template = `<div class="text-red-500 p-4 mt-2"></div>`
|
|
80
|
+
|
|
81
|
+
const sorter = await tailwindClassSorter()
|
|
82
|
+
const output = rewriteString(Herb, template, [sorter])
|
|
83
|
+
// output: "<div class="mt-2 p-4 text-red-500"></div>"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Note:** `rewrite()` returns both the rewritten string (`output`) and the transformed AST (`node`), which allows for partial rewrites and further processing. `rewriteString()` is a convenience wrapper that returns just the string.
|
|
87
|
+
|
|
88
|
+
## Built-in Rewriters
|
|
89
|
+
|
|
90
|
+
### Tailwind Class Sorter
|
|
91
|
+
|
|
92
|
+
Automatically sorts Tailwind CSS classes in `class` attributes according to Tailwind's recommended order.
|
|
93
|
+
|
|
94
|
+
**Usage:**
|
|
95
|
+
```typescript
|
|
96
|
+
import { Herb } from "@herb-tools/node-wasm"
|
|
97
|
+
import { rewriteString } from "@herb-tools/rewriter"
|
|
98
|
+
import { tailwindClassSorter } from "@herb-tools/rewriter/loader"
|
|
99
|
+
|
|
100
|
+
await Herb.load()
|
|
101
|
+
|
|
102
|
+
const template = `<div class="px-4 bg-blue-500 text-white rounded py-2"></div>`
|
|
103
|
+
const sorter = await tailwindClassSorter()
|
|
104
|
+
const output = rewriteString(Herb, template, [sorter])
|
|
105
|
+
// output: "<div class="rounded bg-blue-500 px-4 py-2 text-white"></div>"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Features:**
|
|
109
|
+
- Sorts classes in `class` attributes
|
|
110
|
+
- Auto-discovers Tailwind configuration from your project
|
|
111
|
+
- Supports both Tailwind v3 and v4
|
|
112
|
+
- Works with ERB expressions inside class attributes
|
|
113
|
+
|
|
114
|
+
**Example transformation:**
|
|
115
|
+
|
|
116
|
+
```erb
|
|
117
|
+
<!-- Before -->
|
|
118
|
+
<div class="px-4 bg-blue-500 text-white rounded py-2">
|
|
119
|
+
<span class="font-bold text-lg">Hello</span>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<!-- After -->
|
|
123
|
+
<div class="rounded bg-blue-500 px-4 py-2 text-white">
|
|
124
|
+
<span class="text-lg font-bold">Hello</span>
|
|
125
|
+
</div>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Custom Rewriters
|
|
129
|
+
|
|
130
|
+
You can create custom rewriters to transform templates in any way you need.
|
|
131
|
+
|
|
132
|
+
### Creating an ASTRewriter
|
|
133
|
+
|
|
134
|
+
ASTRewriters receive and modify AST nodes:
|
|
135
|
+
|
|
136
|
+
```javascript [.herb/rewriters/my-rewriter.mjs]
|
|
137
|
+
import { ASTRewriter } from "@herb-tools/rewriter"
|
|
138
|
+
import { Visitor } from "@herb-tools/core"
|
|
139
|
+
|
|
140
|
+
export default class MyASTRewriter extends ASTRewriter {
|
|
141
|
+
get name() {
|
|
142
|
+
return "my-ast-rewriter"
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
get description() {
|
|
146
|
+
return "Transforms the AST"
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Optional: Load configuration or setup
|
|
150
|
+
async initialize(context) {
|
|
151
|
+
// context.baseDir - project root directory
|
|
152
|
+
// context.filePath - current file being processed (optional)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Transform the AST node
|
|
156
|
+
rewrite(node, context) {
|
|
157
|
+
// Use the Visitor pattern to traverse and modify the AST
|
|
158
|
+
const visitor = new MyVisitor()
|
|
159
|
+
visitor.visit(node)
|
|
160
|
+
|
|
161
|
+
// Return the modified node
|
|
162
|
+
return node
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
class MyVisitor extends Visitor {
|
|
167
|
+
visitHTMLElementNode(node) {
|
|
168
|
+
// Modify nodes as needed
|
|
169
|
+
// node.someProperty = "new value"
|
|
170
|
+
|
|
171
|
+
this.visitChildNodes(node)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Creating a StringRewriter
|
|
177
|
+
|
|
178
|
+
StringRewriters receive and modify strings:
|
|
179
|
+
|
|
180
|
+
```javascript [.herb/rewriters/add-newline.mjs]
|
|
181
|
+
import { StringRewriter } from "@herb-tools/rewriter"
|
|
182
|
+
|
|
183
|
+
export default class AddTrailingNewline extends StringRewriter {
|
|
184
|
+
get name() {
|
|
185
|
+
return "add-trailing-newline"
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
get description() {
|
|
189
|
+
return "Ensures file ends with a newline"
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async initialize(context) {
|
|
193
|
+
// Optional setup
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
rewrite(content, context) {
|
|
197
|
+
return content.endsWith("\n") ? content : content + "\n"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Using Custom Rewriters
|
|
203
|
+
|
|
204
|
+
By default, rewriters are auto-discovered from: `.herb/rewriters/**/*.mjs`
|
|
205
|
+
|
|
206
|
+
::: info File Extension
|
|
207
|
+
Custom rewriters must use the `.mjs` extension to avoid Node.js module type warnings. The `.mjs` extension explicitly marks files as ES modules.
|
|
208
|
+
:::
|
|
209
|
+
|
|
210
|
+
#### Configuring in `.herb.yml`
|
|
211
|
+
|
|
212
|
+
Reference custom rewriters by their name in your configuration:
|
|
213
|
+
|
|
214
|
+
```yaml [.herb.yml]
|
|
215
|
+
formatter:
|
|
216
|
+
enabled: true
|
|
217
|
+
rewriter:
|
|
218
|
+
pre:
|
|
219
|
+
- tailwind-class-sorter # Built-in rewriter
|
|
220
|
+
- my-ast-rewriter # Custom rewriter
|
|
221
|
+
post:
|
|
222
|
+
- add-trailing-newline # Custom rewriter
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
When custom rewriters are loaded, the formatter will display them:
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
Loaded 2 custom rewriters:
|
|
229
|
+
• my-ast-rewriter (.herb/rewriters/my-ast-rewriter.mjs)
|
|
230
|
+
• add-trailing-newline (.herb/rewriters/add-newline.mjs)
|
|
231
|
+
|
|
232
|
+
Using 2 pre-format rewriters:
|
|
233
|
+
• tailwind-class-sorter (built-in)
|
|
234
|
+
• my-ast-rewriter (.herb/rewriters/my-ast-rewriter.mjs)
|
|
235
|
+
|
|
236
|
+
Using 1 post-format rewriter:
|
|
237
|
+
• add-trailing-newline (.herb/rewriters/add-newline.mjs)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
::: warning Rewriter Name Clashes
|
|
241
|
+
If a custom rewriter has the same name as a built-in rewriter or another custom rewriter, you'll see a warning. The custom rewriter will override the built-in one.
|
|
242
|
+
:::
|
|
243
|
+
|
|
244
|
+
::: tip Hot Reload
|
|
245
|
+
Custom rewriters are automatically reloaded when changed in editors with the Herb Language Server. No need to restart your editor!
|
|
246
|
+
:::
|
|
247
|
+
|
|
248
|
+
## API Reference
|
|
249
|
+
|
|
250
|
+
### Functions
|
|
251
|
+
|
|
252
|
+
#### `rewrite()`
|
|
253
|
+
|
|
254
|
+
Transform an AST node using the provided rewriters.
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
function rewrite<T extends Node>(
|
|
258
|
+
node: T,
|
|
259
|
+
rewriters: Rewriter[],
|
|
260
|
+
options?: RewriteOptions
|
|
261
|
+
): RewriteResult
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Parameters:**
|
|
265
|
+
- `node`: The AST node to transform
|
|
266
|
+
- `rewriters`: Array of rewriter instances to apply (must be already initialized)
|
|
267
|
+
- `options`: Optional configuration
|
|
268
|
+
- `baseDir`: Base directory for resolving config files (defaults to `process.cwd()`)
|
|
269
|
+
- `filePath`: Optional file path for context
|
|
270
|
+
|
|
271
|
+
**Returns:** Object with:
|
|
272
|
+
- `output`: The rewritten template string
|
|
273
|
+
- `node`: The transformed AST node (preserves input type)
|
|
274
|
+
|
|
275
|
+
#### `rewriteString()`
|
|
276
|
+
|
|
277
|
+
Convenience wrapper around `rewrite()` that parses the template string first and returns just the output string.
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
function rewriteString(
|
|
281
|
+
herb: HerbBackend,
|
|
282
|
+
template: string,
|
|
283
|
+
rewriters: Rewriter[],
|
|
284
|
+
options?: RewriteOptions
|
|
285
|
+
): string
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Parameters:**
|
|
289
|
+
- `herb`: The Herb backend instance for parsing
|
|
290
|
+
- `template`: The HTML+ERB template string to rewrite
|
|
291
|
+
- `rewriters`: Array of rewriter instances to apply (must be already initialized)
|
|
292
|
+
- `options`: Optional configuration (same as `rewrite()`)
|
|
293
|
+
|
|
294
|
+
**Returns:** The rewritten template string
|
|
295
|
+
|
|
296
|
+
### Base Classes
|
|
297
|
+
|
|
298
|
+
#### `ASTRewriter`
|
|
299
|
+
|
|
300
|
+
Base class for rewriters that transform AST nodes:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
import { ASTRewriter } from "@herb-tools/rewriter"
|
|
304
|
+
import type { Node, RewriteContext } from "@herb-tools/rewriter"
|
|
305
|
+
|
|
306
|
+
class MyRewriter extends ASTRewriter {
|
|
307
|
+
abstract get name(): string
|
|
308
|
+
abstract get description(): string
|
|
309
|
+
|
|
310
|
+
async initialize(context: RewriteContext): Promise<void> {
|
|
311
|
+
// Optional initialization
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
abstract rewrite<T extends Node>(node: T, context: RewriteContext): T
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
#### `StringRewriter`
|
|
319
|
+
|
|
320
|
+
Base class for rewriters that transform strings:
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
import { StringRewriter } from "@herb-tools/rewriter"
|
|
324
|
+
import type { RewriteContext } from "@herb-tools/rewriter"
|
|
325
|
+
|
|
326
|
+
class MyRewriter extends StringRewriter {
|
|
327
|
+
abstract get name(): string
|
|
328
|
+
abstract get description(): string
|
|
329
|
+
|
|
330
|
+
async initialize(context: RewriteContext): Promise<void> {
|
|
331
|
+
// Optional initialization
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
abstract rewrite(content: string, context: RewriteContext): string
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## See Also
|
|
339
|
+
|
|
340
|
+
- [Formatter Documentation](/projects/formatter) - Using rewriters with the formatter
|
|
341
|
+
- [Core Documentation](/projects/core) - AST node types and visitor pattern
|
|
342
|
+
- [Config Documentation](/projects/config) - Configuring rewriters in `.herb.yml`
|