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.rb
CHANGED
|
@@ -1,28 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'fileutils'
|
|
4
|
-
require 'astrolabe/builder'
|
|
5
|
-
require_relative 'fast/rewriter'
|
|
6
|
-
|
|
7
|
-
# suppress output to avoid parser gem warnings'
|
|
8
|
-
def suppress_output
|
|
9
|
-
original_stdout = $stdout.clone
|
|
10
|
-
original_stderr = $stderr.clone
|
|
11
|
-
$stderr.reopen File.new('/dev/null', 'w')
|
|
12
|
-
$stdout.reopen File.new('/dev/null', 'w')
|
|
13
|
-
yield
|
|
14
|
-
ensure
|
|
15
|
-
$stdout.reopen original_stdout
|
|
16
|
-
$stderr.reopen original_stderr
|
|
17
|
-
end
|
|
18
4
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
end
|
|
5
|
+
require_relative 'fast/source'
|
|
6
|
+
require_relative 'fast/node'
|
|
7
|
+
require_relative 'fast/rewriter'
|
|
23
8
|
|
|
24
9
|
# Fast is a tool to help you search in the code through the Abstract Syntax Tree
|
|
25
10
|
module Fast
|
|
11
|
+
NODE_PARENTS = ObjectSpace::WeakMap.new
|
|
12
|
+
|
|
13
|
+
class SyntaxError < StandardError; end
|
|
14
|
+
|
|
26
15
|
# Literals are shortcuts allowed inside {ExpressionParser}
|
|
27
16
|
LITERAL = {
|
|
28
17
|
'...' => ->(node) { node&.children&.any? },
|
|
@@ -67,83 +56,95 @@ module Fast
|
|
|
67
56
|
%\d # bind extra arguments to the expression
|
|
68
57
|
/x.freeze
|
|
69
58
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def buffer_name
|
|
74
|
-
expression.source_buffer.name
|
|
59
|
+
class << self
|
|
60
|
+
def ast_node?(node)
|
|
61
|
+
node.respond_to?(:type) && node.respond_to?(:children)
|
|
75
62
|
end
|
|
76
63
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
64
|
+
def prism_ast(content, buffer_name: '(string)')
|
|
65
|
+
require_relative 'fast/prism_adapter'
|
|
66
|
+
result = Fast::PrismAdapter.parse(content, buffer_name: buffer_name)
|
|
67
|
+
return result if result
|
|
68
|
+
|
|
69
|
+
prism_errors = Prism.parse(content).errors
|
|
70
|
+
message = prism_errors.map(&:message).uniq.join("\n")
|
|
71
|
+
raise SyntaxError, message
|
|
80
72
|
end
|
|
81
73
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
expression.source
|
|
74
|
+
def parse_ruby(content, buffer_name: '(string)')
|
|
75
|
+
prism_ast(content, buffer_name: buffer_name)
|
|
85
76
|
end
|
|
86
77
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
File.exist?(buffer_name)
|
|
78
|
+
def parser_ast(content, buffer_name: '(string)')
|
|
79
|
+
prism_ast(content, buffer_name: buffer_name)
|
|
90
80
|
end
|
|
91
81
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
82
|
+
def parser_ast_from_file(file)
|
|
83
|
+
@parser_cache ||= {}
|
|
84
|
+
@parser_cache[file] ||=
|
|
85
|
+
begin
|
|
86
|
+
method =
|
|
87
|
+
if file.end_with?('.sql')
|
|
88
|
+
require_relative 'fast/sql' unless respond_to?(:parse_sql)
|
|
89
|
+
:parse_sql
|
|
90
|
+
else
|
|
91
|
+
:parser_ast
|
|
92
|
+
end
|
|
93
|
+
Fast.public_send(method, IO.read(file), buffer_name: file)
|
|
94
|
+
end
|
|
97
95
|
end
|
|
98
96
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
97
|
+
def parse_file(file)
|
|
98
|
+
return parser_ast_from_file(file) if file.end_with?('.sql')
|
|
99
|
+
|
|
100
|
+
@cache ||= {}
|
|
101
|
+
@cache[file] ||=
|
|
102
|
+
begin
|
|
103
|
+
parse_ruby(IO.read(file), buffer_name: file)
|
|
104
|
+
end
|
|
102
105
|
end
|
|
103
106
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
# @return [Array<Fast::Node>>] with files and results
|
|
108
|
-
def search(pattern, *args)
|
|
109
|
-
Fast.search(pattern, self, *args)
|
|
107
|
+
def summary(code_or_ast, file: nil, command_name: '.summary', level: nil)
|
|
108
|
+
require_relative 'fast/summary'
|
|
109
|
+
Summary.new(code_or_ast, file: file, command_name: command_name, level: level)
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
# @return [Array<Fast::Node>>] with files and results
|
|
116
|
-
def capture(pattern, *args)
|
|
117
|
-
Fast.capture(pattern, self, *args)
|
|
112
|
+
def scan(locations, command_name: '.scan', level: nil)
|
|
113
|
+
require_relative 'fast/scan'
|
|
114
|
+
Scan.new(locations, command_name: command_name, level: level)
|
|
118
115
|
end
|
|
119
|
-
end
|
|
120
116
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
117
|
+
def parser_class
|
|
118
|
+
raise NoMethodError, 'Fast.parser_class was removed; Fast now parses Ruby with Prism'
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def parser_require_path
|
|
122
|
+
raise NoMethodError, 'Fast.parser_require_path was removed; Fast now parses Ruby with Prism'
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def parser_const_name
|
|
126
|
+
raise NoMethodError, 'Fast.parser_const_name was removed; Fast now parses Ruby with Prism'
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def parser_version_supported?(const_name)
|
|
130
|
+
raise NoMethodError, "Fast.parser_version_supported?(#{const_name.inspect}) was removed; Fast now parses Ruby with Prism"
|
|
129
131
|
end
|
|
130
|
-
end
|
|
131
132
|
|
|
132
|
-
class << self
|
|
133
133
|
# @return [Fast::Node] from the parsed content
|
|
134
134
|
# @example
|
|
135
135
|
# Fast.ast("1") # => s(:int, 1)
|
|
136
136
|
# Fast.ast("a.b") # => s(:send, s(:send, nil, :a), :b)
|
|
137
137
|
def ast(content, buffer_name: '(string)')
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
parse_ruby(content, buffer_name: buffer_name)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def validate_ruby!(content, buffer_name: '(string)')
|
|
142
|
+
prism_ast(content, buffer_name: buffer_name)
|
|
143
|
+
true
|
|
141
144
|
end
|
|
142
145
|
|
|
143
146
|
def builder_for(buffer_name)
|
|
144
|
-
|
|
145
|
-
builder.buffer_name = buffer_name
|
|
146
|
-
builder
|
|
147
|
+
raise NoMethodError, "Fast.builder_for(#{buffer_name.inspect}) was removed; Fast now parses Ruby with Prism"
|
|
147
148
|
end
|
|
148
149
|
|
|
149
150
|
# @return [Fast::Node] parsed from file content
|
|
@@ -152,18 +153,7 @@ module Fast
|
|
|
152
153
|
# @example
|
|
153
154
|
# Fast.ast_from_file("example.rb") # => s(...)
|
|
154
155
|
def ast_from_file(file)
|
|
155
|
-
|
|
156
|
-
@cache[file] ||=
|
|
157
|
-
begin
|
|
158
|
-
method =
|
|
159
|
-
if file.end_with?('.sql')
|
|
160
|
-
require_relative 'fast/sql' unless respond_to?(:parse_sql)
|
|
161
|
-
:parse_sql
|
|
162
|
-
else
|
|
163
|
-
:ast
|
|
164
|
-
end
|
|
165
|
-
Fast.public_send(method, IO.read(file), buffer_name: file)
|
|
166
|
-
end
|
|
156
|
+
parse_file(file)
|
|
167
157
|
end
|
|
168
158
|
|
|
169
159
|
# Verify if a given AST matches with a specific pattern
|
|
@@ -230,12 +220,24 @@ module Fast
|
|
|
230
220
|
# @return [Hash[String, Array]] with files and results
|
|
231
221
|
def group_results(group_files, locations, parallel: true)
|
|
232
222
|
files = ruby_files_from(*locations)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
223
|
+
results =
|
|
224
|
+
if parallel
|
|
225
|
+
require 'parallel' unless defined?(Parallel)
|
|
226
|
+
Parallel.map(files) do |file|
|
|
227
|
+
group_files.call(file)
|
|
228
|
+
rescue StandardError => e
|
|
229
|
+
warn "Error processing #{file}: #{e.message}" if Fast.debugging
|
|
230
|
+
nil
|
|
231
|
+
end
|
|
232
|
+
else
|
|
233
|
+
files.map do |file|
|
|
234
|
+
group_files.call(file)
|
|
235
|
+
rescue StandardError => e
|
|
236
|
+
warn "Error processing #{file}: #{e.message}" if Fast.debugging
|
|
237
|
+
nil
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
results.compact.inject(&:merge!) || {}
|
|
239
241
|
end
|
|
240
242
|
|
|
241
243
|
# Capture elements from searches in files. Keep in mind you need to use `$`
|
|
@@ -285,7 +287,11 @@ module Fast
|
|
|
285
287
|
end
|
|
286
288
|
|
|
287
289
|
def expression(string)
|
|
288
|
-
ExpressionParser.new(string)
|
|
290
|
+
parser = ExpressionParser.new(string)
|
|
291
|
+
res = parser.parse
|
|
292
|
+
raise SyntaxError, parser.error_message if parser.tokens_left?
|
|
293
|
+
|
|
294
|
+
res
|
|
289
295
|
end
|
|
290
296
|
|
|
291
297
|
attr_accessor :debugging
|
|
@@ -330,6 +336,73 @@ module Fast
|
|
|
330
336
|
files.reject(&dir_filter)
|
|
331
337
|
end
|
|
332
338
|
|
|
339
|
+
# Folds the AST to a maximum depth, replacing deeper branches with `...`
|
|
340
|
+
# @param node [Fast::Node]
|
|
341
|
+
# @param level [Integer] maximum depth to explore
|
|
342
|
+
# @param current_level [Integer] internal tracker for depth
|
|
343
|
+
# @return [Fast::Node] the folded AST
|
|
344
|
+
def fold_ast(node, level: nil, current_level: 1)
|
|
345
|
+
return node unless ast_node?(node) && node.respond_to?(:updated)
|
|
346
|
+
return node if level.nil?
|
|
347
|
+
|
|
348
|
+
if current_level >= level
|
|
349
|
+
if node.children.any?
|
|
350
|
+
node.updated(nil, [:'...'])
|
|
351
|
+
else
|
|
352
|
+
node
|
|
353
|
+
end
|
|
354
|
+
else
|
|
355
|
+
new_children = node.children.map { |c| fold_ast(c, level: level, current_level: current_level + 1) }
|
|
356
|
+
node.updated(nil, new_children)
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# Folds ruby source code to a maximum depth, replacing deep block bodies with `# ...`
|
|
361
|
+
# @param node [Fast::Node]
|
|
362
|
+
# @param level [Integer] maximum depth to explore
|
|
363
|
+
# @return [String] the folded source representation
|
|
364
|
+
def fold_source(node, level: 1)
|
|
365
|
+
return node if node.is_a?(String)
|
|
366
|
+
return node.loc.expression.source rescue node.to_s unless ast_node?(node) && node.respond_to?(:loc)
|
|
367
|
+
|
|
368
|
+
source = node.loc.expression.source.dup
|
|
369
|
+
root_begin = node.loc.expression.begin_pos
|
|
370
|
+
|
|
371
|
+
regions = []
|
|
372
|
+
|
|
373
|
+
walker = ->(n, current_level) do
|
|
374
|
+
return unless ast_node?(n)
|
|
375
|
+
|
|
376
|
+
if current_level >= level
|
|
377
|
+
body_node = nil
|
|
378
|
+
case n.type
|
|
379
|
+
when :class, :module, :def, :defs, :block
|
|
380
|
+
body_node = n.children.last
|
|
381
|
+
when :begin
|
|
382
|
+
body_node = n
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
if body_node && body_node.loc && body_node.loc.respond_to?(:expression) && body_node.loc.expression
|
|
386
|
+
regions << [
|
|
387
|
+
body_node.loc.expression.begin_pos - root_begin,
|
|
388
|
+
body_node.loc.expression.end_pos - root_begin
|
|
389
|
+
]
|
|
390
|
+
return
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
n.children.each { |c| walker.call(c, current_level + 1) if ast_node?(c) }
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
walker.call(node, 1)
|
|
398
|
+
|
|
399
|
+
regions.sort_by { |r| -r[0] }.each do |start_pos, end_pos|
|
|
400
|
+
source[start_pos...end_pos] = "# ..."
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
source
|
|
404
|
+
end
|
|
405
|
+
|
|
333
406
|
# Extracts a node pattern expression from a given node supressing identifiers and primitive types.
|
|
334
407
|
# Useful to index abstract patterns or similar code structure.
|
|
335
408
|
# @see https://jonatas.github.io/fast/similarity_tutorial/
|
|
@@ -341,7 +414,7 @@ module Fast
|
|
|
341
414
|
# Fast.expression_from(Fast.ast('def name; person.name end')) # => '(def _ (args) (send (send nil _) _))'
|
|
342
415
|
def expression_from(node)
|
|
343
416
|
case node
|
|
344
|
-
when
|
|
417
|
+
when ->(candidate) { ast_node?(candidate) }
|
|
345
418
|
children_expression = node.children.map(&method(:expression_from)).join(' ')
|
|
346
419
|
"(#{node.type}#{" #{children_expression}" if node.children.any?})"
|
|
347
420
|
when nil, 'nil'
|
|
@@ -387,6 +460,7 @@ module Fast
|
|
|
387
460
|
# @param expression [String]
|
|
388
461
|
def initialize(expression)
|
|
389
462
|
@tokens = expression.scan TOKENIZER
|
|
463
|
+
@nesting = []
|
|
390
464
|
end
|
|
391
465
|
|
|
392
466
|
# rubocop:disable Metrics/CyclomaticComplexity
|
|
@@ -394,22 +468,43 @@ module Fast
|
|
|
394
468
|
# rubocop:disable Metrics/MethodLength
|
|
395
469
|
def parse
|
|
396
470
|
case (token = next_token)
|
|
397
|
-
when '('
|
|
398
|
-
|
|
399
|
-
|
|
471
|
+
when '('
|
|
472
|
+
@nesting << ')'
|
|
473
|
+
parse_until_peek(')')
|
|
474
|
+
when '{'
|
|
475
|
+
@nesting << '}'
|
|
476
|
+
Any.new(parse_until_peek('}'))
|
|
477
|
+
when '['
|
|
478
|
+
@nesting << ']'
|
|
479
|
+
All.new(parse_until_peek(']'))
|
|
480
|
+
when ')', '}', ']'
|
|
481
|
+
raise SyntaxError, "Unexpected token: #{token}"
|
|
400
482
|
when /^"/ then FindString.new(token[1..-2])
|
|
401
483
|
when /^#\w/ then MethodCall.new(token[1..])
|
|
402
484
|
when /^\.\w[\w\d_]+\?/ then InstanceMethodCall.new(token[1..])
|
|
403
485
|
when '$' then Capture.new(parse)
|
|
404
|
-
when '!' then (@tokens.any? ? Not.new(parse) : Find.new(token))
|
|
486
|
+
when '!' then (@tokens.any? && !closing_token?(@tokens.first) ? Not.new(parse) : Find.new(token))
|
|
405
487
|
when '?' then Maybe.new(parse)
|
|
406
488
|
when '^' then Parent.new(parse)
|
|
407
489
|
when '\\' then FindWithCapture.new(parse)
|
|
408
490
|
when /^%\d/ then FindFromArgument.new(token[1..])
|
|
491
|
+
when nil then nil
|
|
409
492
|
else Find.new(token)
|
|
410
493
|
end
|
|
411
494
|
end
|
|
412
495
|
|
|
496
|
+
def tokens_left?
|
|
497
|
+
@tokens.any? || @nesting.any?
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def error_message
|
|
501
|
+
if @tokens.any?
|
|
502
|
+
"Unconsumed tokens after parsing: #{@tokens.join(' ')}"
|
|
503
|
+
elsif @nesting.any?
|
|
504
|
+
"Unclosed nesting: expected #{@nesting.reverse.join(', ')}"
|
|
505
|
+
end
|
|
506
|
+
end
|
|
507
|
+
|
|
413
508
|
# rubocop:enable Metrics/CyclomaticComplexity
|
|
414
509
|
# rubocop:enable Metrics/AbcSize
|
|
415
510
|
# rubocop:enable Metrics/MethodLength
|
|
@@ -420,10 +515,19 @@ module Fast
|
|
|
420
515
|
@tokens.shift
|
|
421
516
|
end
|
|
422
517
|
|
|
518
|
+
def closing_token?(token)
|
|
519
|
+
[')', '}', ']'].include?(token)
|
|
520
|
+
end
|
|
521
|
+
|
|
423
522
|
def parse_until_peek(token)
|
|
424
523
|
list = []
|
|
425
524
|
list << parse until @tokens.empty? || @tokens.first == token
|
|
426
|
-
next_token
|
|
525
|
+
last = next_token
|
|
526
|
+
if last == token
|
|
527
|
+
@nesting.pop
|
|
528
|
+
else
|
|
529
|
+
raise SyntaxError, "Expected #{token} but got #{last || 'end of string'}"
|
|
530
|
+
end
|
|
427
531
|
list
|
|
428
532
|
end
|
|
429
533
|
end
|
|
@@ -446,8 +550,14 @@ module Fast
|
|
|
446
550
|
when Find then expression.match?(node)
|
|
447
551
|
when Symbol then compare_symbol_or_head(expression, node)
|
|
448
552
|
when Enumerable
|
|
449
|
-
expression.
|
|
450
|
-
|
|
553
|
+
if expression.last == :'...' || expression.last.is_a?(Find) && expression.last.token == '...'
|
|
554
|
+
expression[0...-1].each_with_index.all? do |exp, i|
|
|
555
|
+
match_recursive(exp, i.zero? ? node : node.children[i - 1])
|
|
556
|
+
end
|
|
557
|
+
else
|
|
558
|
+
expression.each_with_index.all? do |exp, i|
|
|
559
|
+
match_recursive(exp, i.zero? ? node : node.children[i - 1])
|
|
560
|
+
end
|
|
451
561
|
end
|
|
452
562
|
else
|
|
453
563
|
node == expression
|
|
@@ -456,7 +566,7 @@ module Fast
|
|
|
456
566
|
|
|
457
567
|
def compare_symbol_or_head(expression, node)
|
|
458
568
|
case node
|
|
459
|
-
when
|
|
569
|
+
when ->(candidate) { Fast.ast_node?(candidate) }
|
|
460
570
|
node.type == expression.to_sym
|
|
461
571
|
when String
|
|
462
572
|
node == expression.to_s
|
|
@@ -741,10 +851,17 @@ module Fast
|
|
|
741
851
|
|
|
742
852
|
# @return [true] if all children matches with tail
|
|
743
853
|
def match_tail?(tail, child)
|
|
744
|
-
tail.
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
854
|
+
if tail.last.is_a?(Find) && tail.last.token == '...'
|
|
855
|
+
tail[0...-1].each_with_index.all? do |token, i|
|
|
856
|
+
prepare_token(token)
|
|
857
|
+
token.is_a?(Array) ? match?(token, child[i]) : token.match?(child[i])
|
|
858
|
+
end && find_captures
|
|
859
|
+
else
|
|
860
|
+
tail.each_with_index.all? do |token, i|
|
|
861
|
+
prepare_token(token)
|
|
862
|
+
token.is_a?(Array) ? match?(token, child[i]) : token.match?(child[i])
|
|
863
|
+
end && find_captures
|
|
864
|
+
end
|
|
748
865
|
end
|
|
749
866
|
|
|
750
867
|
# Look recursively into @param expression to check if the expression is have
|
data/mkdocs.yml
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
site_name: Fast
|
|
2
2
|
repo_url: https://github.com/jonatas/fast
|
|
3
3
|
edit_uri: edit/master/docs/
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
extra:
|
|
6
|
+
analytics:
|
|
7
|
+
provider: google
|
|
8
|
+
property: G-YKZDZDNRG2
|
|
5
9
|
|
|
6
10
|
theme:
|
|
7
11
|
name: material
|
|
@@ -13,10 +17,15 @@ theme:
|
|
|
13
17
|
extra_css:
|
|
14
18
|
- stylesheets/custom.css
|
|
15
19
|
|
|
20
|
+
plugins:
|
|
21
|
+
- search
|
|
22
|
+
|
|
16
23
|
markdown_extensions:
|
|
17
24
|
- admonition
|
|
18
|
-
-
|
|
19
|
-
|
|
25
|
+
- pymdownx.details
|
|
26
|
+
- pymdownx.superfences
|
|
27
|
+
- pymdownx.tabbed:
|
|
28
|
+
alternate_style: true
|
|
20
29
|
- toc:
|
|
21
30
|
permalink: true
|
|
22
31
|
nav:
|
|
@@ -27,10 +36,16 @@ nav:
|
|
|
27
36
|
- Experiments: experiments.md
|
|
28
37
|
- Shortcuts: shortcuts.md
|
|
29
38
|
- Git Integration: git.md
|
|
39
|
+
- Fast for LLMs and Agents: agents.md
|
|
40
|
+
- MCP Server Tutorial: mcp_tutorial.md
|
|
30
41
|
- Code Similarity: similarity_tutorial.md
|
|
42
|
+
- LLM/Agent Feature TODOs: llm_features.md
|
|
31
43
|
- Pry Integration: pry-integration.md
|
|
32
44
|
- Editors' Integration: editors-integration.md
|
|
33
45
|
- Research: research.md
|
|
34
46
|
- Ideas: ideas.md
|
|
35
47
|
- Videos: videos.md
|
|
36
|
-
- SQL
|
|
48
|
+
- SQL:
|
|
49
|
+
- Intro: sql/index.md
|
|
50
|
+
- Shortcuts: sql/shortcuts.md
|
|
51
|
+
- About: sql-support.md
|
metadata
CHANGED
|
@@ -1,29 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ffast
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jônatas Davi Paganini
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-03-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: astrolabe
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: '0'
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: '0'
|
|
27
13
|
- !ruby/object:Gem::Dependency
|
|
28
14
|
name: coderay
|
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -52,20 +38,6 @@ dependencies:
|
|
|
52
38
|
- - ">="
|
|
53
39
|
- !ruby/object:Gem::Version
|
|
54
40
|
version: '0'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: parser
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - ">="
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '0'
|
|
62
|
-
type: :runtime
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - ">="
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '0'
|
|
69
41
|
- !ruby/object:Gem::Dependency
|
|
70
42
|
name: pg_query
|
|
71
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -271,6 +243,9 @@ executables:
|
|
|
271
243
|
extensions: []
|
|
272
244
|
extra_rdoc_files: []
|
|
273
245
|
files:
|
|
246
|
+
- ".agents/fast-pattern-expert/SKILL.md"
|
|
247
|
+
- ".github/workflows/release.yml"
|
|
248
|
+
- ".github/workflows/ruby.yml"
|
|
274
249
|
- ".gitignore"
|
|
275
250
|
- ".projections.json"
|
|
276
251
|
- ".rspec"
|
|
@@ -288,43 +263,28 @@ files:
|
|
|
288
263
|
- bin/console
|
|
289
264
|
- bin/fast
|
|
290
265
|
- bin/fast-experiment
|
|
266
|
+
- bin/fast-mcp
|
|
291
267
|
- bin/setup
|
|
292
|
-
- docs/command_line.md
|
|
293
|
-
- docs/editors-integration.md
|
|
294
|
-
- docs/experiments.md
|
|
295
|
-
- docs/git.md
|
|
296
|
-
- docs/ideas.md
|
|
297
|
-
- docs/index.md
|
|
298
|
-
- docs/pry-integration.md
|
|
299
|
-
- docs/research.md
|
|
300
|
-
- docs/shortcuts.md
|
|
301
|
-
- docs/similarity_tutorial.md
|
|
302
|
-
- docs/sql-support.md
|
|
303
|
-
- docs/syntax.md
|
|
304
|
-
- docs/videos.md
|
|
305
|
-
- docs/walkthrough.md
|
|
306
|
-
- examples/build_stubbed_and_let_it_be_experiment.rb
|
|
307
|
-
- examples/experimental_replacement.rb
|
|
308
|
-
- examples/find_usage.rb
|
|
309
|
-
- examples/let_it_be_experiment.rb
|
|
310
|
-
- examples/method_complexity.rb
|
|
311
|
-
- examples/search_duplicated.rb
|
|
312
|
-
- examples/similarity_research.rb
|
|
313
|
-
- examples/simple_rewriter.rb
|
|
314
|
-
- experiments/let_it_be_experiment.rb
|
|
315
|
-
- experiments/remove_useless_hook.rb
|
|
316
|
-
- experiments/replace_create_with_build_stubbed.rb
|
|
317
268
|
- fast.gemspec
|
|
269
|
+
- ideia_blog_post.md
|
|
318
270
|
- lib/fast.rb
|
|
319
271
|
- lib/fast/cli.rb
|
|
320
272
|
- lib/fast/experiment.rb
|
|
321
273
|
- lib/fast/git.rb
|
|
274
|
+
- lib/fast/mcp_server.rb
|
|
275
|
+
- lib/fast/node.rb
|
|
276
|
+
- lib/fast/prism_adapter.rb
|
|
322
277
|
- lib/fast/rewriter.rb
|
|
278
|
+
- lib/fast/scan.rb
|
|
323
279
|
- lib/fast/shortcut.rb
|
|
280
|
+
- lib/fast/source.rb
|
|
281
|
+
- lib/fast/source_rewriter.rb
|
|
324
282
|
- lib/fast/sql.rb
|
|
325
283
|
- lib/fast/sql/rewriter.rb
|
|
284
|
+
- lib/fast/summary.rb
|
|
326
285
|
- lib/fast/version.rb
|
|
327
286
|
- mkdocs.yml
|
|
287
|
+
- requirements-docs.txt
|
|
328
288
|
homepage: https://jonatas.github.io/fast/
|
|
329
289
|
licenses:
|
|
330
290
|
- MIT
|
|
@@ -360,9 +320,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
360
320
|
- !ruby/object:Gem::Version
|
|
361
321
|
version: '0'
|
|
362
322
|
requirements: []
|
|
363
|
-
rubygems_version: 3.
|
|
364
|
-
signing_key:
|
|
323
|
+
rubygems_version: 3.5.22
|
|
324
|
+
signing_key:
|
|
365
325
|
specification_version: 4
|
|
366
326
|
summary: 'FAST: Find by AST.'
|
|
367
327
|
test_files: []
|
|
368
|
-
...
|