ffast 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 238c006c217b8838c23488cbfafe15c3e694d69a
4
- data.tar.gz: '095e4dfdba32adf0ddf52da166c27a93c8bbd3bb'
2
+ SHA256:
3
+ metadata.gz: c75d9b66509133b2f2579a03d663a80ece65421f309819f6c04b67c641973816
4
+ data.tar.gz: d172f92548e5080074d9edc2ec23bbc0a3febd3cfcfe1296d743a0a219ebfb8f
5
5
  SHA512:
6
- metadata.gz: 73ce257e59e1bd96cbc62d4b23225cd50f5bfa551ca64eeb38da4693bc832b14203d2335d6e55e389b74fd5feba1e164ed2f1adbcdbeca0a14676115e1df5c02
7
- data.tar.gz: 3133c3f337da981e728ab42ed9bf61eafc907715c3c6bb7c210ff38d6a6c73d3e87cf88625d8cac4dcfa648d3f909b3e6625a502d96c9a2316e04436086c7f8f
6
+ metadata.gz: 12f1c29903c12fe53e0b1a6b9c8c54664eb34411dbb9ae88c813359fe0df342c4a0c4168f13598a357281992ba89f7e5a1bdf8196e4217ce6b451d60d1cef364
7
+ data.tar.gz: 98e8a7f6b9a374197124b2a0b293fececd46bf396f86d6981de27193b9dc5c92532bc66763a31f446dae4d638967ea601f1348f633bbee6ca383ab5a2d63e54d
data/.travis.yml CHANGED
@@ -1,8 +1,8 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.3.3
5
- before_install: gem install bundler -v 1.13.7
4
+ - 2.5.0
5
+ before_install: gem install bundler -v 1.16.1
6
6
  before_script:
7
7
  - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
8
8
  - chmod +x ./cc-test-reporter
data/README.md CHANGED
@@ -7,6 +7,8 @@ Fast is a "Find AST" tool to help you search in the code abstract syntax tree.
7
7
  Ruby allow us to do the same thing in a few ways then it's hard to check
8
8
  how the code is written.
9
9
 
10
+ Check the official documentation: https://jonatas.github.io/fast.
11
+
10
12
  ## Syntax for find in AST
11
13
 
12
14
  The current version cover the following elements:
