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.
metadata CHANGED
@@ -1,113 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ast_transform
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.2
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean-Philippe Duchesne
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-02-25 00:00:00.000000000 Z
11
+ date: 2026-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '2.1'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '2.1'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '13.0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '13.0'
41
- - !ruby/object:Gem::Dependency
42
- name: minitest
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '5.14'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '5.14'
55
- - !ruby/object:Gem::Dependency
56
- name: minitest-reporters
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '1.4'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '1.4'
69
- - !ruby/object:Gem::Dependency
70
- name: pry
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0.14'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0.14'
83
- - !ruby/object:Gem::Dependency
84
- name: simplecov
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '0.22'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '0.22'
97
13
  - !ruby/object:Gem::Dependency
98
14
  name: parser
99
15
  requirement: !ruby/object:Gem::Requirement
100
16
  requirements:
101
17
  - - ">="
102
18
  - !ruby/object:Gem::Version
103
- version: '3.0'
19
+ version: '3.3'
104
20
  type: :runtime
105
21
  prerelease: false
106
22
  version_requirements: !ruby/object:Gem::Requirement
107
23
  requirements:
108
24
  - - ">="
109
25
  - !ruby/object:Gem::Version
110
- version: '3.0'
26
+ version: '3.3'
111
27
  - !ruby/object:Gem::Dependency
112
28
  name: prism
113
29
  requirement: !ruby/object:Gem::Requirement
@@ -128,14 +44,14 @@ dependencies:
128
44
  requirements:
129
45
  - - ">="
130
46
  - !ruby/object:Gem::Version
131
- version: '0.6'
47
+ version: '0.8'
132
48
  type: :runtime
133
49
  prerelease: false
134
50
  version_requirements: !ruby/object:Gem::Requirement
135
51
  requirements:
136
52
  - - ">="
137
53
  - !ruby/object:Gem::Version
138
- version: '0.6'
54
+ version: '0.8'
139
55
  description: An AST transformation framework.
140
56
  email:
141
57
  - jpduchesne89@gmail.com
@@ -146,9 +62,12 @@ files:
146
62
  - ".claude/CLAUDE.md"
147
63
  - ".cursor/rules/base.mdc"
148
64
  - ".cursor/rules/best-practices.mdc"
65
+ - ".github/CODEOWNERS"
149
66
  - ".github/workflows/ci.yml"
150
67
  - ".github/workflows/release.yml"
151
68
  - ".gitignore"
69
+ - ".rubocop.yml"
70
+ - ".ruby-version"
152
71
  - CHANGELOG.md
153
72
  - Gemfile
154
73
  - Gemfile.lock
@@ -158,6 +77,8 @@ files:
158
77
  - ast_transform.gemspec
159
78
  - bin/console
160
79
  - bin/setup
80
+ - dependencies.rb
81
+ - dev.yml
161
82
  - lib/ast_transform.rb
162
83
  - lib/ast_transform/abstract_transformation.rb
163
84
  - lib/ast_transform/instruction_sequence.rb
@@ -165,7 +86,13 @@ files:
165
86
  - lib/ast_transform/instruction_sequence/mixin.rb
166
87
  - lib/ast_transform/instruction_sequence/mixin_utils.rb
167
88
  - lib/ast_transform/kwargs_builder.rb
168
- - lib/ast_transform/source_map.rb
89
+ - lib/ast_transform/layout.rb
90
+ - lib/ast_transform/line_aligned_emitter.rb
91
+ - lib/ast_transform/node.rb
92
+ - lib/ast_transform/statement_renderer.rb
93
+ - lib/ast_transform/testing/assertions.rb
94
+ - lib/ast_transform/thunk.rb
95
+ - lib/ast_transform/thunk_lowering.rb
169
96
  - lib/ast_transform/transformation.rb
170
97
  - lib/ast_transform/transformation_helper.rb
171
98
  - lib/ast_transform/transformer.rb
@@ -182,7 +109,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
182
109
  requirements:
183
110
  - - ">="
184
111
  - !ruby/object:Gem::Version
185
- version: '3.2'
112
+ version: '3.3'
186
113
  required_rubygems_version: !ruby/object:Gem::Requirement
187
114
  requirements:
188
115
  - - ">="
