ffast 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8e1cdbb944bd8939745c07350aaa66e4ff3e9e3313802ca23aaa7fbd33b742d
4
- data.tar.gz: fcd59e90fedc893492aa791e129d18a8b2e2e72e72b8ca61be6be8fd3e4ad6b7
3
+ metadata.gz: e6f6f652de04a7f59c26a9556d136e8992f6cc5631b12a3b4a28d9e6f5784d1a
4
+ data.tar.gz: 62ae2e2bf65a375c78b2d81ac52247b2b5a60a38557540b9e0b0179cc6010e10
5
5
  SHA512:
6
- metadata.gz: f33ebcf4e4275e783d75a761b78fc54a70f789f89ea735011a5a5628c7d2f8630271e29436aa1d3a15418f4186fc42764fad3ca5d72ef69bf2ccc860424299a3
7
- data.tar.gz: ab1c3275a3415aba183ac6e558284d4f30bb5e462454758808df430059f077eb141f18a8ec725ad7738e1e9e3fd350688d674f6691a4d6165e17f039db34c653
6
+ metadata.gz: 72e1918179ab1b17a292975da6d6a2a673937032c6b924db8994edfb607c98cc80d6542a38a25597d506e23ff0c43f18d4b6a01178623c2c3cd0be982dbc4bb0
7
+ data.tar.gz: 5f3f02826dcdc0c13f906dfc5480f693c0cd6c5f6bae135a4f5cb99f7ac583a518a68333d562ad1fe61cec55f0dca8627978b81710d4d04507c89d28e9028131
data/README.md CHANGED
@@ -2,31 +2,34 @@
2
2
 
