ffast 0.0.1 → 0.0.2

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
  SHA1:
3
- metadata.gz: 7d9179d0dbc56cc0f493cdbff51cd926b4476c37
4
- data.tar.gz: 953cef108b8207d8d83aa16681f8f31e3c79b8f8
3
+ metadata.gz: 238c006c217b8838c23488cbfafe15c3e694d69a
4
+ data.tar.gz: '095e4dfdba32adf0ddf52da166c27a93c8bbd3bb'
5
5
  SHA512:
6
- metadata.gz: 25b1bb7d048b2784a8127b60841ed8946495bacace7ad0caa8acc6f256aa1c2b3116491898d8b276321a0c5db847d350763c1bc18ef88c2cfaee4b6fa7a8c682
7
- data.tar.gz: e8e7e051223d4b1c2bb77d5d44c742518f38c7d9bf75c49342dfa495c4c5252e72ed6890d9c6d68c6faf531e1e1166391ae0cd79f20162ed81df77036bd14413
6
+ metadata.gz: 73ce257e59e1bd96cbc62d4b23225cd50f5bfa551ca64eeb38da4693bc832b14203d2335d6e55e389b74fd5feba1e164ed2f1adbcdbeca0a14676115e1df5c02
7
+ data.tar.gz: 3133c3f337da981e728ab42ed9bf61eafc907715c3c6bb7c210ff38d6a6c73d3e87cf88625d8cac4dcfa648d3f909b3e6625a502d96c9a2316e04436086c7f8f
data/.rubocop.yml ADDED
@@ -0,0 +1,37 @@
1
+ # This is the configuration used to check the rubocop source code.
2
+
3
+ require:
4
+ - rubocop-rspec
5
+
6
+ AllCops:
7
+ Exclude:
8
+ - 'tmp/**/*'
9
+ - 'examples/*'
10
+ TargetRubyVersion: 2.3
11
+
12
+ Metrics/LineLength:
13
+ Enabled: false
14
+
15
+ Metrics/BlockLength:
16
+ Exclude:
17
+ - 'spec/**/*'
18
+
19
+ Lint/InterpolationCheck:
20
+ Exclude:
21
+ - 'spec/**/*'
22
+
23
+ Metrics/MethodLength:
24
+ CountComments: false # count full line comments?
25
+ Max: 12
26
+
27
+ Metrics/ModuleLength:
28
+ Enabled: false
29
+
30
+ Layout/MultilineMethodCallIndentation:
31
+ EnforcedStyle: 'indented'
32
+
33
+ RSpec/NestedGroups:
34
+ Max: 4
35
+
36
+ RSpec/ExampleLength:
37
+ Max: 20
data/.travis.yml CHANGED
@@ -3,3 +3,12 @@ language: ruby
3
3
  rvm:
4
4
  - 2.3.3
5
5
  before_install: gem install bundler -v 1.13.7
6
+ before_script:
7
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
8
+ - chmod +x ./cc-test-reporter
9
+ - ./cc-test-reporter before-build
10
+ after_script:
11
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
12
+ script:
13
+ - bundle exec rubocop
14
+ - bundle exec rspec
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in fast.gemspec
data/Guardfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # A sample Guardfile
2
4
  # More info at https://github.com/guard/guard#readme
3
5
 
@@ -19,8 +21,8 @@ guard 'livereload' do
19
21
  watch(%r{lib/.+\.rb$})
20
22
  end
21
23
 
22
- guard :rspec, cmd: "bundle exec rspec" do
23
- require "guard/rspec/dsl"
24
+ guard :rspec, cmd: 'bundle exec rspec' do
25
+ require 'guard/rspec/dsl'
24
26
  dsl = Guard::RSpec::Dsl.new(self)
25
27
 
26
28
  # Feel free to open issues for suggestions and improvements
data/README.md CHANGED
@@ -4,21 +4,33 @@
4
4
 
5
5
  Fast is a "Find AST" tool to help you search in the code abstract syntax tree.
6
6
 
7
- It's inspired on [RuboCop Node Pattern](https://github.com/bbatsov/rubocop/blob/master/lib/rubocop/node_pattern.rb).
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.
8
9
 
9
- To learn more about how AST works, you can install `ruby-parse` and check how is the AST of
10
- your current code.
10
+ ## Syntax for find in AST
11
11
 
12
- `ruby-parse my-file.rb`
12
+ The current version cover the following elements:
13
13
 
