ffast 0.2.4 → 0.2.7

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
  SHA256:
3
- metadata.gz: 4b5b5e27292bfe589236ccf40952b5da73f4dfc4b35df85220d9beebae7c725e
4
- data.tar.gz: 6c4466b554e164c447cfb02760c5b6cd262c0f904f7ea8c35897288efa4668ec
3
+ metadata.gz: ebcc19357d7845b5494ede74a18103b84164bbf9e2de41bf3acb1e4010bdc601
4
+ data.tar.gz: 2bbe9b055db2b8bcc6a855addbf41a642f2c7c74f7cfe015a7e53802b1147113
5
5
  SHA512:
6
- metadata.gz: 0f8ccd9bcd621b69847fd1092b0fa08cada32258a2ec1f6b04654744c8930718628955698673577e4431a6fe6146da744bcb6aafdada442aa08bfe8e5ba32780
7
- data.tar.gz: 263b032df9ed1ea5b8d7cd71e1bd613dc781c53fc1dd10cb8bedbe526d904543e1331bb43f345679954e7ebc404b0bdfb5f965dffd35b74c454038ba7ae0fb31
6
+ metadata.gz: fae498867cea1a193d25b2d10f5c4b21c34ef0f70ac76b83c6db39c7966238ae2f20eb0a537f2036ead6177de36542fa08a98d7789a0724e79a7cb358f860e46
7
+ data.tar.gz: df003c3585970cd06cb82b639ab890d311ae25325b2361cae05d54ef594d0cb0f3043e017b69368fc15495bcc3a20ccb84f276901159542b241d816c7600e7ff
data/lib/fast/cli.rb CHANGED
@@ -106,8 +106,23 @@ module Fast
106
106
  args = args.dup
107
107
  args = replace_args_with_shortcut(args) if shortcut_name_from(args)
108
108
  @colorize = STDOUT.isatty
109
- option_parser.parse! args
109
+ @headless = false
110
+ @bodyless = false
111
+ @captures = false
112
+ @parallel = false
113
+ @debug = false
114
+ @sql = false
115
+ @level = nil
116
+ @show_sexp = false
117
+ @help = false
118
+ @similar = false
119
+ @from_code = false
120
+ @show_link = false
121
+ @show_permalink = false
122
+ @files = []
123
+ option_parser.parse!(args)
110
124
  @pattern, @files = extract_pattern_and_files(args)
125
+ puts "DEBUG: pattern=#{@pattern.inspect} files=#{@files.inspect}" if @debug
111
126
 
112
127
  @sql ||= @files.any? && @files.all? { |file| file.end_with?('.sql') }
113
128
  require 'fast/sql' if @sql
@@ -146,7 +161,7 @@ module Fast
146
161
  @sql = true
147
162
  end
148
163
 
149
- opts.on('--captures', 'Print only captures of the patterns and skip node results') do
164
+ opts.on('-c', '--captures', 'Print only captures of the patterns and skip node results') do
150
165
  @captures = true
151
166
  end
152
167
 
data/lib/fast/node.rb CHANGED
@@ -70,13 +70,11 @@ module Fast
70
70
  end
71
71
 
72
72
  def updated(type = nil, children = nil, properties = nil)
