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