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.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -0
- data/.github/workflows/ci.yml +11 -1
- data/.gitignore +4 -1
- data/.rubocop.yml +5 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile +12 -1
- data/Gemfile.lock +38 -11
- data/README.md +54 -2
- data/Rakefile +8 -7
- data/ast_transform.gemspec +11 -12
- data/bin/console +1 -0
- data/dependencies.rb +11 -0
- data/dev.yml +11 -0
- data/lib/ast_transform/abstract_transformation.rb +10 -1
- data/lib/ast_transform/instruction_sequence/bootsnap_mixin.rb +4 -4
- data/lib/ast_transform/instruction_sequence/mixin.rb +10 -6
- data/lib/ast_transform/instruction_sequence/mixin_utils.rb +1 -1
- data/lib/ast_transform/instruction_sequence.rb +4 -3
- data/lib/ast_transform/kwargs_builder.rb +10 -8
- data/lib/ast_transform/layout.rb +64 -0
- data/lib/ast_transform/line_aligned_emitter.rb +224 -0
- data/lib/ast_transform/node.rb +48 -0
- data/lib/ast_transform/statement_renderer.rb +76 -0
- data/lib/ast_transform/testing/assertions.rb +97 -0
- data/lib/ast_transform/thunk.rb +55 -0
- data/lib/ast_transform/thunk_lowering.rb +240 -0
- data/lib/ast_transform/transformation.rb +19 -22
- data/lib/ast_transform/transformation_helper.rb +115 -4
- data/lib/ast_transform/transformer.rb +19 -25
- data/lib/ast_transform/version.rb +3 -1
- data/lib/ast_transform.rb +11 -15
- metadata +19 -92
- data/lib/ast_transform/source_map.rb +0 -232
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:
|
|
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-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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/
|
|
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.
|
|
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
|