73
- updated_node = self.class.new(
73
+ self.class.new(
74
74
  type || self.type,
75
75
  children || self.children,
76
76
  { location: properties&.fetch(:location, loc) || loc }
77
77
  )
78
- updated_node.send(:assign_parents!)
79
- updated_node
80
78
  end
81
79
 
82
80
  def ==(other)
@@ -159,7 +157,6 @@ module Fast
159
157
  def assign_parents!
160
158
  each_child_node do |child|
161
159
  self.class.set_parent(child, self)
162
- child.send(:assign_parents!) if child.respond_to?(:assign_parents!, true)
163
160
  end
164
161
  end
165
162
 
@@ -87,6 +87,12 @@ module Fast
87
87
  statements.is_a?(Node) ? statements : build_node(:begin, statements, node, source, buffer_name)
88
88
  when Prism::StatementsNode
89
89
  adapt_statements(node, source, buffer_name)
90
+ when Prism::AliasMethodNode, Prism::AliasGlobalVariableNode
91
+ build_node(:alias, [adapt(node.new_name, source, buffer_name), adapt(node.old_name, source, buffer_name)], node, source, buffer_name)
92
+ when Prism::DefinedNode
93
+ build_node(:defined?, [adapt(node.value, source, buffer_name)], node, source, buffer_name)
94
+ when Prism::UndefNode
95
+ build_node(:undef, node.names.map { |name| adapt(name, source, buffer_name) }, node, source, buffer_name)
90
96
  when Prism::ModuleNode
91
97
  build_node(:module, [adapt(node.constant_path, source, buffer_name), adapt(node.body, source, buffer_name)], node, source, buffer_name)
92
98
  when Prism::ClassNode
@@ -125,12 +131,31 @@ module Fast
125
131
  build_node(node.exclude_end? ? :erange : :irange, [adapt(node.left, source, buffer_name), adapt(node.right, source, buffer_name)], node, source, buffer_name)
126
132
  when Prism::BlockArgumentNode
127
133
  build_node(:block_pass, [adapt(node.expression, source, buffer_name)], node, source, buffer_name)
134
+ when Prism::ReturnNode
135
+ build_node(:return, node.arguments&.arguments.to_a.map { |arg| adapt(arg, source, buffer_name) }, node, source, buffer_name)
136
+ when Prism::NextNode
137
+ build_node(:next, node.arguments&.arguments.to_a.map { |arg| adapt(arg, source, buffer_name) }, node, source, buffer_name)
138
+ when Prism::BreakNode
139
+ build_node(:break, node.arguments&.arguments.to_a.map { |arg| adapt(arg, source, buffer_name) }, node, source, buffer_name)
140
+ when Prism::YieldNode
141
+ build_node(:yield, node.arguments&.arguments.to_a.map { |arg| adapt(arg, source, buffer_name) }, node, source, buffer_name)
142
+ when Prism::SuperNode
143
+ build_node(:super, node.arguments&.arguments.to_a.map { |arg| adapt(arg, source, buffer_name) }, node, source, buffer_name)
144
+ when Prism::ForwardingSuperNode
145
+ build_node(:zsuper, [], node, source, buffer_name)
146
+ when Prism::SplatNode
147
+ build_node(:splat, [adapt(node.expression, source, buffer_name)], node, source, buffer_name)
148
+ when Prism::AssocSplatNode
149
+ build_node(:kwsplat, [adapt(node.value, source, buffer_name)], node, source, buffer_name)
128
150
  when Prism::ConstantPathNode
129
151
  build_const_path(node, source, buffer_name)
152
+
130
153
  when Prism::ConstantReadNode
131
154
  build_node(:const, [nil, node.name], node, source, buffer_name)
132
155
  when Prism::ConstantWriteNode
133
156
  build_node(:casgn, [nil, node.name, adapt(node.value, source, buffer_name)], node, source, buffer_name)
157
+ when Prism::ConstantPathWriteNode
158
+ build_node(:casgn, [adapt(node.target, source, buffer_name), nil, adapt(node.value, source, buffer_name)], node, source, buffer_name)
134
159
  when Prism::SymbolNode
135
160
  build_node(:sym, [node.unescaped], node, source, buffer_name)
136
161
  when Prism::StringNode
@@ -143,6 +168,10 @@ module Fast
143
168
  build_node(:dxstr, node.parts.filter_map { |part| adapt(part, source, buffer_name) }, node, source, buffer_name)
144
169
  when Prism::InterpolatedSymbolNode
145
170
  build_node(:dsym, node.parts.filter_map { |part| adapt(part, source, buffer_name) }, node, source, buffer_name)
171
+ when Prism::RegularExpressionNode
172
+ build_node(:regexp, [build_node(:str, [node.unescaped], node, source, buffer_name), build_node(:regopt, regexp_options(node), node, source, buffer_name)], node, source, buffer_name)
173
+ when Prism::InterpolatedRegularExpressionNode
174
+ build_node(:regexp, node.parts.filter_map { |part| adapt(part, source, buffer_name) } + [build_node(:regopt, regexp_options(node), node, source, buffer_name)], node, source, buffer_name)
146
175
  when Prism::ArrayNode
147
176
  build_node(:array, node.elements.map { |child| adapt(child, source, buffer_name) }, node, source, buffer_name)
148
177
  when Prism::HashNode
@@ -153,12 +182,30 @@ module Fast
153
182
  build_node(:pair, [adapt(node.key, source, buffer_name), adapt(node.value, source, buffer_name)], node, source, buffer_name)
154
183
  when Prism::SelfNode
155
184
  build_node(:self, [], node, source, buffer_name)
185
+ when Prism::RedoNode
186
+ build_node(:redo, [], node, source, buffer_name)
187
+ when Prism::RetryNode
188
+ build_node(:retry, [], node, source, buffer_name)
189
+ when Prism::PreExecutionNode
190
+ build_node(:preexe, [adapt_statements(node.statements, source, buffer_name)], node, source, buffer_name)
191
+ when Prism::PostExecutionNode
192
+ build_node(:postexe, [adapt_statements(node.statements, source, buffer_name)], node, source, buffer_name)
193
+ when Prism::NumberedReferenceReadNode
194
+ build_node(:nth_ref, [node.number], node, source, buffer_name)
195
+ when Prism::BackReferenceReadNode
196
+ build_node(:back_ref, [node.name], node, source, buffer_name)
156
197
  when Prism::LocalVariableReadNode
157
198
  build_node(:lvar, [node.name], node, source, buffer_name)
199
+ when Prism::LocalVariableTargetNode
200
+ build_node(:lvasgn, [node.name], node, source, buffer_name)
158
201
  when Prism::InstanceVariableReadNode
159
202
  build_node(:ivar, [node.name], node, source, buffer_name)
203
+ when Prism::GlobalVariableReadNode
204
+ build_node(:gvar, [node.name], node, source, buffer_name)
160
205
  when Prism::InstanceVariableWriteNode, Prism::InstanceVariableOrWriteNode
161
206
  build_node(:ivasgn, [node.name, adapt(node.value, source, buffer_name)], node, source, buffer_name)
207
+ when Prism::GlobalVariableWriteNode
208
+ build_node(:gvasgn, [node.name, adapt(node.value, source, buffer_name)], node, source, buffer_name)
162
209
  when Prism::LocalVariableWriteNode, Prism::LocalVariableOrWriteNode
163
210
  build_node(:lvasgn, [node.name, adapt(node.value, source, buffer_name)], node, source, buffer_name)
164
211
  when Prism::LocalVariableOperatorWriteNode
@@ -173,33 +220,52 @@ module Fast
173
220
  source,
174
221
  buffer_name
175
222
  )
223
+ when Prism::MatchWriteNode
224
+ build_node(:match_with_lvasgn, [adapt(node.call.receiver, source, buffer_name), adapt(node.call.arguments&.arguments&.first, source, buffer_name)], node, source, buffer_name)
225
+ when Prism::MatchLastLineNode
226
+ build_node(:match_current_line, [build_node(:regexp, [build_node(:str, [node.unescaped], node, source, buffer_name), build_node(:regopt, regexp_options(node), node, source, buffer_name)], node, source, buffer_name)], node, source, buffer_name)
176
227
  when Prism::IntegerNode
177
228
  build_node(:int, [node.value], node, source, buffer_name)
178
229
  when Prism::FloatNode
