@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.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.3/templates/javascript/packages/core/src/errors.ts.erb
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.3/templates/javascript/packages/core/src/nodes.ts.erb
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.3/templates/javascript/packages/core/src/node-type-guards.ts.erb
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.3/templates/javascript/packages/core/src/visitor.ts.erb
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