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/README.md
CHANGED
|
@@ -12,6 +12,20 @@ the code was written without an AST.
|
|
|
12
12
|
|
|
13
13
|
Check out the official documentation: https://jonatas.github.io/fast.
|
|
14
14
|
|
|
15
|
+
## Documentation locally
|
|
16
|
+
|
|
17
|
+
The documentation site is built with MkDocs Material and a few Markdown
|
|
18
|
+
extensions. To run it locally:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
python3 -m venv .venv
|
|
22
|
+
source .venv/bin/activate
|
|
23
|
+
python3 -m pip install -r requirements-docs.txt
|
|
24
|
+
mkdocs serve
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Then open `http://127.0.0.1:8000`.
|
|
28
|
+
|
|
15
29
|
## Token Syntax for `find` in AST
|
|
16
30
|
|
|
17
31
|
The current version of Fast covers the following token elements:
|
|
@@ -53,48 +67,64 @@ to represent code called `s-expressions`.
|
|
|
53
67
|
|
|
54
68
|
For example, let's take an `Integer` in Ruby:
|
|
55
69
|
|
|
56
|
-
|
|
70
|
+
```ruby
|
|
71
|
+
1
|
|
72
|
+
```
|
|
57
73
|
|
|
58
|
-
|
|
74
|
+
Its corresponding s-expression would be:
|
|
59
75
|
|
|
60
|
-
|
|
76
|
+
```ruby
|
|
77
|
+
s(:int, 1)
|
|
78
|
+
```
|
|
61
79
|
|
|
62
|
-
`s` in `Fast`
|
|
80
|
+
`s` in `Fast` is a shorthand for creating a `Fast::Node`.
|
|
63
81
|
Each of these nodes has a `#type` and `#children` contained in it:
|
|
64
82
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
83
|
+
```ruby
|
|
84
|
+
def s(type, *children)
|
|
85
|
+
Fast::Node.new(type, children: children)
|
|
86
|
+
end
|
|
87
|
+
```
|
|
68
88
|
|
|
69
89
|
### Variable Assignments
|
|
70
90
|
|
|
71
91
|
Now let's take a look at a local variable assignment:
|
|
72
92
|
|
|
73
|
-
|
|
93
|
+
```ruby
|
|
94
|
+
value = 42
|
|
95
|
+
```
|
|
74
96
|
|
|
75
97
|
It's corresponding s-expression would be:
|
|
76
98
|
|
|
77
|
-
|
|
99
|
+
```ruby
|
|
100
|
+
ast = s(:lvasgn, :value, s(:int, 42))
|
|
101
|
+
```
|
|
78
102
|
|
|
79
103
|
If we wanted to find this particular assignment somewhere in our AST, we can use
|
|
80
104
|
Fast to look for a local variable named `value` with a value `42`:
|
|
81
105
|
|
|
82
|
-
|
|
106
|
+
```ruby
|
|
107
|
+
Fast.match?('(lvasgn value (int 42))', ast) # => true
|
|
108
|
+
```
|
|
83
109
|
|
|
84
110
|
### Wildcard Token
|
|
85
111
|
|
|
86
112
|
If we wanted to find a variable named `value` that was assigned any integer value
|
|
87
113
|
we could replace `42` in our query with an underscore ( `_` ) as a shortcut:
|
|
88
114
|
|
|
89
|
-
|
|
115
|
+
```ruby
|
|
116
|
+
Fast.match?('(lvasgn value (int _))', ast) # => true
|
|
117
|
+
```
|
|
90
118
|
|
|
91
119
|
### Set Inclusion Token
|
|
92
120
|
|
|
93
121
|
If we weren't sure the type of the value we're assigning, we can use our set
|
|
94
122
|
inclusion token (`{}`) from earlier to tell Fast that we expect either a `Float`
|
|
95
123
|
or an `Integer`:
|
|
96
|
-
|
|
97
|
-
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
Fast.match?('(lvasgn value ({float int} _))', ast) # => true
|
|
127
|
+
```
|
|
98
128
|
|
|
99
129
|
### All Matching Token
|
|
100
130
|
|
|
@@ -103,28 +133,38 @@ all matching token (`[]`) to express multiple conditions that need to be true.
|
|
|
103
133
|
In this case we don't want the value to be a `String`, `Hash`, or an `Array` by
|
|
104
134
|
prefixing all of the types with `!`:
|
|
105
135
|
|
|
106
|
-
|
|
136
|
+
```ruby
|
|
137
|
+
Fast.match?('(lvasgn value ([!str !hash !array] _))', ast) # => true
|
|
138
|
+
```
|
|
107
139
|
|
|
108
140
|
### Node Child Token
|
|
109
141
|
|
|
110
142
|
We can match any node with children by using the child token ( `...` ):
|
|
111
143
|
|
|
112
|
-
|
|
144
|
+
```ruby
|
|
145
|
+
Fast.match?('(lvasgn value ...)', ast) # => true
|
|
146
|
+
```
|
|
113
147
|
|
|
114
148
|
We could even match any local variable assignment combining both `_` and `...`:
|
|
115
149
|
|
|
116
|
-
|
|
150
|
+
```ruby
|
|
151
|
+
Fast.match?('(lvasgn _ ...)', ast) # => true
|
|
152
|
+
```
|
|
117
153
|
|
|
118
154
|
### Capturing the Value of an Expression
|
|
119
155
|
|
|
120
156
|
You can use `$` to capture the contents of an expression for later use:
|
|
121
157
|
|
|
122
|
-
|
|
158
|
+
```ruby
|
|
159
|
+
Fast.match?('(lvasgn value $...)', ast) # => [s(:int, 42)]
|
|
160
|
+
```
|
|
123
161
|
|
|
124
162
|
Captures can be used in any position as many times as you want to capture whatever
|
|
125
163
|
information you might need:
|
|
126
164
|
|
|
127
|
-
|
|
165
|
+
```ruby
|
|
166
|
+
Fast.match?('(lvasgn $_ $...)', ast) # => [:value, s(:int, 42)]
|
|
167
|
+
```
|
|
128
168
|
|
|
129
169
|
> Keep in mind that `_` means something not nil and `...` means a node with
|
|
130
170
|
> children.
|
|
@@ -135,44 +175,53 @@ You can also define custom methods to set more complicated rules. Let's say
|
|
|
135
175
|
we're looking for duplicated methods in the same class. We need to collect
|
|
136
176
|
method names and guarantee they are unique.
|
|
137
177
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
178
|
+
```ruby
|
|
179
|
+
def duplicated(method_name)
|
|
180
|
+
@methods ||= []
|
|
181
|
+
already_exists = @methods.include?(method_name)
|
|
182
|
+
@methods << method_name
|
|
183
|
+
already_exists
|
|
184
|
+
end
|
|
144
185
|
|
|
145
|
-
|
|
186
|
+
puts Fast.search_file('(def #duplicated)', 'example.rb')
|
|
187
|
+
```
|
|
146
188
|
|
|
147
189
|
The same principle can be used in the node level or for debugging purposes.
|
|
148
190
|
|
|
191
|
+
```ruby
|
|
149
192
|
require 'pry'
|
|
150
193
|
def debug(node)
|
|
151
194
|
binding.pry
|
|
152
195
|
end
|
|
153
196
|
|
|
154
197
|
puts Fast.search_file('#debug', 'example.rb')
|
|
155
|
-
|
|
198
|
+
```
|
|
156
199
|
If you want to get only `def` nodes you can also intersect expressions with `[]`:
|
|
157
200
|
|
|
158
|
-
|
|
201
|
+
```ruby
|
|
202
|
+
puts Fast.search_file('[ def #debug ]', 'example.rb')
|
|
203
|
+
```
|
|
159
204
|
|
|
160
205
|
### Methods
|
|
161
206
|
|
|
162
207
|
Let's take a look at a method declaration:
|
|
163
208
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
209
|
+
```ruby
|
|
210
|
+
def my_method
|
|
211
|
+
call_other_method
|
|
212
|
+
end
|
|
213
|
+
```
|
|
167
214
|
|
|
168
|
-
|
|
215
|
+
Its corresponding s-expression would be:
|
|
169
216
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
217
|
+
```ruby
|
|
218
|
+
ast =
|
|
219
|
+
s(:def, :my_method,
|
|
220
|
+
s(:args),
|
|
221
|
+
s(:send, nil, :call_other_method))
|
|
222
|
+
```
|
|
174
223
|
|
|
175
|
-
|
|
224
|
+
Note the node `(args)`. We can't use `...` to match it, as it
|
|
176
225
|
has no children (or arguments in this case), but we _can_ match it with a wildcard
|
|
177
226
|
`_` as it's not `nil`.
|
|
178
227
|
|
|
@@ -181,45 +230,55 @@ has no children (or arguments in this case), but we _can_ match it with a wildca
|
|
|
181
230
|
Let's take a look at a few other examples. Sometimes you have a chain of calls on
|
|
182
231
|
a single `Object`, like `a.b.c.d`. Its corresponding s-expression would be:
|
|
183
232
|
|
|
184
|
-
|
|
233
|
+
```ruby
|
|
234
|
+
ast =
|
|
235
|
+
s(:send,
|
|
236
|
+
s(:send,
|
|
185
237
|
s(:send,
|
|
186
|
-
s(:send,
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
:d)
|
|
238
|
+
s(:send, nil, :a),
|
|
239
|
+
:b),
|
|
240
|
+
:c),
|
|
241
|
+
:d)
|
|
242
|
+
```
|
|
192
243
|
|
|
193
244
|
### Alternate Syntax
|
|
194
245
|
|
|
195
246
|
You can also search using nested arrays with **pure values**, or **shortcuts** or
|
|
196
247
|
**procs**:
|
|
197
248
|
|
|
198
|
-
|
|
199
|
-
|
|
249
|
+
```ruby
|
|
250
|
+
Fast.match? [:send, [:send, '...'], :d], ast # => true
|
|
251
|
+
Fast.match? [:send, [:send, '...'], :c], ast # => false
|
|
252
|
+
```
|
|
200
253
|
|
|
201
254
|
Shortcut tokens like child nodes `...` and wildcards `_` are just placeholders
|
|
202
255
|
for procs. If you want, you can even use procs directly like so:
|
|
203
256
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
257
|
+
```ruby
|
|
258
|
+
Fast.match?([
|
|
259
|
+
:send, [
|
|
260
|
+
-> (node) { node.type == :send },
|
|
261
|
+
[:send, '...'],
|
|
262
|
+
:c
|
|
263
|
+
],
|
|
264
|
+
:d
|
|
265
|
+
], ast) # => true
|
|
266
|
+
```
|
|
212
267
|
|
|
213
268
|
This also works with expressions:
|
|
214
269
|
|
|
215
|
-
|
|
270
|
+
```ruby
|
|
271
|
+
Fast.match?('(send (send (send (send nil $_) $_) $_) $_)', ast) # => [:a, :b, :c, :d]
|
|
272
|
+
```
|
|
216
273
|
|
|
217
274
|
### Debugging
|
|
218
275
|
|
|
219
276
|
If you find that a particular expression isn't working, you can use `debug` to
|
|
220
277
|
take a look at what Fast is doing:
|
|
221
278
|
|
|
222
|
-
|
|
279
|
+
```ruby
|
|
280
|
+
Fast.debug { Fast.match?([:int, 1], s(:int, 1)) }
|
|
281
|
+
```
|
|
223
282
|
|
|
224
283
|
Each comparison made while searching will be logged to your console (STDOUT) as
|
|
225
284
|
Fast goes through the AST:
|
|
@@ -233,61 +292,83 @@ We can also dynamically interpolate arguments into our queries using the
|
|
|
233
292
|
interpolation token `%`. This works much like `sprintf` using indexes starting
|
|
234
293
|
from `1`:
|
|
235
294
|
|
|
236
|
-
|
|
295
|
+
```ruby
|
|
296
|
+
Fast.match? '(lvasgn %1 (int _))', ('a = 1'), :a # => true
|
|
297
|
+
```
|
|
237
298
|
|
|
238
299
|
## Using previous captures in search
|
|
239
300
|
|
|
240
301
|
Imagine you're looking for a method that is just delegating something to
|
|
241
302
|
another method, like this `name` method:
|
|
242
303
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
304
|
+
```ruby
|
|
305
|
+
def name
|
|
306
|
+
person.name
|
|
307
|
+
end
|
|
308
|
+
```
|
|
246
309
|
|
|
247
310
|
This can be represented as the following AST:
|
|
248
311
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
312
|
+
```
|
|
313
|
+
(def :name
|
|
314
|
+
(args)
|
|
315
|
+
(send
|
|
316
|
+
(send nil :person) :name))
|
|
317
|
+
```
|
|
253
318
|
|
|
254
319
|
We can create a query that searches for such a method:
|
|
255
320
|
|
|
256
|
-
|
|
321
|
+
```ruby
|
|
322
|
+
Fast.match?('(def $_ ... (send (send nil _) \1))', ast) # => [:name]
|
|
323
|
+
```
|
|
324
|
+
|
|
257
325
|
|
|
258
326
|
## Fast.search
|
|
259
327
|
|
|
260
328
|
Search allows you to go search the entire AST, collecting nodes that matches given
|
|
261
329
|
expression. Any matching node is then returned:
|
|
262
330
|
|
|
263
|
-
|
|
331
|
+
```ruby
|
|
332
|
+
Fast.search('(int _)', Fast.ast('a = 1')) # => s(:int, 1)
|
|
333
|
+
```
|
|
264
334
|
|
|
265
335
|
If you use captures along with a search, both the matching nodes and the
|
|
266
336
|
captures will be returned:
|
|
267
337
|
|
|
268
|
-
|
|
338
|
+
```ruby
|
|
339
|
+
Fast.search('(int $_)', Fast.ast('a = 1')) # => [s(:int, 1), 1]
|
|
340
|
+
```
|
|
341
|
+
|
|
269
342
|
|
|
270
343
|
You can also bind external parameters from the search:
|
|
271
344
|
|
|
272
|
-
|
|
345
|
+
```ruby
|
|
346
|
+
Fast.search('(int %1)', Fast.ast('a = 1'), 1) # => [s(:int, 1)]
|
|
347
|
+
```
|
|
273
348
|
|
|
274
349
|
## Fast.capture
|
|
275
350
|
|
|
276
351
|
To only pick captures and ignore the nodes, use `Fast.capture`:
|
|
277
352
|
|
|
278
|
-
|
|
353
|
+
```ruby
|
|
354
|
+
Fast.capture('(int $_)', Fast.ast('a = 1')) # => 1
|
|
355
|
+
```
|
|
279
356
|
|
|
280
357
|
## Fast.replace
|
|
281
358
|
|
|
282
359
|
Let's consider the following example:
|
|
283
360
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
361
|
+
```ruby
|
|
362
|
+
def name
|
|
363
|
+
person.name
|
|
364
|
+
end
|
|
365
|
+
```
|
|
287
366
|
|
|
288
367
|
And, we want to replace code to use `delegate` in the expression:
|
|
289
368
|
|
|
290
|
-
|
|
369
|
+
```ruby
|
|
370
|
+
delegate :name, to: :person
|
|
371
|
+
```
|
|
291
372
|
|
|
292
373
|
We already target this example using `\1` on
|
|
293
374
|
[Search and refer to previous capture](#using-previous-captures-in-search) and
|
|
@@ -297,59 +378,73 @@ The [Fast.replace](Fast#replace-class_method) yields a #{Fast::Rewriter} context
|
|
|
297
378
|
The internal replace method accepts a range and every `node` have
|
|
298
379
|
a `location` with metadata about ranges of the node expression.
|
|
299
380
|
|
|
300
|
-
|
|
301
|
-
|
|
381
|
+
```ruby
|
|
382
|
+
ast = Fast.ast("def name; person.name end")
|
|
383
|
+
# => s(:def, :name, s(:args), s(:send, s(:send, nil, :person), :name))
|
|
384
|
+
```
|
|
302
385
|
|
|
303
386
|
Generally, we use the `location.expression`:
|
|
304
387
|
|
|
305
|
-
|
|
388
|
+
```ruby
|
|
389
|
+
ast.location.expression # => #<Fast::Source::Range (string) 0...25>
|
|
390
|
+
```
|
|
306
391
|
|
|
307
392
|
But location also brings some metadata about specific fragments:
|
|
308
393
|
|
|
309
|
-
|
|
310
|
-
|
|
394
|
+
```ruby
|
|
395
|
+
ast.location.instance_variables # => [:@keyword, :@operator, :@name, :@end, :@expression, :@node]
|
|
396
|
+
```
|
|
311
397
|
|
|
312
398
|
Range for the keyword that identifies the method definition:
|
|
313
|
-
|
|
314
|
-
|
|
399
|
+
```ruby
|
|
400
|
+
ast.location.keyword # => #<Fast::Source::Range (string) 0...3>
|
|
401
|
+
```
|
|
315
402
|
|
|
316
403
|
You can always pick the source of a source range:
|
|
317
404
|
|
|
318
|
-
|
|
405
|
+
```ruby
|
|
406
|
+
ast.location.keyword.source # => "def"
|
|
407
|
+
```
|
|
319
408
|
|
|
320
409
|
Or only the method name:
|
|
321
410
|
|
|
322
|
-
|
|
323
|
-
|
|
411
|
+
```ruby
|
|
412
|
+
ast.location.name # => #<Fast::Source::Range (string) 4...8>
|
|
413
|
+
ast.location.name.source # => "name"
|
|
414
|
+
```
|
|
324
415
|
|
|
325
416
|
In the context of the rewriter, the objective is removing the method and inserting the new
|
|
326
417
|
delegate content. Then, the scope is `node.location.expression`:
|
|
327
418
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
replace(
|
|
332
|
-
node.location.expression,
|
|
333
|
-
"delegate :#{attribute}, to: :#{object}"
|
|
334
|
-
)
|
|
335
|
-
end
|
|
419
|
+
```ruby
|
|
420
|
+
Fast.replace '(def $_ ... (send (send nil $_) \1))', ast do |node, captures|
|
|
421
|
+
attribute, object = captures
|
|
336
422
|
|
|
423
|
+
replace(
|
|
424
|
+
node.location.expression,
|
|
425
|
+
"delegate :#{attribute}, to: :#{object}"
|
|
426
|
+
)
|
|
427
|
+
end
|
|
428
|
+
```
|
|
337
429
|
|
|
338
430
|
### Replacing file
|
|
339
431
|
|
|
340
432
|
Now let's imagine we have a file like `sample.rb` with the following code:
|
|
341
433
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
434
|
+
```ruby
|
|
435
|
+
def good_bye
|
|
436
|
+
message = ["good", "bye"]
|
|
437
|
+
puts message.join(' ')
|
|
438
|
+
end
|
|
439
|
+
```
|
|
346
440
|
|
|
347
441
|
and we decide to inline the contents of the `message` variable right after
|
|
348
442
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
443
|
+
```ruby
|
|
444
|
+
def good_bye
|
|
445
|
+
puts ["good", "bye"].join(' ')
|
|
446
|
+
end
|
|
447
|
+
```
|
|
353
448
|
|
|
354
449
|
To refactor and reach the proposed example, follow a few steps:
|
|
355
450
|
|
|
@@ -359,22 +454,24 @@ To refactor and reach the proposed example, follow a few steps:
|
|
|
359
454
|
|
|
360
455
|
#### Entire example
|
|
361
456
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
457
|
+
```ruby
|
|
458
|
+
assignment = nil
|
|
459
|
+
Fast.replace_file '({ lvasgn lvar } message )', 'sample.rb' do |node, _|
|
|
460
|
+
# Find a variable assignment
|
|
461
|
+
if node.type == :lvasgn
|
|
462
|
+
assignment = node.children.last
|
|
463
|
+
# Remove the node responsible for the assignment
|
|
464
|
+
remove(node.location.expression)
|
|
465
|
+
# Look for the variable being used
|
|
466
|
+
elsif node.type == :lvar
|
|
467
|
+
# Replace the variable with the contents of the variable
|
|
468
|
+
replace(
|
|
469
|
+
node.location.expression,
|
|
470
|
+
assignment.location.expression.source
|
|
471
|
+
)
|
|
472
|
+
end
|
|
473
|
+
end
|
|
474
|
+
```
|
|
378
475
|
|
|
379
476
|
Keep in mind the current example returns a content output but do not rewrite the
|
|
380
477
|
file.
|
|
@@ -386,14 +483,17 @@ To manipulate ruby files, sometimes you'll need some extra tasks.
|
|
|
386
483
|
## Fast.ast_from_file(file)
|
|
387
484
|
|
|
388
485
|
This method parses code from a file and loads it into an AST representation.
|
|
389
|
-
|
|
390
|
-
|
|
486
|
+
```ruby
|
|
487
|
+
Fast.ast_from_file('sample.rb')
|
|
488
|
+
```
|
|
391
489
|
|
|
392
490
|
## Fast.search_file
|
|
393
491
|
|
|
394
492
|
You can use `search_file` to for search for expressions inside files.
|
|
395
493
|
|
|
396
|
-
|
|
494
|
+
```ruby
|
|
495
|
+
Fast.search_file(expression, 'file.rb')
|
|
496
|
+
```
|
|
397
497
|
|
|
398
498
|
It's a combination of `Fast.ast_from_file` with `Fast.search`.
|
|
399
499
|
|
|
@@ -401,15 +501,22 @@ It's a combination of `Fast.ast_from_file` with `Fast.search`.
|
|
|
401
501
|
|
|
402
502
|
You can use `Fast.capture_file` to only return captures:
|
|
403
503
|
|
|
404
|
-
|
|
405
|
-
|
|
504
|
+
```ruby
|
|
505
|
+
Fast.capture_file('(class (const nil $_))', 'lib/fast.rb')
|
|
506
|
+
# => [:Rewriter, :ExpressionParser, :Find, :FindString, ...]
|
|
507
|
+
```
|
|
406
508
|
|
|
407
509
|
## Fast.ruby_files_from(arguments)
|
|
408
510
|
|
|
409
511
|
The `Fast.ruby_files_from(arguments)` can get all ruby files from file list or folders:
|
|
410
512
|
|
|
411
|
-
|
|
412
|
-
|
|
513
|
+
```ruby
|
|
514
|
+
Fast.ruby_files_from('lib')
|
|
515
|
+
# => ["lib/fast/experiment.rb", "lib/fast/cli.rb", "lib/fast/version.rb", "lib/fast.rb"]
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
> Note: it doesn't support glob special selectors like `*.rb` or `**/*` as it
|
|
519
|
+
> recursively looks for ruby files in the givem params.
|
|
413
520
|
|
|
414
521
|
## `fast` in the command line
|
|
415
522
|
|
|
@@ -532,19 +639,21 @@ from our specs.
|
|
|
532
639
|
|
|
533
640
|
If the spec still pass we can confidently say that the hook is useless.
|
|
534
641
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
642
|
+
```ruby
|
|
643
|
+
Fast.experiment("RSpec/RemoveUselessBeforeAfterHook") do
|
|
644
|
+
# Lookup our spec files
|
|
645
|
+
lookup 'spec'
|
|
538
646
|
|
|
539
|
-
|
|
540
|
-
|
|
647
|
+
# Look for every block starting with before or after
|
|
648
|
+
search "(block (send nil {before after}))"
|
|
541
649
|
|
|
542
|
-
|
|
543
|
-
|
|
650
|
+
# Remove those blocks
|
|
651
|
+
edit { |node| remove(node.loc.expression) }
|
|
544
652
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
653
|
+
# Create a new file, and run RSpec against that new file
|
|
654
|
+
policy { |new_file| system("bin/spring rspec --fail-fast #{new_file}") }
|
|
655
|
+
end
|
|
656
|
+
```
|
|
548
657
|
|
|
549
658
|
- `lookup` can be used to pass in files or folders.
|
|
550
659
|
- `search` contains the expression you want to match
|
|
@@ -578,7 +687,10 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
|
578
687
|
On the console we have a few functions like `s` and `code` to make it easy ;)
|
|
579
688
|
|
|
580
689
|
bin/console
|
|
581
|
-
|
|
690
|
+
|
|
691
|
+
```ruby
|
|
692
|
+
code("a = 1") # => s(:lvasgn, s(:int, 1))
|
|
693
|
+
```
|
|
582
694
|
|
|
583
695
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
584
696
|
|
data/bin/console
CHANGED
|
@@ -3,15 +3,20 @@
|
|
|
3
3
|
|
|
4
4
|
require 'bundler/setup'
|
|
5
5
|
require 'fast'
|
|
6
|
+
require 'fast/sql'
|
|
6
7
|
|
|
7
8
|
def s(type, *children)
|
|
8
|
-
|
|
9
|
+
Fast::Node.new(type, children: children)
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def code(string)
|
|
12
13
|
Fast.ast(string)
|
|
13
14
|
end
|
|
14
15
|
|
|
16
|
+
def sql(string)
|
|
17
|
+
Fast.parse_sql(string)
|
|
18
|
+
end
|
|
19
|
+
|
|
15
20
|
def reload!
|
|
16
21
|
load 'lib/fast.rb'
|
|
17
22
|
end
|
data/bin/fast-experiment
CHANGED
|
@@ -8,6 +8,7 @@ require 'fast/experiment'
|
|
|
8
8
|
require 'coderay'
|
|
9
9
|
|
|
10
10
|
arguments = ARGV
|
|
11
|
+
autoclean = arguments.delete('--autoclean')
|
|
11
12
|
|
|
12
13
|
experiment_files = Fast.ruby_files_from(File.expand_path('../experiments', __dir__))
|
|
13
14
|
experiment_files.each(&method(:require))
|
|
@@ -23,6 +24,7 @@ end
|
|
|
23
24
|
if arguments.any?
|
|
24
25
|
ruby_files = arguments.all? { |e| File.exist?(e) && e.end_with?('.rb') }
|
|
25
26
|
experiments.each do |experiment|
|
|
27
|
+
experiment.autoclean = autoclean
|
|
26
28
|
if ruby_files
|
|
27
29
|
experiment.files = arguments
|
|
28
30
|
else
|
|
@@ -30,4 +32,5 @@ if arguments.any?
|
|
|
30
32
|
end
|
|
31
33
|
end
|
|
32
34
|
end
|
|
35
|
+
experiments.each { |experiment| experiment.autoclean = autoclean }
|
|
33
36
|
experiments.each(&:run)
|