ast_transform 2.1.2 → 3.0.0

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.
@@ -0,0 +1,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ast_transform/node'
4
+ require 'ast_transform/thunk'
5
+ require 'ast_transform/transformation_helper'
6
+
7
+ module ASTTransform
8
+ # Lowers Thunk nodes into plain Ruby ahead of emission. Each unique thunk (grouped by id identity) becomes a
9
+ # hidden proc; each occurrence becomes the proc's call:
10
+ #
11
+ # thunk placed at the execution point
12
+ # => x = x; __ast_thunk_<n>__ = proc { body } (at the body's source lines)
13
+ # ...
14
+ # __ast_thunk_<n>__.call (at the occurrence)
15
+ #
16
+ # Placement is inferred, not authored: the proc's text is inserted into the statement sequence enclosing the
17
+ # occurrence, positioned among its siblings by the body's first source line — the lines the author removed the
18
+ # statements from. A loc-less body has no textual home and packs immediately before its call. Placements never
19
+ # escape a scope boundary (def/class/module bodies absorb their own), because the hidden lvar must share the call's
20
+ # method activation; they DO escape block literals, which close over the defining scope.
21
+ #
22
+ # The closure is a non-lambda proc on purpose: `return` inside a proc returns from the method where the proc was
23
+ # defined, and placement and execution always share one method activation, so a thunked `return` keeps its original
24
+ # meaning. Jump keywords whose owner lies outside the body keep Ruby's native behavior (`break`/`retry` fail loudly,
25
+ # `next`/`redo` silently alter flow) — what a transform chooses to thunk is the transform author's call.
26
+ #
27
+ # The `x = x` pre-declarations cover every local the body assigns at method scope. A local first assigned inside a
28
+ # block literal is block-local, so without a textual method-scope assignment before the proc, thunked assignments
29
+ # would be invisible to the statements that read them after the execution point. Self-assignment registers the name
30
+ # (nil until the thunk runs — exactly what an unexecuted assignment yields) without clobbering an already-assigned
31
+ # value.
32
+ #
33
+ # Stateless: the thunks encountered during one lowering are tracked in a Registry created at +lower+ entry, so an
34
+ # instance is a reusable collaborator.
35
+ class ThunkLowering
36
+ include TransformationHelper
37
+
38
+ # A pending proc definition: +line+ is the body's first source line (nil for fully synthetic bodies),
39
+ # +statements+ the pre-declarations plus the proc assignment.
40
+ Placement = Struct.new(:line, :statements)
41
+
42
+ # Raised when a thunk cannot be placed: its body's source lines fall after the execution point (the hidden
43
+ # proc's text IS its assignment, so a call can never textually precede the body), or two occurrences of the
44
+ # same thunk carry diverging bodies.
45
+ class PlacementError < StandardError; end
46
+
47
+ # The thunks encountered during one lowering, keyed by identity: allocates each thunk's hidden lvar name
48
+ # on first occurrence and verifies later occurrences carry the same body.
49
+ class Registry
50
+ def initialize
51
+ @names_by_id = {}.compare_by_identity
52
+ @bodies_by_id = {}.compare_by_identity
53
+ end
54
+
55
+ def known?(id)
56
+ @names_by_id.key?(id)
57
+ end
58
+
59
+ # @return [Symbol] the hidden lvar name allocated for +id+.
60
+ def register(id, body)
61
+ name = :"__ast_thunk_#{@names_by_id.size + 1}__"
62
+ @names_by_id[id] = name
63
+ @bodies_by_id[id] = body
64
+ name
65
+ end
66
+
67
+ def name_for(id)
68
+ @names_by_id.fetch(id)
69
+ end
70
+
71
+ def verify_same_body!(id, body)
72
+ return if @bodies_by_id[id] == body
73
+
74
+ raise PlacementError,
75
+ 'occurrences of one thunk carry diverging bodies; reuse the same thunk node to multiplex'
76
+ end
77
+
78
+ def hidden_names
79
+ @names_by_id.values
80
+ end
81
+ end
82
+ private_constant :Registry
83
+
84
+ SEQUENCE_TYPES = [:begin, :kwbegin].freeze
85
+ # Scope-opening containers: the hidden lvar cannot be referenced across these boundaries, so placements arising
86
+ # inside must land inside.
87
+ SCOPE_BODY_INDEXES = { def: 2, defs: 3, class: 2, module: 1, sclass: 1 }.freeze
88
+
89
+ # @param node [Parser::AST::Node] tree possibly containing Thunk nodes
90
+ # @return [Parser::AST::Node] tree with thunks lowered to plain Ruby
91
+ # @raise [PlacementError] when a thunk body's source lines fall after its execution point, or occurrences of
92
+ # one thunk diverge
93
+ def lower(node)
94
+ lower_body(node, Registry.new)
95
+ end
96
+
97
+ private
98
+
99
+ # Lowers a node standing in statement-body position (a container's body or the root), absorbing any placements
100
+ # that arise within it.
101
+ def lower_body(node, registry)
102
+ return node unless node.is_a?(::Parser::AST::Node)
103
+ return lower_sequence(node, registry) if SEQUENCE_TYPES.include?(node.type)
104
+
105
+ lowered, placements = lower_expression(node, registry)
106
+ return lowered if placements.empty?
107
+
108
+ # A loc-less :begin in statement position; the emitter flattens it into the surrounding statement stream.
109
+ s(:begin, *placements.flat_map(&:statements), lowered)
110
+ end
111
+
112
+ # Lowers a statement sequence, inserting each placement among the statements by the body's source line.
113
+ def lower_sequence(node, registry)
114
+ statements = []
115
+
116
+ node.children.each_with_index do |child, index|
117
+ lowered, placements = lower_expression(child, registry)
118
+ placements.each do |placement|
119
+ check_placement_precedes_execution!(placement, child, node.children[(index + 1)..])
120
+ statements.insert(insertion_index(statements, placement), *placement.statements)
121
+ end
122
+ statements << lowered
123
+ end
124
+
125
+ node.updated(nil, statements)
126
+ end
127
+
128
+ # Lowers a node in expression position. Returns the lowered node and the placements that must be inserted into
129
+ # the enclosing statement sequence.
130
+ #
131
+ # @return [Array(Parser::AST::Node, Array<Placement>)]
132
+ def lower_expression(node, registry)
133
+ return [node, []] unless node.is_a?(::Parser::AST::Node)
134
+
135
+ case node.type
136
+ when :ast_thunk
137
+ lower_thunk(node, registry)
138
+ when *SEQUENCE_TYPES
139
+ [lower_sequence(node, registry), []]
140
+ when :ensure, :rescue
141
+ [node.updated(nil, node.children.map { |child| lower_body(child, registry) }), []]
142
+ when :resbody
143
+ exceptions, capture, body = node.children
144
+ [node.updated(nil, [exceptions, capture, lower_body(body, registry)]), []]
145
+ else
146
+ lower_generic(node, registry)
147
+ end
148
+ end
149
+
150
+ def lower_generic(node, registry)
151
+ scope_body_index = SCOPE_BODY_INDEXES[node.type]
152
+ pending = []
153
+
154
+ children = node.children.each_with_index.map do |child, index|
155
+ if index == scope_body_index
156
+ lower_body(child, registry)
157
+ else
158
+ lowered, placements = lower_expression(child, registry)
159
+ pending.concat(placements)
160
+ lowered
161
+ end
162
+ end
163
+
164
+ [node.updated(nil, children), pending]
165
+ end
166
+
167
+ # An occurrence of a thunk: the first occurrence of its id yields the placement; every occurrence yields the
168
+ # call.
169
+ def lower_thunk(node, registry)
170
+ id = node.id
171
+
172
+ if registry.known?(id)
173
+ registry.verify_same_body!(id, node.body)
174
+ return [call_node(id, registry), []]
175
+ end
176
+
177
+ name = registry.register(id, node.body)
178
+ lowered_body = lower_sequence(s(:begin, *node.body), registry)
179
+ placement = Placement.new(body_first_line(node.body), placement_statements(name, lowered_body, registry))
180
+ [call_node(id, registry), [placement]]
181
+ end
182
+
183
+ def call_node(id, registry)
184
+ s(:send, s(:lvar, registry.name_for(id)), :call)
185
+ end
186
+
187
+ def placement_statements(name, lowered_body, registry)
188
+ assignment = s(:lvasgn, name, s(:block, s(:send, nil, :proc), s(:args), lowered_body))
189
+ hidden_names = registry.hidden_names
190
+ pre_declared = method_scope_assignments(lowered_body).reject { |local| hidden_names.include?(local) }
191
+ pre_declared.map { |local| s(:lvasgn, local, s(:lvar, local)) } << assignment
192
+ end
193
+
194
+ def body_first_line(body)
195
+ body.filter_map { |statement| statement.loc&.line }.min
196
+ end
197
+
198
+ # The proc's text must precede its call: a placement whose body lines fall at or after the executing statement
199
+ # (or any statement after it) cannot be laid out — the assignment would complete after the call.
200
+ def check_placement_precedes_execution!(placement, executing_statement, following_statements)
201
+ return if placement.line.nil?
202
+
203
+ conflicting = [executing_statement, *following_statements].find do |statement|
204
+ line = statement.is_a?(::Parser::AST::Node) ? statement.loc&.line : nil
205
+ line && line < placement.line
206
+ end
207
+ return if conflicting.nil?
208
+
209
+ raise PlacementError,
210
+ "thunk body's source lines (from line #{placement.line}) fall after its execution point " \
211
+ "(statement at line #{conflicting.loc.line}); a thunk can only delay execution, never text"
212
+ end
213
+
214
+ def insertion_index(statements, placement)
215
+ return statements.size if placement.line.nil?
216
+
217
+ statements.index { |statement| statement.loc&.line && statement.loc.line > placement.line } || statements.size
218
+ end
219
+
220
+ # Node types opening a new local-variable scope: assignments inside them were invisible to the method scope in
221
+ # the original source too, so they get no pre-declaration.
222
+ NEW_SCOPE_TYPES = [:def, :defs, :class, :module, :sclass].freeze
223
+ # Block literals: locals first assigned inside them are block-local (the same lexical rule the pre-declarations
224
+ # exist to work around), but their callee/arguments evaluate at method scope and are still descended.
225
+ BLOCK_TYPES = [:block, :numblock, :itblock].freeze
226
+
227
+ # Locals the thunk body assigns at method scope, in first-assignment order (covers masgn/op_asgn targets — they
228
+ # all carry :lvasgn nodes).
229
+ def method_scope_assignments(node, names = [])
230
+ return names unless node.is_a?(::Parser::AST::Node)
231
+ return names if NEW_SCOPE_TYPES.include?(node.type)
232
+
233
+ names << node.children[0] if node.type == :lvasgn && !names.include?(node.children[0])
234
+
235
+ children = BLOCK_TYPES.include?(node.type) ? [node.children[0]] : node.children
236
+ children.each { |child| method_scope_assignments(child, names) }
237
+ names
238
+ end
239
+ end
240
+ end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
- require 'ast_transform'
3
- require 'ast_transform/abstract_transformation'
4
- require 'ast_transform/transformer'
5
- require 'unparser'
2
+
3
+ require "ast_transform"
4
+ require "ast_transform/abstract_transformation"
5
+ require "ast_transform/transformer"
6
+ require "unparser"
6
7
 
