ffast 0.2.0 → 0.2.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +27 -0
  3. data/.github/workflows/ruby.yml +34 -0
  4. data/.gitignore +2 -0
  5. data/Fastfile +146 -3
  6. data/README.md +244 -132
  7. data/bin/console +6 -1
  8. data/bin/fast-experiment +3 -0
  9. data/bin/fast-mcp +7 -0
  10. data/fast.gemspec +24 -7
  11. data/lib/fast/cli.rb +129 -38
  12. data/lib/fast/experiment.rb +19 -2
  13. data/lib/fast/git.rb +1 -1
  14. data/lib/fast/mcp_server.rb +317 -0
  15. data/lib/fast/node.rb +258 -0
  16. data/lib/fast/prism_adapter.rb +310 -0
  17. data/lib/fast/rewriter.rb +64 -10
  18. data/lib/fast/scan.rb +203 -0
  19. data/lib/fast/shortcut.rb +23 -6
  20. data/lib/fast/source.rb +116 -0
  21. data/lib/fast/source_rewriter.rb +153 -0
  22. data/lib/fast/sql/rewriter.rb +98 -0
  23. data/lib/fast/sql.rb +165 -0
  24. data/lib/fast/summary.rb +435 -0
  25. data/lib/fast/version.rb +1 -1
  26. data/lib/fast.rb +165 -79
  27. data/mkdocs.yml +27 -3
  28. data/requirements-docs.txt +3 -0
  29. metadata +48 -62
  30. data/docs/command_line.md +0 -238
  31. data/docs/editors-integration.md +0 -46
  32. data/docs/experiments.md +0 -153
  33. data/docs/ideas.md +0 -80
  34. data/docs/index.md +0 -402
  35. data/docs/pry-integration.md +0 -27
  36. data/docs/research.md +0 -93
  37. data/docs/shortcuts.md +0 -323
  38. data/docs/similarity_tutorial.md +0 -176
  39. data/docs/syntax.md +0 -395
  40. data/docs/videos.md +0 -16
  41. data/examples/build_stubbed_and_let_it_be_experiment.rb +0 -51
  42. data/examples/experimental_replacement.rb +0 -46
  43. data/examples/find_usage.rb +0 -26
  44. data/examples/let_it_be_experiment.rb +0 -11
  45. data/examples/method_complexity.rb +0 -37
  46. data/examples/search_duplicated.rb +0 -15
  47. data/examples/similarity_research.rb +0 -58
  48. data/examples/simple_rewriter.rb +0 -6
  49. data/experiments/let_it_be_experiment.rb +0 -9
  50. data/experiments/remove_useless_hook.rb +0 -9
  51. data/experiments/replace_create_with_build_stubbed.rb +0 -10
