ffast 0.0.5 → 0.0.6

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 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