7
8
  module ASTTransform
8
9
  class Transformation < ASTTransform::AbstractTransformation
@@ -18,9 +19,9 @@ module ASTTransform
18
19
 
19
20
  count_before_reject = children.size
20
21
 
21
- children.reject!.with_index { |child_node, index|
22
+ children.reject!.with_index do |child_node, index|
22
23
  transform_node?(child_node) && transformable_node?(next_child(node, index))
23
- }
24
+ end
24
25
 
25
26
  processed = process_all(children)
26
27
 
@@ -68,19 +69,14 @@ module ASTTransform
68
69
 
69
70
  def extract_transformation(node)
70
71
  return unless node.is_a?(Parser::AST::Node)
71
- return unless node.children.count >= 2
72
-
73
- if node.children[1] == :new
74
- require_transformation(node)
75
- code = Unparser.unparse(node)
72
+ return if node.children.count < 2
76
73
 
77
- TOPLEVEL_BINDING.eval(code)
78
- else
79
- require_transformation(node)
80
- code = "#{Unparser.unparse(node)}.new"
74
+ require_transformation(node)
75
+ code = Unparser.unparse(node)
76
+ # A bare constant reference is instantiated; an explicit .new is kept as written.
77
+ code = "#{code}.new" unless node.children[1] == :new
81
78
 
