@herb-tools/rewriter 0.9.3 → 0.9.5

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.5",
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.5",
42
+ "@herb-tools/tailwind-class-sorter": "0.9.5",
43
43
  "tinyglobby": "^0.2.15"
44
44
  },
45
45
  "devDependencies": {
46
- "@herb-tools/printer": "0.9.3"
46
+ "@herb-tools/printer": "0.9.5"
47
47
  },
48
48
  "files": [
49
49
  "package.json",
@@ -0,0 +1,170 @@
1
+ import { Visitor, isERBOutputNode, createLiteral, createERBOutputNode, findParentArray, isPrismNodeType } 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
+ return isPrismNodeType(prismNode, STRING_NODE_TYPE) || isPrismNodeType(prismNode, INTERPOLATED_STRING_NODE_TYPE)
106
+ }
107
+
108
+ static extractStringContent(stringNode: PrismNode, source: string): string {
109
+ const unescapedValue = stringNode.unescaped?.value
110
+
111
+ if (typeof unescapedValue === "string") {
112
+ return unescapedValue
113
+ }
114
+
115
+ const location = stringNode.contentLoc
116
+
117
+ if (location) {
118
+ return source.substring(location.startOffset, location.startOffset + location.length)
119
+ }
120
+
121
+ return ""
122
+ }
123
+
124
+ static extractExpressionSource(embeddedNode: PrismNode, source: string): string | null {
125
+ const openingLocation = embeddedNode.openingLoc
126
+ const closingLocation = embeddedNode.closingLoc
127
+
128
+ if (!openingLocation || !closingLocation) return null
129
+
130
+ const expressionStart = openingLocation.startOffset + openingLocation.length
131
+ const expressionEnd = closingLocation.startOffset
132
+
133
+ return source.substring(expressionStart, expressionEnd)
134
+ }
135
+
136
+ static extractReplacementParts(prismNode: PrismNode, source: string): ReplacementPart[] | null {
137
+ if (isPrismNodeType(prismNode, STRING_NODE_TYPE)) {
138
+ const textContent = this.extractStringContent(prismNode, source)
139
+ return [{ type: "text", content: textContent }]
140
+ }
141
+
142
+ if (isPrismNodeType(prismNode, INTERPOLATED_STRING_NODE_TYPE)) {
143
+ const parts = prismNode.parts
144
+
145
+ if (!parts || parts.length === 0) return null
146
+
147
+ const replacementParts: ReplacementPart[] = []
148
+
149
+ for (const part of parts) {
150
+ if (isPrismNodeType(part, STRING_NODE_TYPE)) {
151
+ const textContent = this.extractStringContent(part, source)
152
+
153
+ if (textContent) {
154
+ replacementParts.push({ type: "text", content: textContent })
155
+ }
156
+ } else if (isPrismNodeType(part, EMBEDDED_STATEMENTS_NODE_TYPE)) {
157
+ const expression = this.extractExpressionSource(part, source)
158
+
159
+ if (expression) {
160
+ replacementParts.push({ type: "expression", expression })
161
+ }
162
+ }
163
+ }
164
+
165
+ return replacementParts.length > 0 ? replacementParts : null
166
+ }
167
+
168
+ return null
169
+ }
170
+ }
@@ -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
  }