ffast 0.2.2 → 0.2.3
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/workflows/release.yml +27 -0
- data/.github/workflows/ruby.yml +34 -0
- data/.gitignore +2 -0
- data/Fastfile +102 -15
- 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/lib/fast/cli.rb +58 -26
- data/lib/fast/experiment.rb +19 -2
- data/lib/fast/git.rb +1 -1
- data/lib/fast/mcp_server.rb +317 -0
- data/lib/fast/node.rb +258 -0
- data/lib/fast/prism_adapter.rb +310 -0
- data/lib/fast/rewriter.rb +64 -10
- data/lib/fast/scan.rb +203 -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 +435 -0
- data/lib/fast/version.rb +1 -1
- data/lib/fast.rb +140 -83
- data/mkdocs.yml +19 -4
- data/requirements-docs.txt +3 -0
- metadata +16 -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/docs/shortcuts.md
DELETED
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
# Shortcuts
|
|
2
|
-
|
|
3
|
-
Shortcuts are defined on a `Fastfile` inside any ruby project.
|
|
4
|
-
|
|
5
|
-
!!!info "Use `~/Fastfile`"
|
|
6
|
-
You can also add one extra in your `$HOME` if you want to have something loaded always.
|
|
7
|
-
|
|
8
|
-
By default, the command line interface does not load any `Fastfile` if the
|
|
9
|
-
first param is not a shortcut. It should start with `.`.
|
|
10
|
-
|
|
11
|
-
I'm building several researches and I'll make the examples open here to show
|
|
12
|
-
several interesting cases in action.
|
|
13
|
-
|
|
14
|
-
## List your fast shortcuts
|
|
15
|
-
|
|
16
|
-
As the interface is very rudimentar, let's build a shortcut to print what
|
|
17
|
-
shortcuts are available. This is a good one to your `$HOME/Fastfile`:
|
|
18
|
-
|
|
19
|
-
```ruby
|
|
20
|
-
# List all shortcut with comments
|
|
21
|
-
Fast.shortcut :shortcuts do
|
|
22
|
-
fast_files.each do |file|
|
|
23
|
-
lines = File.readlines(file).map{|line|line.chomp.gsub(/\s*#/,'').strip}
|
|
24
|
-
result = capture_file('(send ... shortcut $(sym _', file)
|
|
25
|
-
result = [result] unless result.is_a?Array
|
|
26
|
-
result.each do |capture|
|
|
27
|
-
target = capture.loc.expression
|
|
28
|
-
puts "fast .#{target.source[1..-1].ljust(30)} # #{lines[target.line-2]}"
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
And using it on `fast` project that loads both `~/Fastfile` and the Fastfile from the project:
|
|
35
|
-
|
|
36
|
-
```
|
|
37
|
-
fast .version # Let's say you'd like to show the version that is over the version file
|
|
38
|
-
fast .parser # Simple shortcut that I used often to show how the expression parser works
|
|
39
|
-
fast .bump_version # Use `fast .bump_version` to rewrite the version file
|
|
40
|
-
fast .shortcuts # List all shortcut with comments
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Search for references
|
|
44
|
-
|
|
45
|
-
I always miss bringing something simple as `grep keyword` where I can leave a simple string and it can
|
|
46
|
-
search in all types of nodes and report interesting things about it.
|
|
47
|
-
|
|
48
|
-
Let's consider a very flexible search that can target any code related to some
|
|
49
|
-
keyword. Considering that we're talking about code indentifiers:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```ruby
|
|
53
|
-
# Search all references about some keyword or regular expression
|
|
54
|
-
Fast.shortcut(:ref) do
|
|
55
|
-
require 'fast/cli'
|
|
56
|
-
Kernel.class_eval do
|
|
57
|
-
def matches_args? identifier
|
|
58
|
-
search = ARGV.last
|
|
59
|
-
regex = Regexp.new(search, Regexp::IGNORECASE)
|
|
60
|
-
case identifier
|
|
61
|
-
when Symbol, String
|
|
62
|
-
regex.match?(identifier) || identifier.to_s.include?(search)
|
|
63
|
-
when Astrolabe::Node
|
|
64
|
-
regex.match?(identifier.to_sexp)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
pattern = <<~FAST
|
|
69
|
-
{
|
|
70
|
-
({class def sym str} #matches_args?)'
|
|
71
|
-
({const send} nil #matches_args?)'
|
|
72
|
-
}
|
|
73
|
-
FAST
|
|
74
|
-
Fast::Cli.run!([pattern, '.', '--parallel'])
|
|
75
|
-
end
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Rails: Show validations from models
|
|
79
|
-
|
|
80
|
-
If the shortcut does not define a block, it works as a holder for arguments from
|
|
81
|
-
the command line.
|
|
82
|
-
|
|
83
|
-
Let's say you always use `fast "(send nil {validate validates})" app/models` to
|
|
84
|
-
check validations in the models. You can define a shortcut to hold the args and
|
|
85
|
-
avoid retyping long lines:
|
|
86
|
-
|
|
87
|
-
```ruby
|
|
88
|
-
# Show validations from app/models
|
|
89
|
-
Fast.shortcut(:validations, "(send nil {validate validates})", "app/models")
|
|
90
|
-
```
|
|
91
|
-
And you can reuse the search with the shortcut starting with a `.`:
|
|
92
|
-
|
|
93
|
-
```
|
|
94
|
-
fast .validations
|
|
95
|
-
```
|
|
96
|
-
And it will also accept params if you want to filter a specific file:
|
|
97
|
-
|
|
98
|
-
```
|
|
99
|
-
fast .validations app/models/user.rb
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
!!! info "Note that you can also use flags in the command line shortcuts"
|
|
103
|
-
|
|
104
|
-
Let's say you also want to use `fast --headless` you can add it to the params:
|
|
105
|
-
|
|
106
|
-
> Fast.shortcut(:validations, "(send nil {validate validates})", "app/models", "--headless")
|
|
107
|
-
|
|
108
|
-
## Automated Refactor: Bump version
|
|
109
|
-
|
|
110
|
-
Let's start with a [real usage](https://github.com/jonatas/fast/blob/master/Fastfile#L20-L34)
|
|
111
|
-
to bump a new version of the gem.
|
|
112
|
-
|
|
113
|
-
```ruby
|
|
114
|
-
Fast.shortcut :bump_version do
|
|
115
|
-
rewrite_file('(casgn nil VERSION (str _)', 'lib/fast/version.rb') do |node|
|
|
116
|
-
target = node.children.last.loc.expression
|
|
117
|
-
pieces = target.source.split('.').map(&:to_i)
|
|
118
|
-
pieces.reverse.each_with_index do |fragment, i|
|
|
119
|
-
if fragment < 9
|
|
120
|
-
pieces[-(i + 1)] = fragment + 1
|
|
121
|
-
break
|
|
122
|
-
else
|
|
123
|
-
pieces[-(i + 1)] = 0
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
replace(target, "'#{pieces.join('.')}'")
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
And then the change is done in the `lib/fast/version.rb`:
|
|
132
|
-
|
|
133
|
-
```diff
|
|
134
|
-
module Fast
|
|
135
|
-
- VERSION = '0.1.6'
|
|
136
|
-
+ VERSION = '0.1.7'
|
|
137
|
-
end
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
## RSpec: Find unused shared contexts
|
|
141
|
-
|
|
142
|
-
If you build shared contexts often, probably you can forget some left overs.
|
|
143
|
-
|
|
144
|
-
The objective of the shortcut is find leftovers from shared contexts.
|
|
145
|
-
|
|
146
|
-
First, the objective is capture all names of the `RSpec.shared_context` or
|
|
147
|
-
`shared_context` declared in the `spec/support` folder.
|
|
148
|
-
|
|
149
|
-
```ruby
|
|
150
|
-
Fast.capture_all('(block (send {nil,_} shared_context (str $_)))', Fast.ruby_files_from('spec/support'))
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
Then, we need to check all the specs and search for `include_context` usages to
|
|
154
|
-
confirm if all defined contexts are being used:
|
|
155
|
-
|
|
156
|
-
```ruby
|
|
157
|
-
specs = Fast.ruby_files_from('spec').select{|f|f !~ %r{spec/support/}}
|
|
158
|
-
Fast.search_all("(send nil include_context (str #register_usage)", specs)
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
Note that we created a new reference to `#register_usage` and we need to define the method too:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
```ruby
|
|
165
|
-
@used = []
|
|
166
|
-
def register_usage context_name
|
|
167
|
-
@used << context_name
|
|
168
|
-
end
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Wrapping up everything in a shortcut:
|
|
172
|
-
|
|
173
|
-
```ruby
|
|
174
|
-
# Show unused shared contexts
|
|
175
|
-
Fast.shortcut(:unused_shared_contexts) do
|
|
176
|
-
puts "Checking shared contexts"
|
|
177
|
-
Kernel.class_eval do
|
|
178
|
-
@used = []
|
|
179
|
-
def register_usage context_name
|
|
180
|
-
@used << context_name
|
|
181
|
-
end
|
|
182
|
-
def show_report! defined_contexts
|
|
183
|
-
unused = defined_contexts.values.flatten - @used
|
|
184
|
-
if unused.any?
|
|
185
|
-
puts "Unused shared contexts", unused
|
|
186
|
-
else
|
|
187
|
-
puts "Good job! all the #{defined_contexts.size} contexts are used!"
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
specs = ruby_files_from('spec/').select{|f|f !~ %r{spec/support/}}
|
|
192
|
-
search_all("(send nil include_context (str #register_usage)", specs)
|
|
193
|
-
defined_contexts = capture_all('(block (send {nil,_} shared_context (str $_)))', ruby_files_from('spec'))
|
|
194
|
-
Kernel.public_send(:show_report!, defined_contexts)
|
|
195
|
-
end
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
!!! faq "Why `#register_usage` is defined on the `Kernel`?"
|
|
199
|
-
Yes! note that the `#register_usage` was forced to be inside `Kernel`
|
|
200
|
-
because of the `shortcut` block that takes the `Fast` context to be easy
|
|
201
|
-
to access in the default functions. As I can define multiple shortcuts
|
|
202
|
-
I don't want to polute my Kernel module with other methods that are not useful.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
## RSpec: Remove unused let
|
|
206
|
-
|
|
207
|
-
!!! hint "First shortcut with experiments"
|
|
208
|
-
If you're not familiar with automated experiments, you can read about it [here](/experiments).
|
|
209
|
-
|
|
210
|
-
The current scenario is similar in terms of search with the previous one, but more advanced
|
|
211
|
-
because we're going to introduce automated refactoring.
|
|
212
|
-
|
|
213
|
-
The idea is simple, if it finds a `let` in a RSpec scenario that is not referenced, it tries to experimentally remove the `let` and run the tests:
|
|
214
|
-
|
|
215
|
-
```ruby
|
|
216
|
-
# Experimental remove `let` that are not referenced in the spec
|
|
217
|
-
Fast.shortcut(:exp_remove_let) do
|
|
218
|
-
require 'fast/experiment'
|
|
219
|
-
Kernel.class_eval do
|
|
220
|
-
file = ARGV.last
|
|
221
|
-
|
|
222
|
-
defined_lets = Fast.capture_file('(block (send nil let (sym $_)))', file).uniq
|
|
223
|
-
@unreferenced= defined_lets.select do |identifier|
|
|
224
|
-
Fast.search_file("(send nil #{identifier})", file).empty?
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
def unreferenced_let?(identifier)
|
|
228
|
-
@unreferenced.include? identifier
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
experiment('RSpec/RemoveUnreferencedLet') do
|
|
233
|
-
lookup ARGV.last
|
|
234
|
-
search '(block (send nil let (sym #unreferenced_let?)))'
|
|
235
|
-
edit { |node| remove(node.loc.expression) }
|
|
236
|
-
policy { |new_file| system("bundle exec rspec --fail-fast #{new_file}") }
|
|
237
|
-
end.run
|
|
238
|
-
end
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
And it will run with a single file from command line:
|
|
242
|
-
|
|
243
|
-
```
|
|
244
|
-
fast .exp_remove_let spec/my_file_spec.rb
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
## FactoryBot: Replace `create` with `build_stubbed`
|
|
248
|
-
|
|
249
|
-
For performance reasons, if we can avoid touching the database the test will
|
|
250
|
-
always be faster.
|
|
251
|
-
|
|
252
|
-
```ruby
|
|
253
|
-
# Experimental switch from `create` to `build_stubbed`
|
|
254
|
-
Fast.shortcut(:exp_build_stubbed) do
|
|
255
|
-
require 'fast/experiment'
|
|
256
|
-
Fast.experiment('FactoryBot/UseBuildStubbed') do
|
|
257
|
-
lookup ARGV.last
|
|
258
|
-
search '(send nil create)'
|
|
259
|
-
edit { |node| replace(node.loc.selector, 'build_stubbed') }
|
|
260
|
-
policy { |new_file| system("bundle exec rspec --fail-fast #{new_file}") }
|
|
261
|
-
end.run
|
|
262
|
-
end
|
|
263
|
-
```
|
|
264
|
-
## RSpec: Use `let_it_be` instead of `let`
|
|
265
|
-
|
|
266
|
-
The `let_it_be` is a simple helper from
|
|
267
|
-
[TestProf](https://test-prof.evilmartians.io/#/let_it_be) gem that can speed up
|
|
268
|
-
the specs by caching some factories using like a `before_all` approach.
|
|
269
|
-
|
|
270
|
-
This experiment hunts for `let(...) { create(...) }` and switch the `let` to
|
|
271
|
-
`let_it_be`:
|
|
272
|
-
|
|
273
|
-
```ruby
|
|
274
|
-
# Experimental replace `let(_)` with `let_it_be` case it calls `create` inside the block
|
|
275
|
-
Fast.shortcut(:exp_let_it_be) do
|
|
276
|
-
require 'fast/experiment'
|
|
277
|
-
Fast.experiment('FactoryBot/LetItBe') do
|
|
278
|
-
lookup ARGV.last
|
|
279
|
-
search '(block (send nil let (sym _)) (args) (send nil create))'
|
|
280
|
-
edit { |node| replace(node.children.first.loc.selector, 'let_it_be') }
|
|
281
|
-
policy { |new_file| system("bin/spring rspec --fail-fast #{new_file}") }
|
|
282
|
-
end.run
|
|
283
|
-
end
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
## RSpec: Remove `before` or `after` blocks
|
|
287
|
-
|
|
288
|
-
From time to time, we forget some left overs like `before` or `after` blocks
|
|
289
|
-
that even removing from the code, the tests still passes. This experiment
|
|
290
|
-
removes the before/after blocks and check if the test passes.
|
|
291
|
-
|
|
292
|
-
```ruby
|
|
293
|
-
# Experimental remove `before` or `after` blocks.
|
|
294
|
-
Fast.shortcut(:exp_remove_before_after) do
|
|
295
|
-
require 'fast/experiment'
|
|
296
|
-
Fast.experiment('RSpec/RemoveBeforeAfter') do
|
|
297
|
-
lookup ARGV.last
|
|
298
|
-
search '(block (send nil {before after}))'
|
|
299
|
-
edit { |node| remove(node.loc.expression) }
|
|
300
|
-
policy { |new_file| system("bin/spring rspec --fail-fast #{new_file}") }
|
|
301
|
-
end.run
|
|
302
|
-
end
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
## RSpec: Show message chains
|
|
306
|
-
|
|
307
|
-
I often forget the syntax and need to search for message chains on specs, so I
|
|
308
|
-
created an shortcut for it.
|
|
309
|
-
|
|
310
|
-
```ruby
|
|
311
|
-
# Show RSpec message chains
|
|
312
|
-
Fast.shortcut(:message_chains, '^^(send nil receive_message_chain)', 'spec')
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
## RSpec: Show nested assertions
|
|
316
|
-
|
|
317
|
-
I love to use nested assertions and I often need examples to refer to them:
|
|
318
|
-
|
|
319
|
-
```ruby
|
|
320
|
-
# Show RSpec nested assertions with .and
|
|
321
|
-
Fast.shortcut(:nested_assertions, '^^(send ... and)', 'spec')
|
|
322
|
-
```
|
|
323
|
-
|
data/docs/similarity_tutorial.md
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
# Research for code similarity
|
|
2
|
-
|
|
3
|
-
This is a small tutorial to explore code similarity.
|
|
4
|
-
|
|
5
|
-
Check the code example [here](https://github.com/jonatas/fast/blob/master/examples/similarity_research.rb).
|
|
6
|
-
|
|
7
|
-
The major idea is register all expression styles and see if we can find some
|
|
8
|
-
similarity between the structures.
|
|
9
|
-
|
|
10
|
-
First we need to create a function that can analyze AST nodes and extract a
|
|
11
|
-
pattern from the expression.
|
|
12
|
-
|
|
13
|
-
The expression needs to generalize final node values and recursively build a
|
|
14
|
-
pattern that can be used as a search expression.
|
|
15
|
-
|
|
16
|
-
```ruby
|
|
17
|
-
def expression_from(node)
|
|
18
|
-
case node
|
|
19
|
-
when Parser::AST::Node
|
|
20
|
-
if node.children.any?
|
|
21
|
-
children_expression = node.children
|
|
22
|
-
.map(&method(:expression_from))
|
|
23
|
-
.join(' ')
|
|
24
|
-
"(#{node.type} #{children_expression})"
|
|
25
|
-
else
|
|
26
|
-
"(#{node.type})"
|
|
27
|
-
end
|
|
28
|
-
when nil, 'nil'
|
|
29
|
-
'nil'
|
|
30
|
-
when Symbol, String, Integer
|
|
31
|
-
'_'
|
|
32
|
-
when Array, Hash
|
|
33
|
-
'...'
|
|
34
|
-
else
|
|
35
|
-
node
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
The pattern generated only flexibilize the search allowing us to group similar nodes.
|
|
41
|
-
|
|
42
|
-
Example:
|
|
43
|
-
|
|
44
|
-
```ruby
|
|
45
|
-
expression_from(code['1']) # =>'(int _)'
|
|
46
|
-
expression_from(code['nil']) # =>'(nil)'
|
|
47
|
-
expression_from(code['a = 1']) # =>'(lvasgn _ (int _))'
|
|
48
|
-
expression_from(code['def name; person.name end']) # =>'(def _ (args) (send (send nil _) _))'
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
The current method can translate all kind of expressions and the next step is
|
|
52
|
-
observe some specific node types and try to group the similarities
|
|
53
|
-
using the pattern generated.
|
|
54
|
-
|
|
55
|
-
```ruby
|
|
56
|
-
Fast.search_file('class', 'lib/fast.rb')
|
|
57
|
-
```
|
|
58
|
-
Capturing the constant name and filtering only for symbols is easy and we can
|
|
59
|
-
see that we have a few classes defined in the the same file.
|
|
60
|
-
|
|
61
|
-
```ruby
|
|
62
|
-
Fast.search_file('(class (const nil $_))','lib/fast.rb').grep(Symbol)
|
|
63
|
-
=> [:Rewriter,
|
|
64
|
-
:ExpressionParser,
|
|
65
|
-
:Find,
|
|
66
|
-
:FindString,
|
|
67
|
-
:FindWithCapture,
|
|
68
|
-
:Capture,
|
|
69
|
-
:Parent,
|
|
70
|
-
:Any,
|
|
71
|
-
:All,
|
|
72
|
-
:Not,
|
|
73
|
-
:Maybe,
|
|
74
|
-
:Matcher,
|
|
75
|
-
:Experiment,
|
|
76
|
-
:ExperimentFile]
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
The idea of this inspecton is build a proof of concept to show the similarity
|
|
80
|
-
of matcher classes because they only define a `match?` method.
|
|
81
|
-
|
|
82
|
-
```ruby
|
|
83
|
-
patterns = Fast.search_file('class','lib/fast.rb').map{|n|Fast.expression_from(n)}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
A simple comparison between the patterns size versus `.uniq.size` can proof if
|
|
87
|
-
the idea will work.
|
|
88
|
-
|
|
89
|
-
```ruby
|
|
90
|
-
patterns.size == patterns.uniq.size
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
It does not work for the matcher cases but we can go deeper and analyze all
|
|
94
|
-
files required by bundler.
|
|
95
|
-
|
|
96
|
-
```ruby
|
|
97
|
-
similarities = {}
|
|
98
|
-
Gem.find_files('*.rb').each do |file|
|
|
99
|
-
Fast.search_file('{block send if while case def defs class module}', file).map do |n|
|
|
100
|
-
key = Fast.expression_from(n)
|
|
101
|
-
similarities[key] ||= Set.new
|
|
102
|
-
similarities[key] << file
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
similarities.delete_if {|k,v|v.size < 2}
|
|
106
|
-
```
|
|
107
|
-
The similarities found are the following:
|
|
108
|
-
|
|
109
|
-
```ruby
|
|
110
|
-
{"(class (const nil _) (const nil _) nil)"=>
|
|
111
|
-
#<Set: {"/Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/parallel-1.12.1/lib/parallel.rb",
|
|
112
|
-
"/Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/method_source-0.9.0/lib/method_source.rb",
|
|
113
|
-
"/Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/2.5.0/rdoc.rb",
|
|
114
|
-
"/Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb",
|
|
115
|
-
"/Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/2.5.0/tsort.rb"}>,
|
|
116
|
-
"(class (const nil _) nil nil)"=>#<Set: {"/Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/2.5.0/ripper.rb", "/Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/2.5.0/cgi.rb"}>}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
And now we can test the expression using the command line tool through the files
|
|
120
|
-
and observe the similarity:
|
|
121
|
-
|
|
122
|
-
```
|
|
123
|
-
⋊> ~ fast "(class (const nil _) (const nil _) nil)" /Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/parallel-1.12.1/lib/parallel.rb /Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/method_source-0.9.0/lib/method_source.rb /Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/2.5.0/rdoc.rb /Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb /Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/2.5.0/tsort.rb
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
Output:
|
|
127
|
-
|
|
128
|
-
```ruby
|
|
129
|
-
# /Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/parallel-1.12.1/lib/parallel.rb:8
|
|
130
|
-
class DeadWorker < StandardError
|
|
131
|
-
end
|
|
132
|
-
# /Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/parallel-1.12.1/lib/parallel.rb:11
|
|
133
|
-
class Break < StandardError
|
|
134
|
-
end
|
|
135
|
-
# /Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/parallel-1.12.1/lib/parallel.rb:14
|
|
136
|
-
class Kill < StandardError
|
|
137
|
-
end
|
|
138
|
-
# /Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/method_source-0.9.0/lib/method_source.rb:16
|
|
139
|
-
class SourceNotFoundError < StandardError; end
|
|
140
|
-
# /Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/2.5.0/rdoc.rb:63
|
|
141
|
-
class Error < RuntimeError; end
|
|
142
|
-
# /Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:338
|
|
143
|
-
class Abort < Exception;end
|
|
144
|
-
# /Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/2.5.0/tsort.rb:125
|
|
145
|
-
class Cyclic < StandardError
|
|
146
|
-
end
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
It works and now we can create a method to do what the command line tool did,
|
|
150
|
-
grouping the patterns and inspecting the occurrences.
|
|
151
|
-
|
|
152
|
-
```ruby
|
|
153
|
-
def similarities.show pattern
|
|
154
|
-
files = self[pattern]
|
|
155
|
-
files.each do |file|
|
|
156
|
-
nodes = Fast.search_file(pattern, file)
|
|
157
|
-
nodes.each do |result|
|
|
158
|
-
Fast.report(result, file: file)
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
And calling the method exploring some "if" similarities, it prints the following
|
|
165
|
-
results:
|
|
166
|
-
|
|
167
|
-
```ruby
|
|
168
|
-
similarities.show "(if (send (const nil _) _ (lvar _)) nil (return (false)))"
|
|
169
|
-
# /Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/2.5.0/resolv.rb:1248
|
|
170
|
-
return false unless Name === other
|
|
171
|
-
# /Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/2.5.0/fileutils.rb:138
|
|
172
|
-
return false unless File.exist?(new)
|
|
173
|
-
# /Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/2.5.0/matrix.rb:1862
|
|
174
|
-
return false unless Vector === other
|
|
175
|
-
```
|
|
176
|
-
|