82
- TOPLEVEL_BINDING.eval(code)
83
- end
79
+ TOPLEVEL_BINDING.eval(code)
84
80
  end
85
81
 
86
82
  def require_transformation(node)
@@ -100,11 +96,12 @@ module ASTTransform
100
96
  acronyms = ASTTransform.acronyms
101
97
  acronym_regex = acronyms.empty? ? /(?=a)b/ : /#{acronyms.join("|")}/
102
98
  return const_name unless /[A-Z-]|::/.match?(const_name)
103
- word = const_name.to_s.gsub("::".freeze, "/".freeze)
104
- word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" }
105
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
106
- word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
107
- word.tr!("-".freeze, "_".freeze)
99
+
100
+ word = const_name.to_s.gsub("::", "/")
101
+ word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{acronym_regex})(?=\b|[^a-z])/) { "#{::Regexp.last_match(1) && "_"}#{::Regexp.last_match(2).downcase}" }
102
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
103
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
104
+ word.tr!("-", "_")
108
105
  word.downcase!
109
106
  word
110
107
  end
@@ -1,16 +1,127 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'parser'
4
+ require 'ast_transform/node'
5
+ require 'ast_transform/thunk'
3
6
 
4
7
  module ASTTransform
8
+ # The transform-authoring layer. Three shapes:
9
+ #
10
+ # - Constructors (+s+, +s_at+): type + children in, fresh node out.
11
+ # - The sequence combinator (+run_after+): sequence in, sequence out — the paved road for execution reordering.
12
+ # - The low-level reordering primitive (+thunk+): statements in, Thunk node out — for execution points inside
13
+ # expressions.
14
+ #
15
+ # The contract these helpers serve: textual order is source order. The emitter places every loc-carrying statement
16
+ # at its source line; when execution order must differ from textual order, authors express it as a thunk instead of
17
+ # moving text.
5
18
  module TransformationHelper
