@herb-tools/rewriter 0.9.3 → 0.9.4

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.
@@ -0,0 +1,21 @@
1
+ import { ASTRewriter } from "../ast-rewriter.js";
2
+ import type { RewriteContext } from "../context.js";
3
+ import type { Node, PrismNode } from "@herb-tools/core";
4
+ export interface TextPart {
5
+ type: "text";
6
+ content: string;
7
+ }
8
+ export interface ExpressionPart {
9
+ type: "expression";
10
+ expression: string;
11
+ }
12
+ export type ReplacementPart = TextPart | ExpressionPart;
13
+ export declare class ERBStringToDirectOutputRewriter extends ASTRewriter {
14
+ get name(): string;
15
+ get description(): string;
16
+ rewrite<T extends Node>(node: T, _context: RewriteContext): T;
17
+ static isStringOutputNode(prismNode: PrismNode): boolean;
18
+ static extractStringContent(stringNode: PrismNode, source: string): string;
19
+ static extractExpressionSource(embeddedNode: PrismNode, source: string): string | null;
20
+ static extractReplacementParts(prismNode: PrismNode, source: string): ReplacementPart[] | null;
21
+ }
@@ -12,5 +12,7 @@ export declare function getBuiltinRewriter(name: string): RewriterClass | undefi
12
12
  */
13
13
  export declare function getBuiltinRewriterNames(): string[];
14
14
  export { ActionViewTagHelperToHTMLRewriter } from "./action-view-tag-helper-to-html.js";
15
+ export { ERBStringToDirectOutputRewriter } from "./erb-string-to-direct-output.js";
15
16
  export { HTMLToActionViewTagHelperRewriter } from "./html-to-action-view-tag-helper.js";
16
17
  export { TailwindClassSorterRewriter } from "./tailwind-class-sorter.js";
18
+ export type { TextPart, ExpressionPart, ReplacementPart } from "./erb-string-to-direct-output.js";
@@ -1,5 +1,6 @@
1
1
  export { ASTRewriter } from "./ast-rewriter.js";
2
2
  export { ActionViewTagHelperToHTMLRewriter } from "./built-ins/action-view-tag-helper-to-html.js";
3
+ export { ERBStringToDirectOutputRewriter } from "./built-ins/erb-string-to-direct-output.js";
3
4
  export { HTMLToActionViewTagHelperRewriter } from "./built-ins/html-to-action-view-tag-helper.js";
4
5
  export { StringRewriter } from "./string-rewriter.js";
5
6
  export { asMutable } from "./mutable.js";
@@ -10,3 +11,4 @@ export type { Mutable } from "./mutable.js";
10
11
  export type { RewriterClass } from "./type-guards.js";
11
12
  export type { Rewriter, RewriteOptions, RewriteResult } from "./rewrite.js";
12
13
  export type { TailwindClassSorterOptions } from "./rewriter-factories.js";
14
+ export type { TextPart, ExpressionPart, ReplacementPart } from "./built-ins/erb-string-to-direct-output.js";
@@ -1,5 +1,6 @@
1
1
  import { TailwindClassSorterRewriter } from "./built-ins/tailwind-class-sorter.js";
2
2
  import { ActionViewTagHelperToHTMLRewriter } from "./built-ins/action-view-tag-helper-to-html.js";
3
+ import { ERBStringToDirectOutputRewriter } from "./built-ins/erb-string-to-direct-output.js";
3
4
  import { HTMLToActionViewTagHelperRewriter } from "./built-ins/html-to-action-view-tag-helper.js";
4
5
  export interface TailwindClassSorterOptions {
5
6
  /**
@@ -29,4 +30,5 @@ export interface TailwindClassSorterOptions {
29
30
  */
30
31
  export declare function tailwindClassSorter(options?: TailwindClassSorterOptions): Promise<TailwindClassSorterRewriter>;
31
32
  export declare function actionViewTagHelperToHTML(): ActionViewTagHelperToHTMLRewriter;
33
+ export declare function erbStringToDirectOutput(): ERBStringToDirectOutputRewriter;
32
34
  export declare function htmlToActionViewTagHelper(): HTMLToActionViewTagHelperRewriter;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herb-tools/rewriter",
3
- "version": "0.9.3",
3
+ "version": "0.9.4",
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.3",
42
- "@herb-tools/tailwind-class-sorter": "0.9.3",
41
+ "@herb-tools/core": "0.9.4",
42
+ "@herb-tools/tailwind-class-sorter": "0.9.4",
43
43
  "tinyglobby": "^0.2.15"
44
44
  },
45
45
  "devDependencies": {
46
- "@herb-tools/printer": "0.9.3"
46
+ "@herb-tools/printer": "0.9.4"
47
47
  },