data/docs/command_line.md DELETED
@@ -1,238 +0,0 @@
1
- # Command line
2
-
3
- When you install the ffast gem, it will also create an executable named `fast`
4
- and you can use it to search and find code using the concept:
5
-
6
- ```
7
- $ fast '(def match?)' lib/fast.rb
8
- ```
9
- - Use `-d` or `--debug` for enable debug mode.
10
- - Use `--ast` to output the AST instead of the original code
11
- - Use `--pry` to jump debugging the first result with pry
12
- - Use `-c` to search from code example
13
- - Use `-s` to search similar code
14
- - Use `-p` to or `--parallel` to use multi core search
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
- ...
40
- ```
41
-
42
- ## `--debug`
43
-
44
- This option will print all matching details while validating each node.
45
-
46
- ```
47
- $ echo 'object.method' > sample.rb
48
- $ fast -d '(send (send nil _) _)' sample.rb
49
- ```
50
-
51
- It will bring details of the expression compiled and each node being validated:
52
-
53
- ```
54
- Expression: f[send] [#<Fast::Find:0x00007f8c53047158 @token="send">, #<Fast::Find:0x00007f8c530470e0 @token="nil">, #<Fast::Find:0x00007f8c53047090 @token="_">] f[_]
55
- send == (send
56
- (send nil :object) :method) # => true
57
- f[send] == (send
58
- (send nil :object) :method) # => true
59
- send == (send nil :object) # => true
60
- f[send] == (send nil :object) # => true
61
- == # => true
62
- f[nil] == # => true
63
- #<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
64
- f[_] == object # => true
65
- [#<Fast::Find:0x00007f8c53047158 @token="send">, #<Fast::Find:0x00007f8c530470e0 @token="nil">, #<Fast::Find:0x00007f8c53047090 @token="_">] == (send nil :object) # => true
66
- #<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
67
- f[_] == method # => true
68
- # sample.rb:1
69
- object.method
70
- ```
71
-
72
- ## `-s` for similarity
73
-
74
- Sometimes you want to search for some similar code like `(send (send (send nil _) _) _)` and we could simply say `a.b.c`.
75
-
76
- The option `-s` build an expression from the code ignoring final values.
77
-
78
- $ echo 'object.method' > sample.rb
79
- $ fast -s 'a.b' sample.rb
80
-
81
- ```ruby
82
- # sample.rb:1
83
- object.method
84
- ```
85
-
86
- See also [Code Similarity](similarity_tutorial.md) tutorial.
87
-
88
- ## `-c` to search from code example
89
-
90
- You can search for the exact expression with `-c`
91
-
92
- $ fast -c 'object.method' sample.rb
93
-
94
- ```ruby
95
- # sample.rb:1
96
- object.method
97
- ```
98
-
99
- Combining with `-d`, in the header you can see the generated expression.
100
-
101
- ```
102
- $ fast -d -c 'object.method' sample.rb | head -n 3
103
-
104
- The generated expression from AST was:
105
- (send
106
- (send nil :object) :method)
107
- ```
108
-
109
- ## Fastfile
110
-
111
- `Fastfile` will loaded when you start a pattern with a dot. It means the pattern
112
- will be a shortcut predefined on these Fastfiles.
113
-
114
- It will make three attempts to load `Fastfile` defined in `$PWD`, `$HOME` or
115
- checking if the `$FAST_FILE_DIR` is configured.
116
-
117
- You can define a `Fastfile` in any project with your custom shortcuts and easy
118
- check some code or run some task.
119
-
120
-
121
- ## Shortcut examples
122
-
123
- Create shortcuts with blocks enables introduce custom coding in
124
- the scope of the `Fast` module.
125
-
126
- ### Print library version.
127
-
128
- Let's say you'd like to show the version of your library. Your regular params
129
- in the command line will look like:
130
-
131
- $ fast '(casgn nil VERSION)' lib/*/version.rb
132
-
133
- It will output but the command is not very handy. In order to just say `fast .version`
134
- you can use the previous snippet in your `Fastfile`.
135
-
136
- ```ruby
137
- Fast.shortcut(:version, '(casgn nil VERSION)', 'lib/fast/version.rb')
138
- ```
139
-
140
- And calling `fast .version` it will output something like this:
141
-
142
- ```ruby
143
- # lib/fast/version.rb:4
144
- VERSION = '0.1.2'
145
- ```
146
-
147
- We can also always override the files params passing some other target file
148
- like `fast .version lib/other/file.rb` and it will reuse the other arguments
149
- from command line but replace the target files.
150
-
151
- ### Bumping a gem version
152
-
153
- While releasing a new gem version, we always need to mechanical go through the
154
- `lib/<your_gem>/version.rb` and change the string value to bump the version
155
- of your library. It's pretty mechanical and here is an example that allow you
156
- to simple use `fast .bump_version`:
157
-
158
- ```ruby
159
- Fast.shortcut :bump_version do
160
- rewrite_file('lib/fast/version.rb', '(casgn nil VERSION (str _)') do |node|
161
- target = node.children.last.loc.expression
162
- pieces = target.source.split(".").map(&:to_i)
163
- pieces.reverse.each_with_index do |fragment,i|
164
- if fragment < 9
165
- pieces[-(i+1)] = fragment +1
166
- break
167
- else
168
- pieces[-(i+1)] = 0
169
- end
170
- end
171
- replace(target, "'#{pieces.join(".")}'")
172
- end
173
- end
174
- ```
175
-
176
- !!! note "Note the shortcut scope"
177
- The shortcut calls `rewrite_file` from `Fast` scope as it use
178
- `Fast.instance_exec` for shortcuts that yields blocks.
179
-
180
- Checking the version:
181
-
182
- ```bash
183
- $ fast .version 13:58:40
184
- # lib/fast/version.rb:4
185
- VERSION = '0.1.2'
186
- ```
187
- Bumping the version:
188
-
189
- ```bash
190
- $ fast .bump_version 13:58:43
191
- ```
192
-
193
- No output because we don't print anything. Checking version again:
194
-
195
- ```bash
196
- $ fast .version 13:58:54
197
- # lib/fast/version.rb:4
198
- VERSION = '0.1.3'
199
- ```
200
-
201
- And now a fancy shortcut to report the other shortcuts :)
202
-
203
- ```ruby
204
- Fast.shortcut :shortcuts do
205
- report(shortcuts.keys)
206
- end
207
- ```
208
-
209
- Or we can make it a bit more friendly and also use Fast to process the shortcut
210
- positions and pick the comment that each shortcut have in the previous line:
211
-
212
- ```ruby
213
- # List all shortcut with comments
214
- Fast.shortcut :shortcuts do
215
- fast_files.each do |file|
216
- lines = File.readlines(file).map{|line|line.chomp.gsub(/\s*#/,'').strip}
217
- result = capture_file('(send ... shortcut $(sym _))', file)
218
- result = [result] unless result.is_a?Array
219
- result.each do |capture|
220
- target = capture.loc.expression
221
- puts "fast .#{target.source[1..-1].ljust(30)} # #{lines[target.line-2]}"
222
- end
223
- end
224
- end
225
- ```
226
-
227
- And it will be printing all loaded shortcuts with comments:
228
-
229
- ```
230
- $ fast .shortcuts
231
- fast .version # Let's say you'd like to show the version that is over the version file
232
- fast .parser # Simple shortcut that I used often to show how the expression parser works
233
- fast .bump_version # Use `fast .bump_version` to rewrite the version file
234
- fast .shortcuts # List all shortcut with comments
235
- ```
236
-
237
- You can find more examples in the [Fastfile](https://github.com/jonatas/fast/tree/master/Fastfile).
238
-
@@ -1,46 +0,0 @@
1
- # Editors' integration
2
-
3
- We don't have any proper integration or official plugins for editors yet.
4
-
5
- Here are a few ideas you can use to make your own flow.
6
-
7
- ## Vim
8
-
9
- Split terminal vertically and open fast focused on build the expression.
10
-
11
- ```vim
12
- nnoremap <Leader>ff :vsplit \| terminal fast "()" % <Left><Left><Left><Left><Left>
13
- ```
14
-
15
- Or you can build a function:
16
-
17
- ```vim
18
- function! s:Fast(args)
19
- let cmd = ''
20
- if !empty(b:ruby_project_root)
21
- let cmd .= 'cd ' . b:ruby_project_root . ' && '
22
- endif
23
-
24
- let cmd .= 'fast --no-color ' . a:args
25
-
26
- let custom_maker = neomake#utils#MakerFromCommand(cmd)
27
- let custom_maker.name = cmd
28
- let custom_maker.cwd = b:ruby_project_root
29
- let custom_maker.remove_invalid_entries = 0
30
- " e.g.:
31
- " # path/to/file.rb:1141
32
- " my_method(
33
- " :boom,
34
- " arg1: 1,
35
- " )
36
- " %W# %f:%l -> start a multiline warning when the line matches '# path/file.rb:1234'
37
- " %-Z# end multiline warning on the next line that starts with '#'
38
- " %C%m continued multiline warning message
39
- let custom_maker.errorformat = '%W# %f:%l, %-Z#, %C%m'
40
- let enabled_makers = [custom_maker]
41
- update | call neomake#Make(0, enabled_makers) | echom "running: " . cmd
42
- endfunction
43
- command! -complete=file -nargs=1 Fast call s:Fast(<q-args>)
44
- ```
45
-
46
- Check the conversation about vim integration [here](https://github.com/jonatas/fast/pull/16#issuecomment-555115606).
data/docs/experiments.md DELETED
@@ -1,153 +0,0 @@
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
-
148
- Or a single file:
149
-
150
- ```
151
- fast-experiment RSpec/ReplaceCreateWithBuildStubbed spec/models/my_spec.rb
152
- ```
153
-
data/docs/ideas.md DELETED
@@ -1,80 +0,0 @@
1
- # Ideas I want to build with Fast
2
-
3
- I don't have all the time I need to develop all the ideas I have to build
4
- around this tool, so here is a dump of a few brainstormings:
5
-
6
- ## Inline target code
7
-
8
- I started [fast-inline](https://github.com/jonatas/fast-inline) that can be
9
- useful to try to see how much every library is used in a project.
10
-
11
- My idea is try to inline some specific method call to understand if it makes
12
- sense to have an entire library in the stock.
13
-
14
- Understanding dependencies and how the code works can be a first step to get an
15
- "algorithm as a service". Instead of loading everything from the library, it
16
- would facilitate the cherry pick of only the proper dependencies necessaries to
17
- run the code you have and not the code that is overloading the project.
18
-
19
- ## Neo4J adapter
20
-
21
- Easy pipe fast results to Neo4J. It would facilitate to explore more complex
22
- scenarios and combine data from other sources.
23
-
24
- ## Git adapter
25
-
26
- Add extra tags to nodes with information from Git.
27
-
28
- * Revision
29
- * Author
30
- * Date
31
-
32
- Tag every node with the proper author.
33
-
34
- ## Ast Diff
35
-
36
- Allow to compare and return a summary of differences between two trees.
37
-
38
- It would be useful to identify renamings or other small changes, like only
39
- changes in comments that does not affect the file and possibly be ignored for
40
- some operations like run or not run tests.
41
-
42
- ## Transition synapses
43
-
44
- Following the previous idea, it would be great if we can understand the
45
- transition synapses and make it easily available to catch up with previous
46
- learnings.
47
-
48
- https://github.com/jonatas/chewy-diff/blob/master/lib/chewy/diff.rb
49
-
50
- This example, shows adds and removals from specific node targets between two
51
- different files.
52
-
53
- If we start tracking AST transition synapses and associating with "Fixes" or
54
- "Reverts" we can predict introduction of new bugs by inpecting if the
55
- introduction of new patterns that can be possibly reverted or improved.
56
-
57
- ## Fast Rewriter with pure strings
58
-
59
- As the AST rewriter adopts a custom block that needs to implement ruby code,
60
- we can expand the a query language for rewriting files without need to take the
61
- custom Ruby block.
62
-
63
- Example:
64
-
65
- ```ruby
66
- Fast.gsub_expression('remove(@expression)') # (node) => { remove(node.location.expression) }
67
- ```
68
-
69
- And later we can bind it in the command line to allow implement custom
70
- replacements without need to write a ruby file.
71
-
72
- ```
73
- fast (def my_target_method) lib spec --rewrite "remove(@expression)"
74
- ```
75
-
76
- or
77
-
78
- ```
79
- fast (def my_target_method) lib spec --rewrite "replace(@name, 'renamed_method')"
80
- ```