6
- def self.included(base)
7
- base.extend(Methods)
8
- base.include(Methods)
19
+ # Raised by authoring helpers (e.g. +s_at+) when a node that must carry a source location does not have one.
20
+ class MissingLocationError < StandardError; end
21
+
22
+ class << self
23
+ def included(base)
24
+ base.extend(Methods)
25
+ base.include(Methods)
26
+ end
9
27
  end
10
28
 
11
29
  module Methods
30
+ # Builds a loc-less node. The emitter packs loc-less nodes onto the current output line — the correct default
31
+ # for synthetic code, which has no source-line truth to preserve.
32
+ #
33
+ # @param type [Symbol] node type
34
+ # @param children [Array] child nodes / literals
35
+ # @param properties [Hash] node properties (e.g. location:)
36
+ # @return [ASTTransform::Node] node routed to its registered class
12
37
  def s(type, *children, **properties)
13
- Parser::AST::Node.new(type, children, properties)
38
+ Node.build(type, children, properties)
39
+ end
40
+
41
+ # Builds a fresh node anchored to another node's source location. Use when composing a replacement tree whose
42
+ # root isn't derived from the node it replaces (otherwise prefer +anchor.updated(...)+). The attached map is a
43
+ # clean expression-only Source::Map over +anchor.loc.expression+ — no stale typed sub-ranges (selector etc.).
44
+ # Anchor inheritance is shallow; children keep or lack their own locs.
45
+ #
46
+ # @param anchor [Parser::AST::Node] node whose line this code replaces
47
+ # @param type [Symbol] node type
48
+ # @param children [Array] child nodes / literals
49
+ # @return [ASTTransform::Node] node carrying anchor's expression range
50
+ # @raise [MissingLocationError] if anchor has no expression location
51
+ def s_at(anchor, type, *children)
52
+ expression = anchor.loc&.expression
53
+ raise MissingLocationError, "anchor #{anchor.type} node has no source location" unless expression
54
+
55
+ s(type, *children, location: ::Parser::Source::Map.new(expression))
56
+ end
57
+
58
+ # The low-level reordering primitive. Thunking is the one reordering lever: text never moves and execution can
59
+ # only move later, so "hoist A above B" is expressed as "run B after A". Returns a single Thunk node: splice it
60
+ # where the statements must RUN — statement position or composed inside an expression, e.g. as an assert_raises
61
+ # block body. The wrapped statements keep their own locs, and the lowering derives the hidden proc's textual
62
+ # placement from them, so the body still emits on its source lines even though execution waits. Reuse the same
63
+ # node to execute one body from several points.
64
+ #
65
+ # Semantics are near-transparent (see ThunkLowering): +return+ still returns from the enclosing method
66
+ # (non-lambda proc), and locals the wrapped statements assign stay method-scope (pre-declared before the proc).
67
+ # Jump keywords whose owner lies outside the wrapped statements keep Ruby's native behavior — +break+/+retry+
68
+ # fail loudly at the jump's own source line, +next+/+redo+ silently end or restart the thunk body. Weigh that
69
+ # when choosing what your surface thunks.
70
+ #
71
+ # Prefer +run_after+ when the execution point sits in the same statement sequence as the statements.
72
+ #
73
+ # @param statements [Array<Parser::AST::Node>] statements to wrap
74
+ # @return [ASTTransform::Thunk] the thunk node
75
+ def thunk(*statements)
76
+ s(:ast_thunk, Thunk::Id.new, *statements)
77
+ end
78
+
79
+ # The paved road for execution reordering in flat statement sequences. Named for the constraint, not the
80
+ # mechanism — with text pinned to source lines the only physical lever is delaying execution, so "run X after
81
+ # Y" is the constraint an author states. Returns a NEW sequence in which the +run+ statements are removed and a
82
+ # thunk wrapping them is inserted immediately after +after+.
83
+ #
84
+ # All membership checks are by identity (equal?), never ==: node equality ignores location, so two textually
85
+ # identical statements on different lines compare == and value matching could splice the wrong one.
86
+ #
87
+ # @param statements [Array<Parser::AST::Node>] the sequence being composed
88
+ # @param run [Array<Parser::AST::Node>] contiguous run of elements of +statements+ (by identity) whose
89
+ # execution must wait
90
+ # @param after [Parser::AST::Node] element of +statements+ (by identity, not inside +run+) the +run+ statements
91
+ # execute after
92
+ # @return [Array<Parser::AST::Node>] new sequence with the thunk placed
93
+ # @raise [ArgumentError] if +run+ is not a contiguous identity-run of +statements+, or +after+ is not an
94
+ # element (or is inside +run+)
95
+ def run_after(statements, run:, after:)
96
+ run_range = contiguous_identity_range(statements, run)
97
+ raise ArgumentError, "run: must be a contiguous run of elements of statements (by identity)" unless run_range
98
+
99
+ after_index = statements.index { |statement| statement.equal?(after) }
100
+ raise ArgumentError, "after: must be an element of statements (by identity)" unless after_index
101
+ raise ArgumentError, "after: cannot be inside run:" if run_range.cover?(after_index)
102
+
103
+ reordered = statements.dup
104
+ reordered[run_range] = []
105
+
106
+ insertion_index = reordered.index { |statement| statement.equal?(after) }
107
+ reordered.insert(insertion_index + 1, thunk(*run))
108
+ end
109
+
110
+ private
111
+
112
+ # The range +members+ occupies in +sequence+, or nil unless members is a non-empty contiguous identity-run in
113
+ # order.
114
+ def contiguous_identity_range(sequence, members)
115
+ return nil if members.empty?
116
+
117
+ start = sequence.index { |element| element.equal?(members.first) }
118
+ return nil unless start
119
+
120
+ contiguous = members.each_with_index.all? do |member, offset|
121
+ sequence[start + offset]&.equal?(member)
122
+ end
123
+
124
+ contiguous ? (start...(start + members.size)) : nil
14
125
  end