179
230
  build_node(:float, [node.value], node, source, buffer_name)
231
+ when Prism::RationalNode
232
+ build_node(:rational, [node.value], node, source, buffer_name)
233
+ when Prism::ImaginaryNode
234
+ build_node(:complex, [node.value], node, source, buffer_name)
180
235
  when Prism::TrueNode
181
236
  build_node(:true, [], node, source, buffer_name)
182
237
  when Prism::FalseNode
183
238
  build_node(:false, [], node, source, buffer_name)
184
239
  when Prism::NilNode
185
240
  build_node(:nil, [], node, source, buffer_name)
241
+ when Prism::AndNode
242
+ build_node(:and, [adapt(node.left, source, buffer_name), adapt(node.right, source, buffer_name)], node, source, buffer_name)
243
+ when Prism::OrNode
244
+ build_node(:or, [adapt(node.left, source, buffer_name), adapt(node.right, source, buffer_name)], node, source, buffer_name)
186
245
  when Prism::IfNode
187
246
  build_node(:if, [adapt(node.predicate, source, buffer_name), adapt(node.statements, source, buffer_name), adapt(node.consequent, source, buffer_name)], node, source, buffer_name)
188
247
  when Prism::UnlessNode
189
248
  build_node(:if, [adapt(node.predicate, source, buffer_name), adapt(node.consequent, source, buffer_name), adapt(node.statements, source, buffer_name)], node, source, buffer_name)
249
+ when Prism::WhileNode
250
+ build_node(:while, [adapt(node.predicate, source, buffer_name), adapt(node.statements, source, buffer_name)], node, source, buffer_name)
251
+ when Prism::UntilNode
252
+ build_node(:until, [adapt(node.predicate, source, buffer_name), adapt(node.statements, source, buffer_name)], node, source, buffer_name)
253
+ when Prism::ForNode
254
+ build_node(:for, [adapt(node.index, source, buffer_name), adapt(node.collection, source, buffer_name), adapt(node.statements, source, buffer_name)], node, source, buffer_name)
255
+ when Prism::MultiWriteNode
256
+ mlhs_children = node.lefts.map { |left| adapt(left, source, buffer_name) }
257
+ mlhs_children << adapt(node.rest, source, buffer_name) if node.rest
258
+ mlhs_children.concat(node.rights.map { |right| adapt(right, source, buffer_name) }) if node.respond_to?(:rights)
259
+ build_node(:masgn, [build_node(:mlhs, mlhs_children, node, source, buffer_name), adapt(node.value, source, buffer_name)], node, source, buffer_name)
190
260
  when Prism::RescueModifierNode
191
261
  build_node(:rescue, [adapt(node.expression, source, buffer_name), build_node(:resbody, [nil, nil, adapt(node.rescue_expression, source, buffer_name)], node, source, buffer_name), nil], node, source, buffer_name)
192
262
  when Prism::CaseNode
193
263
  children = [adapt(node.predicate, source, buffer_name)]
194
264
  children.concat(node.conditions.map { |condition| adapt(condition, source, buffer_name) })
195
- else_clause =
196
- if node.respond_to?(:else_clause)
197
- node.else_clause
198
- elsif node.respond_to?(:consequent)
199
- node.consequent
200
- end
201
- children << adapt_else_clause(else_clause, source, buffer_name) if else_clause
265
+ children << adapt(node.consequent, source, buffer_name) if node.consequent
202
266
  build_node(:case, children, node, source, buffer_name)
267
+ when Prism::HashPatternNode
268
+ build_node(:hash, node.elements.map { |child| adapt(child, source, buffer_name) }, node, source, buffer_name)
203
269
  when Prism::WhenNode
204
270
  condition =
205
271
  if node.conditions.length == 1
@@ -210,12 +276,29 @@ module Fast
210
276
  build_node(:when, [condition, adapt(node.statements, source, buffer_name)].compact, node, source, buffer_name)
211
277
  when Prism::ElseNode
212
278
  adapt_else_clause(node, source, buffer_name)
279
+ when Prism::CallOperatorWriteNode
280
+ operator = node.respond_to?(:binary_operator) ? node.binary_operator : node.operator
281
+ build_node(:op_asgn, [build_node(:send, [adapt(node.receiver, source, buffer_name), node.read_name], node, source, buffer_name), operator, adapt(node.value, source, buffer_name)], node, source, buffer_name)
282
+ when Prism::IndexOperatorWriteNode
283
+ operator = node.respond_to?(:binary_operator) ? node.binary_operator : node.operator
284
+ build_node(:op_asgn, [build_node(:send, [adapt(node.receiver, source, buffer_name), :[]].concat(node.arguments&.arguments.to_a.map { |arg| adapt(arg, source, buffer_name) }), node, source, buffer_name), operator, adapt(node.value, source, buffer_name)], node, source, buffer_name)
213
285
  when Prism::BeginNode, Prism::EmbeddedStatementsNode
214
- statements = adapt_statements(node.statements, source, buffer_name)
215
- children = statements.is_a?(Node) && statements.type == :begin ? statements.children : Array(statements)
216
- build_node(:begin, children, node, source, buffer_name)
286
+ res = adapt_statements(node.statements, source, buffer_name)
287
+ if node.respond_to?(:rescue_clause) && node.rescue_clause
288
+ res = build_node(:rescue, [res].concat(adapt_rescue_clause(node.rescue_clause, source, buffer_name)).concat([adapt(node.else_clause, source, buffer_name)]), node, source, buffer_name)
289
+ end
290
+ if node.respond_to?(:ensure_clause) && node.ensure_clause
291
+ res = build_node(:ensure, [res, adapt(node.ensure_clause, source, buffer_name)], node, source, buffer_name)
292
+ end
293
+ res
294
+ when Prism::RescueNode
295
+ adapt_rescue_clause(node, source, buffer_name)
296
+ when Prism::EnsureNode
297
+ adapt_statements(node.statements, source, buffer_name)
217
298
  when Prism::EmbeddedVariableNode
