ffast 0.2.2 → 0.2.4
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/.agents/fast-pattern-expert/SKILL.md +71 -0
- data/.github/workflows/release.yml +27 -0
- data/.github/workflows/ruby.yml +34 -0
- data/.gitignore +2 -0
- data/Fastfile +105 -18
- data/README.md +21 -7
- data/bin/console +1 -1
- data/bin/fast-experiment +3 -0
- data/bin/fast-mcp +7 -0
- data/fast.gemspec +1 -3
- data/ideia_blog_post.md +36 -0
- data/lib/fast/cli.rb +74 -23
- data/lib/fast/experiment.rb +19 -2
- data/lib/fast/git.rb +1 -1
- data/lib/fast/mcp_server.rb +341 -0
- data/lib/fast/node.rb +258 -0
- data/lib/fast/prism_adapter.rb +327 -0
- data/lib/fast/rewriter.rb +64 -10
- data/lib/fast/scan.rb +207 -0
- data/lib/fast/shortcut.rb +16 -4
- data/lib/fast/source.rb +116 -0
- data/lib/fast/source_rewriter.rb +153 -0
- data/lib/fast/sql/rewriter.rb +36 -7
- data/lib/fast/sql.rb +15 -17
- data/lib/fast/summary.rb +440 -0
- data/lib/fast/version.rb +1 -1
- data/lib/fast.rb +218 -101
- data/mkdocs.yml +19 -4
- data/requirements-docs.txt +3 -0
- metadata +18 -59
- data/docs/command_line.md +0 -238
- data/docs/editors-integration.md +0 -46
- data/docs/experiments.md +0 -155
- data/docs/git.md +0 -115
- data/docs/ideas.md +0 -70
- data/docs/index.md +0 -404
- data/docs/pry-integration.md +0 -27
- data/docs/research.md +0 -93
- data/docs/shortcuts.md +0 -323
- data/docs/similarity_tutorial.md +0 -176
- data/docs/sql-support.md +0 -253
- data/docs/syntax.md +0 -395
- data/docs/videos.md +0 -16
- data/docs/walkthrough.md +0 -135
- data/examples/build_stubbed_and_let_it_be_experiment.rb +0 -51
- data/examples/experimental_replacement.rb +0 -46
- data/examples/find_usage.rb +0 -26
- data/examples/let_it_be_experiment.rb +0 -11
- data/examples/method_complexity.rb +0 -37
- data/examples/search_duplicated.rb +0 -15
- data/examples/similarity_research.rb +0 -58
- data/examples/simple_rewriter.rb +0 -6
- data/experiments/let_it_be_experiment.rb +0 -9
- data/experiments/remove_useless_hook.rb +0 -9
- data/experiments/replace_create_with_build_stubbed.rb +0 -10
data/lib/fast/node.rb
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fast
|
|
4
|
+
class Node
|
|
5
|
+
attr_reader :type, :children, :loc
|
|
6
|
+
|
|
7
|
+
def initialize(type, children = [], properties = {})
|
|
8
|
+
@type = type.to_sym
|
|
9
|
+
@children = Array(children).freeze
|
|
10
|
+
@loc = properties[:location]
|
|
11
|
+
assign_parents!
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def set_parent(node, parent)
|
|
16
|
+
NODE_PARENTS[node] = parent
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def parent_for(node)
|
|
20
|
+
NODE_PARENTS[node]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [String] with path of the file or simply buffer name.
|
|
25
|
+
def buffer_name
|
|
26
|
+
expression.source_buffer.name
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @return [Fast::Source::Range] from the expression
|
|
30
|
+
def expression
|
|
31
|
+
loc.expression
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Backward-compatible alias for callers that still use `location`.
|
|
35
|
+
def location
|
|
36
|
+
loc
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [String] with the content of the #expression
|
|
40
|
+
def source
|
|
41
|
+
expression.source
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @return [Boolean] true if a file exists with the #buffer_name
|
|
45
|
+
def from_file?
|
|
46
|
+
File.exist?(buffer_name)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def each_child_node
|
|
50
|
+
return enum_for(:each_child_node) unless block_given?
|
|
51
|
+
|
|
52
|
+
children.select { |child| Fast.ast_node?(child) }.each { |child| yield child }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def each_descendant(*types, &block)
|
|
56
|
+
return enum_for(:each_descendant, *types) unless block_given?
|
|
57
|
+
|
|
58
|
+
each_child_node do |child|
|
|
59
|
+
yield child if types.empty? || types.include?(child.type)
|
|
60
|
+
child.each_descendant(*types, &block) if child.respond_to?(:each_descendant)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def root?
|
|
65
|
+
parent.nil?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def parent
|
|
69
|
+
self.class.parent_for(self)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def updated(type = nil, children = nil, properties = nil)
|
|
73
|
+
updated_node = self.class.new(
|
|
74
|
+
type || self.type,
|
|
75
|
+
children || self.children,
|
|
76
|
+
{ location: properties&.fetch(:location, loc) || loc }
|
|
77
|
+
)
|
|
78
|
+
updated_node.send(:assign_parents!)
|
|
79
|
+
updated_node
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def ==(other)
|
|
83
|
+
Fast.ast_node?(other) && type == other.type && children == other.children
|
|
84
|
+
end
|
|
85
|
+
alias eql? ==
|
|
86
|
+
|
|
87
|
+
def hash
|
|
88
|
+
[type, children].hash
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def to_a
|
|
92
|
+
children.dup
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def <<(child)
|
|
96
|
+
updated(nil, children + [child])
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def deconstruct
|
|
100
|
+
to_a
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def to_ast
|
|
104
|
+
self
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def to_sexp
|
|
108
|
+
format_node(:sexp)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def to_s
|
|
112
|
+
to_sexp
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def inspect
|
|
116
|
+
format_node(:inspect)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
120
|
+
type_query_method?(method_name) || super
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def method_missing(method_name, *args, &block)
|
|
124
|
+
return type == type_query_name(method_name) if type_query_method?(method_name) && args.empty? && !block
|
|
125
|
+
|
|
126
|
+
super
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @return [Array<String>] with authors from the current expression range
|
|
130
|
+
def blame_authors
|
|
131
|
+
`git blame -L #{expression.first_line},#{expression.last_line} #{buffer_name}`.lines.map do |line|
|
|
132
|
+
line.split('(')[1].split(/\d+/).first.strip
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# @return [String] with the first element from #blame_authors
|
|
137
|
+
def author
|
|
138
|
+
blame_authors.first
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Search recursively into a node and its children using a pattern.
|
|
142
|
+
# @param [String] pattern
|
|
143
|
+
# @param [Array] *args extra arguments to interpolate in the pattern.
|
|
144
|
+
# @return [Array<Fast::Node>>] with files and results
|
|
145
|
+
def search(pattern, *args)
|
|
146
|
+
Fast.search(pattern, self, *args)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Captures elements from search recursively
|
|
150
|
+
# @param [String] pattern
|
|
151
|
+
# @param [Array] *args extra arguments to interpolate in the pattern.
|
|
152
|
+
# @return [Array<Fast::Node>>] with files and results
|
|
153
|
+
def capture(pattern, *args)
|
|
154
|
+
Fast.capture(pattern, self, *args)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
def assign_parents!
|
|
160
|
+
each_child_node do |child|
|
|
161
|
+
self.class.set_parent(child, self)
|
|
162
|
+
child.send(:assign_parents!) if child.respond_to?(:assign_parents!, true)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def type_query_method?(method_name)
|
|
167
|
+
method_name.to_s.end_with?('_type?')
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def type_query_name(method_name)
|
|
171
|
+
method_name.to_s.delete_suffix('_type?').to_sym
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def format_node(style)
|
|
175
|
+
opener = style == :inspect ? "s(:#{type}" : "(#{type}"
|
|
176
|
+
separator = style == :inspect ? ', ' : ' '
|
|
177
|
+
inline_children = children.map { |child| format_atom(child, style, inline: true) }
|
|
178
|
+
return "#{opener})" if inline_children.empty?
|
|
179
|
+
|
|
180
|
+
if children.none? { |child| Fast.ast_node?(child) } && inline_children.all? { |child| !child.include?("\n") }
|
|
181
|
+
return "#{opener}#{separator}#{inline_children.join(separator)})"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
lines = [opener]
|
|
185
|
+
current_line = +''
|
|
186
|
+
|
|
187
|
+
children.each do |child|
|
|
188
|
+
formatted = format_atom(child, style, inline: false)
|
|
189
|
+
if formatted.include?("\n")
|
|
190
|
+
flush_current_line!(lines, current_line, style)
|
|
191
|
+
current_line.clear
|
|
192
|
+
lines << indent_multiline(formatted)
|
|
193
|
+
elsif Fast.ast_node?(child)
|
|
194
|
+
flush_current_line!(lines, current_line, style)
|
|
195
|
+
current_line = formatted.dup
|
|
196
|
+
else
|
|
197
|
+
current_line << separator unless current_line.empty?
|
|
198
|
+
current_line << formatted
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
flush_current_line!(lines, current_line, style)
|
|
203
|
+
if lines.length > 2 && scalar_line?(lines[1], style)
|
|
204
|
+
lines[0] = "#{lines[0]}#{separator}#{lines[1].strip}"
|
|
205
|
+
lines.delete_at(1)
|
|
206
|
+
end
|
|
207
|
+
lines[-1] = "#{lines[-1]})"
|
|
208
|
+
lines.join("\n")
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def flush_current_line!(lines, current_line, style)
|
|
212
|
+
return if current_line.empty?
|
|
213
|
+
|
|
214
|
+
line = style == :inspect ? " #{current_line}," : " #{current_line}"
|
|
215
|
+
lines << line
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def indent_multiline(text)
|
|
219
|
+
text.lines.map { |line| " #{line}" }.join
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def format_atom(atom, style, inline:)
|
|
223
|
+
if Fast.ast_node?(atom)
|
|
224
|
+
text = style == :inspect ? atom.inspect : atom.to_sexp
|
|
225
|
+
return text if inline || !text.include?("\n")
|
|
226
|
+
|
|
227
|
+
style == :inspect ? trim_trailing_comma(text) : text
|
|
228
|
+
else
|
|
229
|
+
format_scalar(atom, style)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def trim_trailing_comma(text)
|
|
234
|
+
lines = text.lines
|
|
235
|
+
lines[-1] = lines[-1].sub(/,\z/, '')
|
|
236
|
+
lines.join
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def scalar_line?(line, style)
|
|
240
|
+
stripped = line.strip
|
|
241
|
+
return false if stripped.empty?
|
|
242
|
+
|
|
243
|
+
opener = style == :inspect ? 's(' : '('
|
|
244
|
+
!stripped.start_with?(opener)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def format_scalar(value, _style)
|
|
248
|
+
case value
|
|
249
|
+
when Symbol, String
|
|
250
|
+
value.inspect
|
|
251
|
+
when Array
|
|
252
|
+
"[#{value.map { |item| format_atom(item, :sexp, inline: true) }.join(', ')}]"
|
|
253
|
+
else
|
|
254
|
+
value.inspect
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'prism'
|
|
4
|
+
require 'fast/source'
|
|
5
|
+
|
|
6
|
+
module Fast
|
|
7
|
+
module PrismAdapter
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
class Location
|
|
11
|
+
attr_accessor :node
|
|
12
|
+
attr_reader :expression
|
|
13
|
+
|
|
14
|
+
def initialize(buffer_name, source, start_offset, end_offset, prism_node: nil)
|
|
15
|
+
@buffer_name = buffer_name
|
|
16
|
+
@source = source
|
|
17
|
+
@prism_node = prism_node
|
|
18
|
+
buffer = Fast::Source.buffer(buffer_name, source: source)
|
|
19
|
+
@expression = Fast::Source.range(
|
|
20
|
+
buffer,
|
|
21
|
+
character_offset(source, start_offset),
|
|
22
|
+
character_offset(source, end_offset)
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def name
|
|
27
|
+
return unless @prism_node&.respond_to?(:name_loc) && @prism_node.name_loc
|
|
28
|
+
|
|
29
|
+
range_for(@prism_node.name_loc)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def selector
|
|
33
|
+
return unless @prism_node&.respond_to?(:message_loc) && @prism_node.message_loc
|
|
34
|
+
|
|
35
|
+
range_for(@prism_node.message_loc)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def operator
|
|
39
|
+
return unless @prism_node&.respond_to?(:operator_loc) && @prism_node.operator_loc
|
|
40
|
+
|
|
41
|
+
range_for(@prism_node.operator_loc)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def character_offset(source, byte_offset)
|
|
47
|
+
source.byteslice(0, byte_offset).to_s.length
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def range_for(prism_location)
|
|
51
|
+
buffer = Fast::Source.buffer(@buffer_name, source: @source)
|
|
52
|
+
Fast::Source.range(
|
|
53
|
+
buffer,
|
|
54
|
+
character_offset(@source, prism_location.start_offset),
|
|
55
|
+
character_offset(@source, prism_location.end_offset)
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
class Node < Fast::Node
|
|
61
|
+
def initialize(type, children:, loc:)
|
|
62
|
+
super(type, Array(children), location: loc)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def updated(type = nil, children = nil, properties = nil)
|
|
66
|
+
self.class.new(
|
|
67
|
+
type || self.type,
|
|
68
|
+
children: children || self.children,
|
|
69
|
+
loc: properties&.fetch(:location, loc) || loc
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def parse(source, buffer_name: '(string)')
|
|
75
|
+
result = Prism.parse(source)
|
|
76
|
+
return unless result.success?
|
|
77
|
+
|
|
78
|
+
adapt(result.value, source, buffer_name)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def adapt(node, source, buffer_name)
|
|
82
|
+
return if node.nil?
|
|
83
|
+
|
|
84
|
+
case node
|
|
85
|
+
when Prism::ProgramNode
|
|
86
|
+
statements = adapt_statements(node.statements, source, buffer_name)
|
|
87
|
+
statements.is_a?(Node) ? statements : build_node(:begin, statements, node, source, buffer_name)
|
|
88
|
+
when Prism::StatementsNode
|
|
89
|
+
adapt_statements(node, source, buffer_name)
|
|
90
|
+
when Prism::ModuleNode
|
|
91
|
+
build_node(:module, [adapt(node.constant_path, source, buffer_name), adapt(node.body, source, buffer_name)], node, source, buffer_name)
|
|
92
|
+
when Prism::ClassNode
|
|
93
|
+
build_node(:class, [adapt(node.constant_path, source, buffer_name), adapt(node.superclass, source, buffer_name), adapt(node.body, source, buffer_name)], node, source, buffer_name)
|
|
94
|
+
when Prism::SingletonClassNode
|
|
95
|
+
build_node(:sclass, [adapt(node.expression, source, buffer_name), adapt(node.body, source, buffer_name)], node, source, buffer_name)
|
|
96
|
+
when Prism::DefNode
|
|
97
|
+
if node.receiver
|
|
98
|
+
build_node(:defs, [adapt(node.receiver, source, buffer_name), node.name, adapt_parameters(node.parameters, source, buffer_name), adapt(node.body, source, buffer_name)], node, source, buffer_name)
|
|
99
|
+
else
|
|
100
|
+
build_node(:def, [node.name, adapt_parameters(node.parameters, source, buffer_name), adapt(node.body, source, buffer_name)], node, source, buffer_name)
|
|
101
|
+
end
|
|
102
|
+
when Prism::BlockNode
|
|
103
|
+
return nil unless node.respond_to?(:call)
|
|
104
|
+
|
|
105
|
+
build_node(:block, [adapt_call_node(node.call, source, buffer_name), adapt_block_parameters(node.parameters, source, buffer_name), adapt(node.body, source, buffer_name)], node, source, buffer_name)
|
|
106
|
+
when Prism::CallNode
|
|
107
|
+
if node.respond_to?(:block) && node.block.is_a?(Prism::BlockNode)
|
|
108
|
+
return build_node(
|
|
109
|
+
:block,
|
|
110
|
+
[
|
|
111
|
+
adapt_call_node(node, source, buffer_name),
|
|
112
|
+
adapt_block_parameters(node.block.parameters, source, buffer_name),
|
|
113
|
+
adapt(node.block.body, source, buffer_name)
|
|
114
|
+
],
|
|
115
|
+
node,
|
|
116
|
+
source,
|
|
117
|
+
buffer_name
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
adapt_call_node(node, source, buffer_name)
|
|
122
|
+
when Prism::ParenthesesNode
|
|
123
|
+
adapt(node.body, source, buffer_name)
|
|
124
|
+
when Prism::RangeNode
|
|
125
|
+
build_node(node.exclude_end? ? :erange : :irange, [adapt(node.left, source, buffer_name), adapt(node.right, source, buffer_name)], node, source, buffer_name)
|
|
126
|
+
when Prism::BlockArgumentNode
|
|
127
|
+
build_node(:block_pass, [adapt(node.expression, source, buffer_name)], node, source, buffer_name)
|
|
128
|
+
when Prism::ConstantPathNode
|
|
129
|
+
build_const_path(node, source, buffer_name)
|
|
130
|
+
when Prism::ConstantReadNode
|
|
131
|
+
build_node(:const, [nil, node.name], node, source, buffer_name)
|
|
132
|
+
when Prism::ConstantWriteNode
|
|
133
|
+
build_node(:casgn, [nil, node.name, adapt(node.value, source, buffer_name)], node, source, buffer_name)
|
|
134
|
+
when Prism::SymbolNode
|
|
135
|
+
build_node(:sym, [node.unescaped], node, source, buffer_name)
|
|
136
|
+
when Prism::StringNode
|
|
137
|
+
build_node(:str, [node.unescaped], node, source, buffer_name)
|
|
138
|
+
when Prism::XStringNode
|
|
139
|
+
build_node(:xstr, [node.unescaped], node, source, buffer_name)
|
|
140
|
+
when Prism::InterpolatedStringNode
|
|
141
|
+
build_node(:dstr, node.parts.filter_map { |part| adapt(part, source, buffer_name) }, node, source, buffer_name)
|
|
142
|
+
when Prism::InterpolatedXStringNode
|
|
143
|
+
build_node(:dxstr, node.parts.filter_map { |part| adapt(part, source, buffer_name) }, node, source, buffer_name)
|
|
144
|
+
when Prism::InterpolatedSymbolNode
|
|
145
|
+
build_node(:dsym, node.parts.filter_map { |part| adapt(part, source, buffer_name) }, node, source, buffer_name)
|
|
146
|
+
when Prism::ArrayNode
|
|
147
|
+
build_node(:array, node.elements.map { |child| adapt(child, source, buffer_name) }, node, source, buffer_name)
|
|
148
|
+
when Prism::HashNode
|
|
149
|
+
build_node(:hash, node.elements.map { |child| adapt(child, source, buffer_name) }, node, source, buffer_name)
|
|
150
|
+
when Prism::KeywordHashNode
|
|
151
|
+
build_node(:hash, node.elements.map { |child| adapt(child, source, buffer_name) }, node, source, buffer_name)
|
|
152
|
+
when Prism::AssocNode
|
|
153
|
+
build_node(:pair, [adapt(node.key, source, buffer_name), adapt(node.value, source, buffer_name)], node, source, buffer_name)
|
|
154
|
+
when Prism::SelfNode
|
|
155
|
+
build_node(:self, [], node, source, buffer_name)
|
|
156
|
+
when Prism::LocalVariableReadNode
|
|
157
|
+
build_node(:lvar, [node.name], node, source, buffer_name)
|
|
158
|
+
when Prism::InstanceVariableReadNode
|
|
159
|
+
build_node(:ivar, [node.name], node, source, buffer_name)
|
|
160
|
+
when Prism::InstanceVariableWriteNode, Prism::InstanceVariableOrWriteNode
|
|
161
|
+
build_node(:ivasgn, [node.name, adapt(node.value, source, buffer_name)], node, source, buffer_name)
|
|
162
|
+
when Prism::LocalVariableWriteNode, Prism::LocalVariableOrWriteNode
|
|
163
|
+
build_node(:lvasgn, [node.name, adapt(node.value, source, buffer_name)], node, source, buffer_name)
|
|
164
|
+
when Prism::LocalVariableOperatorWriteNode
|
|
165
|
+
build_node(
|
|
166
|
+
:op_asgn,
|
|
167
|
+
[
|
|
168
|
+
build_node(:lvasgn, [node.name], node, source, buffer_name),
|
|
169
|
+
(node.respond_to?(:binary_operator) ? node.binary_operator : node.operator),
|
|
170
|
+
adapt(node.value, source, buffer_name)
|
|
171
|
+
],
|
|
172
|
+
node,
|
|
173
|
+
source,
|
|
174
|
+
buffer_name
|
|
175
|
+
)
|
|
176
|
+
when Prism::IntegerNode
|
|
177
|
+
build_node(:int, [node.value], node, source, buffer_name)
|
|
178
|
+
when Prism::FloatNode
|
|
179
|
+
build_node(:float, [node.value], node, source, buffer_name)
|
|
180
|
+
when Prism::TrueNode
|
|
181
|
+
build_node(:true, [], node, source, buffer_name)
|
|
182
|
+
when Prism::FalseNode
|
|
183
|
+
build_node(:false, [], node, source, buffer_name)
|
|
184
|
+
when Prism::NilNode
|
|
185
|
+
build_node(:nil, [], node, source, buffer_name)
|
|
186
|
+
when Prism::IfNode
|
|
187
|
+
build_node(:if, [adapt(node.predicate, source, buffer_name), adapt(node.statements, source, buffer_name), adapt(node.consequent, source, buffer_name)], node, source, buffer_name)
|
|
188
|
+
when Prism::UnlessNode
|
|
189
|
+
build_node(:if, [adapt(node.predicate, source, buffer_name), adapt(node.consequent, source, buffer_name), adapt(node.statements, source, buffer_name)], node, source, buffer_name)
|
|
190
|
+
when Prism::RescueModifierNode
|
|
191
|
+
build_node(:rescue, [adapt(node.expression, source, buffer_name), build_node(:resbody, [nil, nil, adapt(node.rescue_expression, source, buffer_name)], node, source, buffer_name), nil], node, source, buffer_name)
|
|
192
|
+
when Prism::CaseNode
|
|
193
|
+
children = [adapt(node.predicate, source, buffer_name)]
|
|
194
|
+
children.concat(node.conditions.map { |condition| adapt(condition, source, buffer_name) })
|
|
195
|
+
else_clause =
|
|
196
|
+
if node.respond_to?(:else_clause)
|
|
197
|
+
node.else_clause
|
|
198
|
+
elsif node.respond_to?(:consequent)
|
|
199
|
+
node.consequent
|
|
200
|
+
end
|
|
201
|
+
children << adapt_else_clause(else_clause, source, buffer_name) if else_clause
|
|
202
|
+
build_node(:case, children, node, source, buffer_name)
|
|
203
|
+
when Prism::WhenNode
|
|
204
|
+
condition =
|
|
205
|
+
if node.conditions.length == 1
|
|
206
|
+
adapt(node.conditions.first, source, buffer_name)
|
|
207
|
+
else
|
|
208
|
+
build_node(:array, node.conditions.map { |child| adapt(child, source, buffer_name) }, node, source, buffer_name)
|
|
209
|
+
end
|
|
210
|
+
build_node(:when, [condition, adapt(node.statements, source, buffer_name)].compact, node, source, buffer_name)
|
|
211
|
+
when Prism::ElseNode
|
|
212
|
+
adapt_else_clause(node, source, buffer_name)
|
|
213
|
+
when Prism::BeginNode, Prism::EmbeddedStatementsNode
|
|
214
|
+
statements = adapt_statements(node.statements, source, buffer_name)
|
|
215
|
+
children = statements.is_a?(Node) && statements.type == :begin ? statements.children : Array(statements)
|
|
216
|
+
build_node(:begin, children, node, source, buffer_name)
|
|
217
|
+
when Prism::EmbeddedVariableNode
|
|
218
|
+
build_node(:begin, [adapt(node.variable, source, buffer_name)].compact, node, source, buffer_name)
|
|
219
|
+
when Prism::LambdaNode
|
|
220
|
+
build_node(:lambda, [adapt_block_parameters(node.parameters, source, buffer_name), adapt(node.body, source, buffer_name)], node, source, buffer_name)
|
|
221
|
+
else
|
|
222
|
+
nil
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def adapt_statements(node, source, buffer_name)
|
|
227
|
+
return nil unless node
|
|
228
|
+
|
|
229
|
+
children = node.body.filter_map { |child| adapt(child, source, buffer_name) }
|
|
230
|
+
return nil if children.empty?
|
|
231
|
+
return children.first if children.one?
|
|
232
|
+
|
|
233
|
+
build_node(:begin, children, node, source, buffer_name)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def adapt_parameters(node, source, buffer_name)
|
|
237
|
+
return build_node(:args, [], nil, source, buffer_name) unless node
|
|
238
|
+
|
|
239
|
+
children = []
|
|
240
|
+
children.concat(node.requireds.map { |child| adapt_required_parameter(child, source, buffer_name) }) if node.respond_to?(:requireds)
|
|
241
|
+
children.concat(node.optionals.map { |child| build_node(:optarg, [parameter_name(child), adapt(child.value, source, buffer_name)], child, source, buffer_name) }) if node.respond_to?(:optionals)
|
|
242
|
+
children << build_node(:restarg, [parameter_name(node.rest)], node.rest, source, buffer_name) if node.respond_to?(:rest) && node.rest
|
|
243
|
+
children.concat(node.posts.map { |child| adapt_required_parameter(child, source, buffer_name) }) if node.respond_to?(:posts)
|
|
244
|
+
children.concat(node.keywords.map { |child| adapt_keyword_parameter(child, source, buffer_name) }) if node.respond_to?(:keywords)
|
|
245
|
+
children << build_node(:kwrestarg, [parameter_name(node.keyword_rest)], node.keyword_rest, source, buffer_name) if node.respond_to?(:keyword_rest) && node.keyword_rest
|
|
246
|
+
children << build_node(:blockarg, [parameter_name(node.block)], node.block, source, buffer_name) if node.respond_to?(:block) && node.block
|
|
247
|
+
build_node(:args, children, node, source, buffer_name)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def adapt_block_parameters(node, source, buffer_name)
|
|
251
|
+
return build_node(:args, [], nil, source, buffer_name) unless node
|
|
252
|
+
|
|
253
|
+
params = node.respond_to?(:parameters) ? node.parameters : node
|
|
254
|
+
adapt_parameters(params, source, buffer_name)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def adapt_required_parameter(child, source, buffer_name)
|
|
258
|
+
if child.is_a?(Prism::MultiTargetNode)
|
|
259
|
+
mlhs_children = child.lefts.map { |c| adapt_required_parameter(c, source, buffer_name) }
|
|
260
|
+
mlhs_children << build_node(:restarg, [parameter_name(child.rest)], child.rest, source, buffer_name) if child.respond_to?(:rest) && child.rest
|
|
261
|
+
mlhs_children.concat(child.rights.map { |c| adapt_required_parameter(c, source, buffer_name) }) if child.respond_to?(:rights)
|
|
262
|
+
build_node(:mlhs, mlhs_children, child, source, buffer_name)
|
|
263
|
+
else
|
|
264
|
+
build_node(:arg, [parameter_name(child)], child, source, buffer_name)
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def adapt_keyword_parameter(node, source, buffer_name)
|
|
269
|
+
case node
|
|
270
|
+
when Prism::RequiredKeywordParameterNode
|
|
271
|
+
build_node(:kwarg, [parameter_name(node)], node, source, buffer_name)
|
|
272
|
+
when Prism::OptionalKeywordParameterNode
|
|
273
|
+
build_node(:kwoptarg, [parameter_name(node), adapt(node.value, source, buffer_name)], node, source, buffer_name)
|
|
274
|
+
else
|
|
275
|
+
build_node(:arg, [parameter_name(node)], node, source, buffer_name)
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def adapt_call_node(node, source, buffer_name)
|
|
280
|
+
children = [adapt(node.receiver, source, buffer_name), node.name]
|
|
281
|
+
children.concat(node.arguments&.arguments.to_a.map { |arg| adapt(arg, source, buffer_name) } || [])
|
|
282
|
+
children << adapt(node.block, source, buffer_name) if node.respond_to?(:block) && node.block && !node.block.is_a?(Prism::BlockNode)
|
|
283
|
+
return build_node(:send, children, node, source, buffer_name) unless node.respond_to?(:block) && node.block.is_a?(Prism::BlockNode)
|
|
284
|
+
|
|
285
|
+
end_offset = node.block.location.start_offset
|
|
286
|
+
while end_offset > node.location.start_offset && source.byteslice(end_offset - 1, 1)&.match?(/\s/)
|
|
287
|
+
end_offset -= 1
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
loc = Location.new(
|
|
291
|
+
buffer_name,
|
|
292
|
+
source,
|
|
293
|
+
node.location.start_offset,
|
|
294
|
+
end_offset,
|
|
295
|
+
prism_node: node
|
|
296
|
+
)
|
|
297
|
+
send_node = Node.new(:send, children: children, loc: loc)
|
|
298
|
+
loc.node = send_node
|
|
299
|
+
send_node
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def adapt_else_clause(node, source, buffer_name)
|
|
303
|
+
adapt(node.statements, source, buffer_name)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def parameter_name(node)
|
|
307
|
+
node.respond_to?(:name) ? node.name : nil
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def build_const_path(node, source, buffer_name)
|
|
311
|
+
parent = node.parent ? adapt(node.parent, source, buffer_name) : nil
|
|
312
|
+
build_node(:const, [parent, node.child.name], node, source, buffer_name)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def build_node(type, children, prism_node, source, buffer_name)
|
|
316
|
+
loc =
|
|
317
|
+
if prism_node
|
|
318
|
+
Location.new(buffer_name, source, prism_node.location.start_offset, prism_node.location.end_offset, prism_node: prism_node)
|
|
319
|
+
else
|
|
320
|
+
Location.new(buffer_name, source, 0, 0)
|
|
321
|
+
end
|
|
322
|
+
node = Node.new(type, children: children, loc: loc)
|
|
323
|
+
loc.node = node
|
|
324
|
+
node
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|