14
- It will output the AST representation.
14
+ - `()` to represent a **node** search
15
+ - `{}` is for **any** matches like **union** conditions with **or** operator
16
+ - `[]` is for **all** matches like **intersect** conditions with **and** operator
17
+ - `$` is for **capture** current expression
18
+ - `_` is **something** not nil
19
+ - `nil` matches exactly **nil**
20
+ - `...` is a **node** with children
21
+ - `^` is to get the **parent node** of an expression
22
+ - `?` is for **maybe**
23
+ - `\1` to use the first **previous captured** element
24
+ - `""` surround the value with double quotes to match literal strings
25
+
26
+ The syntax is inspired on [RuboCop Node Pattern](https://github.com/bbatsov/rubocop/blob/master/lib/rubocop/node_pattern.rb).
15
27
 
16
28
  ## Installation
17
29
 
18
30
  Add this line to your application's Gemfile:
19
31
 
20
32
  ```ruby
21
- gem 'fast'
33
+ gem 'ffast'
22
34
  ```
23
35
 
24
36
  And then execute:
@@ -27,44 +39,119 @@ And then execute:
27
39
 
28
40
  Or install it yourself as:
29
41
 
30
- $ gem install fast
42
+ $ gem install ffast
31
43
 
32
44
  ## How it works
33
45
 
34
46
  The idea is search in abstract tree using a simple expression build with an array:
35
47
 
36
- The following code:
48
+ A simple integer in ruby:
37
49
 
38
50
  ```ruby
39
- a += 1
51
+ 1
40
52
  ```
41
53
 
42
- Generates the following AST representation:
54
+ Is represented by:
43
55
 
44
56
  ```ruby
45
- ast =
46
- s(:op_asgn,
47
- s(:lvasgn, :a),
48
- :+,
49
- s(:int, 1)
50
- )
57
+ s(:int, 1)
51
58
  ```
52
59
 
53
60
  Basically `s` represents `Parser::AST::Node` and the node has a `#type` and `#children`.
54
61
 
55
- You can try to search by nodes that is using `:op_asgn` with some children using `...`:
62
+ ```ruby
63
+ def s(type, *children)
64
+ Parser::AST::Node.new(type, children)
65
+ end
66
+ ```
67
+
68
+ A local variable assignment:
69
+
70
+ ```ruby
71
+ value = 42
72
+ ```
73
+
74
+ Can be represented with:
75
+
76
+ ```ruby
77
+ ast = s(:lvasgn, :value, s(:int, 42))
78
+ ```
79
+
80
+ Now, lets find local variable named `value` with an value `42`:
81
+
82
+ ```ruby
83
+ Fast.match?(ast, '(lvasgn value (int 42))') # true
84
+ ```
85
+
86
+ Lets abstract a bit and allow some integer value using `_` as a shortcut:
87
+
88
+ ```ruby
89
+ Fast.match?(ast, '(lvasgn value (int _))') # true
90
+ ```
91
+
92
+ Lets abstract more and allow float or integer:
93
+
94
+ ```ruby
95
+ Fast.match?(ast, '(lvasgn value ({float int} _))') # true
96
+ ```
97
+
98
+ Or combine multiple assertions using `[]` to join conditions:
99
+
100
+ ```ruby
101
+ Fast.match?(ast, '(lvasgn value ([!str !hash !array] _))') # true
102
+ ```
103
+
104
+ Matches all local variables not string **and** not hash **and** not array.
105
+
106
+ We can match "a node with children" using `...`:
107
+
108
+ ```ruby
109
+ Fast.match?(ast, '(lvasgn value ...)') # true
110
+ ```
111
+
112
+ You can use `$` to capture a node:
56
113
 
57
114
  ```ruby
58
- Fast.match?(ast, [:op_asgn, '...']) # => true
115
+ Fast.match?(ast, '(lvasgn value $...)') # => [s(:int, 42)]
59
116
  ```
60
117
 
61
- You can also check if the element is not nil with `_`:
118
+ Or match whatever local variable assignment combining both `_` and `...`:
62
119
 
63
120
  ```ruby
64
- Fast.match?(ast, [:op_asgn, '_', '_', '_'])) # => true
121
+ Fast.match?(ast, '(lvasgn _ ...)') # true
65
122
  ```
66
123
 
67
- You can go deeply with the arrays. Let's suppose we have a hardcore call to
124
+ You can also use captures in any levels you want:
125
+
126
+ ```ruby
127
+ Fast.match?(ast, '(lvasgn $_ $...)') # [:value, s(:int, 42)]
128
+ ```
129
+
130
+ Keep in mind that `_` means something not nil and `...` means a node with
131
+ children.
132
+
133
+ Then, if do you get a method declared:
134
+
135
+ ```ruby
136
+ def my_method
137
+ call_other_method
138
+ end
139
+ ```
140
+ It will be represented with the following structure:
141
+
142
+ ```ruby
143
+ ast =
144
+ s(:def, :my_method,
145
+ s(:args),
146
+ s(:send, nil, :call_other_method))
147
+ ```
148
+
149
+ Keep an eye on the node `(args)`.
150
+
151
+ Then you know you can't use `...` but you can match with `(_)` to match with
152
+ such case.
153
+
154
+ Let's test a few other examples. You can go deeply with the arrays. Let's suppose we have a hardcore call to
68
155
  `a.b.c.d` and the following AST represents it:
69
156
 
70
157
  ```ruby
@@ -78,7 +165,8 @@ ast =
78
165
  :d)
79
166
  ```
80
167
 
81
- You can search using sub-arrays in the same way:
168
+ You can search using sub-arrays with **pure values**, or **shortcuts** or
169
+ **procs**:
82
170
 
83
171
  ```ruby
84
172
  Fast.match?(ast, [:send, [:send, '...'], :d]) # => true
@@ -86,11 +174,20 @@ Fast.match?(ast, [:send, [:send, '...'], :c]) # => false
86
174
  Fast.match?(ast, [:send, [:send, [:send, '...'], :c], :d]) # => true
87
175
  ```
88
176
 
89
- It also knows how to parse strings:
177
+ Shortcuts like `...` and `_` are just literals for procs. Then you can use
178
+ procs directly too:
90
179
 
91
180
  ```ruby
92
- expression = '(send (send (send (send nil $_) $_) $_) $_)'
93
- Fast.match?(ast, expression)) # => [:a, :b, :c, :d]
181
+ Fast.match?(ast, [:send, [ -> (node) { node.type == :send }, [:send, '...'], :c], :d]) # => true
182
+ ```
183
+
184
+ And also work with expressions:
185
+
186
+ ```ruby
187
+ Fast.match?(
188
+ ast,
189
+ '(send (send (send (send nil $_) $_) $_) $_)'
190
+ ) # => [:a, :b, :c, :d]
94
191
  ```
95
192
 
96
193
  If something does not work you can debug with a block:
@@ -106,22 +203,198 @@ int == (int 1) # => true
106
203
  1 == 1 # => true
107
204
  ```
108
205
 
109
- ## Search in files
206
+ ## Use previous captures in search
207
+
208
+ Imagine you're looking for a method that is just delegating something to
209
+ another method, like:
210
+
211
+ ```ruby
212
+ def name
213
+ person.name
214
+ end
215
+ ```
216
+
217
+ This can be represented as the following AST:
218
+
219
+ ```
220
+ (def :name
221
+ (args)
222
+ (send
223
+ (send nil :person) :name))
224
+ ```
225
+
226
+ Then, let's build a search for methods that calls an attribute with the same
227
+ name:
228
+
229
+ ```ruby
230
+ Fast.match?(ast,'(def $_ ... (send (send nil _) \1))') # => [:name]
231
+ ```
232
+
233
+ ### Fast.search
234
+
235
+ Search allows you to go deeply in the AST, collecting nodes that matches with
236
+ the expression. It also returns captures if they exist.
237
+
238
+ ```ruby
239
+ Fast.search(code('a = 1'), '(int _)') # => s(:int, 1)
240
+ ```
241
+
242
+ If you use captures, it returns the node and the captures respectively:
243
+
244
+ ```ruby
245
+ Fast.search(code('a = 1'), '(int $_)') # => [s(:int, 1), 1]
246
+ ```
247
+
248
+ ### Fast.capture
249
+
250
+ To pick just the captures and ignore the nodes, use `Fast.capture`:
251
+
252
+ ```ruby
253
+ Fast.capture(code('a = 1'), '(int $_)') # => 1
254
+ ```
255
+ ### Fast.replace
256
+
257
+ And if I want to refactor a code and use `delegate <attribute>, to: <object>`, try with replace:
258
+
259
+ ```ruby
260
+ Fast.replace ast,
261
+ '(def $_ ... (send (send nil $_) \1))',
262
+ -> (node, captures) {
263
+ attribute, object = captures
264
+ replace(
265
+ node.location.expression,
266
+ "delegate :#{attribute}, to: :#{object}"
267
+ )
268
+ }
269
+ ```
270
+
271
+ ### Replacing file
272
+
273
+ Now let's imagine we have real files like `sample.rb` with the following code:
274
+
275
+ ```ruby
276
+ def good_bye
277
+ message = ["good", "bye"]
278
+ puts message.join(' ')
279
+ end
280
+ ```
281
+
282
+ And we decide to remove the `message` variable and put it inline with the `puts`.
283
+
284
+ Basically, we need to find the local variable assignment, store the value in
285
+ memory. Remove the assignment expression and use the value where the variable
286
+ is being called.
287
+
288
+ ```ruby
289
+ assignment = nil
290
+ Fast.replace_file('sample.rb', '({ lvasgn lvar } message )',
291
+ -> (node, _) {
292
+ if node.type == :lvasgn
293
+ assignment = node.children.last
294
+ remove(node.location.expression)
295
+ elsif node.type == :lvar
296
+ replace(node.location.expression, assignment.location.expression.source)
297
+ end
298
+ }
299
+ )
300
+ ```
301
+
302
+ ## Other useful functions
303
+
304
+ To manipulate ruby files, some times you'll need some extra tasks.
305
+
306
+ ### Fast.ast_from_File(file)
307
+
308
+ This method parses the code and load into a AST representation.
309
+
310
+ ```ruby
311
+ Fast.ast_from_file('sample.rb')
312
+ ```
313
+
314
+ ### Fast.search_file
315
+
316
+ You can use `search_file` and pass the path for search for expressions inside
317
+ files.
318
+
319
+ ```ruby
320
+ Fast.search_file('file.rb', expression)
321
+ ```
322
+
323
+ It's simple combination of `Fast.ast_from_file` with `Fast.search`.
324
+
325
+ ### Fast.ruby_files_from(arguments)
326
+
327
+ You'll be probably looking for multiple ruby files, then this method fetches
328
+ all internal `.rb` files
329
+
330
+ ```ruby
331
+ Fast.ruby_files_from(['lib']) # => ["lib/fast.rb"]
332
+ ```
333
+
334
+ ## `fast` in the command line
110
335
 
111
336
  It will also inject a executable named `fast` and you can use it to search and
112
- find code by this kind of expression
337
+ find code using the concept:
113
338
 
114
339
  ```
115
- $ fast '(:def :match_node? _ )' lib/fast.rb 20:36:21
340
+ $ fast '(def match?)' lib/fast.rb
116
341
  ```
117
342
 
118
343
  - Use `-d` or `--debug` for enable debug mode.
119
344
  - Use `--ast` to output the AST instead of the original code
120
345
 
346
+ ## Experiments
347
+
348
+ You can define experiments and build experimental research to improve some code in
349
+ an automated way.
350
+
351
+ Let's create an experiment to try to remove `before` or `after` blocks
352
+ and run specs. If the spec pass without need the hook, the hook is useless.
353
+
354
+ ```ruby
355
+ Fast.experiment("RSpec/RemoveUselessBeforeAfterHook") do
356
+ lookup 'spec'
357
+ search "(block (send nil {before after}))"
358
+ edit {|node| remove(node.loc.expression) }
359
+ policy {|new_file| system("bin/spring rspec --fail-fast #{new_file}") }
360
+ end
361
+ ```
362
+
363
+ - In the `lookup` you can pass files or folders.
364
+ - The `search` contains the expression you want to match
365
+ - With `edit` block you can apply the code change
366
+ - And the `policy` is executed to check if the current change is valuable
367
+
368
+ If the file contains multiple `before` or `after` blocks, each removal will
369
+ occur independently and the successfull removals will be combined as a
370
+ secondary change. The process repeates until find all possible combinations.
371
+
372
+ See more examples in [experiments](experiments) folder.
373
+
374
+ To run multiple experiments, use `fast-experiment` runner:
375
+
376
+ ```
377
+ fast-experiment <experiment-names> <files-or-folders>
378
+ ```
379
+
380
+ You can limit experiments or file escope:
381
+
382
+ ```
383
+ fast-experiment RSpec/RemoveUselessBeforeAfterHook spec/models/**/*_spec.rb
384
+ ```
385
+
121
386
  ## Development
122
387
 
123
388
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
124
389
 
390
+ On the console we have a few functions like `s` and `code` to make it easy ;)
391
+
392
+ $ bin/console
393
+
394
+ ```ruby
395
+ code("a = 1") # => s(:lvasgn, s(:int, 1))
396
+ ```
397
+
125
398
  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).
126
399
 
127
400
  ## Contributing