218
299
  build_node(:begin, [adapt(node.variable, source, buffer_name)].compact, node, source, buffer_name)
300
+ when Prism::ImplicitNode
301
+ adapt(node.value, source, buffer_name)
219
302
  when Prism::LambdaNode
220
303
  build_node(:lambda, [adapt_block_parameters(node.parameters, source, buffer_name), adapt(node.body, source, buffer_name)], node, source, buffer_name)
221
304
  else
@@ -223,6 +306,33 @@ module Fast
223
306
  end
224
307
  end
225
308
 
309
+ def adapt_rescue_clause(node, source, buffer_name)
310
+ resbodies = []
311
+ current = node
312
+ while current && current.is_a?(Prism::RescueNode)
313
+ exceptions = current.exceptions.map { |e| adapt(e, source, buffer_name) }
314
+ exceptions = build_node(:array, exceptions, current, source, buffer_name) if exceptions.any?
315
+ resbodies << build_node(:resbody, [exceptions, adapt(current.reference, source, buffer_name), adapt(current.statements, source, buffer_name)], current, source, buffer_name)
316
+ current = current.respond_to?(:subsequent) ? current.subsequent : current.consequent
317
+ end
318
+ resbodies
319
+ end
320
+
321
+ def adapt_else_clause(node, source, buffer_name)
322
+ adapt(node&.statements, source, buffer_name)
323
+ end
324
+
325
+ def wrap_begin(node, source, buffer_name)
326
+ return nil unless node
327
+
328
+ res = adapt(node, source, buffer_name)
329
+ if res.is_a?(Node) && res.type == :begin
330
+ res
331
+ else
332
+ build_node(:begin, [nil, res], node, source, buffer_name)
333
+ end
334
+ end
335
+
226
336
  def adapt_statements(node, source, buffer_name)
227
337
  return nil unless node
228
338
 
@@ -299,8 +409,12 @@ module Fast
299
409
  send_node
300
410
  end
301
411
 
302
- def adapt_else_clause(node, source, buffer_name)
303
- adapt(node.statements, source, buffer_name)
412
+ def regexp_options(node)
413
+ options = []
414
+ options << :i if node.ignore_case?
415
+ options << :m if node.multi_line?
416
+ options << :x if node.extended?
417
+ options
304
418
  end
305
419
 
306
420
  def parameter_name(node)
@@ -308,8 +422,14 @@ module Fast
308
422
  end
309
423
 
310
424
  def build_const_path(node, source, buffer_name)
311
- parent = node.parent ? adapt(node.parent, source, buffer_name) : nil
312
- build_node(:const, [parent, node.child.name], node, source, buffer_name)
425
+ parent =
426
+ if node.parent
427
+ adapt(node.parent, source, buffer_name)
428
+ elsif node.delimiter_loc
429
+ build_node(:cbase, [], nil, source, buffer_name)
430
+ end
431
+ name = node.respond_to?(:name) ? node.name : node.child.name
432
+ build_node(:const, [parent, name], node, source, buffer_name)
313
433
  end
314
434
 
315
435
  def build_node(type, children, prism_node, source, buffer_name)
data/lib/fast/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fast
4
- VERSION = '0.2.4'
4
+ VERSION = '0.2.7'
5
5
  end
data/lib/fast.rb CHANGED
@@ -41,6 +41,12 @@ module Fast
41
41
  |
42
42
  [\d\w_]+[=\\!\?]? # method names or numbers
43
43
  |
44
+ :[^(){}\[\]\s;]+ # symbols
45
+ |
46
+ \/[^\/ \n]+\/ # regex literals
47
+ |
48
+ ; # semicolons
49
+ |
44
50
  \(|\) # parens `(` and `)` for tuples
45
51
  |
46
52
  \{|\} # curly brackets `{` and `}` for any
@@ -259,6 +265,8 @@ module Fast
259
265
  # otherwise it recursively collect possible children nodes
260
266
  # @yield node and capture if block given
261
267
  def search(pattern, node, *args)
268
+ return [] if node.nil?
269
+
262
270
  if (match = match?(pattern, node, *args))
263
271
  yield node, match if block_given?
264
272
  match != true ? [node, match] : [node]
@@ -277,6 +285,8 @@ module Fast
277
285
  # Only captures from a search
278
286
  # @return [Array<Object>] with all captured elements.
279
287
  def capture(pattern, node)
288
+ return [] if node.nil?
289
+
280
290
  if (match = match?(pattern, node))
281
291
  match == true ? node : match
282
292
  else
@@ -488,6 +498,8 @@ module Fast
488
498
  when '^' then Parent.new(parse)
489
499
  when '\\' then FindWithCapture.new(parse)
490
500
  when /^%\d/ then FindFromArgument.new(token[1..])
501
+ when ';'
502
+ raise SyntaxError, "Semicolons are not allowed in patterns: #{token}"
491
503
  when nil then nil
492
504
  else Find.new(token)
493
505
  end
@@ -548,7 +560,6 @@ module Fast
548
560
  case expression
549
561
  when Proc then expression.call(node)
550
562
  when Find then expression.match?(node)
551
- when Symbol then compare_symbol_or_head(expression, node)
552
563
  when Enumerable
553
564
  if expression.last == :'...' || expression.last.is_a?(Find) && expression.last.token == '...'
554
565
  expression[0...-1].each_with_index.all? do |exp, i|
@@ -560,14 +571,16 @@ module Fast
560
571
  end
561
572
  end
562
573
  else
563
- node == expression
574
+ compare_symbol_or_head(expression, node)
564
575
  end
565
576
  end
