@herb-tools/rewriter 0.8.9 → 0.9.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/dist/index.cjs +20936 -380
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +20935 -381
- package/dist/index.esm.js.map +1 -1
- package/dist/loader.cjs +20843 -448
- package/dist/loader.cjs.map +1 -1
- package/dist/loader.esm.js +20842 -449
- package/dist/loader.esm.js.map +1 -1
- package/dist/types/built-ins/action-view-tag-helper-to-html.d.ts +8 -0
- package/dist/types/built-ins/html-to-action-view-tag-helper.d.ts +8 -0
- package/dist/types/built-ins/index.d.ts +3 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/rewriter-factories.d.ts +4 -0
- package/package.json +4 -4
- package/src/built-ins/action-view-tag-helper-to-html.ts +179 -0
- package/src/built-ins/html-to-action-view-tag-helper.ts +268 -0
- package/src/built-ins/index.ts +10 -3
- package/src/built-ins/tailwind-class-sorter.ts +157 -64
- package/src/index.ts +2 -0
- package/src/rewriter-factories.ts +10 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ASTRewriter } from "../ast-rewriter.js";
|
|
2
|
+
import type { RewriteContext } from "../context.js";
|
|
3
|
+
import type { Node } from "@herb-tools/core";
|
|
4
|
+
export declare class ActionViewTagHelperToHTMLRewriter extends ASTRewriter {
|
|
5
|
+
get name(): string;
|
|
6
|
+
get description(): string;
|
|
7
|
+
rewrite<T extends Node>(node: T, _context: RewriteContext): T;
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ASTRewriter } from "../ast-rewriter.js";
|
|
2
|
+
import type { RewriteContext } from "../context.js";
|
|
3
|
+
import type { Node } from "@herb-tools/core";
|
|
4
|
+
export declare class HTMLToActionViewTagHelperRewriter extends ASTRewriter {
|
|
5
|
+
get name(): string;
|
|
6
|
+
get description(): string;
|
|
7
|
+
rewrite<T extends Node>(node: T, _context: RewriteContext): T;
|
|
8
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { RewriterClass } from "../type-guards.js";
|
|
2
|
-
export { TailwindClassSorterRewriter } from "./tailwind-class-sorter.js";
|
|
3
2
|
/**
|
|
4
3
|
* All built-in rewriters available in the package
|
|
5
4
|
*/
|
|
@@ -12,3 +11,6 @@ export declare function getBuiltinRewriter(name: string): RewriterClass | undefi
|
|
|
12
11
|
* Get all built-in rewriter names
|
|
13
12
|
*/
|
|
14
13
|
export declare function getBuiltinRewriterNames(): string[];
|
|
14
|
+
export { ActionViewTagHelperToHTMLRewriter } from "./action-view-tag-helper-to-html.js";
|
|
15
|
+
export { HTMLToActionViewTagHelperRewriter } from "./html-to-action-view-tag-helper.js";
|
|
16
|
+
export { TailwindClassSorterRewriter } from "./tailwind-class-sorter.js";
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { ASTRewriter } from "./ast-rewriter.js";
|
|
2
|
+
export { ActionViewTagHelperToHTMLRewriter } from "./built-ins/action-view-tag-helper-to-html.js";
|
|
3
|
+
export { HTMLToActionViewTagHelperRewriter } from "./built-ins/html-to-action-view-tag-helper.js";
|
|
2
4
|
export { StringRewriter } from "./string-rewriter.js";
|
|
3
5
|
export { asMutable } from "./mutable.js";
|
|
4
6
|
export { isASTRewriterClass, isStringRewriterClass, isRewriterClass } from "./type-guards.js";
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { TailwindClassSorterRewriter } from "./built-ins/tailwind-class-sorter.js";
|
|
2
|
+
import { ActionViewTagHelperToHTMLRewriter } from "./built-ins/action-view-tag-helper-to-html.js";
|
|
3
|
+
import { HTMLToActionViewTagHelperRewriter } from "./built-ins/html-to-action-view-tag-helper.js";
|
|
2
4
|
export interface TailwindClassSorterOptions {
|
|
3
5
|
/**
|
|
4
6
|
* Base directory for resolving Tailwind configuration
|
|
@@ -26,3 +28,5 @@ export interface TailwindClassSorterOptions {
|
|
|
26
28
|
* @returns A configured and initialized TailwindClassSorterRewriter instance
|
|
27
29
|
*/
|
|
28
30
|
export declare function tailwindClassSorter(options?: TailwindClassSorterOptions): Promise<TailwindClassSorterRewriter>;
|
|
31
|
+
export declare function actionViewTagHelperToHTML(): ActionViewTagHelperToHTMLRewriter;
|
|
32
|
+
export declare function htmlToActionViewTagHelper(): HTMLToActionViewTagHelperRewriter;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herb-tools/rewriter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
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.
|
|
42
|
-
"@herb-tools/tailwind-class-sorter": "0.
|
|
41
|
+
"@herb-tools/core": "0.9.0",
|
|
42
|
+
"@herb-tools/tailwind-class-sorter": "0.9.0",
|
|
43
43
|
"tinyglobby": "^0.2.15"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@herb-tools/printer": "0.
|
|
46
|
+
"@herb-tools/printer": "0.9.0"
|
|
47
47
|
},
|
|
48
48
|
"files": [
|
|
49
49
|
"package.json",
|
|
@@ -0,0 +1,179 @@
|
|
|
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"
|
|
3
|
+
|
|
4
|
+
import { ASTRewriter } from "../ast-rewriter.js"
|
|
5
|
+
import { asMutable } from "../mutable.js"
|
|
6
|
+
|
|
7
|
+
import type { RewriteContext } from "../context.js"
|
|
8
|
+
import type { Node } from "@herb-tools/core"
|
|
9
|
+
|
|
10
|
+
function createWhitespaceNode(): WhitespaceNode {
|
|
11
|
+
return new WhitespaceNode({
|
|
12
|
+
type: "AST_WHITESPACE_NODE",
|
|
13
|
+
location: Location.zero,
|
|
14
|
+
errors: [],
|
|
15
|
+
value: createSyntheticToken(" "),
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class ActionViewTagHelperToHTMLVisitor extends Visitor {
|
|
20
|
+
visitHTMLElementNode(node: HTMLElementNode): void {
|
|
21
|
+
if (!node.element_source) {
|
|
22
|
+
this.visitChildNodes(node)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const openTag = node.open_tag
|
|
27
|
+
|
|
28
|
+
if (!isERBOpenTagNode(openTag)) {
|
|
29
|
+
this.visitChildNodes(node)
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const tagName = openTag.tag_name
|
|
34
|
+
|
|
35
|
+
if (!tagName) {
|
|
36
|
+
this.visitChildNodes(node)
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const htmlChildren: Node[] = []
|
|
41
|
+
|
|
42
|
+
for (const child of openTag.children) {
|
|
43
|
+
if (isRubyHTMLAttributesSplatNode(child)) {
|
|
44
|
+
htmlChildren.push(createWhitespaceNode())
|
|
45
|
+
|
|
46
|
+
htmlChildren.push(new ERBContentNode({
|
|
47
|
+
type: "AST_ERB_CONTENT_NODE",
|
|
48
|
+
location: Location.zero,
|
|
49
|
+
errors: [],
|
|
50
|
+
tag_opening: createSyntheticToken("<%="),
|
|
51
|
+
content: createSyntheticToken(` ${child.content} `),
|
|
52
|
+
tag_closing: createSyntheticToken("%>"),
|
|
53
|
+
parsed: false,
|
|
54
|
+
valid: true,
|
|
55
|
+
prism_node: null,
|
|
56
|
+
}))
|
|
57
|
+
|
|
58
|
+
continue
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
htmlChildren.push(createWhitespaceNode())
|
|
62
|
+
|
|
63
|
+
if (isHTMLAttributeNode(child)) {
|
|
64
|
+
if (child.equals && child.equals.value !== "=") {
|
|
65
|
+
asMutable(child).equals = createSyntheticToken("=")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (child.value) {
|
|
69
|
+
this.transformAttributeValue(child.value)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
htmlChildren.push(child)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const htmlOpenTag = new HTMLOpenTagNode({
|
|
77
|
+
type: "AST_HTML_OPEN_TAG_NODE",
|
|
78
|
+
location: openTag.location,
|
|
79
|
+
errors: [],
|
|
80
|
+
tag_opening: createSyntheticToken("<"),
|
|
81
|
+
tag_name: createSyntheticToken(tagName.value),
|
|
82
|
+
tag_closing: createSyntheticToken(node.is_void ? " />" : ">"),
|
|
83
|
+
children: htmlChildren,
|
|
84
|
+
is_void: node.is_void,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
asMutable(node).open_tag = htmlOpenTag
|
|
88
|
+
|
|
89
|
+
if (node.is_void) {
|
|
90
|
+
asMutable(node).close_tag = null
|
|
91
|
+
} else if (node.close_tag) {
|
|
92
|
+
const htmlCloseTag = new HTMLCloseTagNode({
|
|
93
|
+
type: "AST_HTML_CLOSE_TAG_NODE",
|
|
94
|
+
location: node.close_tag.location,
|
|
95
|
+
errors: [],
|
|
96
|
+
tag_opening: createSyntheticToken("</"),
|
|
97
|
+
tag_name: createSyntheticToken(tagName.value),
|
|
98
|
+
children: [],
|
|
99
|
+
tag_closing: createSyntheticToken(">"),
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
asMutable(node).close_tag = htmlCloseTag
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
asMutable(node).element_source = "HTML"
|
|
106
|
+
|
|
107
|
+
if (node.body) {
|
|
108
|
+
asMutable(node).body = node.body.map(child => {
|
|
109
|
+
if (isRubyLiteralNode(child)) {
|
|
110
|
+
return new ERBContentNode({
|
|
111
|
+
type: "AST_ERB_CONTENT_NODE",
|
|
112
|
+
location: child.location,
|
|
113
|
+
errors: [],
|
|
114
|
+
tag_opening: createSyntheticToken("<%="),
|
|
115
|
+
content: createSyntheticToken(` ${child.content} `),
|
|
116
|
+
tag_closing: createSyntheticToken("%>"),
|
|
117
|
+
parsed: false,
|
|
118
|
+
valid: true,
|
|
119
|
+
prism_node: null
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.visit(child)
|
|
124
|
+
return child
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private transformAttributeValue(value: HTMLAttributeValueNode): void {
|
|
130
|
+
const mutableValue = asMutable(value)
|
|
131
|
+
const hasRubyLiteral = value.children.some(child => isRubyLiteralNode(child))
|
|
132
|
+
|
|
133
|
+
if (hasRubyLiteral) {
|
|
134
|
+
const newChildren: Node[] = value.children.map(child => {
|
|
135
|
+
if (isRubyLiteralNode(child)) {
|
|
136
|
+
return new ERBContentNode({
|
|
137
|
+
type: "AST_ERB_CONTENT_NODE",
|
|
138
|
+
location: child.location,
|
|
139
|
+
errors: [],
|
|
140
|
+
tag_opening: createSyntheticToken("<%="),
|
|
141
|
+
content: createSyntheticToken(` ${child.content} `),
|
|
142
|
+
tag_closing: createSyntheticToken("%>"),
|
|
143
|
+
parsed: false,
|
|
144
|
+
valid: true,
|
|
145
|
+
prism_node: null,
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return child
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
mutableValue.children = newChildren
|
|
153
|
+
|
|
154
|
+
if (!value.quoted) {
|
|
155
|
+
mutableValue.quoted = true
|
|
156
|
+
mutableValue.open_quote = createSyntheticToken('"')
|
|
157
|
+
mutableValue.close_quote = createSyntheticToken('"')
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export class ActionViewTagHelperToHTMLRewriter extends ASTRewriter {
|
|
164
|
+
get name(): string {
|
|
165
|
+
return "action-view-tag-helper-to-html"
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
get description(): string {
|
|
169
|
+
return "Converts ActionView tag helpers (tag.*, content_tag, link_to, turbo_frame_tag) to raw HTML elements"
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
rewrite<T extends Node>(node: T, _context: RewriteContext): T {
|
|
173
|
+
const visitor = new ActionViewTagHelperToHTMLVisitor()
|
|
174
|
+
|
|
175
|
+
visitor.visit(node)
|
|
176
|
+
|
|
177
|
+
return node
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { Visitor, Location, ERBOpenTagNode, ERBEndNode, HTMLElementNode, HTMLVirtualCloseTagNode, createSyntheticToken } from "@herb-tools/core"
|
|
2
|
+
import { getStaticAttributeName, isLiteralNode, isHTMLOpenTagNode, isHTMLTextNode, isHTMLAttributeNode, isERBContentNode, isWhitespaceNode } from "@herb-tools/core"
|
|
3
|
+
|
|
4
|
+
import { ASTRewriter } from "../ast-rewriter.js"
|
|
5
|
+
import { asMutable } from "../mutable.js"
|
|
6
|
+
|
|
7
|
+
import type { RewriteContext } from "../context.js"
|
|
8
|
+
import type { Node, HTMLAttributeValueNode } from "@herb-tools/core"
|
|
9
|
+
|
|
10
|
+
function serializeAttributeValue(value: HTMLAttributeValueNode): string {
|
|
11
|
+
const hasERB = value.children.some(child => isERBContentNode(child))
|
|
12
|
+
|
|
13
|
+
if (hasERB && value.children.length === 1 && isERBContentNode(value.children[0])) {
|
|
14
|
+
return value.children[0].content?.value?.trim() ?? '""'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const parts: string[] = []
|
|
18
|
+
|
|
19
|
+
for (const child of value.children) {
|
|
20
|
+
if (isLiteralNode(child)) {
|
|
21
|
+
parts.push(child.content)
|
|
22
|
+
} else if (isERBContentNode(child)) {
|
|
23
|
+
parts.push(`#{${child.content?.value?.trim() ?? ""}}`)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return `"${parts.join("")}"`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function dashToUnderscore(string: string): string {
|
|
31
|
+
return string.replace(/-/g, "_")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface SerializedAttributes {
|
|
35
|
+
attributes: string
|
|
36
|
+
href: string | null
|
|
37
|
+
id: string | null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function serializeAttributes(children: Node[], options: { extractHref?: boolean, extractId?: boolean } = {}): SerializedAttributes {
|
|
41
|
+
const regular: string[] = []
|
|
42
|
+
const prefixed: Map<string, string[]> = new Map()
|
|
43
|
+
|
|
44
|
+
let href: string | null = null
|
|
45
|
+
let id: string | null = null
|
|
46
|
+
|
|
47
|
+
for (const child of children) {
|
|
48
|
+
if (!isHTMLAttributeNode(child)) continue
|
|
49
|
+
|
|
50
|
+
const name = getStaticAttributeName(child.name!)
|
|
51
|
+
if (!name) continue
|
|
52
|
+
|
|
53
|
+
const value = child.value ? serializeAttributeValue(child.value) : "true"
|
|
54
|
+
|
|
55
|
+
if (options.extractHref && name === "href") {
|
|
56
|
+
href = value
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (options.extractId && name === "id") {
|
|
61
|
+
id = value
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const dataMatch = name.match(/^(data|aria)-(.+)$/)
|
|
66
|
+
|
|
67
|
+
if (dataMatch) {
|
|
68
|
+
const [, prefix, rest] = dataMatch
|
|
69
|
+
|
|
70
|
+
if (!prefixed.has(prefix)) {
|
|
71
|
+
prefixed.set(prefix, [])
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
prefixed.get(prefix)!.push(`${dashToUnderscore(rest)}: ${value}`)
|
|
75
|
+
} else {
|
|
76
|
+
regular.push(`${name}: ${value}`)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const parts = [...regular]
|
|
81
|
+
|
|
82
|
+
for (const [prefix, entries] of prefixed) {
|
|
83
|
+
parts.push(`${prefix}: { ${entries.join(", ")} }`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { attributes: parts.join(", "), href, id }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function isTextOnlyBody(body: Node[]): boolean {
|
|
90
|
+
if (body.length !== 1 || !isHTMLTextNode(body[0])) return false
|
|
91
|
+
|
|
92
|
+
return !body[0].content.includes("\n")
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
class HTMLToActionViewTagHelperVisitor extends Visitor {
|
|
96
|
+
visitHTMLElementNode(node: HTMLElementNode): void {
|
|
97
|
+
const openTag = node.open_tag
|
|
98
|
+
|
|
99
|
+
if (!isHTMLOpenTagNode(openTag)) {
|
|
100
|
+
this.visitChildNodes(node)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const tagName = openTag.tag_name
|
|
105
|
+
|
|
106
|
+
if (!tagName) {
|
|
107
|
+
this.visitChildNodes(node)
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (node.body) {
|
|
112
|
+
for (const child of node.body) {
|
|
113
|
+
this.visit(child)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const isAnchor = tagName.value === "a"
|
|
118
|
+
const isTurboFrame = tagName.value === "turbo-frame"
|
|
119
|
+
const attributes = openTag.children.filter(child => !isWhitespaceNode(child))
|
|
120
|
+
const { attributes: attributesString, href, id } = serializeAttributes(attributes, { extractHref: isAnchor, extractId: isTurboFrame })
|
|
121
|
+
const hasBody = node.body && node.body.length > 0 && !node.is_void
|
|
122
|
+
const isInlineContent = hasBody && isTextOnlyBody(node.body)
|
|
123
|
+
|
|
124
|
+
let content: string
|
|
125
|
+
let elementSource: string
|
|
126
|
+
|
|
127
|
+
if (isAnchor) {
|
|
128
|
+
content = this.buildLinkToContent(node, attributesString, href, isInlineContent)
|
|
129
|
+
elementSource = "ActionView::Helpers::UrlHelper#link_to"
|
|
130
|
+
} else if (isTurboFrame) {
|
|
131
|
+
content = this.buildTurboFrameTagContent(node, attributesString, id, isInlineContent)
|
|
132
|
+
elementSource = "Turbo::FramesHelper#turbo_frame_tag"
|
|
133
|
+
} else {
|
|
134
|
+
content = this.buildTagContent(tagName.value, node, attributesString, isInlineContent)
|
|
135
|
+
elementSource = "ActionView::Helpers::TagHelper#tag"
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const erbOpenTag = new ERBOpenTagNode({
|
|
139
|
+
type: "AST_ERB_OPEN_TAG_NODE",
|
|
140
|
+
location: openTag.location,
|
|
141
|
+
errors: [],
|
|
142
|
+
tag_opening: createSyntheticToken("<%="),
|
|
143
|
+
content: createSyntheticToken(content),
|
|
144
|
+
tag_closing: createSyntheticToken("%>"),
|
|
145
|
+
tag_name: createSyntheticToken(tagName.value),
|
|
146
|
+
children: [],
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
asMutable(node).open_tag = erbOpenTag
|
|
150
|
+
asMutable(node).element_source = elementSource
|
|
151
|
+
|
|
152
|
+
const isInlineForm = isInlineContent || (isTurboFrame && !hasBody)
|
|
153
|
+
|
|
154
|
+
if (node.is_void) {
|
|
155
|
+
asMutable(node).close_tag = null
|
|
156
|
+
} else if (isInlineForm) {
|
|
157
|
+
asMutable(node).body = []
|
|
158
|
+
|
|
159
|
+
const virtualClose = new HTMLVirtualCloseTagNode({
|
|
160
|
+
type: "AST_HTML_VIRTUAL_CLOSE_TAG_NODE",
|
|
161
|
+
location: Location.zero,
|
|
162
|
+
errors: [],
|
|
163
|
+
tag_name: createSyntheticToken(tagName.value),
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
asMutable(node).close_tag = virtualClose
|
|
167
|
+
} else if (node.close_tag) {
|
|
168
|
+
const erbEnd = new ERBEndNode({
|
|
169
|
+
type: "AST_ERB_END_NODE",
|
|
170
|
+
location: node.close_tag.location,
|
|
171
|
+
errors: [],
|
|
172
|
+
tag_opening: createSyntheticToken("<%"),
|
|
173
|
+
content: createSyntheticToken(" end "),
|
|
174
|
+
tag_closing: createSyntheticToken("%>"),
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
asMutable(node).close_tag = erbEnd
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private buildTagContent(tag: string, node: HTMLElementNode, attributes: string, isInlineContent: boolean): string {
|
|
182
|
+
const methodName = dashToUnderscore(tag)
|
|
183
|
+
|
|
184
|
+
if (node.is_void) {
|
|
185
|
+
return attributes
|
|
186
|
+
? ` tag.${methodName} ${attributes} `
|
|
187
|
+
: ` tag.${methodName} `
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (isInlineContent && isHTMLTextNode(node.body[0])) {
|
|
191
|
+
const textContent = node.body[0].content
|
|
192
|
+
|
|
193
|
+
return attributes
|
|
194
|
+
? ` tag.${methodName} "${textContent}", ${attributes} `
|
|
195
|
+
: ` tag.${methodName} "${textContent}" `
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return attributes
|
|
199
|
+
? ` tag.${methodName} ${attributes} do `
|
|
200
|
+
: ` tag.${methodName} do `
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private buildTurboFrameTagContent(node: HTMLElementNode, attributes: string, id: string | null, isInlineContent: boolean): string {
|
|
204
|
+
const args: string[] = []
|
|
205
|
+
|
|
206
|
+
if (id) {
|
|
207
|
+
args.push(id)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (isInlineContent && isHTMLTextNode(node.body[0])) {
|
|
211
|
+
args.push(`"${node.body[0].content}"`)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (attributes) {
|
|
215
|
+
args.push(attributes)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const argString = args.join(", ")
|
|
219
|
+
|
|
220
|
+
if (isInlineContent || !node.body || node.body.length === 0) {
|
|
221
|
+
return argString ? ` turbo_frame_tag ${argString} ` : ` turbo_frame_tag `
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return argString ? ` turbo_frame_tag ${argString} do ` : ` turbo_frame_tag do `
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private buildLinkToContent(node: HTMLElementNode, attribute: string, href: string | null, isInlineContent: boolean): string {
|
|
228
|
+
const args: string[] = []
|
|
229
|
+
|
|
230
|
+
if (isInlineContent && isHTMLTextNode(node.body[0])) {
|
|
231
|
+
args.push(`"${node.body[0].content}"`)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (href) {
|
|
235
|
+
args.push(href)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (attribute) {
|
|
239
|
+
args.push(attribute)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const argString = args.join(", ")
|
|
243
|
+
|
|
244
|
+
if (isInlineContent) {
|
|
245
|
+
return argString ? ` link_to ${argString} ` : ` link_to `
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return argString ? ` link_to ${argString} do ` : ` link_to do `
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export class HTMLToActionViewTagHelperRewriter extends ASTRewriter {
|
|
253
|
+
get name(): string {
|
|
254
|
+
return "html-to-action-view-tag-helper"
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
get description(): string {
|
|
258
|
+
return "Converts raw HTML elements to ActionView tag helpers (tag.*, turbo_frame_tag)"
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
rewrite<T extends Node>(node: T, _context: RewriteContext): T {
|
|
262
|
+
const visitor = new HTMLToActionViewTagHelperVisitor()
|
|
263
|
+
|
|
264
|
+
visitor.visit(node)
|
|
265
|
+
|
|
266
|
+
return node
|
|
267
|
+
}
|
|
268
|
+
}
|
package/src/built-ins/index.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
export { TailwindClassSorterRewriter } from "./tailwind-class-sorter.js"
|
|
1
|
+
import { ActionViewTagHelperToHTMLRewriter } from "./action-view-tag-helper-to-html.js"
|
|
2
|
+
import { HTMLToActionViewTagHelperRewriter } from "./html-to-action-view-tag-helper.js"
|
|
4
3
|
import { TailwindClassSorterRewriter } from "./tailwind-class-sorter.js"
|
|
5
4
|
|
|
5
|
+
import type { RewriterClass } from "../type-guards.js"
|
|
6
|
+
|
|
6
7
|
/**
|
|
7
8
|
* All built-in rewriters available in the package
|
|
8
9
|
*/
|
|
9
10
|
export const builtinRewriters: RewriterClass[] = [
|
|
11
|
+
ActionViewTagHelperToHTMLRewriter,
|
|
12
|
+
HTMLToActionViewTagHelperRewriter,
|
|
10
13
|
TailwindClassSorterRewriter
|
|
11
14
|
]
|
|
12
15
|
|
|
@@ -31,3 +34,7 @@ export function getBuiltinRewriterNames(): string[] {
|
|
|
31
34
|
return instance.name
|
|
32
35
|
})
|
|
33
36
|
}
|
|
37
|
+
|
|
38
|
+
export { ActionViewTagHelperToHTMLRewriter } from "./action-view-tag-helper-to-html.js"
|
|
39
|
+
export { HTMLToActionViewTagHelperRewriter } from "./html-to-action-view-tag-helper.js"
|
|
40
|
+
export { TailwindClassSorterRewriter } from "./tailwind-class-sorter.js"
|