@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
@@ -3,10 +3,12 @@ import { Result } from "./result.js"
3
3
  import { DocumentNode } from "./nodes.js"
4
4
  import { HerbError } from "./errors.js"
5
5
  import { HerbWarning } from "./warning.js"
6
+ import { ParserOptions } from "./parser-options.js"
6
7
 
7
8
  import type { SerializedHerbError } from "./errors.js"
8
9
  import type { SerializedHerbWarning } from "./warning.js"
9
10
  import type { SerializedDocumentNode } from "./nodes.js"
11
+ import type { SerializedParserOptions } from "./parser-options.js"
10
12
 
11
13
  import type { Visitor } from "./visitor.js"
12
14
 
@@ -15,6 +17,7 @@ export type SerializedParseResult = {
15
17
  source: string
16
18
  warnings: SerializedHerbWarning[]
17
19
  errors: SerializedHerbError[]
20
+ options: SerializedParserOptions
18
21
  }
19
22
 
20
23
  /**
@@ -25,6 +28,9 @@ export class ParseResult extends Result {
25
28
  /** The document node generated from the source code. */
26
29
  readonly value: DocumentNode
27
30
 
31
+ /** The parser options used during parsing. */
32
+ readonly options: ParserOptions
33
+
28
34
  /**
29
35
  * Creates a `ParseResult` instance from a serialized result.
30
36
  * @param result - The serialized parse result containing the value and source.
@@ -36,6 +42,7 @@ export class ParseResult extends Result {
36
42
  result.source,
37
43
  result.warnings.map((warning) => HerbWarning.from(warning)),
38
44
  result.errors.map((error) => HerbError.from(error)),
45
+ ParserOptions.from(result.options),
39
46
  )
40
47
  }
41
48
 
@@ -45,15 +52,19 @@ export class ParseResult extends Result {
45
52
  * @param source - The source code that was parsed.
46
53
  * @param warnings - An array of warnings encountered during parsing.
47
54
  * @param errors - An array of errors encountered during parsing.
55
+ * @param options - The parser options used during parsing.
48
56
  */
49
57
  constructor(
50
58
  value: DocumentNode,
51
59
  source: string,
52
60
  warnings: HerbWarning[] = [],
53
61
  errors: HerbError[] = [],
62
+ options: ParserOptions = new ParserOptions(),
54
63
  ) {
55
64
  super(source, warnings, errors)
56
65
  this.value = value
66
+ this.options = options
67
+ this.value.setSource(source)
57
68
  }
58
69
 