3
3
  [![Build Status](https://travis-ci.org/jonatas/fast.svg?branch=master)](https://travis-ci.org/jonatas/fast)
4
4
 
5
- Fast is a "Find AST" tool to help you search in the code abstract syntax tree.
5
+ Fast, short for "Find AST", is a tool to search, prune, and edit Ruby ASTs.
6
6
 
7
- Ruby allow us to do the same thing in a few ways then it's hard to check
8
- how the code is written.
7
+ Ruby is a flexible language that allows us to write code in multiple different ways
8
+ to achieve the same end result, and because of this it's hard to verify how
9
+ the code was written without an AST.
9
10
 
10
- Check the official documentation: https://jonatas.github.io/fast.
11
+ Check out the official documentation: https://jonatas.github.io/fast.
11
12
 
12
- ## Syntax for find in AST
13
+ ## Token Syntax for `find` in AST
13
14
 
14
- The current version cover the following elements:
15
+ The current version of Fast covers the following token elements:
15
16
 
16
- - `()` to represent a **node** search
17
- - `{}` is for **any** matches like **union** conditions with **or** operator
18
- - `[]` is for **all** matches like **intersect** conditions with **and** operator
19
- - `$` is for **capture** current expression
20
- - `_` is **something** not nil
21
- - `nil` matches exactly **nil**
22
- - `...` is a **node** with children
23
- - `^` is to get the **parent node** of an expression
24
- - `?` is for **maybe**
25
- - `\1` to use the first **previous captured** element
26
- - `%1` to bind the first extra argument
27
- - `""` surround the value with double quotes to match literal strings
17
+ - `()` - represents a **node** search
18
+ - `{}` - looks for **any** element to match, like a **Set** inclusion or `any?` in Ruby
19
+ - `[]` - looks for **all** elements to match, like `all?` in Ruby.
20
+ - `$` - will **capture** the contents of the current expression like a `Regex` group
21
+ - `#<method-name>` - will call `<method-name>` with `node` as param allowing you
22
+ to build custom rules.
23
+ - `_` - represents any non-nil value, or **something** being present
24
+ - `nil` - matches exactly **nil**
25
+ - `...` - matches a **node** with children
26
+ - `^` - references the **parent node** of an expression
27
+ - `?` - represents an element which **maybe** present
28
+ - `\1` - represents a substitution for any of the **previously captured** elements
29
+ - `%1` - to bind the first extra argument in an expression
30
+ - `""` - will match a literal string with double quotes
28
31
 
29
- The syntax is inspired on [RuboCop Node Pattern](https://github.com/bbatsov/rubocop/blob/master/lib/rubocop/node_pattern.rb).
32
+ The syntax is inspired by the [RuboCop Node Pattern](https://github.com/bbatsov/rubocop/blob/master/lib/rubocop/node_pattern.rb).
30
33
 
31
34
  ## Installation
32
35
 
@@ -34,21 +37,31 @@ The syntax is inspired on [RuboCop Node Pattern](https://github.com/bbatsov/rubo
34
37
 
35
38
  ## How it works
36
39
 
37
- The idea is search in abstract tree using a simple expression build with an array:
40
+ ### S-Expressions
38
41
 
39
- A simple integer in ruby:
42
+ Fast works by searching the abstract syntax tree using a series of expressions
43
+ to represent code called `s-expressions`.
44
+
45
+ > `s-expressions`, or symbolic expressions, are a way to represent nested data.
46
+ > They originate from the LISP programming language, and are frequetly used in
47
+ > other languages to represent ASTs.
48
+
49
+ ### Integer Literals
50
+
51
+ For example, let's take an `Integer` in Ruby:
40
52
 
41
53
  ```ruby
42
54
  1
43
55
  ```
44
56
 
45
- Is represented by:
57
+ It's corresponding s-expression would be:
46
58
 
47
59
  ```ruby
48
60
  s(:int, 1)
49
61
  ```
50
62
 
51
- Basically `s` represents `Parser::AST::Node` and the node has a `#type` and `#children`.
63
+ `s` in `Fast` and `Parser` are a shorthand for creating an `Parser::AST::Node`.
64
+ Each of these nodes has a `#type` and `#children` contained in it:
52
65
 
53
66
  ```ruby
54
67
  def s(type, *children)
@@ -56,79 +69,138 @@ def s(type, *children)
56
69
  end
57
70
  ```
58
71
 
59
- A local variable assignment:
72
+ ### Variable Assignments
73
+
74
+ Now let's take a look at a local variable assignment:
60
75
 
61
76
  ```ruby
62
77
  value = 42
63
78
  ```
64
79
 
65
- Can be represented with:
80
+ It's corresponding s-expression would be:
66
81
 
67
82
  ```ruby
68
83
  ast = s(:lvasgn, :value, s(:int, 42))
69
84
  ```
70
85
 
71
- Now, lets find local variable named `value` with an value `42`:
86
+ If we wanted to find this particular assignment somewhere in our AST, we can use
87
+ Fast to look for a local variable named `value` with a value `42`:
72
88
 
73
89
  ```ruby
74
- Fast.match?(ast, '(lvasgn value (int 42))') # true
90
+ Fast.match?(ast, '(lvasgn value (int 42))')
91
+ # => true
75
92
  ```
76
93
 
77
- Lets abstract a bit and allow some integer value using `_` as a shortcut:
94
+ ### Wildcard Token
95
+
96
+ If we wanted to find a variable named `value` that was assigned any integer value
97
+ we could replace `42` in our query with an underscore ( `_` ) as a shortcut:
78
98
 
79
99
  ```ruby
80
- Fast.match?(ast, '(lvasgn value (int _))') # true
100
+ Fast.match?(ast, '(lvasgn value (int _))')
101
+ # => true
81
102
  ```
82
103
 
83
- Lets abstract more and allow float or integer:
104
+ ### Set Inclusion Token
105
+
106
+ If we weren't sure the type of the value we're assigning, we can use our set
107
+ inclusion token (`{}`) from earlier to tell Fast that we expect either a `Float`
108
+ or an `Integer`:
84
109
 
85
110
  ```ruby
86
- Fast.match?(ast, '(lvasgn value ({float int} _))') # true
111
+ Fast.match?(ast, '(lvasgn value ({float int} _))')
112
+ # => true
87
113
  ```
88
114
 
89
- Or combine multiple assertions using `[]` to join conditions:
115
+ ### All Matching Token
116
+
117
+ Say we wanted to say what we expect the value's type to _not_ be, we can use the
118
+ all matching token (`[]`) to express multiple conditions that need to be true.
119
+ In this case we don't want the value to be a `String`, `Hash`, or an `Array` by
120
+ prefixing all of the types with `!`:
90
121
 
91
122
  ```ruby
92
123
  Fast.match?(ast, '(lvasgn value ([!str !hash !array] _))') # true
93
124
  ```
94
125
 
95
- Matches all local variables not string **and** not hash **and** not array.
126
+ ### Node Child Token
127
+
128
+ We can match any node with children by using the child token ( `...` ):
129
+
130
+ ```ruby
131
+ Fast.match?(ast, '(lvasgn value ...)')
132
+ # => true
133
+ ```
134
+
135
+ We could even match any local variable assignment combining both `_` and `...`:
136
+
137
+ ```ruby
138
+ Fast.match?(ast, '(lvasgn _ ...)')
139
+ # => true
140
+ ```
141
+
142
+ ### Capturing the Value of an Expression
96
143
 
97
- We can match "a node with children" using `...`:
144
+ You can use `$` to capture the contents of an expression for later use:
98
145
 
99
146
  ```ruby
100
- Fast.match?(ast, '(lvasgn value ...)') # true
147
+ Fast.match?(ast, '(lvasgn value $...)')
148
+ # => [s(:int, 42)]
101
149
  ```
102
150
 
103
- You can use `$` to capture a node:
151
+ Captures can be used in any position as many times as you want to capture whatever
152
+ information you might need:
104
153
 
105
154
  ```ruby
106
- Fast.match?(ast, '(lvasgn value $...)') # => [s(:int, 42)]
155
+ Fast.match?(ast, '(lvasgn $_ $...)')
156
+ # => [:value, s(:int, 42)]
107
157
  ```
108
158
 
109
- Or match whatever local variable assignment combining both `_` and `...`:
159
+ > Keep in mind that `_` means something not nil and `...` means a node with
160
+ > children.
161
+
162
+ ### Calling Custom Methods
163
+
164
+ You can also define custom methods to set more complicated rules. Let's say
165
+ we're looking for duplicated methods in the same class. We need to collect
166
+ method names and guarantee they are unique.
110
167
 
111
168
  ```ruby
112
- Fast.match?(ast, '(lvasgn _ ...)') # true
169
+ def duplicated(method_name)
170
+ @methods ||= []
171
+ already_exists = @methods.include?(method_name)
172
+ @methods << method_name
173
+ already_exists
174
+ end
175
+
176
+ puts Fast.search_file( '(def #duplicated)', 'example.rb')
113
177
  ```
178
+ The same principle can be used in the node level or for debugging purposes.
114
179
 
115
- You can also use captures in any levels you want:
180
+ ```ruby
181
+ require 'pry'
182
+ def debug(node)
183
+ binding.pry
184
+ end
116
185
 
186
+ puts Fast.search_file('#debug', 'example.rb')
187
+ ```
188
+ If you want to get only `def` nodes you can also intersect expressions with `[]`:
117
189
  ```ruby
118
- Fast.match?(ast, '(lvasgn $_ $...)') # [:value, s(:int, 42)]
190
+ puts Fast.search_file('[ def #debug ]', 'example.rb')
119
191
  ```
120
192
 
121
- Keep in mind that `_` means something not nil and `...` means a node with
122
- children.
193
+ ### Methods
123
194
 
124
- Then, if do you get a method declared:
195
+ Let's take a look at a method declaration:
125
196
 
126
197
  ```ruby
127
198
  def my_method
128
199
  call_other_method
129
200
  end
130
201
  ```
131
- It will be represented with the following structure:
202
+
203
+ It's corresponding s-expression would be:
132
204
 
133
205
  ```ruby
134
206
  ast =
@@ -137,13 +209,14 @@ ast =
137
209
  s(:send, nil, :call_other_method))
138
210
  ```
139
211
 
140
- Keep an eye on the node `(args)`.
212
+ Pay close attention to the node `(args)`. We can't use `...` to match it, as it
213
+ has no children (or arguments in this case), but we _can_ match it with a wildcard
214
+ `_` as it's not `nil`.
141
215
 
142
- Then you know you can't use `...` but you can match with `(_)` to match with
143
- such case.
216
+ ### Call Chains
144
217
 
145
- Let's test a few other examples. You can go deeply with the arrays. Let's suppose we have a hardcore call to
146
- `a.b.c.d` and the following AST represents it:
218
+ Let's take a look at a few other examples. Sometimes you have a chain of calls on
219
+ a single `Object`, like `a.b.c.d`. Its corresponding s-expression would be:
147
220
 
148
221
  ```ruby
149
222
  ast =
@@ -156,59 +229,79 @@ ast =
156
229
  :d)
157
230
  ```
158
231
 
159
- You can search using sub-arrays with **pure values**, or **shortcuts** or
232
+ ### Alternate Syntax
233
+
234
+ You can also search using nested arrays with **pure values**, or **shortcuts** or
160
235
  **procs**:
161
236
 
162
237
  ```ruby
163
- Fast.match?(ast, [:send, [:send, '...'], :d]) # => true
164
- Fast.match?(ast, [:send, [:send, '...'], :c]) # => false
165
- Fast.match?(ast, [:send, [:send, [:send, '...'], :c], :d]) # => true
238
+ Fast.match?(ast, [:send, [:send, '...'], :d])
239
+ # => true
240
+
241
+ Fast.match?(ast, [:send, [:send, '...'], :c])
242
+ # => false
243
+
244
+ Fast.match?(ast, [:send, [:send, [:send, '...'], :c], :d])
245
+ # => true
166
246
  ```
167
247
 
168
- Shortcuts like `...` and `_` are just literals for procs. Then you can use
169
- procs directly too:
248
+ Shortcut tokens like child nodes `...` and wildcards `_` are just placeholders
249
+ for procs. If you want, you can even use procs directly like so:
170
250
 
171
251
  ```ruby
172
- Fast.match?(ast, [:send, [ -> (node) { node.type == :send }, [:send, '...'], :c], :d]) # => true
252
+ Fast.match?(ast, [
253
+ :send, [
254
+ -> (node) { node.type == :send },
255
+ [:send, '...'],
256
+ :c
257
+ ],
258
+ :d
259
+ ])
260
+ # => true
173
261
  ```
174
262
 
175
- And also work with expressions:
263
+ This also works with expressions:
176
264
 
177
265
  ```ruby
178
266
  Fast.match?(
179
267
  ast,
180
268
  '(send (send (send (send nil $_) $_) $_) $_)'
181
- ) # => [:a, :b, :c, :d]
269
+ )
270
+ # => [:a, :b, :c, :d]
182
271
  ```
183
272
 
184
- If something does not work you can debug with a block:
273
+ ### Debugging
274
+
275
+ If you find that a particular expression isn't working, you can use `debug` to
276
+ take a look at what Fast is doing:
185
277
 
186
278
  ```ruby
187
279
  Fast.debug { Fast.match?(s(:int, 1), [:int, 1]) }
188
280
  ```
189
281
 
190
- It will output each comparison to stdout:
282
+ Each comparison made while searching will be logged to your console (STDOUT) as
283
+ Fast goes through the AST:
191
284
 
192
285
  ```
193
286
  int == (int 1) # => true
194
287
  1 == 1 # => true
195
288
  ```
196
- ## Bind arguments to expressions
197
289
 
198
- Sometimes we need to define useful functions and bind arguments that will be
199
- based on dynamic decisions or other external input.
200
- For such cases you can bind the arguments with `%` and the index start from `1`.
290
+ ## Bind arguments to expressions
201
291
 
202
- Example:
292
+ We can also dynamically interpolate arguments into our queries using the
293
+ interpolation token `%`. This works much like `sprintf` using indexes starting
294
+ from `1`:
203
295
 
204
296
  ```ruby
205
- Fast.match?(code['a = 1'], '(lvasgn %1 (int _))', :a) # true
297
+ Fast.match?(code('a = 1'), '(lvasgn %1 (int _))', :a)
298
+ # => true
206
299
  ```
207
300
 
208
- ## Use previous captures in search
301
+ ## Using previous captures in search
209
302
 
210
303
  Imagine you're looking for a method that is just delegating something to
211
- another method, like:
304
+ another method, like this `name` method:
212
305
 
213
306
  ```ruby
214
307
  def name
@@ -225,54 +318,68 @@ This can be represented as the following AST:
225
318
  (send nil :person) :name))
226
319
  ```
227
320
 
228
- Then, let's build a search for methods that calls an attribute with the same
229
- name:
321
+ We can create a query that searches for such a method:
230
322
 
231
323
  ```ruby
232
- Fast.match?(ast,'(def $_ ... (send (send nil _) \1))') # => [:name]
324
+ Fast.match?(ast,'(def $_ ... (send (send nil _) \1))')
325
+ # => [:name]
233
326
  ```
234
327
 
235
328
  ## Fast.search
236
329
 
237
- Search allows you to go deeply in the AST, collecting nodes that matches with
238
- the expression. It also returns captures if they exist.
330
+ Search allows you to go search the entire AST, collecting nodes that matches given
331
+ expression. Any matching node is then returned:
239
332
 
240
333
  ```ruby
241
- Fast.search(code('a = 1'), '(int _)') # => s(:int, 1)
334
+ Fast.search(code('a = 1'), '(int _)')
335
+ # => s(:int, 1)
242
336
  ```
243
337
 
244
- If you use captures, it returns the node and the captures respectively:
338
+ If you use captures along with a search, both the matching nodes and the
339
+ captures will be returned:
245
340
 
246
341
  ```ruby
247
- Fast.search(code('a = 1'), '(int $_)') # => [s(:int, 1), 1]
342
+ Fast.search(code('a = 1'), '(int $_)')
343
+ # => [s(:int, 1), 1]
248
344
  ```
249
345
 
250
346
  ## Fast.capture
251
347
 
252
- To pick just the captures and ignore the nodes, use `Fast.capture`:
348
+ To only pick captures and ignore the nodes, use `Fast.capture`:
253
349
 
254
350
  ```ruby
255
- Fast.capture(code('a = 1'), '(int $_)') # => 1
351
+ Fast.capture(code('a = 1'), '(int $_)')
352
+ # => 1
256
353
  ```
257
354
  ## Fast.replace
258
355
 
259
- And if I want to refactor a code and use `delegate <attribute>, to: <object>`, try with replace:
356
+ <!--
357
+ Not sure how this section works, could you explain it in more detail?
358
+
359
+ It looks to capture the name of a method, and then not sure from there. Can
360
+ you provide an example AST to use there?
361
+
362
+ Delegate might be too dense of an example to use.
363
+ -->
364
+
365
+ If we want to replace code, we can use a delegate expression:
260
366
 
261
367
  ```ruby
262
- Fast.replace ast,
263
- '(def $_ ... (send (send nil $_) \1))',
264
- -> (node, captures) {
265
- attribute, object = captures
266
- replace(
267
- node.location.expression,
268
- "delegate :#{attribute}, to: :#{object}"
269
- )
270
- }
368
+ query = '(def $_ ... (send (send nil $_) \1))'
369
+
370
+ Fast.replace ast, query, -> (node, captures) {
371
+ attribute, object = captures
372
+
373
+ replace(
374
+ node.location.expression,
375
+ "delegate :#{attribute}, to: :#{object}"
376
+ )
377
+ }
271
378
  ```
272
379
 
273
380
  ### Replacing file
274
381
 
275
- Now let's imagine we have real files like `sample.rb` with the following code:
382
+ Now let's imagine we have a file like `sample.rb` with the following code:
276
383
 
277
384
  ```ruby
278
385
  def good_bye
@@ -281,33 +388,45 @@ def good_bye
281
388
  end
282
389
  ```
283
390
 
284
- And we decide to remove the `message` variable and put it inline with the `puts`.
391
+ ...and we decide to inline the contents of the `message` variable right after
392
+ `puts`.
393
+
285
394
 
286
- Basically, we need to find the local variable assignment, store the value in
287
- memory. Remove the assignment expression and use the value where the variable
288
- is being called.
395
+ To do this we would need to:
396
+
397
+ * Remove the local variable assignment
398
+ * Store the now-removed variable's value
399
+ * Substitute the value where the variable was used before
289
400
 
290
401
  ```ruby
291
402
  assignment = nil
292
- Fast.replace_file('sample.rb', '({ lvasgn lvar } message )',
293
- -> (node, _) {
294
- if node.type == :lvasgn
295
- assignment = node.children.last
296
- remove(node.location.expression)
297
- elsif node.type == :lvar
298
- replace(node.location.expression, assignment.location.expression.source)
299
- end
300
- }
301
- )
403
+ query = '({ lvasgn lvar } message )'
404
+
405
+ Fast.replace_file('sample.rb', query, -> (node, _) {
406
+ # Find a variable assignment
407
+ if node.type == :lvasgn
408
+ assignment = node.children.last
409
+
410
+ # Remove the node responsible for the assignment
411
+ remove(node.location.expression)
412
+ # Look for the variable being used
413
+ elsif node.type == :lvar
414
+ # Replace the variable with the contents of the variable
415
+ replace(
416
+ node.location.expression,
417
+ assignment.location.expression.source
418
+ )
419
+ end
420
+ })
302
421
  ```
303
422
 
304
423
  ## Other useful functions
305
424
 
306
- To manipulate ruby files, some times you'll need some extra tasks.
425
+ To manipulate ruby files, sometimes you'll need some extra tasks.
307
426
 
308
427
  ## Fast.ast_from_File(file)
309
428
 
310
- This method parses the code and load into a AST representation.
429
+ This method parses code from a file and loads it into an AST representation.
311
430
 
312
431
  ```ruby
313
432
  Fast.ast_from_file('sample.rb')
@@ -315,20 +434,17 @@ Fast.ast_from_file('sample.rb')
315
434
 
316
435
  ## Fast.search_file
317
436
 
318
- You can use `search_file` and pass the path for search for expressions inside
319
- files.
437
+ You can use `search_file` to for search for expressions inside files.
320
438
 
321
439
  ```ruby
322
440
  Fast.search_file(expression, 'file.rb')
323
441
  ```
324
442
 
325
- It's simple combination of `Fast.ast_from_file` with `Fast.search`.
326
-
443
+ It's a combination of `Fast.ast_from_file` with `Fast.search`.
327
444
 
328
445
  ## Fast.capture_file
329
446
 
330
- You can also use `Fast.capture_file` that is similar but return only the
331
- captures:
447
+ You can use `Fast.capture_file` to only return captures:
332
448
 
333
449
  ```ruby
334
450
  Fast.capture_file('(class (const nil $_))', 'lib/fast.rb')
@@ -337,36 +453,46 @@ captures:
337
453
 
338
454
  ## Fast.ruby_files_from(arguments)
339
455
 
340
- You'll be probably looking for multiple ruby files, then this method fetches
341
- all internal `.rb` files
456
+ `Fast.ruby_files_from(arguments)` can get all Ruby files in a location:
342
457
 
343
458
  ```ruby
344
- Fast.ruby_files_from('lib') # => ["lib/fast.rb"]
459
+ Fast.ruby_files_from('lib')
460
+ # => ["lib/fast.rb"]
345
461
  ```
346
462
 
347
463
  ## `fast` in the command line
348
464
 
349
- It will also inject a executable named `fast` and you can use it to search and
350
- find code using the concept:
465
+ Fast also comes with a command line utility called `fast`. You can use it to
466
+ search and find code much like the library version:
351
467
 
352
468
  ```
353
469
  $ fast '(def match?)' lib/fast.rb
354
470
  ```
355
471
 
472
+ The CLI tool takes the following flags
473
+
356
474
  - Use `-d` or `--debug` for enable debug mode.
357
475
  - Use `--ast` to output the AST instead of the original code
358
476
  - Use `--pry` to jump debugging the first result with pry
359
477
  - Use `-c` to search from code example
360
478
  - Use `-s` to search similar code
361
479
 
480
+ ### Fast with Pry
481
+
482
+ You can use `--pry` to stop on a particular source node, and run Pry at that
483
+ location:
484
+
362
485
  ```
363
486
  $ fast '(block (send nil it))' spec --pry
364
487
  ```
365
- And inside pry session, you can use `result` as the first result or `results`
366
- to use all occurrences found.
488
+
489
+ Inside the pry session you can access `result` for the first result that was
490
+ located, or `results` to get all of the occurrences found.
491
+
492
+ Let's take a look at `results`:
367
493
 
368
494
  ```ruby
369
- results.map{|e|e.children[0].children[2]}
495
+ results.map { |e| e.children[0].children[2] }
370
496
  # => [s(:str, "parses ... as Find"),
371
497
  # s(:str, "parses $ as Capture"),
372
498
  # s(:str, "parses quoted values as strings"),
@@ -374,9 +500,16 @@ results.map{|e|e.children[0].children[2]}
374
500
  # s(:str, "parses [] as All"), ...]
375
501
  ```
376
502
 
377
- Getting all `it` blocks without description:
503
+ ### Fast with RSpec
504
+
505
+ Let's say we wanted to get all the `it` blocks in our `RSpec` code that
506
+ currently do not have descriptions:
378
507
 
379
- $ fast '(block (send nil it (nil)) (args ) (!str)) ) )' spec
508
+ ```
509
+ $ fast '(block (send nil it (nil)) (args) (!str)) ) )' spec
510
+ ```
511
+
512
+ This will return the following:
380
513
 
381
514
  ```ruby
382
515
  # spec/fast_spec.rb:166
@@ -390,31 +523,44 @@ it { expect(described_class).to be_match(code['"string"'], '(str "string")') }
390
523
 
391
524
  ## Experiments
392
525
 
393
- You can define experiments and build experimental research to improve some code in
394
- an automated way.
526
+ Experiments can be used to run experiments against your code in an automated
527
+ fashion. These experiments can be used to test the effectiveness of things
528
+ like performance enhancements, or if a replacement piece of code actually works
529
+ or not.
530
+
531
+ Let's create an experiment to try and remove all `before` and `after` blocks
532
+ from our specs.
395
533
 
396
- Let's create an experiment to try to remove `before` or `after` blocks
397
- and run specs. If the spec pass without need the hook, the hook is useless.
534
+ If the spec still pass we can confidently say that the hook is useless.
398
535
 
399
536
  ```ruby
400
537
  Fast.experiment("RSpec/RemoveUselessBeforeAfterHook") do
538
+ # Lookup our spec files
401
539
  lookup 'spec'
540
+
541
+ # Look for every block starting with before or after
402
542
  search "(block (send nil {before after}))"
403
- edit {|node| remove(node.loc.expression) }
404
- policy {|new_file| system("bin/spring rspec --fail-fast #{new_file}") }
543
+
544
+ # Remove those blocks
545
+ edit { |node| remove(node.loc.expression) }
546
+
547
+ # Create a new file, and run RSpec against that new file
548
+ policy { |new_file| system("bin/spring rspec --fail-fast #{new_file}") }
405
549
  end
406
550
  ```
407
551
 
408
- - In the `lookup` you can pass files or folders.
409
- - The `search` contains the expression you want to match
410
- - With `edit` block you can apply the code change
411
- - And the `policy` is executed to check if the current change is valuable
552
+ - `lookup` can be used to pass in files or folders.
553
+ - `search` contains the expression you want to match
554
+ - `edit` is used to apply code change
555
+ - `policy` is what we execute to verify the current change still passes
556
+
557
+ Each removal of a `before` and `after` block will occur in isolation to verify
558
+ each one of them independently of the others. Each successful removal will be
559
+ kept in a secondary change until we run out of blocks to remove.
412
560
 
413
- If the file contains multiple `before` or `after` blocks, each removal will
414
- occur independently and the successfull removals will be combined as a
415
- secondary change. The process repeates until find all possible combinations.
561
+ You can see more examples in the [experiments](experiments) folder.
416
562
 
417
- See more examples in [experiments](experiments) folder.
563
+ ### Running Multiple Experiments
418
564
 
419
565
  To run multiple experiments, use `fast-experiment` runner:
420
566
 
@@ -422,7 +568,7 @@ To run multiple experiments, use `fast-experiment` runner:
422
568
  fast-experiment <experiment-names> <files-or-folders>
423
569
  ```
424
570
 
425
- You can limit experiments or file escope:
571
+ You can limit the scope of experiments:
426
572
 
427
573
  ```
428
574
  fast-experiment RSpec/RemoveUselessBeforeAfterHook spec/models/**/*_spec.rb
@@ -434,7 +580,9 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
434
580
 
435
581
  On the console we have a few functions like `s` and `code` to make it easy ;)
436
582
 
437
- $ bin/console
583
+ ```
584
+ $ bin/console
585
+ ```
438
586
 
439
587
  ```ruby
440
588
  code("a = 1") # => s(:lvasgn, s(:int, 1))
@@ -446,10 +594,8 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
446
594
 
447
595
  Bug reports and pull requests are welcome on GitHub at https://github.com/jonatas/fast. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
448
596
 
449
-
450
597
  ## License
451
598
 
452
599
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
453
600
 
454
-
455
601
  See more on the [official documentation](https://jonatas.github.io/fast).
@@ -21,7 +21,7 @@ end
21
21
 
22
22
  And use it in the console:
23
23
 
24
- `pry
25
- fast '(def match?)' lib/fast.rb`
24
+ ```pry
25
+ fast '(def match?)' lib/fast.rb
26
26
  ```
27
27
 
data/docs/syntax.md CHANGED
@@ -368,3 +368,38 @@ ANSWER = 42
368
368
  ANSWER
369
369
  ```
370
370
 
371
+ ## Calling Custom Methods
372
+
373
+ Custom methods can let you into ruby doman for more complicated rules. Let's say
374
+ we're looking for duplicated methods in the same class. We need to collect
375
+ method names and guarantee they are unique.
376
+
377
+ ```ruby
378
+ def duplicated(method_name)
379
+ @methods ||= []
380
+ already_exists = @methods.include?(method_name)
381
+ @methods << method_name
382
+ already_exists
383
+ end
384
+
385
+ puts Fast.search_file( '(def #duplicated)', 'example.rb')
386
+ ```
387
+ The same principle can be used in the node level or for debugging purposes.
388
+
389
+ ```ruby
390
+ require 'pry'
391
+ def debug(node)
392
+ binding.pry
393
+ end
394
+
395
+ puts Fast.search_file('#debug', 'example.rb')
396
+ ```
397
+ If you want to get only `def` nodes you can also intersect expressions with `[]`:
398
+ ```ruby
399
+ puts Fast.search_file('[ def #debug ]', 'example.rb')
400
+ ```
401
+ Or if you want to debug a very specific expression you can use `()` to specify
402
+ more details of the node
403
+ ```ruby
404
+ puts Fast.search_file('[ (def a) #debug ]', 'example.rb')
405
+ ```
@@ -0,0 +1,15 @@
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
+
data/lib/fast/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fast
4
- VERSION = '0.0.5'
4
+ VERSION = '0.0.6'
5
5
  end
data/lib/fast.rb CHANGED
@@ -54,6 +54,8 @@ module Fast
54
54
  |
55
55
  \$ # capture
56
56
  |
57
+ \#\w[\d\w_]+[\\!\?]? # custom method call
58
+ |
57
59
  \\\d # find using captured expression
58
60
  |
59
61
  %\d # find using binded argument
@@ -258,6 +260,7 @@ module Fast
258
260
  when '{' then Any.new(parse_until_peek('}'))
259
261
  when '[' then All.new(parse_until_peek(']'))
260
262
  when /^"/ then FindString.new(token[1..-2])
263
+ when /^#\w/ then MethodCall.new(token[1..-1])
261
264
  when '$' then Capture.new(parse)
262
265
  when '!' then (@tokens.any? ? Not.new(parse) : Find.new(token))
263
266
  when '?' then Maybe.new(parse)
@@ -375,6 +378,17 @@ module Fast
375
378
  end
376
379
  end
377
380
 
381
+ # Find using custom methods
382
+ class MethodCall < Find
383
+ def initialize(method_name)
384
+ @method_name = method_name
385
+ end
386
+
387
+ def match?(node)
388
+ Kernel.send(@method_name, node)
389
+ end
390
+ end
391
+
378
392
  # Allow use previous captures while searching in the AST.
379
393
  # Use `\\1` to point the match to the first captured element
380
394
  class FindWithCapture < Find
data/mkdocs.yml CHANGED
@@ -17,6 +17,6 @@ nav:
17
17
  - Introduction: index.md
18
18
  - Syntax: syntax.md
19
19
  - Command Line: command_line.md
20
- - Pry Integration: pry-integration.md
21
20
  - Experiments: experiments.md
22
21
  - Code Similarity: similarity_tutorial.md
22
+ - Pry Integration: pry-integration.md
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffast
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jônatas Davi Paganini
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-05 00:00:00.000000000 Z
11
+ date: 2019-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: coderay
@@ -213,6 +213,7 @@ files:
213
213
  - examples/find_usage.rb
214
214
  - examples/let_it_be_experiment.rb
215
215
  - examples/method_complexity.rb
216
+ - examples/search_duplicated.rb
216
217
  - examples/similarity_research.rb
217
218
  - experiments/let_it_be_experiment.rb
218
219
  - experiments/remove_useless_hook.rb