@@ -1,232 +0,0 @@
1
- # frozen_string_literal: true
2
- require 'parser'
3
-
4
- module ASTTransform
5
- class SourceMap
6
- class << self
7
- # Registers the given SourceMap.
8
- #
9
- # @param source_map [SourceMap] The source map to be registered.
10
- #
11
- # @return [void]
12
- def register_source_map(source_map)
13
- source_maps[source_map.transformed_file_path] = source_map
14
-
15
- nil
16
- end
17
-
18
- # Retrieves the SourceMap for the given +file_path+.
19
- #
20
- # @param file_path [String] The transformed file path.
21
- #
22
- # @return [SourceMap|nil] The associated source map.
23
- def for_file_path(file_path)
24
- source_maps[file_path]
25
- end
26
-
27
- private
28
-
29
- def source_maps
30
- @@source_maps ||= {}
31
- end
32
- end
33
-
34
- # Constructs a new SourceMap instance.
35
- #
36
- # Note: +source_ranges_ast+ and +transformed_ranges_ast+ must be equivalent ASTs.
37
- #
38
- # @param source_file_path [String] The path to the source file.
39
- # @param transformed_file_path [String] The path to the transformed file.
40
- # @param source_ranges_ast [Parser::AST::Node] A transformed AST that contains the source code ranges.
41
- # @param transformed_ranges_ast [Parser::AST::Node] A transformed AST that contains the ranges for the executed
42
- # code.
43
- def initialize(source_file_path, transformed_file_path, source_ranges_ast, transformed_ranges_ast)
44
- @source_file_path = source_file_path
45
- @transformed_file_path = transformed_file_path
46
- @source_ranges_ast = source_ranges_ast
47
- @transformed_ranges_ast = transformed_ranges_ast
48
-
49
- @lines = Hash.new { |hash, key| hash[key] = [] }
50
- extract_source_map_data(@transformed_ranges_ast, [])
51
- @source_map = build_source_map.freeze
52
- end
53
-
54
- attr_reader :source_file_path, :transformed_file_path, :source_map
55
-
56
- # Retrieves the mapped line number for the given +line_number+.
57
- #
58
- # @param line_number [Integer] The line number in the executed code to be mapped to the source.
59
- #
60
- # @return [Integer|nil] The mapped line number, otherwise nil if not found.
61
- def line(line_number)
62
- @source_map[line_number]
63
- end
64
-
65
- # Retrieves the line count for the executed code.
66
- #
67
- # @return [Integer] The line count.
68
- def line_count
69
- @transformed_ranges_ast&.loc&.expression&.last_line || 0
70
- end
71
-
72
- private
73
-
74
- # Extracts SourceMap data from the given node.
75
- #
76
- # @param node [Parser::AST::Node] The node containing ranges for the executed code.
77
- #
78
- # @return [void]
79
- def extract_source_map_data(node, indexes)
80
- return false unless node&.is_a?(Parser::AST::Node)
81
-
82
- range = node.loc&.expression
83
-
84
- if range && range.line == range.last_line
85
- @lines[range.line] << indexes.dup
86
- end
87
-
88
- node.children.each.with_index do |child, index|
89
- extract_source_map_data(child, indexes.dup << index)
90
- end
91
-
92
- nil
93
- end
94
-
95
- # Builds the source map.
96
- #
97
- # @return [Hash] A Hash containing line numbers from executed code to source code.
98
- def build_source_map
99
- (1..line_count).each.with_object({}) {|it, hash| hash[it] = source_line(it) }
100
- end
101
-
102
- # Retrieves the source line for the given +line_number+ in the executed code.
103
- #
104
- # @param line_number [Integer] The line number in the executed code.
105
- #
106
- # @return [Integer|nil] The line number in the source code, or nil if cannot be mapped.
107
- def source_line(line_number)
108
- if @lines.key?(line_number)
109
- @lines[line_number].each do |dig_array|
110
- source_node = approximate_dig_last_valid_node(@source_ranges_ast, dig_array)
111
- next unless source_node
112
-
113
- range = search_range(source_node, 1)
114
- return range.line if range
115
- end
116
- end
117
-
118
- nil
119
- end
120
-
121
- # Recursively look for node represented by +indexes+ in +node+. If not found, goes back +depth+ nodes and search for
122
- # the node pointed to by +indexes+.
123
- #
124
- # @param node [Parser::AST::Node] The node to search into. This must be a node in the +@source_ranges_ast+.
125
- # @param indexes [Array<Integer>] Child indexes pointing to the node we're looking for in +node+.
126
- # @param depth [Integer] Number of nodes to go up to search for the node pointed to by +indexes+.
127
- #
128
- # @return [Parser::AST::Node|nil] The node found, nil otherwise.
129
- def approximate_dig_last_valid_node(node, indexes, depth = 1)
130
- return node if indexes.empty?
131
-
132
- result = dig_node(node, indexes)
133
- return result if result.is_a?(Parser::AST::Node) || depth <= 0
134
-
135
- queried_node = dig_last_valid_node(@transformed_ranges_ast, indexes)
136
-
137
- last_known_index = dig_last_valid_node_index(node, indexes[0...-depth])
138
- query_indexes = indexes[0...last_known_index]
139
-
140
- last_known_node = dig_node(node, query_indexes)
141
-
142
- search_node(last_known_node, queried_node)
143
- end
144
-
145
- # Recursively search the children of +node+ for an equivalent +queried_node+.
146
- #
147
- # @param node [Parser::AST::Node] The current node to search in.
148
- # @param queried_node [Parser::AST::Node] The equivalent node to search for.
149
- #
150
- # @return [Parser::AST::Node|nil] The found node from the +node+ graph, nil otherwise.
151
- def search_node(node, queried_node)
152
- return unless node&.is_a?(Parser::AST::Node)
153
- return node if node == queried_node
154
-
155
- node.children.each do |child_node|
156
- result = search_node(child_node, queried_node)
157
- return result if result
158
- end
159
-
160
- nil
161
- end
162
-
163
- # Recursively search the given +node+ for a range.
164
- #
165
- # @param node [Parser::AST::Node] The current node to search in.
166
- # @param max_range [Integer|nil] The max range to consider valid. Nil means any range is valid. If 1, only ranges
167
- # which span one line will be considered, etc...
168
- #
169
- # @return [Parser::Source::Range|nil] The range, or nil if no range was found. This occurs when the tree contains
170
- # no ranges, i.e. they're all virtually built nodes.
171
- def search_range(node, max_range = nil)
172
- return unless node&.is_a?(Parser::AST::Node)
173
-
174
- range = node.loc&.expression
175
- if range && max_range && range.last_line - range.line < max_range || range && max_range.nil?
176
- return range
177
- else
178
- node.children.each do |child_node|
179
- result = search_range(child_node, max_range)
180
- return result if result
181
- end
182
- end
183
-
184
- nil
185
- end
186
-
187
- # Finds the index for the last valid node represented by +indexes+ in the children of +node+.
188
- #
189
- # @param node [Parser::AST::Node] The current node to search in.
190
- # @param indexes [Array<Integer>] The array of indexes pointing to the child node to be retrieved from +node+.
191
- #
192
- # @return [Integer|nil] The index of the node if found, nil otherwise.
193
- def dig_last_valid_node_index(node, indexes)
194
- return if indexes.empty?
195
-
196
- result = dig_node(node, indexes)
197
- current_index = indexes&.size
198
- return current_index if result.is_a?(Parser::AST::Node)
199
-
200
- dig_last_valid_node_index(node, indexes[0...-1])
201
- end
202
-
203
- # Recursively look for the node represented by +indexes+ in the children of +node+. If not found, returns the last
204
- # valid node.
205
- #
206
- # @param node [Parser::AST::Node] The node to look into.
207
- # @param indexes [Array<Integer>] The array of indexes pointing to the child node to be retrieved from +node+.
208
- #
209
- # @return [Parser::AST::Node|nil] The node if found, nil otherwise.
210
- def dig_last_valid_node(node, indexes)
211
- return node if indexes.empty?
212
-
213
- result = dig_node(node, indexes)
214
- return result if result.is_a?(Parser::AST::Node)
215
-
216
- dig_last_valid_node(node, indexes[0...-1])
217
- end
218
-
219
- # Recursively look for the node represented by +indexes+ in the children of +node+.
220
- #
221
- # @param node [Parser::AST::Node] The node to look into.
222
- # @param indexes [Array<Integer>] The array of indexes pointing to the child node to be retrieved from +node+.
223
- #
224
- # @return [Parser::AST::Node|nil] The node if found, nil otherwise.
225
- def dig_node(node, indexes)
226
- indexes.inject(node) do |node, index|
227
- return nil unless node.is_a?(Parser::AST::Node)
228
- node.children[index]
229
- end
230
- end
231
- end
232
- end