@herb-tools/core 0.8.10 → 0.9.1

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.
Files changed (39) hide show
  1. package/dist/herb-core.browser.js +22728 -320
  2. package/dist/herb-core.browser.js.map +1 -1
  3. package/dist/herb-core.cjs +22815 -321
  4. package/dist/herb-core.cjs.map +1 -1
  5. package/dist/herb-core.esm.js +22728 -320
  6. package/dist/herb-core.esm.js.map +1 -1
  7. package/dist/herb-core.umd.js +22815 -321
  8. package/dist/herb-core.umd.js.map +1 -1
  9. package/dist/types/ast-utils.d.ts +185 -4
  10. package/dist/types/backend.d.ts +6 -6
  11. package/dist/types/diagnostic.d.ts +6 -0
  12. package/dist/types/errors.d.ts +390 -25
  13. package/dist/types/extract-ruby-options.d.ts +6 -0
  14. package/dist/types/herb-backend.d.ts +15 -7
  15. package/dist/types/index.d.ts +2 -0
  16. package/dist/types/node-type-guards.d.ts +113 -32
  17. package/dist/types/nodes.d.ts +465 -49
  18. package/dist/types/parse-result.d.ts +7 -1
  19. package/dist/types/parser-options.d.ts +33 -2
  20. package/dist/types/prism/index.d.ts +28 -0
  21. package/dist/types/prism/inspect.d.ts +3 -0
  22. package/dist/types/util.d.ts +0 -1
  23. package/dist/types/visitor.d.ts +19 -1
  24. package/package.json +4 -1
  25. package/src/ast-utils.ts +564 -8
  26. package/src/backend.ts +7 -7
  27. package/src/diagnostic.ts +7 -0
  28. package/src/errors.ts +1221 -76
  29. package/src/extract-ruby-options.ts +11 -0
  30. package/src/herb-backend.ts +30 -15
  31. package/src/index.ts +2 -0
  32. package/src/node-type-guards.ts +281 -33
  33. package/src/nodes.ts +1309 -100
  34. package/src/parse-result.ts +11 -0
  35. package/src/parser-options.ts +62 -2
  36. package/src/prism/index.ts +44 -0
  37. package/src/prism/inspect.ts +118 -0
  38. package/src/util.ts +0 -12
  39. package/src/visitor.ts +66 -1
@@ -1,5 +1,6 @@
1
- import { Node, ERBNode, ERBContentNode, HTMLElementNode, HTMLOpenTagNode, HTMLCloseTagNode, HTMLAttributeNameNode, HTMLCommentNode } from "./nodes.js";
2
- import type { Location } from "./location.js";
1
+ import { Node, LiteralNode, ERBNode, ERBContentNode, HTMLElementNode, HTMLOpenTagNode, HTMLCloseTagNode, HTMLAttributeNode, HTMLAttributeNameNode, HTMLAttributeValueNode, HTMLCommentNode, WhitespaceNode } from "./nodes.js";
2
+ import { Location } from "./location.js";
3
+ import { Token } from "./token.js";
3
4
  import type { Position } from "./position.js";
