ffast 0.2.0 → 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 +146 -3
- data/README.md +244 -132
- data/bin/console +6 -1
- data/bin/fast-experiment +3 -0
- data/bin/fast-mcp +7 -0
- data/fast.gemspec +24 -7
- data/lib/fast/cli.rb +129 -38
- 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 +23 -6
- data/lib/fast/source.rb +116 -0
- data/lib/fast/source_rewriter.rb +153 -0
- data/lib/fast/sql/rewriter.rb +98 -0
- data/lib/fast/sql.rb +165 -0
- data/lib/fast/summary.rb +435 -0
- data/lib/fast/version.rb +1 -1
- data/lib/fast.rb +165 -79
- data/mkdocs.yml +27 -3
- data/requirements-docs.txt +3 -0
- metadata +48 -62
- data/docs/command_line.md +0 -238
- data/docs/editors-integration.md +0 -46
- data/docs/experiments.md +0 -153
- data/docs/ideas.md +0 -80
- data/docs/index.md +0 -402
- 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/syntax.md +0 -395
- data/docs/videos.md +0 -16
- 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/syntax.md
DELETED
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
# Syntax
|
|
2
|
-
|
|
3
|
-
The syntax is inspired on [RuboCop Node Pattern](https://github.com/bbatsov/rubocop/blob/master/lib/rubocop/node_pattern.rb).
|
|
4
|
-
|
|
5
|
-
You can find a great tutorial about RuboCop node pattern in the
|
|
6
|
-
[official documentation](https://rubocop.readthedocs.io/en/latest/node_pattern/).
|
|
7
|
-
|
|
8
|
-
## Code example
|
|
9
|
-
|
|
10
|
-
Let's consider the following `example.rb` code example:
|
|
11
|
-
|
|
12
|
-
```ruby
|
|
13
|
-
class Example
|
|
14
|
-
ANSWER = 42
|
|
15
|
-
def magic
|
|
16
|
-
rand(ANSWER)
|
|
17
|
-
end
|
|
18
|
-
def duplicate(value)
|
|
19
|
-
value * 2
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Looking the AST representation we have:
|
|
25
|
-
|
|
26
|
-
$ ruby-parse example.rb
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
(class
|
|
30
|
-
(const nil :Example) nil
|
|
31
|
-
(begin
|
|
32
|
-
(casgn nil :ANSWER
|
|
33
|
-
(int 42))
|
|
34
|
-
(def :magic
|
|
35
|
-
(args)
|
|
36
|
-
(send nil :rand
|
|
37
|
-
(const nil :ANSWER)))
|
|
38
|
-
(def :duplicate
|
|
39
|
-
(args
|
|
40
|
-
(arg :value))
|
|
41
|
-
(send
|
|
42
|
-
(lvar :value) :*
|
|
43
|
-
(int 2)))))
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Now, let's explore all details of the current AST, combining with the syntax
|
|
47
|
-
operators.
|
|
48
|
-
|
|
49
|
-
Fast works with a single word that will be the node type.
|
|
50
|
-
|
|
51
|
-
A simple search of `def` nodes can be done and will also print the code.
|
|
52
|
-
|
|
53
|
-
$ fast def example.rb
|
|
54
|
-
|
|
55
|
-
```ruby
|
|
56
|
-
# example.rb:3
|
|
57
|
-
def magic
|
|
58
|
-
rand(ANSWER)
|
|
59
|
-
end
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
or check the `casgn` that will show constant assignments:
|
|
63
|
-
|
|
64
|
-
$ fast casgn example.rb
|
|
65
|
-
|
|
66
|
-
```ruby
|
|
67
|
-
# example.rb:2
|
|
68
|
-
ANSWER = 42
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## `()` to represent a **node** search
|
|
72
|
-
|
|
73
|
-
To specify details about a node, the `(` means navigate deeply into a node and
|
|
74
|
-
go deep into the expression.
|
|
75
|
-
|
|
76
|
-
$ fast '(casgn' example.rb
|
|
77
|
-
|
|
78
|
-
```ruby
|
|
79
|
-
# example.rb:2
|
|
80
|
-
ANSWER = 42
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Fast matcher never checks the end of the expression and close parens are not
|
|
84
|
-
necessary. We keep them for the sake of specify more node details but the
|
|
85
|
-
expression works with incomplete parens.
|
|
86
|
-
|
|
87
|
-
$ fast '(casgn)' example.rb
|
|
88
|
-
|
|
89
|
-
```ruby
|
|
90
|
-
# example.rb:2
|
|
91
|
-
ANSWER = 42
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Closing extra params also don't have a side effect.
|
|
95
|
-
|
|
96
|
-
$ fast '(casgn))' example.rb
|
|
97
|
-
|
|
98
|
-
```ruby
|
|
99
|
-
# example.rb:2
|
|
100
|
-
ANSWER = 42
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
It also automatically flat parens case you put more levels in the beginning.
|
|
104
|
-
|
|
105
|
-
$ fast '((casgn))' example.rb
|
|
106
|
-
|
|
107
|
-
```ruby
|
|
108
|
-
# example.rb:2
|
|
109
|
-
ANSWER = 42
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
For checking AST details while doing some search, you can use `--ast` in the
|
|
113
|
-
command line for printing the AST instead of the code:
|
|
114
|
-
|
|
115
|
-
$ fast '((casgn ' example.rb --ast
|
|
116
|
-
|
|
117
|
-
```ruby
|
|
118
|
-
# example.rb:2
|
|
119
|
-
(casgn nil :ANSWER
|
|
120
|
-
(int 42))
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
## `_` is **something** not nil
|
|
124
|
-
|
|
125
|
-
Let's enhance our current expression and specify that we're looking for constant
|
|
126
|
-
assignments of integers ignoring values and constant names replacing with `_`.
|
|
127
|
-
|
|
128
|
-
$ fast '(casgn nil _ (int _))' example.rb
|
|
129
|
-
|
|
130
|
-
```ruby
|
|
131
|
-
# example.rb:2
|
|
132
|
-
ANSWER = 42
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
Keep in mind that `_` means not nil and `(casgn _ _ (int _))` would not
|
|
136
|
-
match.
|
|
137
|
-
|
|
138
|
-
Let's search for integer nodes:
|
|
139
|
-
|
|
140
|
-
$ fast int example.rb
|
|
141
|
-
```ruby
|
|
142
|
-
# example.rb:2
|
|
143
|
-
42
|
|
144
|
-
# example.rb:7
|
|
145
|
-
2
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
The current search show the nodes but they are not so useful without understand
|
|
149
|
-
the expression in their context. We need to check their `parent`.
|
|
150
|
-
|
|
151
|
-
## `^` is to get the **parent node** of an expression
|
|
152
|
-
|
|
153
|
-
By default, Parser::AST::Node does not have access to parent and for accessing
|
|
154
|
-
it you can say `^` for reaching the parent.
|
|
155
|
-
|
|
156
|
-
$ fast '^int' example.rb
|
|
157
|
-
|
|
158
|
-
```ruby
|
|
159
|
-
# example.rb:2
|
|
160
|
-
ANSWER = 42
|
|
161
|
-
# example.rb:7
|
|
162
|
-
value * 2
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
And using it multiple times will make the node match from levels up:
|
|
166
|
-
|
|
167
|
-
$ fast '^^int' example.rb
|
|
168
|
-
|
|
169
|
-
```ruby
|
|
170
|
-
# example.rb:2
|
|
171
|
-
ANSWER = 42
|
|
172
|
-
def magic
|
|
173
|
-
rand(ANSWER)
|
|
174
|
-
end
|
|
175
|
-
def duplicate(value)
|
|
176
|
-
value * 2
|
|
177
|
-
end
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
## `[]` join conditions
|
|
181
|
-
|
|
182
|
-
Let's hunt for integer nodes that the parent is also a method:
|
|
183
|
-
|
|
184
|
-
$ fast '[ ^^int def ]' example.rb
|
|
185
|
-
|
|
186
|
-
The match will filter only nodes that matches all internal expressions.
|
|
187
|
-
|
|
188
|
-
```ruby
|
|
189
|
-
# example.rb:6
|
|
190
|
-
def duplicate(value)
|
|
191
|
-
value * 2
|
|
192
|
-
end
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
The expression is matching nodes that have a integer granchild and also with
|
|
196
|
-
type `def`.
|
|
197
|
-
|
|
198
|
-
## `...` is a **node** with children
|
|
199
|
-
|
|
200
|
-
Looking the method representation we have:
|
|
201
|
-
|
|
202
|
-
$ fast def example.rb --ast
|
|
203
|
-
|
|
204
|
-
```ruby
|
|
205
|
-
# example.rb:3
|
|
206
|
-
(def :magic
|
|
207
|
-
(args)
|
|
208
|
-
(send nil :rand
|
|
209
|
-
(const nil :ANSWER)))
|
|
210
|
-
# example.rb:6
|
|
211
|
-
(def :duplicate
|
|
212
|
-
(args
|
|
213
|
-
(arg :value))
|
|
214
|
-
(send
|
|
215
|
-
(lvar :value) :*
|
|
216
|
-
(int 2)))
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
And if we want to delimit only methods with arguments:
|
|
220
|
-
|
|
221
|
-
$ fast '(def _ ...)' example.rb
|
|
222
|
-
|
|
223
|
-
```ruby
|
|
224
|
-
# example.rb:6
|
|
225
|
-
def duplicate(value)
|
|
226
|
-
value * 2
|
|
227
|
-
end
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
If we use `(def _ _)` instead it will match both methods because `(args)`
|
|
231
|
-
does not have children but is not nil.
|
|
232
|
-
|
|
233
|
-
## `$` is for **capture** current expression
|
|
234
|
-
|
|
235
|
-
Now, let's say we want to extract some method name from current classes.
|
|
236
|
-
|
|
237
|
-
In such case we don't want to have the node definition but only return the node
|
|
238
|
-
name.
|
|
239
|
-
|
|
240
|
-
```ruby
|
|
241
|
-
# example.rb:2
|
|
242
|
-
def magic
|
|
243
|
-
rand(ANSWER)
|
|
244
|
-
end
|
|
245
|
-
# example.rb:
|
|
246
|
-
magic
|
|
247
|
-
# example.rb:9
|
|
248
|
-
def duplicate(value)
|
|
249
|
-
value * 2
|
|
250
|
-
end
|
|
251
|
-
# example.rb:
|
|
252
|
-
duplicate
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
One extra method name was printed because of `$` is capturing the element.
|
|
256
|
-
|
|
257
|
-
## `nil` matches exactly **nil**
|
|
258
|
-
|
|
259
|
-
Nil is used in the code as a node type but parser gem also represents empty
|
|
260
|
-
spaces in expressions with nil.
|
|
261
|
-
|
|
262
|
-
Example, a method call from Kernel is a `send` from `nil` calling the method
|
|
263
|
-
while I can also send a method call from a class.
|
|
264
|
-
|
|
265
|
-
```
|
|
266
|
-
$ ruby-parse -e 'method'
|
|
267
|
-
(send nil :method)
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
And a method from a object will have the nested target not nil.
|
|
271
|
-
|
|
272
|
-
```
|
|
273
|
-
$ ruby-parse -e 'object.method'
|
|
274
|
-
(send
|
|
275
|
-
(send nil :object) :method)
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
Let's build a serch for any calls from `nil`:
|
|
279
|
-
|
|
280
|
-
$ fast '(_ nil _)' example.rb
|
|
281
|
-
|
|
282
|
-
```ruby
|
|
283
|
-
# example.rb:3
|
|
284
|
-
Example
|
|
285
|
-
# example.rb:4
|
|
286
|
-
ANSWER = 42
|
|
287
|
-
# example.rb:6
|
|
288
|
-
rand(ANSWER)
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
Double check the expressions that have matched printing the AST:
|
|
292
|
-
|
|
293
|
-
$ fast '(_ nil _)' example.rb --ast
|
|
294
|
-
|
|
295
|
-
```ruby
|
|
296
|
-
# example.rb:3
|
|
297
|
-
(const nil :Example)
|
|
298
|
-
# example.rb:4
|
|
299
|
-
(casgn nil :ANSWER
|
|
300
|
-
(int 42))
|
|
301
|
-
# example.rb:6
|
|
302
|
-
(send nil :rand
|
|
303
|
-
(const nil :ANSWER))
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
## `{}` is for **any** matches like **union** conditions with **or** operator
|
|
307
|
-
|
|
308
|
-
Let's say we to add check all occurrencies of the constant `ANSWER`.
|
|
309
|
-
|
|
310
|
-
We'll need to get both `casgn` and `const` node types. For such cases we can
|
|
311
|
-
surround the expressions with `{}` and it will return if the node matches with
|
|
312
|
-
any of the internal expressions.
|
|
313
|
-
|
|
314
|
-
$ fast '({casgn const} nil ANSWER)' example.rb
|
|
315
|
-
|
|
316
|
-
```
|
|
317
|
-
# example.rb:4
|
|
318
|
-
ANSWER = 42
|
|
319
|
-
# example.rb:6
|
|
320
|
-
ANSWER
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
## `#` for custom methods
|
|
324
|
-
|
|
325
|
-
Custom methods can let you into ruby doman for more complicated rules. Let's say
|
|
326
|
-
we're looking for duplicated methods in the same class. We need to collect
|
|
327
|
-
method names and guarantee they are unique.
|
|
328
|
-
|
|
329
|
-
```ruby
|
|
330
|
-
def duplicated(method_name)
|
|
331
|
-
@methods ||= []
|
|
332
|
-
already_exists = @methods.include?(method_name)
|
|
333
|
-
@methods << method_name
|
|
334
|
-
already_exists
|
|
335
|
-
end
|
|
336
|
-
|
|
337
|
-
puts Fast.search_file( '(def #duplicated)', 'example.rb')
|
|
338
|
-
```
|
|
339
|
-
The same principle can be used in the node level or for debugging purposes.
|
|
340
|
-
|
|
341
|
-
```ruby
|
|
342
|
-
require 'pry'
|
|
343
|
-
def debug(node)
|
|
344
|
-
binding.pry
|
|
345
|
-
end
|
|
346
|
-
|
|
347
|
-
puts Fast.search_file('#debug', 'example.rb')
|
|
348
|
-
```
|
|
349
|
-
If you want to get only `def` nodes you can also intersect expressions with `[]`:
|
|
350
|
-
```ruby
|
|
351
|
-
puts Fast.search_file('[ def #debug ]', 'example.rb')
|
|
352
|
-
```
|
|
353
|
-
Or if you want to debug a very specific expression you can use `()` to specify
|
|
354
|
-
more details of the node
|
|
355
|
-
```ruby
|
|
356
|
-
puts Fast.search_file('[ (def a) #debug ]', 'example.rb')
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
## `.` for instance methods
|
|
360
|
-
|
|
361
|
-
You can also call instance methods using `.<method-name>`.
|
|
362
|
-
|
|
363
|
-
Example `nil` is the same of calling `nil?` and you can also use `(int .odd?)`
|
|
364
|
-
to pick only odd integers. The `int` fragment can also be `int_type?`.
|
|
365
|
-
|
|
366
|
-
## `\1` for first previous capture
|
|
367
|
-
|
|
368
|
-
Imagine you're looking for a method that is just delegating something to
|
|
369
|
-
another method, like:
|
|
370
|
-
|
|
371
|
-
```ruby
|
|
372
|
-
def name
|
|
373
|
-
person.name
|
|
374
|
-
end
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
This can be represented as the following AST:
|
|
378
|
-
|
|
379
|
-
```
|
|
380
|
-
(def :name
|
|
381
|
-
(args)
|
|
382
|
-
(send
|
|
383
|
-
(send nil :person) :name))
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
Then, let's build a search for methods that calls an attribute with the same
|
|
387
|
-
name:
|
|
388
|
-
|
|
389
|
-
```ruby
|
|
390
|
-
Fast.match?('(def $_ ... (send (send nil _) \1))', ast) # => [:name]
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
With the method name being captured with `$_` it can be later referenced in the
|
|
394
|
-
expression with `\1`. If the search contains multiple captures, the `\2`,`\3`
|
|
395
|
-
can be used as the sequence of captures.
|
data/docs/videos.md
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# Videos
|
|
2
|
-
|
|
3
|
-
- [Ruby Kaigi TakeOut 2020: Grepping Ruby code like a boss](https://www.youtube.com/watch?v=YzcYXB4L2so&feature=youtu.be&t=11855)
|
|
4
|
-
|
|
5
|
-
<iframe width="560" height="315" src="https://www.youtube.com/embed/YzcYXB4L2so?start=11855" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
|
6
|
-
|
|
7
|
-
Also, similar livecoding session at [RubyConf Brazil 2019 (Portuguese)](https://www.eventials.com/locaweb/jonatas-paganini-live-coding-grepping-ruby-code-like-a-boss/#_=_).
|
|
8
|
-
|
|
9
|
-
- Introduction to [inline code](https://www.youtube.com/watch?v=KQXglNLUv7o).
|
|
10
|
-
<iframe width="560" height="315" src="https://www.youtube.com/embed/KQXglNLUv7o" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
|
11
|
-
|
|
12
|
-
- [Making local variables inline](https://www.youtube.com/watch?v=JD44nhegCRs)
|
|
13
|
-
<iframe width="560" height="315" src="https://www.youtube.com/embed/YN0s9kV1A2A" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
|
14
|
-
|
|
15
|
-
- [Making methods inline](https://www.youtube.com/watch?v=JD44nhegCRs)
|
|
16
|
-
<iframe width="560" height="315" src="https://www.youtube.com/embed/YN0s9kV1A2A" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'fast'
|
|
4
|
-
require 'open3'
|
|
5
|
-
require 'ostruct'
|
|
6
|
-
require 'fileutils'
|
|
7
|
-
|
|
8
|
-
# Usage instructions:
|
|
9
|
-
# 1. Add the following to your project's Gemfile: gem 'ffast' (yes, two "f"s)
|
|
10
|
-
# 2. Copy this file to your Rails project root directory
|
|
11
|
-
# 3. Run: bundle exec ruby build_stubbed_and_let_it_be_experiment.rb
|
|
12
|
-
|
|
13
|
-
# List of spec files you want to experiment with. One per line.
|
|
14
|
-
FILE_NAMES = %w[
|
|
15
|
-
# spec/model/foo_spec.rb
|
|
16
|
-
# spec/model/bar_spec.rb
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
def execute_rspec(file_name)
|
|
20
|
-
rspec_command = "bin/spring rspec --fail-fast --format progress #{file_name}"
|
|
21
|
-
stdout_str, stderr_str, status = Open3.capture3(rspec_command)
|
|
22
|
-
execution_time = /Finished in (.*?) seconds/.match(stdout_str)[1]
|
|
23
|
-
print stderr_str.gsub(/Running via Spring preloader.*?$/, '').chomp unless status.success?
|
|
24
|
-
OpenStruct.new(success: status.success?, execution_time: execution_time)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def delete_temp_files(original_file_name)
|
|
28
|
-
file_path = File.dirname(original_file_name)
|
|
29
|
-
file_name = File.basename(original_file_name)
|
|
30
|
-
Dir.glob("#{file_path}/experiment*#{file_name}").each { |file| File.delete(file)}
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
FILE_NAMES.each do |original_file_name|
|
|
34
|
-
Fast.experiment('RSpec/ReplaceCreateWithBuildStubbed') do
|
|
35
|
-
lookup original_file_name
|
|
36
|
-
search '(block (send nil let (sym _)) (args) $(send nil create))'
|
|
37
|
-
edit { |_, (create)| replace(create.loc.selector, 'build_stubbed') }
|
|
38
|
-
policy { |experiment_file_name| execute_rspec(experiment_file_name) }
|
|
39
|
-
end.run
|
|
40
|
-
|
|
41
|
-
Fast.experiment('RSpec/LetItBe') do
|
|
42
|
-
lookup original_file_name
|
|
43
|
-
search '(block $(send nil let! (sym _)) (args) (send nil create))'
|
|
44
|
-
edit { |_, (let)| replace(let.loc.selector, 'let_it_be') }
|
|
45
|
-
policy { |experiment_file_name| execute_rspec(experiment_file_name) }
|
|
46
|
-
end.run
|
|
47
|
-
|
|
48
|
-
delete_temp_files(original_file_name)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
$LOAD_PATH << File.expand_path('../lib', __dir__)
|
|
4
|
-
require 'fast'
|
|
5
|
-
|
|
6
|
-
# It's a simple script that you can try to replace
|
|
7
|
-
# `create` by `build_stubbed` and it moves the file if
|
|
8
|
-
# successfully passed the specs
|
|
9
|
-
#
|
|
10
|
-
# $ ruby experimental_replacement.rb spec/*/*_spec.rb
|
|
11
|
-
def experimental_spec(file, name)
|
|
12
|
-
parts = file.split('/')
|
|
13
|
-
dir = parts[0..-2]
|
|
14
|
-
filename = "experiment_#{name}_#{parts[-1]}"
|
|
15
|
-
File.join(*dir, filename)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def experiment(file, name, search, replacement)
|
|
19
|
-
ast = Fast.ast_from_file(file)
|
|
20
|
-
|
|
21
|
-
results = Fast.search(ast, search)
|
|
22
|
-
unless results.empty?
|
|
23
|
-
new_content = Fast.replace_file(file, search, replacement)
|
|
24
|
-
new_spec = experimental_spec(file, name)
|
|
25
|
-
return if File.exist?(new_spec)
|
|
26
|
-
File.open(new_spec, 'w+') { |f| f.puts new_content }
|
|
27
|
-
if system("bin/spring rspec --fail-fast #{new_spec}")
|
|
28
|
-
system "mv #{new_spec} #{file}"
|
|
29
|
-
puts "✅ #{file}"
|
|
30
|
-
else
|
|
31
|
-
system "rm #{new_spec}"
|
|
32
|
-
puts "🔴 #{file}"
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
rescue StandardError
|
|
36
|
-
# Avoid stop because weird errors like encoding issues
|
|
37
|
-
puts "🔴🔴 🔴 #{file}: #{$ERROR_INFO}"
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
ARGV.each do |file|
|
|
41
|
-
[
|
|
42
|
-
# Thread.new { experiment(file, 'build_stubbed', '(send nil create)', ->(node) { replace(node.location.selector, 'build_stubbed') }) },
|
|
43
|
-
experiment(file, 'seed', '(send nil create)', ->(node) { replace(node.location.selector, 'seed') })
|
|
44
|
-
|
|
45
|
-
] # .each(&:join)
|
|
46
|
-
end
|
data/examples/find_usage.rb
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# List files that matches with some expression
|
|
5
|
-
# Usage:
|
|
6
|
-
#
|
|
7
|
-
# ruby examples/find_usage.rb defs
|
|
8
|
-
#
|
|
9
|
-
# Or be explicit about directory or folder:
|
|
10
|
-
#
|
|
11
|
-
# ruby examples/find_usage.rb defs lib/
|
|
12
|
-
$LOAD_PATH.unshift(File.expand_path('lib', __dir__))
|
|
13
|
-
|
|
14
|
-
require 'fast'
|
|
15
|
-
require 'coderay'
|
|
16
|
-
|
|
17
|
-
arguments = ARGV
|
|
18
|
-
pattern = arguments.shift
|
|
19
|
-
files = Fast.ruby_files_from(arguments.any? ? arguments : '.')
|
|
20
|
-
files.select do |file|
|
|
21
|
-
begin
|
|
22
|
-
puts file if Fast.search_file(pattern, file).any?
|
|
23
|
-
rescue Parser::SyntaxError
|
|
24
|
-
[]
|
|
25
|
-
end
|
|
26
|
-
end
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require '../../fast/lib/fast'
|
|
4
|
-
|
|
5
|
-
# For specs using `let(:something) { create ... }` it tries to use `let_it_be` instead
|
|
6
|
-
Fast.experiment('RSpec/LetItBe') do
|
|
7
|
-
lookup 'spec/models'
|
|
8
|
-
search '(block $(send nil let (sym _)) (args) (send nil create))'
|
|
9
|
-
edit { |_, (let)| replace(let.loc.selector, 'let_it_be') }
|
|
10
|
-
policy { |new_file| system("bin/spring rspec --fail-fast #{new_file}") }
|
|
11
|
-
end.run
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
$LOAD_PATH << File.expand_path('../lib', __dir__)
|
|
4
|
-
require 'fast'
|
|
5
|
-
|
|
6
|
-
def node_size(node)
|
|
7
|
-
return 1 unless node.respond_to?(:children)
|
|
8
|
-
children = node.children
|
|
9
|
-
return 1 if children.empty? || children.length == 1
|
|
10
|
-
nodes, syms = children.partition { |e| e.respond_to?(:children) }
|
|
11
|
-
1 + syms.length + (nodes.map(&method(:node_size)).inject(:+) || 0)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def method_complexity(file)
|
|
15
|
-
ast = Fast.ast_from_file(file)
|
|
16
|
-
Fast.search(ast, '(class ...)').map do |node_class|
|
|
17
|
-
manager_name = node_class.children.first.children.last
|
|
18
|
-
|
|
19
|
-
defs = Fast.search(node_class, '(def !{initialize} ... ... )')
|
|
20
|
-
|
|
21
|
-
defs.map do |node|
|
|
22
|
-
complexity = node_size(node)
|
|
23
|
-
method_name = node.children.first
|
|
24
|
-
{ "#{manager_name}##{method_name}" => complexity }
|
|
25
|
-
end.inject(:merge) || {}
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
files = ARGV || Dir['**/*.rb']
|
|
30
|
-
|
|
31
|
-
complexities = files.map(&method(:method_complexity)).flatten.inject(:merge!)
|
|
32
|
-
|
|
33
|
-
puts '| Method | Complexity |'
|
|
34
|
-
puts '| ------ | ---------- |'
|
|
35
|
-
complexities.sort_by { |_, v| -v }.map do |method, complexity|
|
|
36
|
-
puts "| #{method} | #{complexity} |"
|
|
37
|
-
end
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
require 'fast'
|
|
2
|
-
|
|
3
|
-
# Search for duplicated methods interpolating the method and collecting previous
|
|
4
|
-
# method names. Returns true if the name already exists in the same class level.
|
|
5
|
-
# Note that this example will work only in a single file because it does not
|
|
6
|
-
# cover any detail on class level.
|
|
7
|
-
def duplicated(method_name)
|
|
8
|
-
@methods ||= []
|
|
9
|
-
already_exists = @methods.include?(method_name)
|
|
10
|
-
@methods << method_name
|
|
11
|
-
already_exists
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
puts Fast.search_file( '(def #duplicated)', 'example.rb')
|
|
15
|
-
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
require 'bundler/setup'
|
|
2
|
-
require 'fast'
|
|
3
|
-
require 'coderay'
|
|
4
|
-
require 'pp'
|
|
5
|
-
require 'set'
|
|
6
|
-
|
|
7
|
-
arguments = ARGV
|
|
8
|
-
pattern = arguments.shift || '{ block case send def defs while class if }'
|
|
9
|
-
|
|
10
|
-
files = Fast.ruby_files_from(*%w(spec lib app gems)) +
|
|
11
|
-
Dir[File.join(Gem.path.first,'**/*.rb')]
|
|
12
|
-
|
|
13
|
-
total = files.count
|
|
14
|
-
pattern = Fast.expression(pattern)
|
|
15
|
-
|
|
16
|
-
similarities = {}
|
|
17
|
-
|
|
18
|
-
def similarities.show pattern
|
|
19
|
-
files = self[pattern]
|
|
20
|
-
files.each do |file|
|
|
21
|
-
nodes = Fast.search_file(pattern, file)
|
|
22
|
-
nodes.each do |result|
|
|
23
|
-
Fast.report(result, file: file)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def similarities.top
|
|
29
|
-
self.transform_values(&:size)
|
|
30
|
-
.sort_by{|search,results|search.size / results.size}
|
|
31
|
-
.reverse.select{|k,v|v > 10}[0,10]
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
begin
|
|
35
|
-
files.each_with_index do |file, i|
|
|
36
|
-
progress = ((i / total.to_f) * 100.0).round(2)
|
|
37
|
-
print "\r (#{i}/#{total}) #{progress}% Researching on #{file}"
|
|
38
|
-
begin
|
|
39
|
-
results = Fast.search_file(pattern, file) || []
|
|
40
|
-
rescue
|
|
41
|
-
next
|
|
42
|
-
end
|
|
43
|
-
results.each do |n|
|
|
44
|
-
search = Fast.expression_from(n)
|
|
45
|
-
similarities[search] ||= Set.new
|
|
46
|
-
similarities[search] << file
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
rescue Interrupt
|
|
50
|
-
# require 'pry'; binding.pry
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
puts "mapped #{similarities.size} cases"
|
|
54
|
-
similarities.delete_if {|k,v| k.size < 30 || v.size < 5}
|
|
55
|
-
puts "Removing the small ones we have #{similarities.size} similarities"
|
|
56
|
-
|
|
57
|
-
similarities.show similarities.top[0][0]
|
|
58
|
-
|
data/examples/simple_rewriter.rb
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# For specs using `let!(:something) { create ... }` it tries to use `let_it_be` instead
|
|
4
|
-
Fast.experiment('RSpec/LetItBe') do
|
|
5
|
-
lookup 'spec'
|
|
6
|
-
search '(block $(send nil let! (sym _)) (args) (send nil create))'
|
|
7
|
-
edit { |_, (let)| replace(let.loc.selector, 'let_it_be') }
|
|
8
|
-
policy { |new_file| system("bin/spring rspec --fail-fast #{new_file}") }
|
|
9
|
-
end
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Experimentally remove a before or an after block
|
|
4
|
-
Fast.experiment('RSpec/RemoveUselessBeforeAfterHook') do
|
|
5
|
-
lookup 'spec'
|
|
6
|
-
search '(block (send nil {before after}))'
|
|
7
|
-
edit { |node| remove(node.loc.expression) }
|
|
8
|
-
policy { |new_file| system("bin/spring rspec --fail-fast #{new_file}") }
|
|
9
|
-
end
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# For specs using `let(:something) { create ... }` it tries to use
|
|
4
|
-
# `build_stubbed` instead
|
|
5
|
-
Fast.experiment('RSpec/ReplaceCreateWithBuildStubbed') do
|
|
6
|
-
lookup 'spec'
|
|
7
|
-
search '(block (send nil let (sym _)) (args) $(send nil create))'
|
|
8
|
-
edit { |_, (create)| replace(create.loc.selector, 'build_stubbed') }
|
|
9
|
-
policy { |new_file| system("bin/spring rspec --format progress --fail-fast #{new_file}") }
|
|
10
|
-
end
|