ffast 0.1.9 → 0.2.2

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