59
70
  /**
@@ -1,7 +1,67 @@
1
- export interface ParserOptions {
1
+ export interface ParseOptions {
2
2
  track_whitespace?: boolean
3
+ analyze?: boolean
4
+ strict?: boolean
5
+ action_view_helpers?: boolean
6
+ render_nodes?: boolean
7
+ prism_nodes?: boolean
8
+ prism_nodes_deep?: boolean
9
+ prism_program?: boolean
3
10
  }
4
11
 
5
- export const DEFAULT_PARSER_OPTIONS: ParserOptions = {
12
+ export type SerializedParserOptions = Required<ParseOptions>
13
+
14
+ export const DEFAULT_PARSER_OPTIONS: SerializedParserOptions = {
6
15
  track_whitespace: false,
16
+ analyze: true,
17
+ strict: true,
18
+ action_view_helpers: false,
19
+ render_nodes: false,
20
+ prism_nodes: false,
21
+ prism_nodes_deep: false,
22
+ prism_program: false,
23
+ }
24
+
25
+ /**
26
+ * Represents the parser options used during parsing.
27
+ */
28
+ export class ParserOptions {
29
+ /** Whether strict mode was enabled during parsing. */
30
+ readonly strict: boolean
31
+
32
+ /** Whether whitespace tracking was enabled during parsing. */
33
+ readonly track_whitespace: boolean
34
+
35
+ /** Whether analysis was performed during parsing. */
36
+ readonly analyze: boolean
37
+
38
+ /** Whether ActionView tag helper transformation was enabled during parsing. */
39
+ readonly action_view_helpers: boolean
40
+
41
+ /** Whether ActionView render call detection was enabled during parsing. */
42
+ readonly render_nodes: boolean
43
+
44
+ /** Whether Prism node serialization was enabled during parsing. */
45
+ readonly prism_nodes: boolean
46
+
47
+ /** Whether deep Prism node serialization was enabled during parsing. */
48
+ readonly prism_nodes_deep: boolean
49
+
50
+ /** Whether the full Prism ProgramNode was serialized on the DocumentNode. */
51
+ readonly prism_program: boolean
52
+
53
+ static from(options: SerializedParserOptions): ParserOptions {
54
+ return new ParserOptions(options)
55
+ }
56
+
57
+ constructor(options: ParseOptions = {}) {
58
+ this.strict = options.strict ?? DEFAULT_PARSER_OPTIONS.strict
59
+ this.track_whitespace = options.track_whitespace ?? DEFAULT_PARSER_OPTIONS.track_whitespace
60
+ this.analyze = options.analyze ?? DEFAULT_PARSER_OPTIONS.analyze
61
+ this.action_view_helpers = options.action_view_helpers ?? DEFAULT_PARSER_OPTIONS.action_view_helpers
62
+ this.render_nodes = options.render_nodes ?? DEFAULT_PARSER_OPTIONS.render_nodes
63
+ this.prism_nodes = options.prism_nodes ?? DEFAULT_PARSER_OPTIONS.prism_nodes
64
+ this.prism_nodes_deep = options.prism_nodes_deep ?? DEFAULT_PARSER_OPTIONS.prism_nodes_deep
65
+ this.prism_program = options.prism_program ?? DEFAULT_PARSER_OPTIONS.prism_program
66
+ }
7
67
  }