15
126
  end
16
127
  end
@@ -1,17 +1,20 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'prism'
3
4
  require 'prism/translation/parser'
4
5
  require 'unparser'
5
6
  require 'ast_transform/kwargs_builder'
6
- require 'ast_transform/source_map'
7
+ require 'ast_transform/line_aligned_emitter'
7
8
 
8
9
  module ASTTransform
9
10
  class Transformer
10
11
  # Constructs a new Transformer instance.
11
12
  #
12
13
  # @param transformations [Array<ASTTransform::AbstractTransformation>] The transformations to be run.
13
- def initialize(*transformations)
14
+ # @param emitter [ASTTransform::LineAlignedEmitter] The emitter rendering transformed ASTs back to source.
15
+ def initialize(*transformations, emitter: LineAlignedEmitter.new)
14
16
  @transformations = transformations
17
+ @emitter = emitter
15
18
  end
16
19
 
17
20
  # Builds the AST for the given +source+.
@@ -20,7 +23,7 @@ module ASTTransform
20
23
  # @param file_path [String] The file_path. This is important for source mapping in backtraces.
21
24
  #
22
25
  # @return [Parser::AST::Node] The AST.
23
- def build_ast(source, file_path: 'tmp')
26
+ def build_ast(source, file_path: "tmp")
24
27
  buffer = create_buffer(source, file_path)
