ffast 0.2.2 → 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 (54) 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 +102 -15
  6. data/README.md +21 -7
  7. data/bin/console +1 -1
  8. data/bin/fast-experiment +3 -0
  9. data/bin/fast-mcp +7 -0
  10. data/fast.gemspec +1 -3
  11. data/lib/fast/cli.rb +58 -26
  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 +16 -4
  20. data/lib/fast/source.rb +116 -0
  21. data/lib/fast/source_rewriter.rb +153 -0
  22. data/lib/fast/sql/rewriter.rb +36 -7
  23. data/lib/fast/sql.rb +15 -17
  24. data/lib/fast/summary.rb +435 -0
  25. data/lib/fast/version.rb +1 -1
  26. data/lib/fast.rb +140 -83
  27. data/mkdocs.yml +19 -4
  28. data/requirements-docs.txt +3 -0
  29. metadata +16 -59
  30. data/docs/command_line.md +0 -238
  31. data/docs/editors-integration.md +0 -46
  32. data/docs/experiments.md +0 -155
  33. data/docs/git.md +0 -115
  34. data/docs/ideas.md +0 -70
  35. data/docs/index.md +0 -404
  36. data/docs/pry-integration.md +0 -27
  37. data/docs/research.md +0 -93
  38. data/docs/shortcuts.md +0 -323
  39. data/docs/similarity_tutorial.md +0 -176
  40. data/docs/sql-support.md +0 -253
  41. data/docs/syntax.md +0 -395
  42. data/docs/videos.md +0 -16
  43. data/docs/walkthrough.md +0 -135
  44. data/examples/build_stubbed_and_let_it_be_experiment.rb +0 -51
  45. data/examples/experimental_replacement.rb +0 -46
  46. data/examples/find_usage.rb +0 -26
  47. data/examples/let_it_be_experiment.rb +0 -11
  48. data/examples/method_complexity.rb +0 -37
  49. data/examples/search_duplicated.rb +0 -15
  50. data/examples/similarity_research.rb +0 -58
  51. data/examples/simple_rewriter.rb +0 -6
  52. data/experiments/let_it_be_experiment.rb +0 -9
  53. data/experiments/remove_useless_hook.rb +0 -9
  54. data/experiments/replace_create_with_build_stubbed.rb +0 -10