566
577
 
567
578
  def compare_symbol_or_head(expression, node)
579
+ return true if node.nil? && (expression.nil? || expression == :nil)
580
+
568
581
  case node
569
582
  when ->(candidate) { Fast.ast_node?(candidate) }
570
- node.type == expression.to_sym
583
+ node.type == expression&.to_sym
571
584
  when String
572
585
  node == expression.to_s
573
586
  when TrueClass
@@ -615,6 +628,8 @@ module Fast
615
628
  case token
616
629
  when /^\d+\.\d*/ then token.to_f
617
630
  when /^\d+/ then token.to_i
631
+ when /^:/ then token[1..].to_sym
632
+ when /^\/.*\/$/ then Regexp.new(token[1..-2])
618
633
  else token.to_sym
619
634
  end
620
635
  end
@@ -774,7 +789,7 @@ module Fast
774
789
  # Fast.expression("{int float}")
775
790
  class Any < Find
776
791
  def match?(node)
777
- token.any? { |expression| Fast.match?(expression, node) }
792
+ token.any? { |expression| !!Fast.match?(expression, node) }
778
793
  end
779
794
 
780
795
  def to_s
@@ -785,7 +800,7 @@ module Fast
785
800
  # Intersect expressions. Works like a **AND** operator.
786
801
  class All < Find
787
802
  def match?(node)
788
- token.all? { |expression| expression.match?(node) }
803
+ token.all? { |expression| !!expression.match?(node) }
789
804
  end
790
805
 
791
806
  def to_s
@@ -831,7 +846,7 @@ module Fast
831
846
  def initialize(pattern, ast, *args)
832
847
  @ast = ast
833
848
  @expression = if pattern.is_a?(String)
834
- Fast.expression(pattern)
849
+ Array(Fast.expression(pattern))
835
850
  else
836
851
  [*pattern].map(&Find.method(:new))
837
852
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffast
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jônatas Davi Paganini
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-31 00:00:00.000000000 Z
11
+ date: 2026-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: coderay
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: racc
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: rake
141
155
  requirement: !ruby/object:Gem::Requirement
@@ -240,33 +254,19 @@ email:
240
254
  executables:
241
255
  - fast
242
256
  - fast-experiment
257
+ - fast-mcp
243
258
  extensions: []
244
259
  extra_rdoc_files: []
245
260
  files:
246
261
  - ".agents/fast-pattern-expert/SKILL.md"
247
- - ".github/workflows/release.yml"
248
- - ".github/workflows/ruby.yml"
249
- - ".gitignore"
250
- - ".projections.json"
251
- - ".rspec"
252
- - ".rubocop.yml"
253
- - ".sourcelevel.yml"
254
- - ".travis.yml"
255
- - CODE_OF_CONDUCT.md
256
262
  - Fastfile
257
- - Gemfile
258
- - Guardfile
259
263
  - LICENSE.txt
260
264
  - README.md
261
- - Rakefile
262
- - TODO.md
263
265
  - bin/console
264
266
  - bin/fast
265
267
  - bin/fast-experiment
266
268
  - bin/fast-mcp
267
269
  - bin/setup
268
- - fast.gemspec
269
- - ideia_blog_post.md
270
270
  - lib/fast.rb
271
271
  - lib/fast/cli.rb
272
272
  - lib/fast/experiment.rb
@@ -283,8 +283,6 @@ files:
283
283
  - lib/fast/sql/rewriter.rb
284
284
  - lib/fast/summary.rb
285
285
  - lib/fast/version.rb
286
- - mkdocs.yml
287
- - requirements-docs.txt
288
286
  homepage: https://jonatas.github.io/fast/
289
287
  licenses:
290
288
  - MIT
@@ -308,7 +306,6 @@ post_install_message: |2+
308
306
  rdoc_options: []
309
307
  require_paths:
310
308
  - lib
311
- - experiments
312
309
  required_ruby_version: !ruby/object:Gem::Requirement
313
310
  requirements:
314
311
  - - ">="