25
28
  parser.parse(buffer)
26
29
  end
@@ -39,16 +42,17 @@ module ASTTransform
39
42
  #
40
43
  # @param source [String] The input source code to be transformed.
41
44
  #
42
- # @return [String] The transformed code.
45
+ # @return [String] The transformed code, line-aligned (see #transform_file_source).
43
46
  def transform(source)
44
47
  ast = build_ast(source)
45
48
  transformed_ast = transform_ast(ast)
46
- Unparser.unparse(transformed_ast)
49
+ @emitter.emit(transformed_ast, 'tmp')
47
50
  end
48
51
 
49
52
  # Transforms the give +file_path+.
50
53
  #
51
- # @param file_path [String] The input file to be transformed. This is required for source mapping in backtraces.
54
+ # @param file_path [String] The input file to be transformed. Statement placement (and therefore
55
+ # backtrace and breakpoint line numbers) is derived from this file's source locations.
52
56
  # @param transformed_file_path [String] The file path to the transformed file.
53
57
  #
54
58
  # @return [String] The transformed code.
@@ -60,21 +64,19 @@ module ASTTransform
60
64
  # Transforms the given +source+ in +file_path+.
61
65
  #
62
66
  # @param source [String] The input source code to be transformed.
63
- # @param file_path [String] The file path for the input +source+. This is required for source mapping in backtraces.
64
- # @param transformed_file_path [String] The file path to the transformed filed. This is required to register the
65
- # SourceMap.
67
+ # @param file_path [String] The file path for the input +source+. Statement placement (and
68
+ # therefore backtrace and breakpoint line numbers) is derived from the source locations parsed
69
+ # under this path.
70
+ # @param transformed_file_path [String] The file path the transformed file will be written to.
66
71
  #