@@ -0,0 +1,44 @@
1
+ import { deserialize } from "@ruby/prism/src/deserialize.js"
2
+ import type { ParseResult as PrismParseResult } from "@ruby/prism/src/deserialize.js"
3
+
4
+ export * as PrismNodes from "@ruby/prism/src/nodes.js"
5
+
6
+ export { Visitor as PrismVisitor, BasicVisitor as PrismBasicVisitor } from "@ruby/prism/src/visitor.js"
7
+
8
+ export type PrismNode = any
9
+ export type PrismLocation = { startOffset: number; length: number }
10
+ export type { PrismParseResult }
11
+
12
+ export { inspectPrismNode, inspectPrismSerialized } from "./inspect.js"
13
+
14
+ /**
15
+ * Deserialize a Prism parse result from the raw bytes produced by pm_serialize().
16
+ *
17
+ * @param bytes - The serialized bytes (from prism_serialized field on ERB nodes)
18
+ * @param source - The original source string that was parsed
19
+ * @returns The deserialized Prism ParseResult containing the AST
20
+ */
21
+ export function deserializePrismParseResult(bytes: Uint8Array, source: string): PrismParseResult {
22
+ const sourceBytes = new TextEncoder().encode(source)
23
+
24
+ return deserialize(sourceBytes, bytes)
25
+ }
26
+
27
+ /**
28
+ * Deserialize a Prism node from the raw bytes produced by pm_serialize().
29
+ * pm_serialize() serializes a single node subtree, so the ParseResult's
30
+ * value is the Prism node directly (not wrapped in ProgramNode).
31
+ *
32
+ * @param bytes - The serialized bytes (from prism_serialized field on ERB nodes)
33
+ * @param source - The original source string that was parsed
34
+ * @returns The Prism node, or null if deserialization fails
35
+ */
36
+ export function deserializePrismNode(bytes: Uint8Array, source: string): PrismNode | null {
37
+ try {
38
+ const result = deserializePrismParseResult(bytes, source)
39
+
40
+ return result.value ?? null
41
+ } catch {
42
+ return null
43
+ }
44
+ }
@@ -0,0 +1,118 @@
1
+ import { deserializePrismNode } from "./index.js"
2
+
3
+ import type { PrismLocation } from "./index.js"
4
+ import type { PrismNode } from "./index.js"
5
+
6
+ function offsetToLineColumn(source: string, offset: number): string {
7
+ let line = 1
8
+ let column = 0
9
+
10
+ for (let i = 0; i < offset && i < source.length; i++) {
11
+ if (source[i] === "\n") {
12
+ line++
13
+ column = 0
14
+ } else {
15
+ column++
16
+ }
17
+ }
18
+
19
+ return `${line}:${column}`
20
+ }
21
+
22
+ function formatLocation(location: PrismLocation, source: string): string {
23
+ const start = offsetToLineColumn(source, location.startOffset)
24
+ const end = offsetToLineColumn(source, location.startOffset + location.length)
25
+
26
+ return `(${start})-(${end})`
27
+ }
28
+
29
+ function isPrismNode(value: any): boolean {
30
+ return value && typeof value === "object" && typeof value.toJSON === "function" && value.location
31
+ }
32
+
33
+ export function inspectPrismNode(node: PrismNode, source: string, prefix: string = ""): string {
34
+ if (!node) return "∅\n"
35
+
36
+ const nodeName = typeof node.toJSON === "function" ? node.toJSON().type : node.constructor.name
37
+ let output = ""
38
+
39
+ output += `@ ${nodeName}`
40
+
41
+ if (node.location) {
42
+ output += ` (location: ${formatLocation(node.location, source)})`
43
+ }
44
+
45
+ output += "\n"
46
+
47
+ const fields = getNodeFields(node)
48
+
49
+ fields.forEach((field, index) => {
50
+ const isLastField = index === fields.length - 1
51
+ const symbol = isLastField ? "└── " : "├── "
52
+ const childPrefix = prefix + (isLastField ? " " : "│ ")
53
+ const value = node[field]
54
+
55
+ if (value === null || value === undefined) {
56
+ output += `${prefix}${symbol}${field}: ∅\n`
57
+ } else if (typeof value === "string") {
58
+ output += `${prefix}${symbol}${field}: ${JSON.stringify(value)}\n`
59
+ } else if (typeof value === "number" || typeof value === "boolean") {
60
+ output += `${prefix}${symbol}${field}: ${value}\n`
61
+ } else if (Array.isArray(value)) {
62
+ output += `${prefix}${symbol}${field}: `
63
+
64
+ if (value.length === 0) {
65
+ output += "[]\n"
66
+ } else {
67
+ output += `(${value.length} item${value.length === 1 ? "" : "s"})\n`
68
+
69
+ value.forEach((item: any, i: number) => {
70
+ const isLastItem = i === value.length - 1
71
+ const itemSymbol = isLastItem ? "└── " : "├── "
72
+ const itemPrefix = childPrefix + (isLastItem ? " " : "│ ")
73
+
74
+ if (isPrismNode(item)) {
75
+ output += `${childPrefix}${itemSymbol}${inspectPrismNode(item, source, itemPrefix).trimStart()}`
76
+ } else {
77
+ output += `${childPrefix}${itemSymbol}${item}\n`
78
+ }
79
+ })
80
+ }
81
+ } else if (isPrismNode(value)) {
82
+ output += `${prefix}${symbol}${field}:\n`
83
+ output += `${childPrefix}└── ${inspectPrismNode(value, source, childPrefix + " ").trimStart()}`
84
+ } else if (typeof value === "object" && value.startOffset !== undefined) {
85
+ output += `${prefix}${symbol}${field}: (location: ${formatLocation(value, source)})\n`
86
+ } else if (typeof value === "object" && "value" in value && "encoding" in value) {
87
+ output += `${prefix}${symbol}${field}: ${JSON.stringify(value.value)}\n`
88
+ } else {
89
+ output += `${prefix}${symbol}${field}: ${String(value)}\n`
90
+ }
91
+ })
92
+
93
+ return output
94
+ }
95
+
96
+ function getNodeFields(node: PrismNode): string[] {
97
+ const skip = new Set(["nodeID", "location", "flags"])
98
+ const fields: string[] = []
99
+
100
+ for (const key of Object.keys(node)) {
101
+ if (!skip.has(key)) {
102
+ fields.push(key)
103
+ }
104
+ }
105
+
106
+ return fields
107
+ }
108
+
109
+ export function inspectPrismSerialized(bytes: Uint8Array, source: string, prefix: string = ""): string {
110
+ try {
111
+ const node = deserializePrismNode(bytes, source)
112
+ if (!node) return "∅"
113
+
114
+ return "\n" + prefix + "└── " + inspectPrismNode(node, source, prefix + " ").trimStart().trimEnd()
115
+ } catch {
116
+ return `(${bytes.length} bytes, deserialize error)`
117
+ }
118
+ }
package/src/util.ts CHANGED
@@ -5,15 +5,3 @@ export function ensureString(object: any): string {
5
5
 
6
6
  throw new TypeError("Argument must be a string")
7
7
  }