data/docs/sql-support.md DELETED
@@ -1,253 +0,0 @@
1
- # SQL Support
2
-
3
- Fast is partially supporting SQL syntax. Behind the scenes it parses SQL using
4
- [pg_query](https://github.com/pganalyze/pg_query) and simplifies it to AST Nodes
5
- using the same interface. It's using Postgresql parser behind the scenes,
6
- but probably could be useful for other SQL similar diallects.
7
-
8
-
9
- !!! info "fast auto detects SQL files in the command line"
10
-
11
- By default, this module is not included into the main library.
12
- Fast can auto-detect file extensions and choose the sql path in case the
13
- file relates to sql.
14
-
15
- Use `fast --sql` in case you want to force the usage of the SQL parser.
16
-
17
- ```
18
-
19
- ```
20
-
21
-
22
- # Parsing a sql content
23
-
24
- ```ruby
25
- require 'fast/sql'
26
- ast = Fast.parse_sql('select 1')
27
- # => s(:select_stmt,
28
- # s(:target_list,
29
- # s(:res_target,
30
- # s(:val,
31
- # s(:a_const,
32
- # s(:val,
33
- # s(:integer,
34
- # s(:ival, 1))))))))
35
- ```
36
-
37
- ## Why it's interesting to use AST for SQL?
38
-
39
- Both SQL are available and do the same thing:
40
- ```sql
41
- select * from customers
42
- ```
43
- or
44
- ```sql
45
- table customers
46
- ```
47
-
48
- they have exactly the same objective but written down in very different syntax.
49
-
50
- Give a try:
51
-
52
- ```ruby
53
- Fast.parse_sql("select * from customers") == Fast.parse_sql("table customers") # => true
54
- ```
55
-
56
- ## Match
57
-
58
- Use `match?` with your node pattern to traverse the abstract syntax tree.
59
-
60
- ```ruby
61
- Fast.match?("(select_stmt ...)", ast) # => true
62
- ```
63
-
64
- Use `$` to capture elements from the AST:
65
-
66
- ```ruby
67
- Fast.match?("(select_stmt $...)", ast)
68
- => [s(:target_list,
69
- s(:res_target,
70
- s(:val,
71
- s(:a_const,
72
- s(:val,
73
- s(:integer,
74
- s(:ival, 1)))))))]
75
-
76
- ```
77
-
78
- You can dig deeper into the AST specifying nodes:
79
-
80
- ```ruby
81
- Fast.match?("(select_stmt (target_list (res_target (val ($...)))))", ast)
82
- # => [s(:a_const,
83
- # s(:val,
84
- # s(:integer,
85
- # s(:ival, 1))))]
86
- ```
87
-
88
- And ignoring node types or values using `_`. Check all [syntax](/syntax) options.
89
-
90
- ```ruby
91
- Fast.match?("(select_stmt (_ (_ (val ($...)))))", ast)
92
- # => [s(:a_const,
93
- # s(:val,
94
- # s(:integer,
95
- # s(:ival, 1))))]
96
- ```
97
-
98
- ## Search directly from the AST
99
-
100
- You can also search directly from nodes and keep digging:
101
-
102
- ```ruby
103
- ast = Fast.parse_sql('select 1');
104
- ast.search('ival') # => [s(:ival, s(:ival, 1))]
105
- ```
106
-
107
- Use first to return the node directly:
108
-
109
- ```ruby
110
- ast.first('(ival (ival _))') #=> s(:ival, s(:ival, 1))
111
- ```
112
-
113
- Combine the `capture` method with `$`:
114
-
115
- ```ruby
116
- ast.capture('(ival (ival $_))') # => [1]
117
- ```
118
-
119
-
120
- # Examples
121
-
122
- Let's dive into a more complex example capturing fields and from clause of a
123
- condition. Let's start parsing the sql:
124
-
125
- ## Capturing fields and where clause
126
-
127
-
128
- ```ruby
129
- ast = Fast.parse_sql('select name from customer')
130
- # => s(:select_stmt,
131
- # s(:target_list,
132
- # s(:res_target,
133
- # s(:val,
134
- # s(:column_ref,
135
- # s(:fields,
136
- # s(:string,
137
- # s(:str, "name"))))))),
138
- # s(:from_clause,
139
- # s(:range_var,
140
- # s(:relname, "customer"),
141
- # s(:inh, true),
142
- # s(:relpersistence, "p"))))
143
- ```
144
-
145
- Now, let's build the expression to get the fields and from_clause.
146
-
147
- ```ruby
148
- cols_and_from = "
149
- (select_stmt
150
- (target_list (res_target (val (column_ref (fields $...)))))
151
- (from_clause (range_var $(relname _))))
152
- "
153
- ```
154
-
155
- Now, we can use `Fast.capture` or `Fast.match?` to extract the values from the
156
- AST.
157
-
158
- ```ruby
159
- Fast.capture(cols_and_from, ast)
160
- # => [s(:string,
161
- # s(:str, "name")), s(:relname, "customer")]
162
- ```
163
-
164
- ## Search inside
165
-
166
- ```ruby
167
- relname = Fast.parse_sql('select name from customer').search('relname').first
168
- # => s(:relname, "customer")
169
- ```
170
-
171
- Find the location of a node.
172
-
173
- ```ruby
174
- relname.location # => #<Parser::Source::Map:0x00007fd3bcb0b7f0
175
- # @expression=#<Parser::Source::Range (sql) 17...25>,
176
- # @node=s(:relname, "customer")>
177
- ```
178
-
179
- The location can be useful to allow you to do refactorings and find specific
180
- delimitations of objects in the string.
181
-
182
- The attribute `expression` gives access to the source range.
183
-
184
- ```ruby
185
- relname.location.expression
186
- # => #<Parser::Source::Range (sql) 17...25>
187
- ```
188
-
189
- The `source_buffer` is shared and can be accessed through the expression.
190
-
191
- ```ruby
192
- relname.location.expression.source_buffer
193
- # => #<Fast::SQL::SourceBuffer:0x00007fd3bc2a6420
194
- # @name="(sql)",
195
- # @source="select name from customer",
196
- # @tokens=
197
- # [<PgQuery::ScanToken: start: 0, end: 6, token: :SELECT, keyword_kind: :RESERVED_KEYWORD>,
198
- # <PgQuery::ScanToken: start: 7, end: 11, token: :NAME_P, keyword_kind: :UNRESERVED_KEYWORD>,
199
- # <PgQuery::ScanToken: start: 12, end: 16, token: :FROM, keyword_kind: :RESERVED_KEYWORD>,
200
- # <PgQuery::ScanToken: start: 17, end: 25, token: :IDENT, keyword_kind: :NO_KEYWORD>]>
201
- ```
202
-
203
- The tokens are useful to find the proper node location during the build but
204
- they're not available for all the nodes, so, it can be very handy as an extra
205
- reference.
206
-
207
- ## Replace
208
-
209
- Replace fragments of your SQL based on AST can also be done with all the work
210
- inherited from Parser::TreeRewriter components.
211
-
212
- ```ruby
213
- Fast.parse_sql('select 1').replace('ival', '2') # => "select 2"
214
- ```
215
-
216
- The previous example is a syntax sugar for the following code:
217
-
218
- ```ruby
219
- Fast.replace_sql('ival',
220
- Fast.parse_sql('select 1'),
221
- &->(node){ replace(node.location.expression, '2') }
222
- ) # => "select 2"
223
- ```
224
-
225
- The last argument is a proc that runs on the [parser tree rewriter](https://www.rubydoc.info/gems/parser/Parser/TreeRewriter
226
- ) scope.
227
-
228
- Let's break down the previous code:
229
-
230
- ```ruby
231
- ast = Fast.parse_sql("select 1")
232
- # => s(:select_stmt,
233
- # s(:target_list,
234
- # s(:res_target,
235
- # s(:val,
236
- # s(:a_const,
237
- # s(:ival,
238
- # s(:ival, 1)))))))
239
- ```
240
-
241
- The pattern is simply matching node type that is `ival` but it could be a complex expression
242
- like `(val (a_const (val (ival (ival _)))))`.
243
-
244
- Completing the example:
245
-
246
- ```ruby
247
- Fast.replace_sql("ival", ast, &-> (n) { replace(n.loc.expression, "3") })
248
- # => "select 3"
249
- ```
250
-
251
- `loc` is a shortcut for `location` attribute.
252
-
253
-
data/docs/syntax.md DELETED
@@ -1,395 +0,0 @@
1
- # Syntax
2
-
3
- The syntax is inspired on [RuboCop Node Pattern](https://github.com/bbatsov/rubocop/blob/master/lib/rubocop/node_pattern.rb).
4
-
5
- You can find a great tutorial about RuboCop node pattern in the
6
- [official documentation](https://rubocop.readthedocs.io/en/latest/node_pattern/).
7
-
8
- ## Code example
9
-
10
- Let's consider the following `example.rb` code example:
11
-
12
- ```ruby
13
- class Example
14
- ANSWER = 42
15
- def magic
16
- rand(ANSWER)
17
- end
18
- def duplicate(value)
19
- value * 2
20
- end
21
- end
22
- ```
23
-
24
- Looking the AST representation we have:
25
-
26
- $ ruby-parse example.rb
27
-
28
- ```
29
- (class
30
- (const nil :Example) nil
31
- (begin
32
- (casgn nil :ANSWER
33
- (int 42))
34
- (def :magic
35
- (args)
36
- (send nil :rand
37
- (const nil :ANSWER)))
38
- (def :duplicate
39
- (args
40
- (arg :value))
41
- (send
42
- (lvar :value) :*
43
- (int 2)))))
44
- ```
45
-
46
- Now, let's explore all details of the current AST, combining with the syntax
47
- operators.
48
-
49
- Fast works with a single word that will be the node type.
50
-
51
- A simple search of `def` nodes can be done and will also print the code.
52
-
53
- $ fast def example.rb
54
-
55
- ```ruby
56
- # example.rb:3
57
- def magic
58
- rand(ANSWER)
59
- end
60
- ```
61
-
62
- or check the `casgn` that will show constant assignments:
63
-
64
- $ fast casgn example.rb
65
-
66
- ```ruby
67
- # example.rb:2
68
- ANSWER = 42
69
- ```
70
-
71
- ## `()` to represent a **node** search
72
-
73
- To specify details about a node, the `(` means navigate deeply into a node and
74
- go deep into the expression.
75
-
76
- $ fast '(casgn' example.rb
77
-
78
- ```ruby
79
- # example.rb:2
80
- ANSWER = 42
81
- ```
82
-
83
- Fast matcher never checks the end of the expression and close parens are not
84
- necessary. We keep them for the sake of specify more node details but the
85
- expression works with incomplete parens.
86
-
87
- $ fast '(casgn)' example.rb
88
-
89
- ```ruby
90
- # example.rb:2
91
- ANSWER = 42
92
- ```
93
-
94
- Closing extra params also don't have a side effect.
95
-
96
- $ fast '(casgn))' example.rb
97
-
98
- ```ruby
99
- # example.rb:2
100
- ANSWER = 42
101
- ```
102
-
103
- It also automatically flat parens case you put more levels in the beginning.
104
-
105
- $ fast '((casgn))' example.rb
106
-
107
- ```ruby
108
- # example.rb:2
109
- ANSWER = 42
110
- ```
111
-
112
- For checking AST details while doing some search, you can use `--ast` in the
113
- command line for printing the AST instead of the code:
114
-
115
- $ fast '((casgn ' example.rb --ast
116
-
117
- ```ruby
118
- # example.rb:2
119
- (casgn nil :ANSWER
120
- (int 42))
121
- ```
122
-
123
- ## `_` is **something** not nil
124
-
125
- Let's enhance our current expression and specify that we're looking for constant
126
- assignments of integers ignoring values and constant names replacing with `_`.
127
-
128
- $ fast '(casgn nil _ (int _))' example.rb
129
-
130
- ```ruby
131
- # example.rb:2
132
- ANSWER = 42
133
- ```
134
-
135
- Keep in mind that `_` means not nil and `(casgn _ _ (int _))` would not
136
- match.
137
-
138
- Let's search for integer nodes:
139
-
140
- $ fast int example.rb
141
- ```ruby
142
- # example.rb:2
143
- 42
144
- # example.rb:7
145
- 2
146
- ```
147
-
148
- The current search show the nodes but they are not so useful without understand
149
- the expression in their context. We need to check their `parent`.
150
-
151
- ## `^` is to get the **parent node** of an expression
152
-
153
- By default, Parser::AST::Node does not have access to parent and for accessing
154
- it you can say `^` for reaching the parent.
155
-
156
- $ fast '^int' example.rb
157
-
158
- ```ruby
159
- # example.rb:2
160
- ANSWER = 42
161
- # example.rb:7
162
- value * 2
163
- ```
164
-
165
- And using it multiple times will make the node match from levels up:
166
-
167
- $ fast '^^int' example.rb
168
-
169
- ```ruby
170
- # example.rb:2
171
- ANSWER = 42
172
- def magic
173
- rand(ANSWER)
174
- end
175
- def duplicate(value)
176
- value * 2
177
- end
178
- ```
179
-
180
- ## `[]` join conditions
181
-
182
- Let's hunt for integer nodes that the parent is also a method:
183
-
184
- $ fast '[ ^^int def ]' example.rb
185
-
186
- The match will filter only nodes that matches all internal expressions.
187
-
188
- ```ruby
189
- # example.rb:6
190
- def duplicate(value)
191
- value * 2
192
- end
193
- ```
194
-
195
- The expression is matching nodes that have a integer granchild and also with
196
- type `def`.
197
-
198
- ## `...` is a **node** with children
199
-
200
- Looking the method representation we have:
201
-
202
- $ fast def example.rb --ast
203
-
204
- ```ruby
205
- # example.rb:3
206
- (def :magic
207
- (args)
208
- (send nil :rand
209
- (const nil :ANSWER)))
210
- # example.rb:6
211
- (def :duplicate
212
- (args
213
- (arg :value))
214
- (send
215
- (lvar :value) :*
216
- (int 2)))
217
- ```
218
-
219
- And if we want to delimit only methods with arguments:
220
-
221
- $ fast '(def _ ...)' example.rb
222
-
223
- ```ruby
224
- # example.rb:6
225
- def duplicate(value)
226
- value * 2
227
- end
228
- ```
229
-
230
- If we use `(def _ _)` instead it will match both methods because `(args)`
231
- does not have children but is not nil.
232
-
233
- ## `$` is for **capture** current expression
234
-
235
- Now, let's say we want to extract some method name from current classes.
236
-
237
- In such case we don't want to have the node definition but only return the node
238
- name.
239
-
240
- ```ruby
241
- # example.rb:2
242
- def magic
243
- rand(ANSWER)
244
- end
245
- # example.rb:
246
- magic
247
- # example.rb:9
248
- def duplicate(value)
249
- value * 2
250
- end
251
- # example.rb:
252
- duplicate
253
- ```
254
-
255
- One extra method name was printed because of `$` is capturing the element.
256
-
257
- ## `nil` matches exactly **nil**
258
-
259
- Nil is used in the code as a node type but parser gem also represents empty
260
- spaces in expressions with nil.
261
-
262
- Example, a method call from Kernel is a `send` from `nil` calling the method
263
- while I can also send a method call from a class.
264
-
265
- ```
266
- $ ruby-parse -e 'method'
267
- (send nil :method)
268
- ```
269
-
270
- And a method from a object will have the nested target not nil.
271
-
272
- ```
273
- $ ruby-parse -e 'object.method'
274
- (send
275
- (send nil :object) :method)
276
- ```
277
-
278
- Let's build a serch for any calls from `nil`:
279
-
280
- $ fast '(_ nil _)' example.rb
281
-
282
- ```ruby
283
- # example.rb:3
284
- Example
285
- # example.rb:4
286
- ANSWER = 42
287
- # example.rb:6
288
- rand(ANSWER)
289
- ```
290
-
291
- Double check the expressions that have matched printing the AST:
292
-
293
- $ fast '(_ nil _)' example.rb --ast
294
-
295
- ```ruby
296
- # example.rb:3
297
- (const nil :Example)
298
- # example.rb:4
299
- (casgn nil :ANSWER
300
- (int 42))
301
- # example.rb:6
302
- (send nil :rand
303
- (const nil :ANSWER))
304
- ```
305
-
306
- ## `{}` is for **any** matches like **union** conditions with **or** operator
307
-
308
- Let's say we to add check all occurrencies of the constant `ANSWER`.
309
-
310
- We'll need to get both `casgn` and `const` node types. For such cases we can
311
- surround the expressions with `{}` and it will return if the node matches with
312
- any of the internal expressions.
313
-
314
- $ fast '({casgn const} nil ANSWER)' example.rb
315
-
316
- ```
317
- # example.rb:4
318
- ANSWER = 42
319
- # example.rb:6
320
- ANSWER
321
- ```
322
-
323
- ## `#` for custom methods
324
-
325
- Custom methods can let you into ruby doman for more complicated rules. Let's say
326
- we're looking for duplicated methods in the same class. We need to collect
327
- method names and guarantee they are unique.
328
-
329
- ```ruby
330
- def duplicated(method_name)
331
- @methods ||= []
332
- already_exists = @methods.include?(method_name)
333
- @methods << method_name
334
- already_exists
335
- end
336
-
337
- puts Fast.search_file( '(def #duplicated)', 'example.rb')
338
- ```
339
- The same principle can be used in the node level or for debugging purposes.
340
-
341
- ```ruby
342
- require 'pry'
343
- def debug(node)
344
- binding.pry
345
- end
346
-
347
- puts Fast.search_file('#debug', 'example.rb')
348
- ```
349
- If you want to get only `def` nodes you can also intersect expressions with `[]`:
350
- ```ruby
351
- puts Fast.search_file('[ def #debug ]', 'example.rb')
352
- ```
353
- Or if you want to debug a very specific expression you can use `()` to specify
354
- more details of the node
355
- ```ruby
356
- puts Fast.search_file('[ (def a) #debug ]', 'example.rb')
357
- ```
358
-
359
- ## `.` for instance methods
360
-
361
- You can also call instance methods using `.<method-name>`.
362
-
363
- Example `nil` is the same of calling `nil?` and you can also use `(int .odd?)`
364
- to pick only odd integers. The `int` fragment can also be `int_type?`.
365
-
366
- ## `\1` for first previous capture
367
-
368
- Imagine you're looking for a method that is just delegating something to
369
- another method, like:
370
-
371
- ```ruby
372
- def name
373
- person.name
374
- end
375
- ```
376
-
377
- This can be represented as the following AST:
378
-
379
- ```
380
- (def :name
381
- (args)
382
- (send
383
- (send nil :person) :name))
384
- ```
385
-
386
- Then, let's build a search for methods that calls an attribute with the same
387
- name:
388
-
389
- ```ruby
390
- Fast.match?('(def $_ ... (send (send nil _) \1))', ast) # => [:name]
391
- ```
392
-
393
- With the method name being captured with `$_` it can be later referenced in the
394
- expression with `\1`. If the search contains multiple captures, the `\2`,`\3`
395
- can be used as the sequence of captures.
data/docs/videos.md DELETED
@@ -1,16 +0,0 @@
1
- # Videos
2
-
3
- - [Ruby Kaigi TakeOut 2020: Grepping Ruby code like a boss](https://www.youtube.com/watch?v=YczrZQC9aP8&amp;feature=youtu.be&amp)
4
-
5
- <iframe width="560" height="315" src="https://www.youtube.com/embed/YczrZQC9aP8?" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
6
-
7
- Also, similar livecoding session at [RubyConf Brazil 2019 (Portuguese)](https://www.eventials.com/locaweb/jonatas-paganini-live-coding-grepping-ruby-code-like-a-boss/#_=_).
8
-
9
- - Introduction to [inline code](https://www.youtube.com/watch?v=KQXglNLUv7o).
10
- <iframe width="560" height="315" src="https://www.youtube.com/embed/KQXglNLUv7o" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
11
-
12
- - [Making local variables inline](https://www.youtube.com/watch?v=JD44nhegCRs)
13
- <iframe width="560" height="315" src="https://www.youtube.com/embed/YN0s9kV1A2A" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
14
-
15
- - [Making methods inline](https://www.youtube.com/watch?v=JD44nhegCRs)
16
- <iframe width="560" height="315" src="https://www.youtube.com/embed/YN0s9kV1A2A" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>