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