48
48
  "files": [
49
49
  "package.json",
@@ -0,0 +1,176 @@
1
+ import { Visitor, isERBOutputNode, createLiteral, createERBOutputNode, findParentArray } from "@herb-tools/core"
2
+
3
+ import { ASTRewriter } from "../ast-rewriter.js"
4
+
5
+ import type { RewriteContext } from "../context.js"
6
+ import type { Node, ERBContentNode, PrismNode } from "@herb-tools/core"
7
+
8
+ const STRING_NODE_TYPE = "StringNode"
9
+ const INTERPOLATED_STRING_NODE_TYPE = "InterpolatedStringNode"
10
+ const EMBEDDED_STATEMENTS_NODE_TYPE = "EmbeddedStatementsNode"
11
+
12
+ export interface TextPart {
13
+ type: "text"
14
+ content: string
15
+ }
16
+
17
+ export interface ExpressionPart {
18
+ type: "expression"
19
+ expression: string
20
+ }
21
+
22
+ export type ReplacementPart = TextPart | ExpressionPart
23
+
24
+ class ERBStringToDirectOutputVisitor extends Visitor {
25
+ private root: Node
26
+
27
+ constructor(root: Node) {
28
+ super()
29
+ this.root = root
30
+ }
31
+
32
+ visitERBContentNode(node: ERBContentNode): void {
33
+ if (!isERBOutputNode(node)) {
34
+ this.visitChildNodes(node)
35
+ return
36
+ }
37
+
38
+ const prismNode = node.prismNode
39
+ if (!prismNode) {
40
+ this.visitChildNodes(node)
41
+ return
42
+ }
43
+
44
+ const source = node.source
45
+ if (!source) {
46
+ this.visitChildNodes(node)
47
+ return
48
+ }
49
+
50
+ if (!ERBStringToDirectOutputRewriter.isStringOutputNode(prismNode)) {
51
+ this.visitChildNodes(node)
52
+ return
53
+ }
54
+
55
+ const replacementParts = ERBStringToDirectOutputRewriter.extractReplacementParts(prismNode, source)
56
+
57
+ if (!replacementParts) {
58
+ this.visitChildNodes(node)
59
+ return
60
+ }
61
+
62
+ const tagOpening = node.tag_opening?.value ?? "<%="
63
+ const tagClosing = node.tag_closing?.value ?? "%>"
64
+
65
+ const parentInfo = findParentArray(this.root, node)
66
+
67
+ if (!parentInfo) {
68
+ this.visitChildNodes(node)
69
+ return
70
+ }
71
+
72
+ const { array: parentArray, index: nodeIndex } = parentInfo
73
+ const replacementNodes: Node[] = []
74
+
75
+ for (const part of replacementParts) {
76
+ if (part.type === "text") {
77
+ replacementNodes.push(createLiteral(part.content))
78
+ } else {
79
+ replacementNodes.push(createERBOutputNode(` ${part.expression.trim()} `, tagOpening, tagClosing))
80
+ }
81
+ }
82
+
83
+ parentArray.splice(nodeIndex, 1, ...replacementNodes)
84
+ }
85
+ }
86
+
87
+ export class ERBStringToDirectOutputRewriter extends ASTRewriter {
88
+ get name(): string {
89
+ return "erb-string-to-direct-output"
90
+ }
91
+
92
+ get description(): string {
93
+ return "Replaces ERB string output with direct text and expression tags"
94
+ }
95
+
96
+ rewrite<T extends Node>(node: T, _context: RewriteContext): T {
97
+ const visitor = new ERBStringToDirectOutputVisitor(node)
98
+
99
+ visitor.visit(node)
100
+
101
+ return node
102
+ }
103
+
104
+ static isStringOutputNode(prismNode: PrismNode): boolean {
105
+ const nodeType = prismNode.constructor.name
106
+
107
+ return nodeType === STRING_NODE_TYPE || nodeType === INTERPOLATED_STRING_NODE_TYPE
108
+ }
109
+
110
+ static extractStringContent(stringNode: PrismNode, source: string): string {
111
+ const unescapedValue = stringNode.unescaped?.value
112
+
113
+ if (typeof unescapedValue === "string") {
114
+ return unescapedValue
115
+ }
116
+
117
+ const location = stringNode.contentLoc
118
+
119
+ if (location) {
120
+ return source.substring(location.startOffset, location.startOffset + location.length)
121
+ }
122
+
123
+ return ""
124
+ }
125
+
126
+ static extractExpressionSource(embeddedNode: PrismNode, source: string): string | null {
127
+ const openingLocation = embeddedNode.openingLoc
128
+ const closingLocation = embeddedNode.closingLoc
129
+
130
+ if (!openingLocation || !closingLocation) return null
131
+
132
+ const expressionStart = openingLocation.startOffset + openingLocation.length
133
+ const expressionEnd = closingLocation.startOffset
134
+
135
+ return source.substring(expressionStart, expressionEnd)
136
+ }
137
+
138
+ static extractReplacementParts(prismNode: PrismNode, source: string): ReplacementPart[] | null {
139
+ const nodeType = prismNode.constructor.name
140
+
141
+ if (nodeType === STRING_NODE_TYPE) {
142
+ const textContent = this.extractStringContent(prismNode, source)
143
+ return [{ type: "text", content: textContent }]
144
+ }
145
+
146
+ if (nodeType === INTERPOLATED_STRING_NODE_TYPE) {
147
+ const parts = prismNode.parts
148
+
149
+ if (!parts || parts.length === 0) return null
150
+
151
+ const replacementParts: ReplacementPart[] = []
152
+
153
+ for (const part of parts) {
154
+ const partType = part.constructor.name
155
+
156
+ if (partType === STRING_NODE_TYPE) {
157
+ const textContent = this.extractStringContent(part, source)
158
+
159
+ if (textContent) {
160
+ replacementParts.push({ type: "text", content: textContent })
161
+ }
162
+ } else if (partType === EMBEDDED_STATEMENTS_NODE_TYPE) {
163
+ const expression = this.extractExpressionSource(part, source)
164
+
165
+ if (expression) {
166
+ replacementParts.push({ type: "expression", expression })
167
+ }
168
+ }
169
+ }
170
+
171
+ return replacementParts.length > 0 ? replacementParts : null
172
+ }
173
+
174
+ return null
175
+ }
176
+ }
@@ -1,4 +1,5 @@
1
1
  import { ActionViewTagHelperToHTMLRewriter } from "./action-view-tag-helper-to-html.js"
2
+ import { ERBStringToDirectOutputRewriter } from "./erb-string-to-direct-output.js"
2
3
  import { HTMLToActionViewTagHelperRewriter } from "./html-to-action-view-tag-helper.js"
3
4
  import { TailwindClassSorterRewriter } from "./tailwind-class-sorter.js"
4
5
 
@@ -9,6 +10,7 @@ import type { RewriterClass } from "../type-guards.js"
9
10
  */
10
11
  export const builtinRewriters: RewriterClass[] = [
11
12
  ActionViewTagHelperToHTMLRewriter,
13
+ ERBStringToDirectOutputRewriter,
12
14
  HTMLToActionViewTagHelperRewriter,
13
15
  TailwindClassSorterRewriter
14
16
  ]
@@ -36,5 +38,8 @@ export function getBuiltinRewriterNames(): string[] {
36
38
  }
37
39
 
38
40
  export { ActionViewTagHelperToHTMLRewriter } from "./action-view-tag-helper-to-html.js"
41
+ export { ERBStringToDirectOutputRewriter } from "./erb-string-to-direct-output.js"
39
42
  export { HTMLToActionViewTagHelperRewriter } from "./html-to-action-view-tag-helper.js"
40
43
  export { TailwindClassSorterRewriter } from "./tailwind-class-sorter.js"
44
+
45
+ export type { TextPart, ExpressionPart, ReplacementPart } from "./erb-string-to-direct-output.js"
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { ASTRewriter } from "./ast-rewriter.js"
2
2
  export { ActionViewTagHelperToHTMLRewriter } from "./built-ins/action-view-tag-helper-to-html.js"
3
+ export { ERBStringToDirectOutputRewriter } from "./built-ins/erb-string-to-direct-output.js"
3
4
  export { HTMLToActionViewTagHelperRewriter } from "./built-ins/html-to-action-view-tag-helper.js"
4
5
  export { StringRewriter } from "./string-rewriter.js"
5
6
 
@@ -13,3 +14,4 @@ export type { Mutable } from "./mutable.js"
13
14
  export type { RewriterClass } from "./type-guards.js"
14
15
  export type { Rewriter, RewriteOptions, RewriteResult } from "./rewrite.js"
15
16
  export type { TailwindClassSorterOptions } from "./rewriter-factories.js"
17
+ export type { TextPart, ExpressionPart, ReplacementPart } from "./built-ins/erb-string-to-direct-output.js"
@@ -1,5 +1,6 @@
1
1
  import { TailwindClassSorterRewriter } from "./built-ins/tailwind-class-sorter.js"
2
2
  import { ActionViewTagHelperToHTMLRewriter } from "./built-ins/action-view-tag-helper-to-html.js"
3
+ import { ERBStringToDirectOutputRewriter } from "./built-ins/erb-string-to-direct-output.js"
3
4
  import { HTMLToActionViewTagHelperRewriter } from "./built-ins/html-to-action-view-tag-helper.js"
4
5
 
5
6
  export interface TailwindClassSorterOptions {
@@ -42,6 +43,10 @@ export function actionViewTagHelperToHTML(): ActionViewTagHelperToHTMLRewriter {
42
43
  return new ActionViewTagHelperToHTMLRewriter()
43
44
  }
44
45
 
46
+ export function erbStringToDirectOutput(): ERBStringToDirectOutputRewriter {
47
+ return new ERBStringToDirectOutputRewriter()
48
+ }
49
+
45
50
  export function htmlToActionViewTagHelper(): HTMLToActionViewTagHelperRewriter {
46
51
  return new HTMLToActionViewTagHelperRewriter()
47
52
  }