@herb-tools/rewriter 0.9.1 → 0.9.3
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/dist/index.cjs +616 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +616 -18
- package/dist/index.esm.js.map +1 -1
- package/dist/loader.cjs +616 -18
- package/dist/loader.cjs.map +1 -1
- package/dist/loader.esm.js +616 -18
- package/dist/loader.esm.js.map +1 -1
- package/dist/types/built-ins/action-view-tag-helper-to-html.d.ts +1 -1
- package/package.json +4 -4
- package/src/built-ins/action-view-tag-helper-to-html.ts +55 -11
- package/src/built-ins/html-to-action-view-tag-helper.ts +66 -5
|
@@ -4,5 +4,5 @@ import type { Node } from "@herb-tools/core";
|
|
|
4
4
|
export declare class ActionViewTagHelperToHTMLRewriter extends ASTRewriter {
|
|
5
5
|
get name(): string;
|
|
6
6
|
get description(): string;
|
|
7
|
-
rewrite<T extends Node>(node: T,
|
|
7
|
+
rewrite<T extends Node>(node: T, context: RewriteContext): T;
|
|
8
8
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herb-tools/rewriter",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"description": "Rewriter system for transforming HTML+ERB AST nodes and formatted strings",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://herb-tools.dev",
|
|
@@ -38,12 +38,12 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@herb-tools/core": "0.9.
|
|
42
|
-
"@herb-tools/tailwind-class-sorter": "0.9.
|
|
41
|
+
"@herb-tools/core": "0.9.3",
|
|
42
|
+
"@herb-tools/tailwind-class-sorter": "0.9.3",
|
|
43
43
|
"tinyglobby": "^0.2.15"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@herb-tools/printer": "0.9.
|
|
46
|
+
"@herb-tools/printer": "0.9.3"
|
|
47
47
|
},
|
|
48
48
|
"files": [
|
|
49
49
|
"package.json",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Visitor, Location, HTMLOpenTagNode, HTMLCloseTagNode, HTMLElementNode, HTMLAttributeValueNode, WhitespaceNode, ERBContentNode } from "@herb-tools/core"
|
|
2
|
-
import { isHTMLAttributeNode, isERBOpenTagNode, isRubyLiteralNode, isRubyHTMLAttributesSplatNode, createSyntheticToken } from "@herb-tools/core"
|
|
2
|
+
import { isHTMLAttributeNode, isERBOpenTagNode, isRubyLiteralNode, isRubyHTMLAttributesSplatNode, isWhitespaceNode, createSyntheticToken } from "@herb-tools/core"
|
|
3
3
|
|
|
4
4
|
import { ASTRewriter } from "../ast-rewriter.js"
|
|
5
5
|
import { asMutable } from "../mutable.js"
|
|
@@ -17,6 +17,45 @@ function createWhitespaceNode(): WhitespaceNode {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
class ActionViewTagHelperToHTMLVisitor extends Visitor {
|
|
20
|
+
private shallow: boolean
|
|
21
|
+
private includeBody: boolean
|
|
22
|
+
|
|
23
|
+
constructor(options: { shallow?: boolean; includeBody?: boolean } = {}) {
|
|
24
|
+
super()
|
|
25
|
+
this.shallow = options.shallow ?? false
|
|
26
|
+
this.includeBody = options.includeBody ?? true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
visitHTMLOpenTagNode(node: HTMLOpenTagNode): void {
|
|
30
|
+
const newChildren: Node[] = []
|
|
31
|
+
|
|
32
|
+
for (let index = 0; index < node.children.length; index++) {
|
|
33
|
+
const child = node.children[index]
|
|
34
|
+
|
|
35
|
+
if (isHTMLAttributeNode(child)) {
|
|
36
|
+
if (child.equals && child.equals.value !== "=") {
|
|
37
|
+
asMutable(child).equals = createSyntheticToken("=")
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (child.value) {
|
|
41
|
+
this.transformAttributeValue(child.value)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const previous = index > 0 ? node.children[index - 1] : null
|
|
45
|
+
|
|
46
|
+
if (!previous || !isWhitespaceNode(previous)) {
|
|
47
|
+
newChildren.push(createWhitespaceNode())
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
newChildren.push(child)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
asMutable(node).children = newChildren
|
|
55
|
+
|
|
56
|
+
this.visitChildNodes(node)
|
|
57
|
+
}
|
|
58
|
+
|
|
20
59
|
visitHTMLElementNode(node: HTMLElementNode): void {
|
|
21
60
|
if (!node.element_source) {
|
|
22
61
|
this.visitChildNodes(node)
|
|
@@ -104,7 +143,9 @@ class ActionViewTagHelperToHTMLVisitor extends Visitor {
|
|
|
104
143
|
|
|
105
144
|
asMutable(node).element_source = "HTML"
|
|
106
145
|
|
|
107
|
-
if (
|
|
146
|
+
if (!this.includeBody) {
|
|
147
|
+
asMutable(node).body = []
|
|
148
|
+
} else if (node.body) {
|
|
108
149
|
asMutable(node).body = node.body.map(child => {
|
|
109
150
|
if (isRubyLiteralNode(child)) {
|
|
110
151
|
return new ERBContentNode({
|
|
@@ -120,7 +161,10 @@ class ActionViewTagHelperToHTMLVisitor extends Visitor {
|
|
|
120
161
|
})
|
|
121
162
|
}
|
|
122
163
|
|
|
123
|
-
this.
|
|
164
|
+
if (!this.shallow) {
|
|
165
|
+
this.visit(child)
|
|
166
|
+
}
|
|
167
|
+
|
|
124
168
|
return child
|
|
125
169
|
})
|
|
126
170
|
}
|
|
@@ -150,12 +194,12 @@ class ActionViewTagHelperToHTMLVisitor extends Visitor {
|
|
|
150
194
|
})
|
|
151
195
|
|
|
152
196
|
mutableValue.children = newChildren
|
|
197
|
+
}
|
|
153
198
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
199
|
+
if (!value.quoted) {
|
|
200
|
+
mutableValue.quoted = true
|
|
201
|
+
mutableValue.open_quote = createSyntheticToken('"')
|
|
202
|
+
mutableValue.close_quote = createSyntheticToken('"')
|
|
159
203
|
}
|
|
160
204
|
}
|
|
161
205
|
}
|
|
@@ -166,11 +210,11 @@ export class ActionViewTagHelperToHTMLRewriter extends ASTRewriter {
|
|
|
166
210
|
}
|
|
167
211
|
|
|
168
212
|
get description(): string {
|
|
169
|
-
return "Converts ActionView tag helpers
|
|
213
|
+
return "Converts ActionView tag helpers to raw HTML elements"
|
|
170
214
|
}
|
|
171
215
|
|
|
172
|
-
rewrite<T extends Node>(node: T,
|
|
173
|
-
const visitor = new ActionViewTagHelperToHTMLVisitor()
|
|
216
|
+
rewrite<T extends Node>(node: T, context: RewriteContext): T {
|
|
217
|
+
const visitor = new ActionViewTagHelperToHTMLVisitor({ shallow: context.shallow, includeBody: context.includeBody })
|
|
174
218
|
|
|
175
219
|
visitor.visit(node)
|
|
176
220
|
|
|
@@ -35,14 +35,16 @@ interface SerializedAttributes {
|
|
|
35
35
|
attributes: string
|
|
36
36
|
href: string | null
|
|
37
37
|
id: string | null
|
|
38
|
+
src: string | null
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
function serializeAttributes(children: Node[], options: { extractHref?: boolean, extractId?: boolean } = {}): SerializedAttributes {
|
|
41
|
+
function serializeAttributes(children: Node[], options: { extractHref?: boolean, extractId?: boolean, extractSrc?: boolean } = {}): SerializedAttributes {
|
|
41
42
|
const regular: string[] = []
|
|
42
43
|
const prefixed: Map<string, string[]> = new Map()
|
|
43
44
|
|
|
44
45
|
let href: string | null = null
|
|
45
46
|
let id: string | null = null
|
|
47
|
+
let src: string | null = null
|
|
46
48
|
|
|
47
49
|
for (const child of children) {
|
|
48
50
|
if (!isHTMLAttributeNode(child)) continue
|
|
@@ -62,6 +64,11 @@ function serializeAttributes(children: Node[], options: { extractHref?: boolean,
|
|
|
62
64
|
continue
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
if (options.extractSrc && name === "src") {
|
|
68
|
+
src = value
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
|
|
65
72
|
const dataMatch = name.match(/^(data|aria)-(.+)$/)
|
|
66
73
|
|
|
67
74
|
if (dataMatch) {
|
|
@@ -83,7 +90,7 @@ function serializeAttributes(children: Node[], options: { extractHref?: boolean,
|
|
|
83
90
|
parts.push(`${prefix}: { ${entries.join(", ")} }`)
|
|
84
91
|
}
|
|
85
92
|
|
|
86
|
-
return { attributes: parts.join(", "), href, id }
|
|
93
|
+
return { attributes: parts.join(", "), href, id, src }
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
function isTextOnlyBody(body: Node[]): boolean {
|
|
@@ -116,8 +123,11 @@ class HTMLToActionViewTagHelperVisitor extends Visitor {
|
|
|
116
123
|
|
|
117
124
|
const isAnchor = tagName.value === "a"
|
|
118
125
|
const isTurboFrame = tagName.value === "turbo-frame"
|
|
126
|
+
const isScript = tagName.value === "script"
|
|
127
|
+
const isImg = tagName.value === "img"
|
|
119
128
|
const attributes = openTag.children.filter(child => !isWhitespaceNode(child))
|
|
120
|
-
const
|
|
129
|
+
const hasSrcAttribute = (isScript || isImg) && attributes.some(child => isHTMLAttributeNode(child) && getStaticAttributeName(child.name!) === "src")
|
|
130
|
+
const { attributes: attributesString, href, id, src } = serializeAttributes(attributes, { extractHref: isAnchor, extractId: isTurboFrame, extractSrc: isScript || isImg })
|
|
121
131
|
const hasBody = node.body && node.body.length > 0 && !node.is_void
|
|
122
132
|
const isInlineContent = hasBody && isTextOnlyBody(node.body)
|
|
123
133
|
|
|
@@ -130,6 +140,15 @@ class HTMLToActionViewTagHelperVisitor extends Visitor {
|
|
|
130
140
|
} else if (isTurboFrame) {
|
|
131
141
|
content = this.buildTurboFrameTagContent(node, attributesString, id, isInlineContent)
|
|
132
142
|
elementSource = "Turbo::FramesHelper#turbo_frame_tag"
|
|
143
|
+
} else if (isScript && hasSrcAttribute) {
|
|
144
|
+
content = this.buildJavascriptIncludeTagContent(attributesString, src)
|
|
145
|
+
elementSource = "ActionView::Helpers::AssetTagHelper#javascript_include_tag"
|
|
146
|
+
} else if (isScript) {
|
|
147
|
+
content = this.buildJavascriptTagContent(node, attributesString, isInlineContent)
|
|
148
|
+
elementSource = "ActionView::Helpers::JavaScriptHelper#javascript_tag"
|
|
149
|
+
} else if (isImg) {
|
|
150
|
+
content = this.buildImageTagContent(attributesString, src)
|
|
151
|
+
elementSource = "ActionView::Helpers::AssetTagHelper#image_tag"
|
|
133
152
|
} else {
|
|
134
153
|
content = this.buildTagContent(tagName.value, node, attributesString, isInlineContent)
|
|
135
154
|
elementSource = "ActionView::Helpers::TagHelper#tag"
|
|
@@ -149,7 +168,8 @@ class HTMLToActionViewTagHelperVisitor extends Visitor {
|
|
|
149
168
|
asMutable(node).open_tag = erbOpenTag
|
|
150
169
|
asMutable(node).element_source = elementSource
|
|
151
170
|
|
|
152
|
-
const
|
|
171
|
+
const isInlineLiteralContent = isScript && hasBody && node.body.length === 1 && isLiteralNode(node.body[0]) && !node.body[0].content.includes("\n")
|
|
172
|
+
const isInlineForm = isInlineContent || isInlineLiteralContent || (isTurboFrame && !hasBody) || (isScript && hasSrcAttribute) || isImg
|
|
153
173
|
|
|
154
174
|
if (node.is_void) {
|
|
155
175
|
asMutable(node).close_tag = null
|
|
@@ -224,6 +244,47 @@ class HTMLToActionViewTagHelperVisitor extends Visitor {
|
|
|
224
244
|
return argString ? ` turbo_frame_tag ${argString} do ` : ` turbo_frame_tag do `
|
|
225
245
|
}
|
|
226
246
|
|
|
247
|
+
private buildJavascriptTagContent(node: HTMLElementNode, attributes: string, isInlineContent: boolean): string {
|
|
248
|
+
const bodyNode = node.body?.[0]
|
|
249
|
+
const isInlineLiteral = bodyNode && isLiteralNode(bodyNode) && !bodyNode.content.includes("\n")
|
|
250
|
+
const isInlineText = isInlineContent && isHTMLTextNode(bodyNode)
|
|
251
|
+
|
|
252
|
+
if (isInlineText || isInlineLiteral) {
|
|
253
|
+
const textContent = isHTMLTextNode(bodyNode) ? bodyNode.content : bodyNode.content
|
|
254
|
+
const args = [`"${textContent}"`]
|
|
255
|
+
|
|
256
|
+
if (attributes) args.push(attributes)
|
|
257
|
+
|
|
258
|
+
return ` javascript_tag ${args.join(", ")} `
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return attributes
|
|
262
|
+
? ` javascript_tag ${attributes} do `
|
|
263
|
+
: ` javascript_tag do `
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private buildJavascriptIncludeTagContent(attributes: string, source: string | null): string {
|
|
267
|
+
const args: string[] = []
|
|
268
|
+
|
|
269
|
+
if (source) args.push(source)
|
|
270
|
+
if (attributes) args.push(attributes)
|
|
271
|
+
|
|
272
|
+
const argString = args.join(", ")
|
|
273
|
+
|
|
274
|
+
return argString ? ` javascript_include_tag ${argString} ` : ` javascript_include_tag `
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private buildImageTagContent(attributes: string, source: string | null): string {
|
|
278
|
+
const args: string[] = []
|
|
279
|
+
|
|
280
|
+
if (source) args.push(source)
|
|
281
|
+
if (attributes) args.push(attributes)
|
|
282
|
+
|
|
283
|
+
const argString = args.join(", ")
|
|
284
|
+
|
|
285
|
+
return argString ? ` image_tag ${argString} ` : ` image_tag `
|
|
286
|
+
}
|
|
287
|
+
|
|
227
288
|
private buildLinkToContent(node: HTMLElementNode, attribute: string, href: string | null, isInlineContent: boolean): string {
|
|
228
289
|
const args: string[] = []
|
|
229
290
|
|
|
@@ -255,7 +316,7 @@ export class HTMLToActionViewTagHelperRewriter extends ASTRewriter {
|
|
|
255
316
|
}
|
|
256
317
|
|
|
257
318
|
get description(): string {
|
|
258
|
-
return "Converts raw HTML elements to ActionView tag helpers (tag.*, turbo_frame_tag)"
|
|
319
|
+
return "Converts raw HTML elements to ActionView tag helpers (tag.*, turbo_frame_tag, javascript_tag, javascript_include_tag, image_tag)"
|
|
259
320
|
}
|
|
260
321
|
|
|
261
322
|
rewrite<T extends Node>(node: T, _context: RewriteContext): T {
|