@@ -27,18 +29,6 @@ The syntax is inspired on [RuboCop Node Pattern](https://github.com/bbatsov/rubo
27
29
 
28
30
  ## Installation
29
31
 
30
- Add this line to your application's Gemfile:
31
-
32
- ```ruby
33
- gem 'ffast'
34
- ```
35
-
36
- And then execute:
37
-
38
- $ bundle
39
-
40
- Or install it yourself as:
41
-
42
32
  $ gem install ffast
43
33
 
44
34
  ## How it works
@@ -230,7 +220,7 @@ name:
230
220
  Fast.match?(ast,'(def $_ ... (send (send nil _) \1))') # => [:name]
231
221
  ```
232
222
 
233
- ### Fast.search
223
+ ## Fast.search
234
224
 
235
225
  Search allows you to go deeply in the AST, collecting nodes that matches with
236
226
  the expression. It also returns captures if they exist.
@@ -245,14 +235,14 @@ If you use captures, it returns the node and the captures respectively:
245
235
  Fast.search(code('a = 1'), '(int $_)') # => [s(:int, 1), 1]
246
236
  ```
247
237
 
248
- ### Fast.capture
238
+ ## Fast.capture
249
239
 
250
240
  To pick just the captures and ignore the nodes, use `Fast.capture`:
251
241
 
252
242
  ```ruby
253
243
  Fast.capture(code('a = 1'), '(int $_)') # => 1
254
244
  ```
255
- ### Fast.replace
245
+ ## Fast.replace
256
246
 
257
247
  And if I want to refactor a code and use `delegate <attribute>, to: <object>`, try with replace:
258
248
 
@@ -303,7 +293,7 @@ Fast.replace_file('sample.rb', '({ lvasgn lvar } message )',
303
293
 
304
294
  To manipulate ruby files, some times you'll need some extra tasks.
305
295
 
306
- ### Fast.ast_from_File(file)
296
+ ## Fast.ast_from_File(file)
307
297
 
308
298
  This method parses the code and load into a AST representation.
309
299
 
@@ -311,7 +301,7 @@ This method parses the code and load into a AST representation.
311
301
  Fast.ast_from_file('sample.rb')
312
302
  ```
313
303
 
314
- ### Fast.search_file
304
+ ## Fast.search_file
315
305
 
316
306
  You can use `search_file` and pass the path for search for expressions inside
317
307
  files.
@@ -322,7 +312,7 @@ Fast.search_file('file.rb', expression)
322
312
 
323
313
  It's simple combination of `Fast.ast_from_file` with `Fast.search`.
324
314
 
325
- ### Fast.ruby_files_from(arguments)
315
+ ## Fast.ruby_files_from(arguments)
326
316
 
327
317
  You'll be probably looking for multiple ruby files, then this method fetches
328
318
  all internal `.rb` files
@@ -342,6 +332,38 @@ $ fast '(def match?)' lib/fast.rb
342
332
 
343
333
  - Use `-d` or `--debug` for enable debug mode.
344
334
  - Use `--ast` to output the AST instead of the original code
335
+ - Use `--pry` to jump debugging the first result with pry
336
+ - Use `-c` to search from code example
337
+ - Use `-s` to search similar code
338
+
339
+ ```
340
+ $ fast '(block (send nil it))' spec --pry
341
+ ```
342
+ And inside pry session, you can use `result` as the first result or `results`
343
+ to use all occurrences found.
344
+
345
+ ```ruby
346
+ results.map{|e|e.children[0].children[2]}
347
+ # => [s(:str, "parses ... as Find"),
348
+ # s(:str, "parses $ as Capture"),
349
+ # s(:str, "parses quoted values as strings"),
350
+ # s(:str, "parses {} as Any"),
351
+ # s(:str, "parses [] as All"), ...]
352
+ ```
353
+
354
+ Getting all `it` blocks without description:
355
+
356
+ $ fast '(block (send nil it (nil)) (args ) (!str)) ) )' spec
357
+
358
+ ```ruby
359
+ # spec/fast_spec.rb:166
360
+ it { expect(described_class).to be_match(s(:int, 1), '(...)') }
361
+ # spec/fast_spec.rb:167
362
+ it { expect(described_class).to be_match(s(:int, 1), '(_ _)') }
363
+ # spec/fast_spec.rb:168
364
+ it { expect(described_class).to be_match(code['"string"'], '(str "string")') }
365
+ # ... more results
366
+ ```
345
367
 
346
368
  ## Experiments
347
369
 
@@ -406,3 +428,5 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/jonata
406
428
 
407
429
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
408
430
 
431
+
432
+ See more on the [official documentation](https://jonatas.github.io/fast).
data/bin/fast CHANGED
@@ -7,37 +7,58 @@ require 'fast'
7
7
  require 'coderay'
8
8
 
9
9
  arguments = ARGV
10
- pattern = arguments.shift
11
10
  show_sexp = arguments.delete('--ast')
12
11
  pry = arguments.delete('--pry')
12
+ from_code = arguments.delete('--code') || arguments.delete('-c')
13
+ similar = arguments.delete('--similar') || arguments.delete('-s')
13
14
  debug = arguments.delete('--debug') || arguments.delete('-d')
14
- files = Fast.ruby_files_from(*arguments || '.')
15
15
 
16
- pattern = Fast.expression(pattern)
16
+ pattern = arguments.shift
17
+
18
+ if similar || from_code
19
+ ast = Fast.ast(pattern)
20
+ if similar
21
+ puts "Looking for code similar to #{pattern}" if debug
22
+ pattern = Fast.expression_from(ast)
23
+ elsif from_code
24
+ pattern = ast.to_sexp
25
+ puts 'The generated expression from AST was:', pattern if debug
26
+ end
27
+ end
28
+ arguments << '.' if arguments.empty?
29
+
30
+ files = Fast.ruby_files_from(*arguments)
17
31
 
18
- puts "Expression: #{pattern.map(&:to_s)}" if debug
32
+ expression = Fast.expression(pattern)
33
+
34
+ puts "Expression: #{expression.map(&:to_s).join(' ')}" if debug
19
35
 
20
36
  files.each do |file|
21
37
  results =
22
38
  if debug
23
- Fast.debug { Fast.search_file(pattern, file) }
39
+ Fast.debug { Fast.search_file(expression, file) }
24
40
  else
25
41
  begin
26
- Fast.search_file(pattern, file)
42
+ Fast.search_file(expression, file)
27
43
  rescue Parser::SyntaxError
28
- puts "Ops! An error occurred trying to search in #{pattern.inspect} in #{file}", $ERROR_INFO, $ERROR_POSITION
44
+ if debug
45
+ puts "Ops! An error occurred trying to search in #{expression.inspect} in #{file}",
46
+ $ERROR_INFO,
47
+ $ERROR_POSITION
48
+ end
29
49
  end
30
50
  end
31
51
 
32
52
  next unless results
33
53
 
34
- results.each do |node|
54
+ results.each do |result|
35
55
  next if result.nil? || result == []
56
+
36
57
  if pry
37
58
  require 'pry'
38
59
  binding.pry # rubocop:disable Lint/Debugger
39
60
  else
40
- Fast.report(node, file: file, show_sexp: show_sexp)
61
+ Fast.report(result, file: file, show_sexp: show_sexp)
41
62
  end
42
63
  end
43
64
  end
@@ -0,0 +1,112 @@
1
+ # Command line
2
+
3
+ It will also inject a executable named `fast` and you can use it to search and
4
+ find code using the concept:
5
+
6
+ ```
7
+ $ fast '(def match?)' lib/fast.rb
8
+ ```
9
+
10
+ - Use `-d` or `--debug` for enable debug mode.
11
+ - Use `--ast` to output the AST instead of the original code
12
+ - Use `--pry` to jump debugging the first result with pry
13
+ - Use `-c` to search from code example
14
+ - Use `-s` to search similar code
15
+
16
+ ## `--pry`
17
+
18
+ $ fast '(block (send nil it))' spec --pry
19
+
20
+ And inside pry session, you can use `result` as the first result or `results`
21
+ to use all occurrences found.
22
+
23
+ ```ruby
24
+ results.map{|e|e.children[0].children[2]}
25
+ # => [s(:str, "parses ... as Find"),
26
+ # s(:str, "parses $ as Capture"),
27
+ # s(:str, "parses quoted values as strings"),
28
+ # s(:str, "parses {} as Any"),
29
+ # s(:str, "parses [] as All"), ...]
30
+ ```
31
+
32
+ Getting all `it` blocks without description:
33
+
34
+ $ fast '(block (send nil it (nil)) (args ) (!str)) ) )' spec
35
+
36
+ ```ruby
37
+ # spec/fast_spec.rb:166
38
+ it { expect(described_class).to be_match(s(:int, 1), '(...)') }
39
+ # spec/fast_spec.rb:167
40
+ it { expect(described_class).to be_match(s(:int, 1), '(_ _)') }
41
+ # spec/fast_spec.rb:168
42
+ it { expect(described_class).to be_match(code['"string"'], '(str "string")') }
43
+ # ... more results
44
+ ```
45
+
46
+ ## `--debug`
47
+
48
+ This option will print all matching details while validating each node.
49
+
50
+ ```
51
+ $ echo 'object.method' > sample.rb
52
+ $ fast -d '(send (send nil _) _)' sample.rb
53
+ ```
54
+
55
+ It will bring details of the expression compiled and each node being validated:
56
+
57
+ ```
58
+ Expression: f[send] [#<Fast::Find:0x00007f8c53047158 @token="send">, #<Fast::Find:0x00007f8c530470e0 @token="nil">, #<Fast::Find:0x00007f8c53047090 @token="_">] f[_]
59
+ send == (send
60
+ (send nil :object) :method) # => true
61
+ f[send] == (send
62
+ (send nil :object) :method) # => true
63
+ send == (send nil :object) # => true
64
+ f[send] == (send nil :object) # => true
65
+ == # => true
66
+ f[nil] == # => true
67
+ #<Proc:0x00007f8c53057af8@/Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/ffast-0.0.2/lib/fast.rb:25 (lambda)> == object # => true
68
+ f[_] == object # => true
69
+ [#<Fast::Find:0x00007f8c53047158 @token="send">, #<Fast::Find:0x00007f8c530470e0 @token="nil">, #<Fast::Find:0x00007f8c53047090 @token="_">] == (send nil :object) # => true
70
+ #<Proc:0x00007f8c53057af8@/Users/jonatasdp/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/ffast-0.0.2/lib/fast.rb:25 (lambda)> == method # => true
71
+ f[_] == method # => true
72
+ # sample.rb:1
73
+ object.method
74
+ ```
75
+
76
+ ## `-s` for similarity
77
+
78
+ Sometimes you want to search for some similar code like `(send (send (send nil _) _) _)` and we could simply say `a.b.c`.
79
+
80
+ The option `-s` build an expression from the code ignoring final values.
81
+
82
+ $ echo 'object.method' > sample.rb
83
+ $ fast -s 'a.b' sample.rb
84
+
85
+ ```ruby
86
+ # sample.rb:1
87
+ object.method
88
+ ```
89
+
90
+ See also [Code Similarity](simi)ilarity_tutorial.md) tutorial.
91
+
92
+ # `-c` to search from code example
93
+
94
+ You can search for the exact expression with `-c`
95
+
96
+ $ fast -c 'object.method' sample.rb
97
+
98
+ ```ruby
99
+ # sample.rb:1
100
+ object.method
101
+ ```
102
+
103
+ Combining with `-d`, in the header you can see the generated expression.
104
+
105
+ ```
106
+ $ fast -d -c 'object.method' sample.rb | head -n 3
107
+
108
+ The generated expression from AST was:
109
+ (send
110
+ (send nil :object) :method)
111
+ ```
112
+
@@ -0,0 +1,147 @@
1
+ # Experiments
2
+
3
+ Experiments allow us to play with AST and do some code transformation, execute
4
+ some code and continue combining successful transformations.
5
+
6
+ The major idea is try a new approach without any promise and if it works
7
+ continue transforming the code.
8
+
9
+ ## Replace `FactoryBot#create` with `build_stubbed`.
10
+
11
+ Let's look into the following spec example:
12
+
13
+ ```ruby
14
+ describe "my spec" do
15
+ let(:user) { create(:user) }
16
+ let(:address) { create(:address) }
17
+ # ...
18
+ end
19
+ ```
20
+
21
+ Let's say we're amazed with `FactoryBot#build_stubbed` and want to build a small
22
+ bot to make the changes in a entire code base. Skip some database
23
+ touches while testing huge test suites are always a good idea.
24
+
25
+ First we can hunt for the cases we want to find:
26
+
27
+ ```
28
+ $ ruby-parse -e "create(:user)"
29
+ (send nil :create
30
+ (sym :user))
31
+ ```
32
+
33
+ Using `fast` in the command line to see real examples in the `spec` folder:
34
+
35
+ ```
36
+ $ fast "(send nil create)" spec
37
+ ```
38
+
39
+ If you don't have a real project but want to test, just create a sample ruby
40
+ file with the code example above.
41
+
42
+ Running it in a big codebase will probably find a few examples of blocks.
43
+
44
+ The next step is build a replacement of each independent occurrence to use
45
+ `build_stubbed` instead of create and combine the successful ones, run again and
46
+ combine again, until try all kind of successful replacements combined.
47
+
48
+ Considering we have the following code in `sample_spec.rb`:
49
+
50
+ ```ruby
51
+ describe "my spec" do
52
+ let(:user) { create(:user) }
53
+ let(:address) { create(:address) }
54
+ # ...
55
+ end
56
+ ```
57
+
58
+ Let's create the experiment that will contain the nodes that are target to be
59
+ executed and what we want to do when we find the node.
60
+
61
+ ```ruby
62
+ experiment = Fast.experiment('RSpec/ReplaceCreateWithBuildStubbed') do
63
+ search '(send nil create)'
64
+ edit { |node| replace(node.loc.selector, 'build_stubbed') }
65
+ end
66
+ ```
67
+
68
+ If we use `Fast.replace_file` it will replace all occurrences in the same run
69
+ and that's one of the motivations behind create the `ExperimentFile` class.
70
+
71
+ Executing a partial replacement of the first occurrence:
72
+
73
+ ```ruby
74
+ experiment_file = Fast::ExperimentFile.new('sample_spec.rb', experiment) }
75
+ puts experiment_file.partial_replace(1)
76
+ ```
77
+
78
+ The command will output the following code:
79
+
80
+ ```ruby
81
+ describe "my spec" do
82
+ let(:user) { build_stubbed(:user) }
83
+ let(:address) { create(:address) }
84
+ # ...
85
+ end
86
+ ```
87
+
88
+ ## Remove useless before block
89
+
90
+ Imagine the following code sample:
91
+
92
+ ```ruby
93
+ describe "my spec" do
94
+ before { create(:user) }
95
+ # ...
96
+ after { User.delete_all }
97
+ end
98
+ ```
99
+
100
+ And now, we can define an experiment that removes the entire code block and run
101
+ the experimental specs.
102
+
103
+ ```ruby
104
+ experiment = Fast.experiment('RSpec/RemoveUselessBeforeAfterHook') do
105
+ lookup 'spec'
106
+ search '(block (send nil {before after}))'
107
+ edit { |node| remove(node.loc.expression) }
108
+ policy { |new_file| system("bin/spring rspec --fail-fast #{new_file}") }
109
+ end
110
+ ```
111
+
112
+ To run the experiment you can simply say:
113
+
114
+ ```ruby
115
+ experiment.run
116
+ ```
117
+
118
+ Or drop the code into `experiments` folder and use the `fast-experiment` command
119
+ line tool.
120
+
121
+ $ fast-experiment RSpec/RemoveUselessBeforeAfterHook spec
122
+
123
+ ## DSL
124
+
125
+ - In the `lookup` you can pass files or folders.
126
+ - The `search` contains the expression you want to match
127
+ - With `edit` block you can apply the code change
128
+ - And the `policy` is executed to check if the current change is valuable
129
+
130
+ If the file contains multiple `before` or `after` blocks, each removal will
131
+ occur independently and the successfull removals will be combined as a
132
+ secondary change. The process repeates until find all possible combinations.
133
+
134
+ See more examples in [experiments](experiments) folder.
135
+
136
+ To run multiple experiments, use `fast-experiment` runner:
137
+
138
+ ```
139
+ fast-experiment <experiment-names> <files-or-folders>
140
+ ```
141
+
142
+ You can limit experiments or file escope:
143
+
144
+ ```
145
+ fast-experiment RSpec/RemoveUselessBeforeAfterHook spec/models/**/*_spec.rb
146
+ ```
147
+