@@ -1,27 +0,0 @@
1
- name: Release Gem
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v*' # Triggers when a new tag like v0.2.0 is pushed
7
-
8
- jobs:
9
- release:
10
- name: Build and Release
11
- runs-on: ubuntu-22.04
12
- permissions:
13
- contents: read # Required to checkout the code
14
- id-token: write # Required for RubyGems Trusted Publishing
15
-
16
- steps:
17
- - name: Checkout code
18
- uses: actions/checkout@v4
19
-
20
- - name: Set up Ruby
21
- uses: ruby/setup-ruby@v1
22
- with:
23
- ruby-version: '3.3'
24
- bundler-cache: true
25
-
26
- - name: Publish to RubyGems
27
- uses: rubygems/release-gem@v1
@@ -1,34 +0,0 @@
1
- # This workflow uses actions that are not certified by GitHub.
2
- # They are provided by a third-party and are governed by
3
- # separate terms of service, privacy policy, and support
4
- # documentation.
5
- # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
- # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
-
8
- name: Ruby
9
-
10
- on:
11
- push:
12
- branches: [ "master" ]
13
- pull_request:
14
- branches: [ "master" ]
15
-
16
- permissions:
17
- contents: read
18
-
19
- jobs:
20
- test:
21
- runs-on: ubuntu-22.04
22
- strategy:
23
- matrix:
24
- ruby-version: ['3.0', '3.1', '3.2', '3.3']
25
-
26
- steps:
27
- - uses: actions/checkout@v4
28
- - name: Set up Ruby
29
- uses: ruby/setup-ruby@v1
30
- with:
31
- ruby-version: ${{ matrix.ruby-version }}
32
- bundler-cache: true
33
- - name: Run tests
34
- run: bundle exec rake
data/.gitignore DELETED
@@ -1,14 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /site/
9
- /spec/reports/
10
- /tmp/
11
- .venv/
12
-
13
- # rspec failure tracking
14
- .rspec_status
data/.projections.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "README.md": { "type": "readme" },
3
- "lib/*.rb": { "type": "lib", "alternate": "spec/{}_spec.rb" }
4
- }
data/.rspec DELETED
@@ -1,2 +0,0 @@
1
- --format documentation
2
- --color
data/.rubocop.yml DELETED
@@ -1,160 +0,0 @@
1
- # This is the configuration used to check the rubocop source code.
2
-
3
- require:
4
- - rubocop-rspec
5
- - rubocop-performance
6
-
7
- AllCops:
8
- Exclude:
9
- - 'tmp/**/*'
10
- - 'examples/*'
11
- TargetRubyVersion: 2.6
12
-
13
- Layout/LineLength:
14
- Enabled: false
15
-
16
- Layout/EmptyLinesAroundAttributeAccessor:
17
- Enabled: false
18
-
19
- Layout/SpaceAroundMethodCallOperator:
20
- Enabled: false
21
-
22
- Lint/BinaryOperatorWithIdenticalOperands:
23
- Enabled: true
24
-
25
- Lint/DuplicateElsifCondition:
26
- Enabled: false
27
-
28
- Lint/DuplicateRescueException:
29
- Enabled: false
30
-
31
- Lint/EmptyConditionalBody:
32
- Enabled: false
33
-
34
- Lint/FloatComparison:
35
- Enabled: false
36
-
37
- Lint/MissingSuper:
38
- Enabled: false
39
-
40
- Lint/OutOfRangeRegexpRef:
41
- Enabled: false
42
-
43
- Lint/SelfAssignment:
44
- Enabled: false
45
-
46
- Lint/TopLevelReturnWithArgument:
47
- Enabled: false
48
-
49
- Lint/UnreachableLoop:
50
- Enabled: false
51
-
52
-
53
- Lint/DeprecatedOpenSSLConstant:
54
- Enabled: false
55
-
56
- Lint/MixedRegexpCaptureTypes:
57
- Enabled: false
58
-
59
- Lint/RaiseException:
60
- Enabled: true
61
-
62
- Lint/StructNewOverride:
63
- Enabled: true
64
-
65
- Style/AccessorGrouping:
66
- Enabled: false
67
-
68
- Style/ArrayCoercion:
69
- Enabled: false
70
-
71
- Style/BisectedAttrAccessor:
72
- Enabled: true
73
-
74
- Style/CaseLikeIf:
75
- Enabled: true
76
-
77
- Style/ExplicitBlockArgument:
78
- Enabled: false
79
-
80
- Style/ExponentialNotation:
81
- Enabled: true
82
-
83
- Style/GlobalStdStream:
84
- Enabled: false
85
-
86
- Style/HashAsLastArrayItem:
87
- Enabled: false
88
-
89
- Style/HashLikeCase:
90
- Enabled: true
91
-
92
- Style/OptionalBooleanParameter:
93
- Enabled: true
94
-
95
- Style/RedundantAssignment:
96
- Enabled: true
97
-
98
- Style/RedundantFetchBlock:
99
- Enabled: true
100
-
101
- Style/RedundantFileExtensionInRequire:
102
- Enabled: true
103
-
104
- Style/SingleArgumentDig:
105
- Enabled: true
106
-
107
- Style/StringConcatenation:
108
- Enabled: true
109
-
110
- Style/RedundantRegexpCharacterClass:
111
- Enabled: false
112
-
113
- Style/RedundantRegexpEscape:
114
- Enabled: false
115
-
116
- Style/SlicingWithRange:
117
- Enabled: true
118
-
119
- Metrics/BlockLength:
120
- Exclude:
121
- - 'spec/**/*'
122
- - 'fast.gemspec'
123
-
124
- Lint/InterpolationCheck:
125
- Exclude:
126
- - 'spec/**/*'
127
-
128
- Metrics/MethodLength:
129
- CountComments: false # count full line comments?
130
- Max: 12
131
-
132
- Metrics/ModuleLength:
133
- Enabled: false
134
-
135
- Layout/MultilineMethodCallIndentation:
136
- EnforcedStyle: 'indented'
137
-
138
- RSpec/NestedGroups:
139
- Max: 4
140
-
141
- RSpec/ExampleLength:
142
- Max: 20
143
-
144
- RSpec/MultipleExpectations:
145
- Enabled: false
146
-
147
- RSpec/DescribedClass:
148
- Enabled: false
149
-
150
- RSpec/ImplicitSubject:
151
- Enabled: false
152
-
153
- Style/HashEachMethods:
154
- Enabled: true
155
-
156
- Style/HashTransformKeys:
157
- Enabled: true
158
-
159
- Style/HashTransformValues:
160
- Enabled: true
data/.sourcelevel.yml DELETED
@@ -1,2 +0,0 @@
1
- pull_requests:
2
- comments: false
data/.travis.yml DELETED
@@ -1,18 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- env:
4
- global:
5
- - CC_TEST_REPORTER_ID=cf3977cb8c335147723d765c91877e0506ba43e56a22a0dc5b83d7fb969cf5e4
6
- rvm:
7
- - 2.6.3
8
- before_install:
9
- gem install bundler -v 2.1.4
10
- before_script:
11
- - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
12
- - chmod +x ./cc-test-reporter
13
- - ./cc-test-reporter before-build
14
- after_script:
15
- - ./cc-test-reporter after-build -t simplecov --exit-code $TRAVIS_TEST_RESULT
16
- script:
17
- - bundle exec rubocop --fail-level warning --display-only-fail-level-offenses
18
- - bundle exec rspec
data/CODE_OF_CONDUCT.md DELETED
@@ -1,74 +0,0 @@
1
- # Contributor Covenant Code of Conduct
2
-
3
- ## Our Pledge
4
-
5
- In the interest of fostering an open and welcoming environment, we as
6
- contributors and maintainers pledge to making participation in our project and
7
- our community a harassment-free experience for everyone, regardless of age, body
8
- size, disability, ethnicity, gender identity and expression, level of experience,
9
- nationality, personal appearance, race, religion, or sexual identity and
10
- orientation.
11
-
12
- ## Our Standards
13
-
14
- Examples of behavior that contributes to creating a positive environment
15
- include:
16
-
17
- * Using welcoming and inclusive language
18
- * Being respectful of differing viewpoints and experiences
19
- * Gracefully accepting constructive criticism
20
- * Focusing on what is best for the community
21
- * Showing empathy towards other community members
22
-
23
- Examples of unacceptable behavior by participants include:
24
-
25
- * The use of sexualized language or imagery and unwelcome sexual attention or
26
- advances
27
- * Trolling, insulting/derogatory comments, and personal or political attacks
28
- * Public or private harassment
29
- * Publishing others' private information, such as a physical or electronic
30
- address, without explicit permission
31
- * Other conduct which could reasonably be considered inappropriate in a
32
- professional setting
33
-
34
- ## Our Responsibilities
35
-
36
- Project maintainers are responsible for clarifying the standards of acceptable
37
- behavior and are expected to take appropriate and fair corrective action in
38
- response to any instances of unacceptable behavior.
39
-
40
- Project maintainers have the right and responsibility to remove, edit, or
41
- reject comments, commits, code, wiki edits, issues, and other contributions
42
- that are not aligned to this Code of Conduct, or to ban temporarily or
43
- permanently any contributor for other behaviors that they deem inappropriate,
44
- threatening, offensive, or harmful.
45
-
46
- ## Scope
47
-
48
- This Code of Conduct applies both within project spaces and in public spaces
49
- when an individual is representing the project or its community. Examples of
50
- representing a project or community include using an official project e-mail
51
- address, posting via an official social media account, or acting as an appointed
52
- representative at an online or offline event. Representation of a project may be
53
- further defined and clarified by project maintainers.
54
-
55
- ## Enforcement
56
-
57
- Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
- reported by contacting the project team at jonatas.paganini@toptal.com. All
59
- complaints will be reviewed and investigated and will result in a response that
60
- is deemed necessary and appropriate to the circumstances. The project team is
61
- obligated to maintain confidentiality with regard to the reporter of an incident.
62
- Further details of specific enforcement policies may be posted separately.
63
-
64
- Project maintainers who do not follow or enforce the Code of Conduct in good
65
- faith may face temporary or permanent repercussions as determined by other
66
- members of the project's leadership.
67
-
68
- ## Attribution
69
-
70
- This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
- available at [http://contributor-covenant.org/version/1/4][version]
72
-
73
- [homepage]: http://contributor-covenant.org
74
- [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile DELETED
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- # Specify your gem's dependencies in fast.gemspec
6
- gemspec
data/Guardfile DELETED
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # A sample Guardfile
4
- # More info at https://github.com/guard/guard#readme
5
-
6
- ## Uncomment and set this to only include directories you want to watch
7
- # directories %w(app lib config test spec features) \
8
- # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
9
-
10
- ## Note: if you are using the `directories` clause above and you are not
11
- ## watching the project directory ('.'), then you will want to move
12
- ## the Guardfile to a watched dir and symlink it back, e.g.
13
- #
14
- # $ mkdir config
15
- # $ mv Guardfile config/
16
- # $ ln -s config/Guardfile .
17
- #
18
- # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
19
-
20
- guard 'livereload' do
21
- watch(%r{lib/.+\.rb$})
22
- end
23
-
24
- guard :rspec, cmd: 'bundle exec rspec' do
25
- require 'guard/rspec/dsl'
26
- dsl = Guard::RSpec::Dsl.new(self)
27
-
28
- # Feel free to open issues for suggestions and improvements
29
-
30
- # RSpec files
31
- rspec = dsl.rspec
32
- watch(rspec.spec_helper) { rspec.spec_dir }
33
- watch(rspec.spec_support) { rspec.spec_dir }
34
- watch(rspec.spec_files)
35
-
36
- # Ruby files
37
- ruby = dsl.ruby
38
- dsl.watch_spec_files_for(ruby.lib_files)
39
- end
data/Rakefile DELETED
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/gem_tasks'
4
- require 'rspec/core/rake_task'
5
-
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
- task default: :spec
data/TODO.md DELETED
@@ -1,3 +0,0 @@
1
- - [ ] Add matcher diagnostics. Allow check details of each matcher in the three
2
- - [ ] Split stuff into files and add tests for each class
3
- - [ ] Validate expressions and raise errors for invalid expressions
data/fast.gemspec DELETED
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- lib = File.expand_path('lib', __dir__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
- require 'fast/version'
6
-
7
- Gem::Specification.new do |spec|
8
- spec.name = 'ffast'
9
- spec.version = Fast::VERSION
10
- spec.required_ruby_version = '>= 2.6'
11
- spec.authors = ['Jônatas Davi Paganini']
12
- spec.email = ['jonatasdp@gmail.com']
13
- spec.homepage = 'https://jonatas.github.io/fast/'
14
-
15
- spec.summary = 'FAST: Find by AST.'
16
- spec.description = 'Allow you to search for code using node pattern syntax.'
17
- spec.license = 'MIT'
18
-
19
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
20
- f.match(%r{^(test|spec|experiments|examples|features|docs|assets|stylesheets|site)/})
21
- end
22
-
23
- spec.post_install_message = <<~THANKS
24
-
25
- ==========================================================
26
- Yay! Thanks for installing
27
-
28
- ___ __ ___
29
- |__ /\ /__` |
30
- | /~~\ .__/ |
31
-
32
- To interactive learn about the gem in the terminal use:
33
-
34
- fast .intro
35
-
36
- More docs at: https://jonatas.github.io/fast/
37
- ==========================================================
38
-
39
- THANKS
40
-
41
- spec.bindir = 'bin'
42
- spec.executables = %w[fast fast-experiment]
43
- spec.require_paths = %w[lib experiments]
44
-
45
- spec.add_dependency 'coderay'
46
- spec.add_dependency 'parallel'
47
- spec.add_dependency 'pg_query'
48
-
49
- spec.add_development_dependency 'bundler'
50
- spec.add_development_dependency 'git'
51
- spec.add_development_dependency 'guard'
52
- spec.add_development_dependency 'guard-livereload'
53
- spec.add_development_dependency 'guard-rspec'
54
- spec.add_development_dependency 'pry'
55
- spec.add_development_dependency 'rake'
56
- spec.add_development_dependency 'rspec'
57
- spec.add_development_dependency 'rspec-its'
58
- spec.add_development_dependency 'rubocop'
59
- spec.add_development_dependency 'rubocop-performance'
60
- spec.add_development_dependency 'rubocop-rspec'
61
- spec.add_development_dependency 'simplecov'
62
- end
data/ideia_blog_post.md DELETED
@@ -1,36 +0,0 @@
1
- # Empowering LLM Agents with Natural Language to Fast Patterns
2
-
3
- Today I'm excited to announce a significant set of improvements to `fast`, specifically designed to bridge the gap between human reasoning (and LLM agents) and the technical precision of Ruby AST searching.
4
-
5
- ## The Problem: AST Patterns are Hard
6
-
7
- Searching through code using AST patterns is incredibly powerful, but constructing the right S-expression can be daunting. Whether you're a human or an AI agent, getting the syntax just right—especially for complex queries—often involves a lot of trial and error.
8
-
9
- ## The Solution: NL-to-Fast Translation
10
-
11
- We've introduced three new tools to solve this:
12
-
13
- 1. **The `fast-pattern-expert` Skill**: A specialized Gemini CLI skill that provides deep guidance, syntax references, and few-shot examples for translating structural descriptions of Ruby code into valid `Fast` patterns.
14
- 2. **`validate_fast_pattern` MCP Tool**: A new tool for our Model Context Protocol (MCP) server that allows agents to verify their generated patterns before executing them.
15
- 3. **`--validate-pattern` CLI Flag**: A quick way for humans to check if their pattern syntax is correct, checking for balanced nesting and valid token types.
16
-
17
- ### Example in Action:
18
- **Query:** "Find all classes that inherit from `BaseService` and have a `call` method."
19
- **Pattern:** `[(class _ (const nil BaseService)) (def call)]`
20
-
21
- ## Robustness and Precision
22
-
23
- We've also beefed up the core of `fast`:
24
- - **Stricter Validation**: The `ExpressionParser` now tracks nesting levels and unconsumed tokens, providing helpful error messages instead of silently failing or returning partial matches.
25
- - **Improved `...` Matcher**: The `...` literal now acts as a "rest-of-children" matcher when used at the end of a pattern, making it much easier to match methods with any number of arguments.
26
- - **Graceful Degradation**: Multi-file scans (`.scan`) now degrade gracefully if a single file fails to parse, ensuring your reconnaissance isn't aborted by one unsupported syntax node.
27
-
28
- ## A Safer Release Workflow
29
-
30
- On the operational side, I'm moving our gem release process to **GitHub Actions**. By releasing from a clean CI sandbox instead of my local machine, we ensure a much higher level of security and reproducibility. This move minimizes the risk of local environment contamination and provides a transparent, auditable path from source to RubyGems.
31
-
32
- ## Release 0.2.4
33
-
34
- To celebrate these features, we are releasing version `0.2.4` today!
35
-
36
- Happy searching!
data/mkdocs.yml DELETED
@@ -1,51 +0,0 @@
1
- site_name: Fast
2
- repo_url: https://github.com/jonatas/fast
3
- edit_uri: edit/master/docs/
4
-
5
- extra:
6
- analytics:
7
- provider: google
8
- property: G-YKZDZDNRG2
9
-
10
- theme:
11
- name: material
12
- palette:
13
- primary: indigo
14
- accent: pink
15
- logo: assets/logo.png
16
- favicon: assets/favicon.png
17
- extra_css:
18
- - stylesheets/custom.css
19
-
20
- plugins:
21
- - search
22
-
23
- markdown_extensions:
24
- - admonition
25
- - pymdownx.details
26
- - pymdownx.superfences
27
- - pymdownx.tabbed:
28
- alternate_style: true
29
- - toc:
30
- permalink: true
31
- nav:
32
- - Introduction: index.md
33
- - Walkthrough: walkthrough.md
34
- - Syntax: syntax.md
35
- - Command Line: command_line.md
36
- - Experiments: experiments.md
37
- - Shortcuts: shortcuts.md
38
- - Git Integration: git.md
39
- - Fast for LLMs and Agents: agents.md
40
- - MCP Server Tutorial: mcp_tutorial.md
41
- - Code Similarity: similarity_tutorial.md
42
- - LLM/Agent Feature TODOs: llm_features.md
43
- - Pry Integration: pry-integration.md
44
- - Editors' Integration: editors-integration.md
45
- - Research: research.md
46
- - Ideas: ideas.md
47
- - Videos: videos.md
48
- - SQL:
49
- - Intro: sql/index.md
50
- - Shortcuts: sql/shortcuts.md
51
- - About: sql-support.md
@@ -1,3 +0,0 @@
1
- mkdocs>=1.6,<2.0
2
- mkdocs-material>=9.5,<10.0
3
- pymdown-extensions>=10.0,<11.0