67
- # @return [String] The transformed code.
68
- def transform_file_source(source, file_path, transformed_file_path)
72
+ # @return [String] The transformed code, line-aligned: every statement carrying a source
73
+ # location is emitted at its original source line.
74
+ def transform_file_source(source, file_path, _transformed_file_path)
69
75
  source_ast = build_ast(source, file_path: file_path)
70
- # At this point, the transformed_ast contains line number mappings for the original +source+.
76
+ # At this point, the transformed_ast contains source locations for the original +source+.
71
77
  transformed_ast = transform_ast(source_ast)
72
78
 
73
- transformed_source = Unparser.unparse(transformed_ast)
74
-
75
- register_source_map(file_path, transformed_file_path, transformed_ast, transformed_source)
76
-
77
- transformed_source
79
+ @emitter.emit(transformed_ast, file_path)
78
80
  end
79
81
 
80
82
  # Transforms the given +ast+.
@@ -101,13 +103,5 @@ module ASTTransform
101
103
  @parser&.reset
102
104
  @parser ||= Prism::Translation::Parser.new(ASTTransform::KwargsBuilder.new)
103
105
  end
104
-
105
- def register_source_map(source_file_path, transformed_file_path, transformed_ast, transformed_source)
106
- # The transformed_source is re-parsed to get the correct line numbers for the transformed_ast, which is the code
107
- # that will run.
108
- rewritten_ast = build_ast(transformed_source)
109
- source_map = ASTTransform::SourceMap.new(source_file_path, transformed_file_path, transformed_ast, rewritten_ast)
110
- ASTTransform::SourceMap.register_source_map(source_map)
111
- end
112
106
  end
113
107
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ASTTransform
2
- VERSION = "2.1.2"
4
+ VERSION = "3.0.0"
3
5
  end
data/lib/ast_transform.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "ast_transform/version"
4
- require 'ast_transform/instruction_sequence'
5
- require 'ast_transform/instruction_sequence/mixin'
6
- require 'ast_transform/instruction_sequence/bootsnap_mixin'
4
+ require "ast_transform/instruction_sequence"
5
+ require "ast_transform/instruction_sequence/mixin"
6
+ require "ast_transform/instruction_sequence/bootsnap_mixin"
7
7
 
8
8
  module ASTTransform
9
9
  DEFAULT_OUTPUT_PATH = Pathname.new("").join("tmp", "ast_transform").to_s
@@ -19,22 +19,18 @@ module ASTTransform
19
19
  end
20
20
 
21
21
  def install
22
- @installed ||= begin
23
- if defined?(Bootsnap) && ASTTransform::InstructionSequence.using_bootsnap_compilation?
24
- class << Bootsnap::CompileCache::ISeq
25
- prepend ::ASTTransform::InstructionSequence::BootsnapMixin
26
- end
27
- else
28
- class << RubyVM::InstructionSequence
29
- prepend ::ASTTransform::InstructionSequence::Mixin
30
- end
22
+ @installed ||= if defined?(Bootsnap) && ASTTransform::InstructionSequence.using_bootsnap_compilation?
23
+ class << Bootsnap::CompileCache::ISeq
24
+ prepend ::ASTTransform::InstructionSequence::BootsnapMixin
25
+ end
26
+ else
27
+ class << RubyVM::InstructionSequence
28
+ prepend ::ASTTransform::InstructionSequence::Mixin
31
29
  end
32
30
  end
33
31
  end
34
32
 
35
- def output_path=(path)
36
- @output_path = path
37
- end
33
+ attr_writer :output_path
38
34
 
39
35
  def output_path
40
36
  @output_path || DEFAULT_OUTPUT_PATH