@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/loader.esm.js
CHANGED
|
@@ -207,7 +207,7 @@ class Token {
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
210
|
-
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.
|
|
210
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.4/templates/javascript/packages/core/src/errors.ts.erb
|
|
211
211
|
class HerbError {
|
|
212
212
|
type;
|
|
213
213
|
message;
|
|
@@ -20386,7 +20386,7 @@ function deserializePrismNode(bytes, source) {
|
|
|
20386
20386
|
}
|
|
20387
20387
|
|
|
20388
20388
|
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
20389
|
-
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.
|
|
20389
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.4/templates/javascript/packages/core/src/nodes.ts.erb
|
|
20390
20390
|
class Node {
|
|
20391
20391
|
type;
|
|
20392
20392
|
location;
|
|
@@ -23655,7 +23655,7 @@ class ParseResult extends Result {
|
|
|
23655
23655
|
}
|
|
23656
23656
|
|
|
23657
23657
|
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
23658
|
-
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.
|
|
23658
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.4/templates/javascript/packages/core/src/node-type-guards.ts.erb
|
|
23659
23659
|
/**
|
|
23660
23660
|
* Type guard functions for AST nodes.
|
|
23661
23661
|
* These functions provide type checking by combining both instanceof
|
|
@@ -23998,6 +23998,31 @@ function isERBInNode(node) {
|
|
|
23998
23998
|
return false;
|
|
23999
23999
|
return node instanceof ERBInNode || node.type === "AST_ERB_IN_NODE" || node.constructor.type === "AST_ERB_IN_NODE";
|
|
24000
24000
|
}
|
|
24001
|
+
/**
|
|
24002
|
+
* Checks if a node is any ERB node type
|
|
24003
|
+
*/
|
|
24004
|
+
function isERBNode(node) {
|
|
24005
|
+
return isERBOpenTagNode(node) ||
|
|
24006
|
+
isERBContentNode(node) ||
|
|
24007
|
+
isERBEndNode(node) ||
|
|
24008
|
+
isERBElseNode(node) ||
|
|
24009
|
+
isERBIfNode(node) ||
|
|
24010
|
+
isERBBlockNode(node) ||
|
|
24011
|
+
isERBWhenNode(node) ||
|
|
24012
|
+
isERBCaseNode(node) ||
|
|
24013
|
+
isERBCaseMatchNode(node) ||
|
|
24014
|
+
isERBWhileNode(node) ||
|
|
24015
|
+
isERBUntilNode(node) ||
|
|
24016
|
+
isERBForNode(node) ||
|
|
24017
|
+
isERBRescueNode(node) ||
|
|
24018
|
+
isERBEnsureNode(node) ||
|
|
24019
|
+
isERBBeginNode(node) ||
|
|
24020
|
+
isERBUnlessNode(node) ||
|
|
24021
|
+
isERBRenderNode(node) ||
|
|
24022
|
+
isERBStrictLocalsNode(node) ||
|
|
24023
|
+
isERBYieldNode(node) ||
|
|
24024
|
+
isERBInNode(node);
|
|
24025
|
+
}
|
|
24001
24026
|
/**
|
|
24002
24027
|
* Map of node classes to their corresponding type guard functions
|
|
24003
24028
|
*
|
|
@@ -24160,6 +24185,17 @@ function isToken(object) {
|
|
|
24160
24185
|
function isParseResult(object) {
|
|
24161
24186
|
return (object instanceof ParseResult) || (object?.constructor?.name === "ParseResult" && "value" in object);
|
|
24162
24187
|
}
|
|
24188
|
+
|
|
24189
|
+
/**
|
|
24190
|
+
* Checks if a node is an ERB output node (generates content: <%= %> or <%== %>)
|
|
24191
|
+
*/
|
|
24192
|
+
function isERBOutputNode(node) {
|
|
24193
|
+
if (!isERBNode(node))
|
|
24194
|
+
return false;
|
|
24195
|
+
if (!node.tag_opening?.value)
|
|
24196
|
+
return false;
|
|
24197
|
+
return ["<%=", "<%=="].includes(node.tag_opening?.value);
|
|
24198
|
+
}
|
|
24163
24199
|
/**
|
|
24164
24200
|
* Extracts a static string from an array of literal nodes
|
|
24165
24201
|
* Returns null if any node is not a literal node
|
|
@@ -24324,12 +24360,88 @@ function getNodesBeforePosition(nodes, position, inclusive = false) {
|
|
|
24324
24360
|
function getNodesAfterPosition(nodes, position, inclusive = true) {
|
|
24325
24361
|
return nodes.filter(node => node.location && isPositionAfter(node.location.start, position, inclusive));
|
|
24326
24362
|
}
|
|
24363
|
+
// --- AST Mutation Utilities ---
|
|
24364
|
+
const CHILD_ARRAY_PROPS = ["children", "body", "statements", "conditions"];
|
|
24365
|
+
const LINKED_NODE_PROPS = ["subsequent", "else_clause"];
|
|
24366
|
+
/**
|
|
24367
|
+
* Finds the array containing a target node in the AST, along with its index.
|
|
24368
|
+
* Traverses child arrays and linked node properties (e.g., `subsequent`, `else_clause`).
|
|
24369
|
+
*
|
|
24370
|
+
* Useful for autofix operations that need to splice nodes in/out of their parent array.
|
|
24371
|
+
*
|
|
24372
|
+
* @param root - The root node to search from
|
|
24373
|
+
* @param target - The node to find
|
|
24374
|
+
* @returns The containing array and the target's index, or null if not found
|
|
24375
|
+
*/
|
|
24376
|
+
function findParentArray(root, target) {
|
|
24377
|
+
const search = (node) => {
|
|
24378
|
+
const record = node;
|
|
24379
|
+
for (const prop of CHILD_ARRAY_PROPS) {
|
|
24380
|
+
const array = record[prop];
|
|
24381
|
+
if (Array.isArray(array)) {
|
|
24382
|
+
const index = array.indexOf(target);
|
|
24383
|
+
if (index !== -1) {
|
|
24384
|
+
return { array, index };
|
|
24385
|
+
}
|
|
24386
|
+
}
|
|
24387
|
+
}
|
|
24388
|
+
for (const prop of CHILD_ARRAY_PROPS) {
|
|
24389
|
+
const array = record[prop];
|
|
24390
|
+
if (Array.isArray(array)) {
|
|
24391
|
+
for (const child of array) {
|
|
24392
|
+
if (child && typeof child === 'object' && 'type' in child) {
|
|
24393
|
+
const result = search(child);
|
|
24394
|
+
if (result) {
|
|
24395
|
+
return result;
|
|
24396
|
+
}
|
|
24397
|
+
}
|
|
24398
|
+
}
|
|
24399
|
+
}
|
|
24400
|
+
}
|
|
24401
|
+
for (const prop of LINKED_NODE_PROPS) {
|
|
24402
|
+
const value = record[prop];
|
|
24403
|
+
if (value && typeof value === 'object' && 'type' in value) {
|
|
24404
|
+
const result = search(value);
|
|
24405
|
+
if (result) {
|
|
24406
|
+
return result;
|
|
24407
|
+
}
|
|
24408
|
+
}
|
|
24409
|
+
}
|
|
24410
|
+
return null;
|
|
24411
|
+
};
|
|
24412
|
+
return search(root);
|
|
24413
|
+
}
|
|
24414
|
+
/**
|
|
24415
|
+
* Creates a synthetic LiteralNode with the given content and zero location.
|
|
24416
|
+
* Useful for inserting whitespace or newlines during AST mutations.
|
|
24417
|
+
*/
|
|
24418
|
+
function createLiteral(content) {
|
|
24419
|
+
return new LiteralNode({
|
|
24420
|
+
type: "AST_LITERAL_NODE",
|
|
24421
|
+
content,
|
|
24422
|
+
location: Location.zero,
|
|
24423
|
+
errors: [],
|
|
24424
|
+
});
|
|
24425
|
+
}
|
|
24327
24426
|
function createSyntheticToken(value, type = "TOKEN_SYNTHETIC") {
|
|
24328
24427
|
return new Token(value, Range.zero, Location.zero, type);
|
|
24329
24428
|
}
|
|
24429
|
+
function createERBOutputNode(expression, tagOpening = "<%=", tagClosing = "%>") {
|
|
24430
|
+
return new ERBContentNode({
|
|
24431
|
+
type: "AST_ERB_CONTENT_NODE",
|
|
24432
|
+
tag_opening: createSyntheticToken(tagOpening),
|
|
24433
|
+
content: createSyntheticToken(expression),
|
|
24434
|
+
tag_closing: createSyntheticToken(tagClosing),
|
|
24435
|
+
parsed: false,
|
|
24436
|
+
valid: true,
|
|
24437
|
+
prism_node: null,
|
|
24438
|
+
location: Location.zero,
|
|
24439
|
+
errors: [],
|
|
24440
|
+
});
|
|
24441
|
+
}
|
|
24330
24442
|
|
|
24331
24443
|
// NOTE: This file is generated by the templates/template.rb script and should not
|
|
24332
|
-
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.
|
|
24444
|
+
// be modified manually. See /Users/marcoroth/Development/herb-release-0.9.4/templates/javascript/packages/core/src/visitor.ts.erb
|
|
24333
24445
|
class Visitor {
|
|
24334
24446
|
visit(node) {
|
|
24335
24447
|
if (!node)
|
|
@@ -24724,6 +24836,127 @@ class ActionViewTagHelperToHTMLRewriter extends ASTRewriter {
|
|
|
24724
24836
|
}
|
|
24725
24837
|
}
|
|
24726
24838
|
|
|
24839
|
+
const STRING_NODE_TYPE = "StringNode";
|
|
24840
|
+
const INTERPOLATED_STRING_NODE_TYPE = "InterpolatedStringNode";
|
|
24841
|
+
const EMBEDDED_STATEMENTS_NODE_TYPE = "EmbeddedStatementsNode";
|
|
24842
|
+
class ERBStringToDirectOutputVisitor extends Visitor {
|
|
24843
|
+
root;
|
|
24844
|
+
constructor(root) {
|
|
24845
|
+
super();
|
|
24846
|
+
this.root = root;
|
|
24847
|
+
}
|
|
24848
|
+
visitERBContentNode(node) {
|
|
24849
|
+
if (!isERBOutputNode(node)) {
|
|
24850
|
+
this.visitChildNodes(node);
|
|
24851
|
+
return;
|
|
24852
|
+
}
|
|
24853
|
+
const prismNode = node.prismNode;
|
|
24854
|
+
if (!prismNode) {
|
|
24855
|
+
this.visitChildNodes(node);
|
|
24856
|
+
return;
|
|
24857
|
+
}
|
|
24858
|
+
const source = node.source;
|
|
24859
|
+
if (!source) {
|
|
24860
|
+
this.visitChildNodes(node);
|
|
24861
|
+
return;
|
|
24862
|
+
}
|
|
24863
|
+
if (!ERBStringToDirectOutputRewriter.isStringOutputNode(prismNode)) {
|
|
24864
|
+
this.visitChildNodes(node);
|
|
24865
|
+
return;
|
|
24866
|
+
}
|
|
24867
|
+
const replacementParts = ERBStringToDirectOutputRewriter.extractReplacementParts(prismNode, source);
|
|
24868
|
+
if (!replacementParts) {
|
|
24869
|
+
this.visitChildNodes(node);
|
|
24870
|
+
return;
|
|
24871
|
+
}
|
|
24872
|
+
const tagOpening = node.tag_opening?.value ?? "<%=";
|
|
24873
|
+
const tagClosing = node.tag_closing?.value ?? "%>";
|
|
24874
|
+
const parentInfo = findParentArray(this.root, node);
|
|
24875
|
+
if (!parentInfo) {
|
|
24876
|
+
this.visitChildNodes(node);
|
|
24877
|
+
return;
|
|
24878
|
+
}
|
|
24879
|
+
const { array: parentArray, index: nodeIndex } = parentInfo;
|
|
24880
|
+
const replacementNodes = [];
|
|
24881
|
+
for (const part of replacementParts) {
|
|
24882
|
+
if (part.type === "text") {
|
|
24883
|
+
replacementNodes.push(createLiteral(part.content));
|
|
24884
|
+
}
|
|
24885
|
+
else {
|
|
24886
|
+
replacementNodes.push(createERBOutputNode(` ${part.expression.trim()} `, tagOpening, tagClosing));
|
|
24887
|
+
}
|
|
24888
|
+
}
|
|
24889
|
+
parentArray.splice(nodeIndex, 1, ...replacementNodes);
|
|
24890
|
+
}
|
|
24891
|
+
}
|
|
24892
|
+
class ERBStringToDirectOutputRewriter extends ASTRewriter {
|
|
24893
|
+
get name() {
|
|
24894
|
+
return "erb-string-to-direct-output";
|
|
24895
|
+
}
|
|
24896
|
+
get description() {
|
|
24897
|
+
return "Replaces ERB string output with direct text and expression tags";
|
|
24898
|
+
}
|
|
24899
|
+
rewrite(node, _context) {
|
|
24900
|
+
const visitor = new ERBStringToDirectOutputVisitor(node);
|
|
24901
|
+
visitor.visit(node);
|
|
24902
|
+
return node;
|
|
24903
|
+
}
|
|
24904
|
+
static isStringOutputNode(prismNode) {
|
|
24905
|
+
const nodeType = prismNode.constructor.name;
|
|
24906
|
+
return nodeType === STRING_NODE_TYPE || nodeType === INTERPOLATED_STRING_NODE_TYPE;
|
|
24907
|
+
}
|
|
24908
|
+
static extractStringContent(stringNode, source) {
|
|
24909
|
+
const unescapedValue = stringNode.unescaped?.value;
|
|
24910
|
+
if (typeof unescapedValue === "string") {
|
|
24911
|
+
return unescapedValue;
|
|
24912
|
+
}
|
|
24913
|
+
const location = stringNode.contentLoc;
|
|
24914
|
+
if (location) {
|
|
24915
|
+
return source.substring(location.startOffset, location.startOffset + location.length);
|
|
24916
|
+
}
|
|
24917
|
+
return "";
|
|
24918
|
+
}
|
|
24919
|
+
static extractExpressionSource(embeddedNode, source) {
|
|
24920
|
+
const openingLocation = embeddedNode.openingLoc;
|
|
24921
|
+
const closingLocation = embeddedNode.closingLoc;
|
|
24922
|
+
if (!openingLocation || !closingLocation)
|
|
24923
|
+
return null;
|
|
24924
|
+
const expressionStart = openingLocation.startOffset + openingLocation.length;
|
|
24925
|
+
const expressionEnd = closingLocation.startOffset;
|
|
24926
|
+
return source.substring(expressionStart, expressionEnd);
|
|
24927
|
+
}
|
|
24928
|
+
static extractReplacementParts(prismNode, source) {
|
|
24929
|
+
const nodeType = prismNode.constructor.name;
|
|
24930
|
+
if (nodeType === STRING_NODE_TYPE) {
|
|
24931
|
+
const textContent = this.extractStringContent(prismNode, source);
|
|
24932
|
+
return [{ type: "text", content: textContent }];
|
|
24933
|
+
}
|
|
24934
|
+
if (nodeType === INTERPOLATED_STRING_NODE_TYPE) {
|
|
24935
|
+
const parts = prismNode.parts;
|
|
24936
|
+
if (!parts || parts.length === 0)
|
|
24937
|
+
return null;
|
|
24938
|
+
const replacementParts = [];
|
|
24939
|
+
for (const part of parts) {
|
|
24940
|
+
const partType = part.constructor.name;
|
|
24941
|
+
if (partType === STRING_NODE_TYPE) {
|
|
24942
|
+
const textContent = this.extractStringContent(part, source);
|
|
24943
|
+
if (textContent) {
|
|
24944
|
+
replacementParts.push({ type: "text", content: textContent });
|
|
24945
|
+
}
|
|
24946
|
+
}
|
|
24947
|
+
else if (partType === EMBEDDED_STATEMENTS_NODE_TYPE) {
|
|
24948
|
+
const expression = this.extractExpressionSource(part, source);
|
|
24949
|
+
if (expression) {
|
|
24950
|
+
replacementParts.push({ type: "expression", expression });
|
|
24951
|
+
}
|
|
24952
|
+
}
|
|
24953
|
+
}
|
|
24954
|
+
return replacementParts.length > 0 ? replacementParts : null;
|
|
24955
|
+
}
|
|
24956
|
+
return null;
|
|
24957
|
+
}
|
|
24958
|
+
}
|
|
24959
|
+
|
|
24727
24960
|
function serializeAttributeValue(value) {
|
|
24728
24961
|
const hasERB = value.children.some(child => isERBContentNode(child));
|
|
24729
24962
|
if (hasERB && value.children.length === 1 && isERBContentNode(value.children[0])) {
|
|
@@ -26065,6 +26298,7 @@ class TailwindClassSorterRewriter extends ASTRewriter {
|
|
|
26065
26298
|
*/
|
|
26066
26299
|
const builtinRewriters = [
|
|
26067
26300
|
ActionViewTagHelperToHTMLRewriter,
|
|
26301
|
+
ERBStringToDirectOutputRewriter,
|
|
26068
26302
|
HTMLToActionViewTagHelperRewriter,
|
|
26069
26303
|
TailwindClassSorterRewriter
|
|
26070
26304
|
];
|
|
@@ -26113,5 +26347,5 @@ async function tailwindClassSorter(options = {}) {
|
|
|
26113
26347
|
return rewriter;
|
|
26114
26348
|
}
|
|
26115
26349
|
|
|
26116
|
-
export { ASTRewriter, ActionViewTagHelperToHTMLRewriter, CustomRewriterLoader, HTMLToActionViewTagHelperRewriter, StringRewriter, TailwindClassSorterRewriter, asMutable, builtinRewriters, getBuiltinRewriter, getBuiltinRewriterNames, isASTRewriterClass, isRewriterClass, isStringRewriterClass, rewrite, rewriteString, tailwindClassSorter };
|
|
26350
|
+
export { ASTRewriter, ActionViewTagHelperToHTMLRewriter, CustomRewriterLoader, ERBStringToDirectOutputRewriter, HTMLToActionViewTagHelperRewriter, StringRewriter, TailwindClassSorterRewriter, asMutable, builtinRewriters, getBuiltinRewriter, getBuiltinRewriterNames, isASTRewriterClass, isRewriterClass, isStringRewriterClass, rewrite, rewriteString, tailwindClassSorter };
|
|
26117
26351
|
//# sourceMappingURL=loader.esm.js.map
|