4
5
  export type ERBOutputNode = ERBNode & {
5
6
  tag_opening: {
@@ -68,7 +69,7 @@ export declare function hasStaticAttributeName(attributeNameNode: HTMLAttributeN
68
69
  /**
69
70
  * Checks if an HTML attribute name node has dynamic content (contains ERB)
70
71
  */
71
- export declare function hasDynamicAttributeName(attributeNameNode: HTMLAttributeNameNode): boolean;
72
+ export declare function hasDynamicAttributeNameNode(attributeNameNode: HTMLAttributeNameNode): boolean;
72
73
  /**
73
74
  * Gets the static string value of an HTML attribute name node
74
75
  * Returns null if the attribute name contains dynamic content (ERB)
@@ -80,13 +81,148 @@ export declare function getStaticAttributeName(attributeNameNode: HTMLAttributeN
80
81
  */
81
82
  export declare function getCombinedAttributeName(attributeNameNode: HTMLAttributeNameNode): string;
82
83
  /**
83
- * Gets the tag name of an HTML element node
84
+ * Gets the tag name of an HTML element, open tag, or close tag node.
85
+ * Returns null if the node is null/undefined.
84
86
  */
85
87
  export declare function getTagName(node: HTMLElementNode | HTMLOpenTagNode | HTMLCloseTagNode): string;
88
+ export declare function getTagName(node: HTMLElementNode | HTMLOpenTagNode | HTMLCloseTagNode | null | undefined): string | null;
89
+ /**
90
+ * Gets the lowercased tag name of an HTML element, open tag, or close tag node.
91
+ * Similar to `Element.localName` in the DOM API.
92
+ * Returns null if the node is null/undefined.
93
+ */
94
+ export declare function getTagLocalName(node: HTMLElementNode | HTMLOpenTagNode | HTMLCloseTagNode): string;
95
+ export declare function getTagLocalName(node: HTMLElementNode | HTMLOpenTagNode | HTMLCloseTagNode | null | undefined): string | null;
86
96
  /**
87
97
  * Check if a node is a comment (HTML comment or ERB comment)
88
98
  */
89
99
  export declare function isCommentNode(node: Node): node is HTMLCommentNode | ERBCommentNode;
100
+ /**
101
+ * Gets the open tag node from an HTMLElementNode, handling both regular and conditional open tags.
102
+ * For conditional open tags, returns null.
103
+ * If given an HTMLOpenTagNode directly, returns it as-is.
104
+ */
105
+ export declare function getOpenTag(node: HTMLElementNode | HTMLOpenTagNode | null | undefined): HTMLOpenTagNode | null;
106
+ /**
107
+ * Gets attributes from an HTMLElementNode or HTMLOpenTagNode
108
+ */
109
+ export declare function getAttributes(node: HTMLElementNode | HTMLOpenTagNode | null | undefined): HTMLAttributeNode[];
110
+ /**
111
+ * Gets the attribute name from an HTMLAttributeNode (lowercased)
112
+ * Returns null if the attribute name contains dynamic content (ERB)
113
+ */
114
+ export declare function getAttributeName(attributeNode: HTMLAttributeNode, lowercase?: boolean): string | null;
115
+ /**
116
+ * Checks if an attribute value contains only static content (no ERB).
117
+ * Accepts an HTMLAttributeNode directly, or an element/open tag + attribute name.
118
+ * Returns false for null/undefined input.
119
+ */
120
+ export declare function hasStaticAttributeValue(attributeNode: HTMLAttributeNode | null | undefined): boolean;
121
+ export declare function hasStaticAttributeValue(node: HTMLElementNode | HTMLOpenTagNode | null | undefined, attributeName: string): boolean;
122
+ /**
123
+ * Gets the static string value of an attribute (returns null if it contains ERB).
124
+ * Accepts an HTMLAttributeNode directly, or an element/open tag + attribute name.
125
+ * Returns null for null/undefined input.
126
+ */
127
+ export declare function getStaticAttributeValue(attributeNode: HTMLAttributeNode | null | undefined): string | null;
128
+ export declare function getStaticAttributeValue(node: HTMLElementNode | HTMLOpenTagNode | null | undefined, attributeName: string): string | null;
129
+ /**
130
+ * Attributes whose values are space-separated token lists.
131
+ */
132
+ export declare const TOKEN_LIST_ATTRIBUTES: Set<string>;
133
+ /**
134
+ * Splits a space-separated attribute value into individual tokens.
135
+ * Accepts a string, or an element/open tag + attribute name to look up.
136
+ * Returns an empty array for null/undefined/empty input.
137
+ */
138
+ export declare function getTokenList(value: string | null | undefined): string[];
139
+ export declare function getTokenList(node: HTMLElementNode | HTMLOpenTagNode | null | undefined, attributeName: string): string[];
140
+ /**
141
+ * Finds an attribute by name in a list of attribute nodes
142
+ */
143
+ export declare function findAttributeByName(attributes: Node[], attributeName: string): HTMLAttributeNode | null;
144
+ /**
145
+ * Gets a specific attribute from an HTMLElementNode or HTMLOpenTagNode by name
146
+ */
147
+ export declare function getAttribute(node: HTMLElementNode | HTMLOpenTagNode | null | undefined, attributeName: string): HTMLAttributeNode | null;
148
+ /**
149
+ * Checks if an element or open tag has a specific attribute
150
+ */
151
+ export declare function hasAttribute(node: HTMLElementNode | HTMLOpenTagNode | null | undefined, attributeName: string): boolean;
152
+ /**
153
+ * Checks if an attribute has a dynamic (ERB-containing) name.
154
+ */
155
+ export declare function hasDynamicAttributeName(attributeNode: HTMLAttributeNode): boolean;
156
+ /**
157
+ * Gets the combined string representation of an attribute name (including ERB syntax).
158
+ * Accepts an HTMLAttributeNode (wraps the core HTMLAttributeNameNode-level check).
159
+ */
160
+ export declare function getCombinedAttributeNameString(attributeNode: HTMLAttributeNode): string;
161
+ /**
162
+ * Checks if an attribute value contains dynamic content (ERB)
163
+ */
164
+ export declare function hasDynamicAttributeValue(attributeNode: HTMLAttributeNode): boolean;
165
+ /**
166
+ * Gets the value nodes array from an attribute for dynamic inspection
167
+ */
168
+ export declare function getAttributeValueNodes(attributeNode: HTMLAttributeNode): Node[];
169
+ /**
170
+ * Checks if an attribute value contains any static content (for validation purposes)
171
+ */
172
+ export declare function hasStaticAttributeValueContent(attributeNode: HTMLAttributeNode): boolean;
173
+ /**
174
+ * Gets the static content of an attribute value (all literal parts combined).
175
+ * Unlike getStaticAttributeValue, this extracts only the static portions from mixed content.
176
+ * Returns the concatenated literal content, or null if no literal nodes exist.
177
+ */
178
+ export declare function getStaticAttributeValueContent(attributeNode: HTMLAttributeNode): string | null;
179
+ /**
180
+ * Gets the combined attribute value including both static text and ERB tag syntax.
181
+ * For ERB nodes, includes the full tag syntax (e.g., "<%= foo %>").
182
+ * Returns null if the attribute has no value.
183
+ */
184
+ export declare function getAttributeValue(attributeNode: HTMLAttributeNode): string | null;
185
+ /**
186
+ * Checks if an attribute has a value node
187
+ */
188
+ export declare function hasAttributeValue(attributeNode: HTMLAttributeNode): boolean;
189
+ /**
190
+ * Gets the quote type used for an attribute value
191
+ */
192
+ export declare function getAttributeValueQuoteType(node: HTMLAttributeNode | HTMLAttributeValueNode): "single" | "double" | "none" | null;
193
+ /**
194
+ * Checks if an attribute value is quoted
195
+ */
196
+ export declare function isAttributeValueQuoted(attributeNode: HTMLAttributeNode): boolean;
197
+ /**
198
+ * Iterates over all attributes of an element or open tag node
199
+ */
200
+ export declare function forEachAttribute(node: HTMLElementNode | HTMLOpenTagNode, callback: (attributeNode: HTMLAttributeNode) => void): void;
201
+ /**
202
+ * Checks if a node is a whitespace-only literal or text node (no visible content)
203
+ */
204
+ export declare function isPureWhitespaceNode(node: Node): boolean;
205
+ /**
206
+ * Splits literal nodes at whitespace boundaries into separate nodes.
207
+ * Non-literal nodes are passed through unchanged.
208
+ *
209
+ * For example, a literal `"bg-blue-500 text-white"` becomes two literals:
210
+ * `"bg-blue-500"` and `" "` and `"text-white"`.
211
+ */
212
+ export declare function splitLiteralsAtWhitespace(nodes: Node[]): Node[];
213
+ /**
214
+ * Groups split nodes into "class groups" where each group represents a single
215
+ * class name (possibly spanning multiple nodes when ERB is interpolated).
216
+ *
217
+ * For example, `text-<%= color %>-500 bg-blue-500` produces two groups:
218
+ * - [`text-`, ERB, `-500`] (interpolated class)
219
+ * - [`bg-blue-500`] (static class)
220
+ *
221
+ * The key heuristic: a hyphen at a node boundary means the nodes are part of
222
+ * the same class name (e.g., `bg-` + ERB + `-500`), while whitespace means
223
+ * a new class name starts.
224
+ */
225
+ export declare function groupNodesByClass(nodes: Node[]): Node[][];
90
226
  /**
91
227
  * Compares two positions to determine if they are equal
92
228
  * Returns true if pos1 and pos2 are at the same location
@@ -136,3 +272,48 @@ export declare function getNodesBeforePosition<T extends Node>(nodes: T[], posit
136
272
  * @param inclusive - If true, includes nodes that start exactly at the position (default: true, matching typical boundary behavior)
137
273
  */
138
274
  export declare function getNodesAfterPosition<T extends Node>(nodes: T[], position: Position, inclusive?: boolean): T[];
275
+ /**
276
+ * Checks if two attributes are structurally equivalent (same name and value),
277
+ * ignoring positional data like location and range.
278
+ */
279
+ export declare function isEquivalentAttribute(first: HTMLAttributeNode, second: HTMLAttributeNode): boolean;
280
+ /**
281
+ * Checks if two open tags are structurally equivalent (same tag name and attributes),
282
+ * ignoring positional data like location and range.
283
+ */
284
+ export declare function isEquivalentOpenTag(first: HTMLOpenTagNode, second: HTMLOpenTagNode): boolean;
285
+ /**
286
+ * Checks if two elements have structurally equivalent open tags (same tag name and attributes),
287
+ * ignoring positional data like location and range. Does not compare body or close tag.
288
+ */
289
+ export declare function isEquivalentElement(first: HTMLElementNode, second: HTMLElementNode): boolean;
290
+ /**
291
+ * Finds the array containing a target node in the AST, along with its index.
292
+ * Traverses child arrays and linked node properties (e.g., `subsequent`, `else_clause`).
293
+ *
294
+ * Useful for autofix operations that need to splice nodes in/out of their parent array.
295
+ *
296
+ * @param root - The root node to search from
297
+ * @param target - The node to find
298
+ * @returns The containing array and the target's index, or null if not found
299
+ */
300
+ export declare function findParentArray(root: Node, target: Node): {
301
+ array: Node[];
302
+ index: number;
303
+ } | null;
304
+ /**
305
+ * Removes a node from an array, also removing an adjacent preceding
306
+ * whitespace-only literal if present.
307
+ */
308
+ export declare function removeNodeFromArray(array: Node[], node: Node): void;
309
+ /**
310
+ * Replaces an element in an array with its body (children), effectively unwrapping it.
311
+ */
312
+ export declare function replaceNodeWithBody(array: Node[], element: HTMLElementNode): void;
313
+ /**
314
+ * Creates a synthetic LiteralNode with the given content and zero location.
315
+ * Useful for inserting whitespace or newlines during AST mutations.
316
+ */
317
+ export declare function createLiteral(content: string): LiteralNode;
318
+ export declare function createSyntheticToken(value: string, type?: string): Token;
319
+ export declare function createWhitespaceNode(): WhitespaceNode;
@@ -1,17 +1,17 @@
1
1
  import type { SerializedParseResult } from "./parse-result.js";
2
2
  import type { SerializedLexResult } from "./lex-result.js";
3
- import type { ParserOptions } from "./parser-options.js";
3
+ import type { ParseOptions } from "./parser-options.js";
4
+ import type { ExtractRubyOptions } from "./extract-ruby-options.js";
4
5
  interface LibHerbBackendFunctions {
5
6
  lex: (source: string) => SerializedLexResult;
6
- lexFile: (path: string) => SerializedLexResult;
7
- parse: (source: string, options?: ParserOptions) => SerializedParseResult;
8
- parseFile: (path: string) => SerializedParseResult;
9
- extractRuby: (source: string) => string;
7
+ parse: (source: string, options?: ParseOptions) => SerializedParseResult;
8
+ extractRuby: (source: string, options?: ExtractRubyOptions) => string;
10
9
  extractHTML: (source: string) => string;
10
+ parseRuby: (source: string) => Uint8Array | null;
11
11
  version: () => string;
12
12
  }
13
13
  export type BackendPromise = () => Promise<LibHerbBackend>;
14
- declare const expectedFunctions: readonly ["parse", "lex", "parseFile", "lexFile", "extractRuby", "extractHTML", "version"];
14
+ declare const expectedFunctions: readonly ["parse", "lex", "extractRuby", "extractHTML", "parseRuby", "version"];
15
15
  type LibHerbBackendFunctionName = (typeof expectedFunctions)[number];
16
16
  export declare function _TYPECHECK(): {
17
17
  checkFunctionsExist: true;
@@ -1,5 +1,6 @@
1
1
  import { Location, SerializedLocation } from "./location.js";
2
2
  export type DiagnosticSeverity = "error" | "warning" | "info" | "hint";
3
+ export type DiagnosticTag = "unnecessary" | "deprecated";
3
4
  /**
4
5
  * Base interface for all diagnostic information in Herb tooling.
5
6
  * This includes parser errors, lexer errors, lint offenses, and any other
@@ -26,6 +27,10 @@ export interface Diagnostic {
26
27
  * Optional source that generated this diagnostic (e.g., "parser", "linter", "lexer")
27
28
  */
28
29
  source?: string;
30
+ /**
31
+ * Optional diagnostic tags for additional metadata (e.g., "unnecessary", "deprecated")
32
+ */
33
+ tags?: DiagnosticTag[];
29
34
  }
30
35
  /**
31
36
  * Serialized form of a Diagnostic for JSON representation
@@ -36,6 +41,7 @@ export interface SerializedDiagnostic {
36
41
  severity: DiagnosticSeverity;
37
42
  code?: string;
38
43
  source?: string;
44
+ tags?: DiagnosticTag[];
39
45
  }
40
46
  export type MonacoSeverity = "error" | "warning" | "info";
41
47
  /**