@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.
- package/dist/index.cjs +238 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +238 -5
- package/dist/index.esm.js.map +1 -1
- package/dist/loader.cjs +239 -4
- package/dist/loader.cjs.map +1 -1
- package/dist/loader.esm.js +239 -5
- package/dist/loader.esm.js.map +1 -1
- package/dist/types/built-ins/erb-string-to-direct-output.d.ts +21 -0
- package/dist/types/built-ins/index.d.ts +2 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/rewriter-factories.d.ts +2 -0
- package/package.json +4 -4
- package/src/built-ins/erb-string-to-direct-output.ts +176 -0
- package/src/built-ins/index.ts +5 -0
- package/src/index.ts +2 -0
- package/src/rewriter-factories.ts +5 -0
package/dist/index.esm.js
CHANGED
|
@@ -203,7 +203,7 @@ class Token {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
206
|
-
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.
|
|
206
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.4/templates/javascript/packages/core/src/errors.ts.erb
|
|
207
207
|
class HerbError {
|
|
208
208
|
type;
|
|
209
209
|
message;
|
|
@@ -20382,7 +20382,7 @@ function deserializePrismNode(bytes, source) {
|
|
|
20382
20382
|
}
|
|
20383
20383
|
|
|
20384
20384
|
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
20385
|
-
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.
|
|
20385
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.4/templates/javascript/packages/core/src/nodes.ts.erb
|
|
20386
20386
|
class Node {
|
|
20387
20387
|
type;
|
|
20388
20388
|
location;
|
|
@@ -23651,7 +23651,7 @@ class ParseResult extends Result {
|
|
|
23651
23651
|
}
|
|
23652
23652
|
|
|
23653
23653
|
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
23654
|
-
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.
|
|
23654
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.4/templates/javascript/packages/core/src/node-type-guards.ts.erb
|
|
23655
23655
|
/**
|
|
23656
23656
|
* Type guard functions for AST nodes.
|
|
23657
23657
|
* These functions provide type checking by combining both instanceof
|
|
@@ -23994,6 +23994,31 @@ function isERBInNode(node) {
|
|
|
23994
23994
|
return false;
|
|
23995
23995
|
return node instanceof ERBInNode || node.type === "AST_ERB_IN_NODE" || node.constructor.type === "AST_ERB_IN_NODE";
|
|
23996
23996
|
}
|
|
23997
|
+
/**
|
|
23998
|
+
* Checks if a node is any ERB node type
|
|
23999
|
+
*/
|
|
24000
|
+
function isERBNode(node) {
|
|
24001
|
+
return isERBOpenTagNode(node) ||
|
|
24002
|
+
isERBContentNode(node) ||
|
|
24003
|
+
isERBEndNode(node) ||
|
|
24004
|
+
isERBElseNode(node) ||
|
|
24005
|
+
isERBIfNode(node) ||
|
|
24006
|
+
isERBBlockNode(node) ||
|
|
24007
|
+
isERBWhenNode(node) ||
|
|
24008
|
+
isERBCaseNode(node) ||
|
|
24009
|
+
isERBCaseMatchNode(node) ||
|
|
24010
|
+
isERBWhileNode(node) ||
|
|
24011
|
+
isERBUntilNode(node) ||
|
|
24012
|
+
isERBForNode(node) ||
|
|
24013
|
+
isERBRescueNode(node) ||
|
|
24014
|
+
isERBEnsureNode(node) ||
|
|
24015
|
+
isERBBeginNode(node) ||
|
|
24016
|
+
isERBUnlessNode(node) ||
|
|
24017
|
+
isERBRenderNode(node) ||
|
|
24018
|
+
isERBStrictLocalsNode(node) ||
|
|
24019
|
+
isERBYieldNode(node) ||
|
|
24020
|
+
isERBInNode(node);
|
|
24021
|
+
}
|
|
23997
24022
|
/**
|
|
23998
24023
|
* Map of node classes to their corresponding type guard functions
|
|
23999
24024
|
*
|
|
@@ -24156,6 +24181,17 @@ function isToken(object) {
|
|
|
24156
24181
|
function isParseResult(object) {
|
|
24157
24182
|
return (object instanceof ParseResult) || (object?.constructor?.name === "ParseResult" && "value" in object);
|
|
24158
24183
|
}
|
|
24184
|
+
|
|
24185
|
+
/**
|
|
24186
|
+
* Checks if a node is an ERB output node (generates content: <%= %> or <%== %>)
|
|
24187
|
+
*/
|
|
24188
|
+
function isERBOutputNode(node) {
|
|
24189
|
+
if (!isERBNode(node))
|
|
24190
|
+
return false;
|
|
24191
|
+
if (!node.tag_opening?.value)
|
|
24192
|
+
return false;
|
|
24193
|
+
return ["<%=", "<%=="].includes(node.tag_opening?.value);
|
|
24194
|
+
}
|
|
24159
24195
|
/**
|
|
24160
24196
|
* Extracts a static string from an array of literal nodes
|
|
24161
24197
|
* Returns null if any node is not a literal node
|
|
@@ -24215,12 +24251,88 @@ function getNodesBeforePosition(nodes, position, inclusive = false) {
|
|
|
24215
24251
|
function getNodesAfterPosition(nodes, position, inclusive = true) {
|
|
24216
24252
|
return nodes.filter(node => node.location && isPositionAfter(node.location.start, position, inclusive));
|
|
24217
24253
|
}
|
|
24254
|
+
// --- AST Mutation Utilities ---
|
|
24255
|
+
const CHILD_ARRAY_PROPS = ["children", "body", "statements", "conditions"];
|
|
24256
|
+
const LINKED_NODE_PROPS = ["subsequent", "else_clause"];
|
|
24257
|
+
/**
|
|
24258
|
+
* Finds the array containing a target node in the AST, along with its index.
|
|
24259
|
+
* Traverses child arrays and linked node properties (e.g., `subsequent`, `else_clause`).
|
|
24260
|
+
*
|
|
24261
|
+
* Useful for autofix operations that need to splice nodes in/out of their parent array.
|
|
24262
|
+
*
|
|
24263
|
+
* @param root - The root node to search from
|
|
24264
|
+
* @param target - The node to find
|
|
24265
|
+
* @returns The containing array and the target's index, or null if not found
|
|
24266
|
+
*/
|
|
24267
|
+
function findParentArray(root, target) {
|
|
24268
|
+
const search = (node) => {
|
|
24269
|
+
const record = node;
|
|
24270
|
+
for (const prop of CHILD_ARRAY_PROPS) {
|
|
24271
|
+
const array = record[prop];
|
|
24272
|
+
if (Array.isArray(array)) {
|
|
24273
|
+
const index = array.indexOf(target);
|
|
24274
|
+
if (index !== -1) {
|
|
24275
|
+
return { array, index };
|
|
24276
|
+
}
|
|
24277
|
+
}
|
|
24278
|
+
}
|
|
24279
|
+
for (const prop of CHILD_ARRAY_PROPS) {
|
|
24280
|
+
const array = record[prop];
|
|
24281
|
+
if (Array.isArray(array)) {
|
|
24282
|
+
for (const child of array) {
|
|
24283
|
+
if (child && typeof child === 'object' && 'type' in child) {
|
|
24284
|
+
const result = search(child);
|
|
24285
|
+
if (result) {
|
|
24286
|
+
return result;
|
|
24287
|
+
}
|
|
24288
|
+
}
|
|
24289
|
+
}
|
|
24290
|
+
}
|
|
24291
|
+
}
|
|
24292
|
+
for (const prop of LINKED_NODE_PROPS) {
|
|
24293
|
+
const value = record[prop];
|
|
24294
|
+
if (value && typeof value === 'object' && 'type' in value) {
|
|
24295
|
+
const result = search(value);
|
|
24296
|
+
if (result) {
|
|
24297
|
+
return result;
|
|
24298
|
+
}
|
|
24299
|
+
}
|
|
24300
|
+
}
|
|
24301
|
+
return null;
|
|
24302
|
+
};
|
|
24303
|
+
return search(root);
|
|
24304
|
+
}
|
|
24305
|
+
/**
|
|
24306
|
+
* Creates a synthetic LiteralNode with the given content and zero location.
|
|
24307
|
+
* Useful for inserting whitespace or newlines during AST mutations.
|
|
24308
|
+
*/
|
|
24309
|
+
function createLiteral(content) {
|
|
24310
|
+
return new LiteralNode({
|
|
24311
|
+
type: "AST_LITERAL_NODE",
|
|
24312
|
+
content,
|
|
24313
|
+
location: Location.zero,
|
|
24314
|
+
errors: [],
|
|
24315
|
+
});
|
|
24316
|
+
}
|
|
24218
24317
|
function createSyntheticToken(value, type = "TOKEN_SYNTHETIC") {
|
|
24219
24318
|
return new Token(value, Range.zero, Location.zero, type);
|
|
24220
24319
|
}
|
|
24320
|
+
function createERBOutputNode(expression, tagOpening = "<%=", tagClosing = "%>") {
|
|
24321
|
+
return new ERBContentNode({
|
|
24322
|
+
type: "AST_ERB_CONTENT_NODE",
|
|
24323
|
+
tag_opening: createSyntheticToken(tagOpening),
|
|
24324
|
+
content: createSyntheticToken(expression),
|
|
24325
|
+
tag_closing: createSyntheticToken(tagClosing),
|
|
24326
|
+
parsed: false,
|
|
24327
|
+
valid: true,
|
|
24328
|
+
prism_node: null,
|
|
24329
|
+
location: Location.zero,
|
|
24330
|
+
errors: [],
|
|
24331
|
+
});
|
|
24332
|
+
}
|
|
24221
24333
|
|
|
24222
24334
|
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
24223
|
-
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.
|
|
24335
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.4/templates/javascript/packages/core/src/visitor.ts.erb
|
|
24224
24336
|
class Visitor {
|
|
24225
24337
|
visit(node) {
|
|
24226
24338
|
if (!node)
|
|
@@ -24615,6 +24727,127 @@ class ActionViewTagHelperToHTMLRewriter extends ASTRewriter {
|
|
|
24615
24727
|
}
|
|
24616
24728
|
}
|
|
24617
24729
|
|
|
24730
|
+
const STRING_NODE_TYPE = "StringNode";
|
|
24731
|
+
const INTERPOLATED_STRING_NODE_TYPE = "InterpolatedStringNode";
|
|
24732
|
+
const EMBEDDED_STATEMENTS_NODE_TYPE = "EmbeddedStatementsNode";
|
|
24733
|
+
class ERBStringToDirectOutputVisitor extends Visitor {
|
|
24734
|
+
root;
|
|
24735
|
+
constructor(root) {
|
|
24736
|
+
super();
|
|
24737
|
+
this.root = root;
|
|
24738
|
+
}
|
|
24739
|
+
visitERBContentNode(node) {
|
|
24740
|
+
if (!isERBOutputNode(node)) {
|
|
24741
|
+
this.visitChildNodes(node);
|
|
24742
|
+
return;
|
|
24743
|
+
}
|
|
24744
|
+
const prismNode = node.prismNode;
|
|
24745
|
+
if (!prismNode) {
|
|
24746
|
+
this.visitChildNodes(node);
|
|
24747
|
+
return;
|
|
24748
|
+
}
|
|
24749
|
+
const source = node.source;
|
|
24750
|
+
if (!source) {
|
|
24751
|
+
this.visitChildNodes(node);
|
|
24752
|
+
return;
|
|
24753
|
+
}
|
|
24754
|
+
if (!ERBStringToDirectOutputRewriter.isStringOutputNode(prismNode)) {
|
|
24755
|
+
this.visitChildNodes(node);
|
|
24756
|
+
return;
|
|
24757
|
+
}
|
|
24758
|
+
const replacementParts = ERBStringToDirectOutputRewriter.extractReplacementParts(prismNode, source);
|
|
24759
|
+
if (!replacementParts) {
|
|
24760
|
+
this.visitChildNodes(node);
|
|
24761
|
+
return;
|
|
24762
|
+
}
|
|
24763
|
+
const tagOpening = node.tag_opening?.value ?? "<%=";
|
|
24764
|
+
const tagClosing = node.tag_closing?.value ?? "%>";
|
|
24765
|
+
const parentInfo = findParentArray(this.root, node);
|
|
24766
|
+
if (!parentInfo) {
|
|
24767
|
+
this.visitChildNodes(node);
|
|
24768
|
+
return;
|
|
24769
|
+
}
|
|
24770
|
+
const { array: parentArray, index: nodeIndex } = parentInfo;
|
|
24771
|
+
const replacementNodes = [];
|
|
24772
|
+
for (const part of replacementParts) {
|
|
24773
|
+
if (part.type === "text") {
|
|
24774
|
+
replacementNodes.push(createLiteral(part.content));
|
|
24775
|
+
}
|
|
24776
|
+
else {
|
|
24777
|
+
replacementNodes.push(createERBOutputNode(` ${part.expression.trim()} `, tagOpening, tagClosing));
|
|
24778
|
+
}
|
|
24779
|
+
}
|
|
24780
|
+
parentArray.splice(nodeIndex, 1, ...replacementNodes);
|
|
24781
|
+
}
|
|
24782
|
+
}
|
|
24783
|
+
class ERBStringToDirectOutputRewriter extends ASTRewriter {
|
|
24784
|
+
get name() {
|
|
24785
|
+
return "erb-string-to-direct-output";
|
|
24786
|
+
}
|
|
24787
|
+
get description() {
|
|
24788
|
+
return "Replaces ERB string output with direct text and expression tags";
|
|
24789
|
+
}
|
|
24790
|
+
rewrite(node, _context) {
|
|
24791
|
+
const visitor = new ERBStringToDirectOutputVisitor(node);
|
|
24792
|
+
visitor.visit(node);
|
|
24793
|
+
return node;
|
|
24794
|
+
}
|
|
24795
|
+
static isStringOutputNode(prismNode) {
|
|
24796
|
+
const nodeType = prismNode.constructor.name;
|
|
24797
|
+
return nodeType === STRING_NODE_TYPE || nodeType === INTERPOLATED_STRING_NODE_TYPE;
|
|
24798
|
+
}
|
|
24799
|
+
static extractStringContent(stringNode, source) {
|
|
24800
|
+
const unescapedValue = stringNode.unescaped?.value;
|
|
24801
|
+
if (typeof unescapedValue === "string") {
|
|
24802
|
+
return unescapedValue;
|
|
24803
|
+
}
|
|
24804
|
+
const location = stringNode.contentLoc;
|
|
24805
|
+
if (location) {
|
|
24806
|
+
return source.substring(location.startOffset, location.startOffset + location.length);
|
|
24807
|
+
}
|
|
24808
|
+
return "";
|
|
24809
|
+
}
|
|
24810
|
+
static extractExpressionSource(embeddedNode, source) {
|
|
24811
|
+
const openingLocation = embeddedNode.openingLoc;
|
|
24812
|
+
const closingLocation = embeddedNode.closingLoc;
|
|
24813
|
+
if (!openingLocation || !closingLocation)
|
|
24814
|
+
return null;
|
|
24815
|
+
const expressionStart = openingLocation.startOffset + openingLocation.length;
|
|
24816
|
+
const expressionEnd = closingLocation.startOffset;
|
|
24817
|
+
return source.substring(expressionStart, expressionEnd);
|
|
24818
|
+
}
|
|
24819
|
+
static extractReplacementParts(prismNode, source) {
|
|
24820
|
+
const nodeType = prismNode.constructor.name;
|
|
24821
|
+
if (nodeType === STRING_NODE_TYPE) {
|
|
24822
|
+
const textContent = this.extractStringContent(prismNode, source);
|
|
24823
|
+
return [{ type: "text", content: textContent }];
|
|
24824
|
+
}
|
|
24825
|
+
if (nodeType === INTERPOLATED_STRING_NODE_TYPE) {
|
|
24826
|
+
const parts = prismNode.parts;
|
|
24827
|
+
if (!parts || parts.length === 0)
|
|
24828
|
+
return null;
|
|
24829
|
+
const replacementParts = [];
|
|
24830
|
+
for (const part of parts) {
|
|
24831
|
+
const partType = part.constructor.name;
|
|
24832
|
+
if (partType === STRING_NODE_TYPE) {
|
|
24833
|
+
const textContent = this.extractStringContent(part, source);
|
|
24834
|
+
if (textContent) {
|
|
24835
|
+
replacementParts.push({ type: "text", content: textContent });
|
|
24836
|
+
}
|
|
24837
|
+
}
|
|
24838
|
+
else if (partType === EMBEDDED_STATEMENTS_NODE_TYPE) {
|
|
24839
|
+
const expression = this.extractExpressionSource(part, source);
|
|
24840
|
+
if (expression) {
|
|
24841
|
+
replacementParts.push({ type: "expression", expression });
|
|
24842
|
+
}
|
|
24843
|
+
}
|
|
24844
|
+
}
|
|
24845
|
+
return replacementParts.length > 0 ? replacementParts : null;
|
|
24846
|
+
}
|
|
24847
|
+
return null;
|
|
24848
|
+
}
|
|
24849
|
+
}
|
|
24850
|
+
|
|
24618
24851
|
function serializeAttributeValue(value) {
|
|
24619
24852
|
const hasERB = value.children.some(child => isERBContentNode(child));
|
|
24620
24853
|
if (hasERB && value.children.length === 1 && isERBContentNode(value.children[0])) {
|
|
@@ -25536,5 +25769,5 @@ function rewriteString(herb, template, rewriters, options = {}) {
|
|
|
25536
25769
|
return output;
|
|
25537
25770
|
}
|
|
25538
25771
|
|
|
25539
|
-
export { ASTRewriter, ActionViewTagHelperToHTMLRewriter, HTMLToActionViewTagHelperRewriter, StringRewriter, asMutable, isASTRewriterClass, isRewriterClass, isStringRewriterClass, rewrite, rewriteString };
|
|
25772
|
+
export { ASTRewriter, ActionViewTagHelperToHTMLRewriter, ERBStringToDirectOutputRewriter, HTMLToActionViewTagHelperRewriter, StringRewriter, asMutable, isASTRewriterClass, isRewriterClass, isStringRewriterClass, rewrite, rewriteString };
|
|
25540
25773
|
//# sourceMappingURL=index.esm.js.map
|