8
-
9
- export function convertToUTF8(string: string) {
10
- const bytes = []
11
-
12
- for (let i = 0; i < string.length; i++) {
13
- bytes.push(string.charCodeAt(i))
14
- }
15
-
16
- const decoder = new TextDecoder("utf-8")
17
-
18
- return decoder.decode(new Uint8Array(bytes))
19
- }
package/src/visitor.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // NOTE: This file is generated by the templates/template.rb script and should not
2
- // be modified manually. See /Users/marcoroth/Development/herb-release-0.8.10/templates/javascript/packages/core/src/visitor.ts.erb
2
+ // be modified manually. See /Users/marcoroth/Development/herb-release-0.9.1/templates/javascript/packages/core/src/visitor.ts.erb
3
3
 
4
4
  import {
5
5
  Node,
@@ -7,11 +7,18 @@ import {
7
7
  DocumentNode,
8
8
  LiteralNode,
9
9
  HTMLOpenTagNode,
10
+ HTMLConditionalOpenTagNode,
10
11
  HTMLCloseTagNode,
12
+ HTMLOmittedCloseTagNode,
13
+ HTMLVirtualCloseTagNode,
11
14
  HTMLElementNode,
15
+ HTMLConditionalElementNode,
12
16
  HTMLAttributeValueNode,
13
17
  HTMLAttributeNameNode,
14
18
  HTMLAttributeNode,
19
+ RubyLiteralNode,
20
+ RubyHTMLAttributesSplatNode,
21
+ ERBOpenTagNode,
15
22
  HTMLTextNode,
16
23
  HTMLCommentNode,
17
24
  HTMLDoctypeNode,
@@ -33,6 +40,8 @@ import {
33
40
  ERBEnsureNode,
34
41
  ERBBeginNode,
35
42
  ERBUnlessNode,
43
+ RubyRenderLocalNode,
44
+ ERBRenderNode,
36
45
  ERBYieldNode,
37
46
  ERBInNode,
38
47
  } from "./nodes.js"
@@ -48,11 +57,18 @@ export interface IVisitor {
48
57
  visitDocumentNode(node: DocumentNode): void
49
58
  visitLiteralNode(node: LiteralNode): void
50
59
  visitHTMLOpenTagNode(node: HTMLOpenTagNode): void
60
+ visitHTMLConditionalOpenTagNode(node: HTMLConditionalOpenTagNode): void
51
61
  visitHTMLCloseTagNode(node: HTMLCloseTagNode): void
62
+ visitHTMLOmittedCloseTagNode(node: HTMLOmittedCloseTagNode): void
63
+ visitHTMLVirtualCloseTagNode(node: HTMLVirtualCloseTagNode): void
52
64
  visitHTMLElementNode(node: HTMLElementNode): void
65
+ visitHTMLConditionalElementNode(node: HTMLConditionalElementNode): void
53
66
  visitHTMLAttributeValueNode(node: HTMLAttributeValueNode): void
54
67
  visitHTMLAttributeNameNode(node: HTMLAttributeNameNode): void
55
68
  visitHTMLAttributeNode(node: HTMLAttributeNode): void
69
+ visitRubyLiteralNode(node: RubyLiteralNode): void
70
+ visitRubyHTMLAttributesSplatNode(node: RubyHTMLAttributesSplatNode): void
71
+ visitERBOpenTagNode(node: ERBOpenTagNode): void
56
72
  visitHTMLTextNode(node: HTMLTextNode): void
57
73
  visitHTMLCommentNode(node: HTMLCommentNode): void
58
74
  visitHTMLDoctypeNode(node: HTMLDoctypeNode): void
@@ -74,6 +90,8 @@ export interface IVisitor {
74
90
  visitERBEnsureNode(node: ERBEnsureNode): void
75
91
  visitERBBeginNode(node: ERBBeginNode): void
76
92
  visitERBUnlessNode(node: ERBUnlessNode): void
93
+ visitRubyRenderLocalNode(node: RubyRenderLocalNode): void
94
+ visitERBRenderNode(node: ERBRenderNode): void
77
95
  visitERBYieldNode(node: ERBYieldNode): void
78
96
  visitERBInNode(node: ERBInNode): void
79
97
  visitNode(node: Node): void
@@ -118,16 +136,36 @@ export class Visitor implements IVisitor {
118
136
  this.visitChildNodes(node)
119
137
  }
120
138
 
139
+ visitHTMLConditionalOpenTagNode(node: HTMLConditionalOpenTagNode): void {
140
+ this.visitNode(node)
141
+ this.visitChildNodes(node)
142
+ }
143
+
121
144
  visitHTMLCloseTagNode(node: HTMLCloseTagNode): void {
122
145
  this.visitNode(node)
123
146
  this.visitChildNodes(node)
124
147
  }
125
148
 
149
+ visitHTMLOmittedCloseTagNode(node: HTMLOmittedCloseTagNode): void {
150
+ this.visitNode(node)
151
+ this.visitChildNodes(node)
152
+ }
153
+
154
+ visitHTMLVirtualCloseTagNode(node: HTMLVirtualCloseTagNode): void {
155
+ this.visitNode(node)
156
+ this.visitChildNodes(node)
157
+ }
158
+
126
159
  visitHTMLElementNode(node: HTMLElementNode): void {
127
160
  this.visitNode(node)
128
161
  this.visitChildNodes(node)
129
162
  }
130
163
 
164
+ visitHTMLConditionalElementNode(node: HTMLConditionalElementNode): void {
165
+ this.visitNode(node)
166
+ this.visitChildNodes(node)
167
+ }
168
+
131
169
  visitHTMLAttributeValueNode(node: HTMLAttributeValueNode): void {
132
170
  this.visitNode(node)
133
171
  this.visitChildNodes(node)
@@ -143,6 +181,22 @@ export class Visitor implements IVisitor {
143
181
  this.visitChildNodes(node)
144
182
  }
145
183
 
184
+ visitRubyLiteralNode(node: RubyLiteralNode): void {
185
+ this.visitNode(node)
186
+ this.visitChildNodes(node)
187
+ }
188
+
189
+ visitRubyHTMLAttributesSplatNode(node: RubyHTMLAttributesSplatNode): void {
190
+ this.visitNode(node)
191
+ this.visitChildNodes(node)
192
+ }
193
+
194
+ visitERBOpenTagNode(node: ERBOpenTagNode): void {
195
+ this.visitNode(node)
196
+ this.visitERBNode(node)
197
+ this.visitChildNodes(node)
198
+ }
199
+
146
200
  visitHTMLTextNode(node: HTMLTextNode): void {
147
201
  this.visitNode(node)
148
202
  this.visitChildNodes(node)
@@ -263,6 +317,17 @@ export class Visitor implements IVisitor {
263
317
  this.visitChildNodes(node)
264
318
  }
265
319
 
320
+ visitRubyRenderLocalNode(node: RubyRenderLocalNode): void {
321
+ this.visitNode(node)
322
+ this.visitChildNodes(node)
323
+ }
324
+
325
+ visitERBRenderNode(node: ERBRenderNode): void {
326
+ this.visitNode(node)
327
+ this.visitERBNode(node)
328
+ this.visitChildNodes(node)
329
+ }
330
+
266
331
  visitERBYieldNode(node: ERBYieldNode): void {
267
332
  this.visitNode(node)
268
333
  this.visitERBNode(node)