Module: Fast

Defined in:
lib/fast.rb,
lib/fast/cli.rb,
lib/fast/git.rb,
lib/fast/sql.rb,
lib/fast/node.rb,
lib/fast/scan.rb,
lib/fast/source.rb,
lib/fast/summary.rb,
lib/fast/version.rb,
lib/fast/rewriter.rb,
lib/fast/shortcut.rb,
lib/fast/experiment.rb,
lib/fast/mcp_server.rb,
lib/fast/sql/rewriter.rb,
lib/fast/prism_adapter.rb,
lib/fast/source_rewriter.rb

Overview

Allow to replace code managing multiple replacements and combining replacements. Useful for large codebase refactor and multiple replacements in the same file.

Defined Under Namespace

Modules: PrismAdapter, SQL, Source, SymbolExtension Classes: All, Any, Capture, Cli, Experiment, ExperimentCombinations, ExperimentFile, ExpressionParser, Find, FindFromArgument, FindString, FindWithCapture, InstanceMethodCall, Matcher, Maybe, McpServer, MethodCall, Node, Not, Parent, Rewriter, Scan, Shortcut, SourceRewriter, Summary, SyntaxError

Constant Summary collapse

NODE_PARENTS =
ObjectSpace::WeakMap.new
LITERAL =

Literals are shortcuts allowed inside ExpressionParser

{
  '...' => ->(node) { node&.children&.any? },
  '_' => ->(node) { !node.nil? },
  'nil' => nil
}.freeze
TOKENIZER =

Allowed tokens in the node pattern domain

%r/
  [\+\-\/\*\\!]         # operators or negation
  |
  ===?                  # == or ===
  |
  \d+\.\d*              # decimals and floats
  |
  "[^"]+"               # strings
  |
  _                     # something not nil: match
  |
  \.{3}                 # a node with children: ...
  |
  \[|\]                 # square brackets `[` and `]` for all
  |
  \^                    # node has children with
  |
  \?                    # maybe expression
  |
  [\d\w_]+[=\\!\?]?     # method names or numbers
  |
  :[^(){}\[\]\s;]+      # symbols
  |
  \/[^\/ \n]+\/         # regex literals
  |
  ;                     # semicolons
  |
  \(|\)                 # parens `(` and `)` for tuples
  |
  \{|\}                 # curly brackets `{` and `}` for any
  |
  \$                    # capture
  |
  \#\w[\d\w_]+[\\!\?]?  # custom method call
  |
  \.\w[\d\w_]+\?       # instance method call
  |
  \\\d                  # find using captured expression
  |
  %\d                   # bind extra arguments to the expression
/x.freeze
VERSION =
'0.2.7'
LOOKUP_FAST_FILES_DIRECTORIES =

Where to search for Fastfile archives?

  1. Current directory that the command is being runned
  2. Home folder
  3. Using the FAST_FILE_DIR variable to set an extra folder
[
  Dir.pwd,
  ENV['HOME'],
  ENV['FAST_FILE_DIR'],
  File.join(File.dirname(__FILE__), '..', '..')
].reverse.compact.map(&File.method(:expand_path)).uniq.freeze

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.debuggingObject

Returns the value of attribute debugging.



307
308
309
# File 'lib/fast.rb', line 307

def debugging
  @debugging
end

.experimentsObject (readonly)

Returns the value of attribute experiments.



30
31
32
# File 'lib/fast/experiment.rb', line 30

def experiments
  @experiments
end

Class Method Details

.ast(content, buffer_name: '(string)') ⇒ Fast::Node

Returns from the parsed content.

Examples:

Fast.ast("1") # => s(:int, 1)
Fast.ast("a.b") # => s(:send, s(:send, nil, :a), :b)

Returns:



143
144
145
# File 'lib/fast.rb', line 143

def ast(content, buffer_name: '(string)')
  parse_ruby(content, buffer_name: buffer_name)
end

.ast_from_file(file) ⇒ Fast::Node

caches the content based on the filename. Also, it can parse SQL files.

Examples:

Fast.ast_from_file("example.rb") # => s(...)

Returns:



161
162
163
# File 'lib/fast.rb', line 161

def ast_from_file(file)
  parse_file(file)
end

.ast_node?(node) ⇒ Boolean

Returns:

  • (Boolean)


66
67
68
# File 'lib/fast.rb', line 66

def ast_node?(node)
  node.respond_to?(:type) && node.respond_to?(:children)
end

.build_grouped_search(method_name, pattern, on_result) ⇒ Proc

Returns binding pattern argument from a given method_name.

Parameters:

  • method_name (Symbol)

    with :capture_file or :search_file

  • pattern (String)

    to match in a search to any file

  • on_result (Proc)

    is a callback that can be notified soon it matches

Returns:

  • (Proc)

    binding pattern argument from a given method_name.



209
210
211
212
213
214
215
216
217
218
# File 'lib/fast.rb', line 209

def build_grouped_search(method_name, pattern, on_result)
  search_pattern = method(method_name).curry.call(pattern)
  proc do |file|
    results = search_pattern.call(file)
    next if results.nil? || results.empty?

    on_result&.(file, results)
    { file => results }
  end
end

.builder_for(buffer_name) ⇒ Object

Raises:

  • (NoMethodError)


152
153
154
# File 'lib/fast.rb', line 152

def builder_for(buffer_name)
  raise NoMethodError, "Fast.builder_for(#{buffer_name.inspect}) was removed; Fast now parses Ruby with Prism"
end

.capture(pattern, node) ⇒ Array<Object>

Only captures from a search

Returns:

  • (Array<Object>)

    with all captured elements.



287
288
289
290
291
292
293
294
295
296
297
# File 'lib/fast.rb', line 287

def capture(pattern, node)
  return [] if node.nil?

  if (match = match?(pattern, node))
    match == true ? node : match
  else
    node.each_child_node
      .flat_map { |child| capture(pattern, child) }
      .compact.flatten
  end
end

.capture_all(pattern, locations = ['.'], parallel: true, on_result: nil) ⇒ Hash<String,Object>

Capture with pattern on a directory or multiple files

Parameters:

  • pattern (String)
  • locations (Array<String>) (defaults to: ['.'])

    where to search. Default is '.'

Returns:

  • (Hash<String,Object>)

    with files and captures



200
201
202
203
# File 'lib/fast.rb', line 200

def capture_all(pattern, locations = ['.'], parallel: true, on_result: nil)
  group_results(build_grouped_search(:capture_file, pattern, on_result),
                locations, parallel: parallel)
end

.capture_file(pattern, file) ⇒ Array<Object>

Capture elements from searches in files. Keep in mind you need to use $ in the pattern to make it work.

Returns:

  • (Array<Object>)

    captured from the pattern matched in the file



252
253
254
255
256
257
258
259
260
261
# File 'lib/fast.rb', line 252

def capture_file(pattern, file)
  node = ast_from_file(file)
  return [] unless node
  case node
  when Array
    node.map { |n| capture(pattern, n) }.flatten.compact
  else
    capture pattern, node
  end
end

.debugObject

Utility function to inspect search details using debug block.

It prints output of all matching cases.

int == (int 1) # => true 1 == 1 # => true

Examples:

Fast.debug do
   Fast.match?([:int, 1], s(:int, 1))
end


319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/fast.rb', line 319

def debug
  return yield if debugging

  self.debugging = true
  result = nil
  Find.class_eval do
    alias_method :original_match_recursive, :match_recursive
    alias_method :match_recursive, :debug_match_recursive
    result = yield
    alias_method :match_recursive, :original_match_recursive # rubocop:disable Lint/DuplicateMethods
  end
  self.debugging = false
  result
end

.experiment(name, &block) ⇒ Object

Fast.experiment is a shortcut to define new experiments and allow them to work together in experiment combinations.

The following experiment look into spec folder and try to remove before and after blocks on testing code. Sometimes they're not effective and we can avoid the hard work of do it manually.

If the spec does not fail, it keeps the change.

Examples:

Remove useless before and after block

Fast.experiment("RSpec/RemoveUselessBeforeAfterHook") do
  lookup 'spec'
  search "(block (send nil {before after}))"
  edit { |node| remove(node.loc.expression) }
  policy { |new_file| system("rspec --fail-fast #{new_file}") }
end


25
26
27
28
# File 'lib/fast/experiment.rb', line 25

def experiment(name, &block)
  @experiments ||= {}
  @experiments[name] = Experiment.new(name, &block)
end

.expression(string) ⇒ Object

Raises:



299
300
301
302
303
304
305
# File 'lib/fast.rb', line 299

def expression(string)
  parser = ExpressionParser.new(string)
  res = parser.parse
  raise SyntaxError, parser.error_message if parser.tokens_left?

  res
end

.expression_from(node) ⇒ String

Extracts a node pattern expression from a given node supressing identifiers and primitive types. Useful to index abstract patterns or similar code structure.

Examples:

Fast.expression_from(Fast.ast('1')) # => '(int _)'
Fast.expression_from(Fast.ast('a = 1')) # => '(lvasgn _ (int _))'
Fast.expression_from(Fast.ast('def name; person.name end')) # => '(def _ (args) (send (send nil _) _))'

Parameters:

Returns:

  • (String)

    with an pattern to search from it.

See Also:



425
426
427
428
429
430
431
432
433
434
435
# File 'lib/fast.rb', line 425

def expression_from(node)
  case node
  when ->(candidate) { ast_node?(candidate) }
    children_expression = node.children.map(&method(:expression_from)).join(' ')
    "(#{node.type}#{" #{children_expression}" if node.children.any?})"
  when nil, 'nil'
    'nil'
  when Symbol, String, Numeric
    '_'
  end
end

.fast_filesArray<String>

Returns with existent Fastfiles from LOOKUP_FAST_FILES_DIRECTORIES.

Returns:



33
34
35
36
37
# File 'lib/fast/shortcut.rb', line 33

def fast_files
  @fast_files ||= LOOKUP_FAST_FILES_DIRECTORIES.compact
    .map { |dir| File.join(dir, 'Fastfile') }
    .select(&File.method(:exist?))
end

.first_position_from_expression(node) ⇒ Object

If a node is the first on it's line, print since the beginning of the line to show the proper whitespaces for identing the next lines of the code.



67
68
69
70
71
72
73
74
# File 'lib/fast/cli.rb', line 67

def first_position_from_expression(node)
  expression = node.loc.expression
  if node.respond_to?(:parent) && node.parent && node.parent.loc.expression.line != expression.line
    expression.begin_pos - expression.column
  else
    expression.begin_pos
  end
end

.fold_ast(node, level: nil, current_level: 1) ⇒ Fast::Node

Folds the AST to a maximum depth, replacing deeper branches with ...

Parameters:

  • node (Fast::Node)
  • level (Integer) (defaults to: nil)

    maximum depth to explore

  • current_level (Integer) (defaults to: 1)

    internal tracker for depth

Returns:



354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
# File 'lib/fast.rb', line 354

def fold_ast(node, level: nil, current_level: 1)
  return node unless ast_node?(node) && node.respond_to?(:updated)
  return node if level.nil?

  if current_level >= level
    if node.children.any?
      node.updated(nil, [:'...'])
    else
      node
    end
  else
    new_children = node.children.map { |c| fold_ast(c, level: level, current_level: current_level + 1) }
    node.updated(nil, new_children)
  end
end

.fold_source(node, level: 1) ⇒ String

Folds ruby source code to a maximum depth, replacing deep block bodies with # ...

Parameters:

  • node (Fast::Node)
  • level (Integer) (defaults to: 1)

    maximum depth to explore

Returns:

  • (String)

    the folded source representation



374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/fast.rb', line 374

def fold_source(node, level: 1)
  return node if node.is_a?(String)
  return node.loc.expression.source rescue node.to_s unless ast_node?(node) && node.respond_to?(:loc)

  source = node.loc.expression.source.dup
  root_begin = node.loc.expression.begin_pos

  regions = []

  walker = ->(n, current_level) do
    return unless ast_node?(n)

    if current_level >= level
      body_node = nil
      case n.type
      when :class, :module, :def, :defs, :block
        body_node = n.children.last
      when :begin
        body_node = n
      end

      if body_node && body_node.loc && body_node.loc.respond_to?(:expression) && body_node.loc.expression
        regions << [
          body_node.loc.expression.begin_pos - root_begin,
          body_node.loc.expression.end_pos - root_begin
        ]
        return
      end
    end

    n.children.each { |c| walker.call(c, current_level + 1) if ast_node?(c) }
  end

  walker.call(node, 1)

  regions.sort_by { |r| -r[0] }.each do |start_pos, end_pos|
    source[start_pos...end_pos] = "# ..."
  end

  source
end

.group_results(group_files, locations, parallel: true) ⇒ Hash[String, Array]

Compact grouped results by file allowing parallel processing. parallel or not. while it process several locations in parallel.

Parameters:

  • group_files (Proc)

    allows to define a search that can be executed

  • on_result (Proc)

    allows to define a callback for fast feedback

  • parallel (Boolean) (defaults to: true)

    runs the group_files in parallel

Returns:

  • (Hash[String, Array])

    with files and results



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/fast.rb', line 227

def group_results(group_files, locations, parallel: true)
  files = ruby_files_from(*locations)
  results =
    if parallel
      require 'parallel' unless defined?(Parallel)
      Parallel.map(files) do |file|
        group_files.call(file)
      rescue StandardError => e
        warn "Error processing #{file}: #{e.message}" if Fast.debugging
        nil
      end
    else
      files.map do |file|
        group_files.call(file)
      rescue StandardError => e
        warn "Error processing #{file}: #{e.message}" if Fast.debugging
        nil
      end
    end
  results.compact.inject(&:merge!) || {}
end

.highlight(node, show_sexp: false, colorize: true, sql: false, level: nil) ⇒ Object

Highligh some source code based on the node. Useful for printing code with syntax highlight.

Parameters:

  • show_sexp (Boolean) (defaults to: false)

    prints node expression instead of code

  • colorize (Boolean) (defaults to: true)

    skips CodeRay processing when false.

  • level (Integer) (defaults to: nil)

    defines the max depth to print the AST.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/fast/cli.rb', line 28

def highlight(node, show_sexp: false, colorize: true, sql: false, level: nil)
  output =
    if node.respond_to?(:loc) && !show_sexp
      if level
        Fast.fold_source(node, level: level)
      else
        wrap_source_range(node).source
      end
    elsif show_sexp && level && Fast.ast_node?(node)
      Fast.fold_ast(node, level: level).to_s
    elsif show_sexp
      node.to_s
    else
      node.to_s
    end
  return output unless colorize

  CodeRay.scan(output, sql ? :sql : :ruby).term
end

.last_position_from_expression(node) ⇒ Object

If a method call contains a heredoc, it should print the STR around it too.



60
61
62
63
# File 'lib/fast/cli.rb', line 60

def last_position_from_expression(node)
  internal_heredoc = node.each_descendant(:str).select { |n| n.loc.respond_to?(:heredoc_end) }
  internal_heredoc.map { |n| n.loc.heredoc_end.end_pos }.max if internal_heredoc.any?
end

.load_fast_files!Object

Loads Fastfiles from fast_files list



40
41
42
43
44
45
46
47
48
# File 'lib/fast/shortcut.rb', line 40

def load_fast_files!
  @loaded_fast_files ||= []
  fast_files.each do |file|
    next if @loaded_fast_files.include?(file)

    load file
    @loaded_fast_files << file
  end
end

.match?(pattern, ast, *args) ⇒ Boolean

Verify if a given AST matches with a specific pattern

Examples:

Fast.match?("int", Fast.ast("1")) # => true

Returns:

  • (Boolean)

    case matches ast with the current expression



169
170
171
# File 'lib/fast.rb', line 169

def match?(pattern, ast, *args)
  Matcher.new(pattern, ast, *args).match?
end

.parse_file(file) ⇒ Object



103
104
105
106
107
108
109
110
111
# File 'lib/fast.rb', line 103

def parse_file(file)
  return parser_ast_from_file(file) if file.end_with?('.sql')

  @cache ||= {}
  @cache[file] ||=
    begin
      parse_ruby(IO.read(file), buffer_name: file)
    end
end

.parse_ruby(content, buffer_name: '(string)') ⇒ Object



80
81
82
# File 'lib/fast.rb', line 80

def parse_ruby(content, buffer_name: '(string)')
  prism_ast(content, buffer_name: buffer_name)
end

.parse_sql(statement, buffer_name: "(sql)") ⇒ Fast::Node

ast = Fast.parse_sql("select 'hello AST'") => s(:select_stmt, s(:target_list, s(:res_target, s(:val, s(:a_const, s(:val, s(:string, s(:str, "hello AST")))))))) s represents a Fast::Node with additional methods to access the tokens and location of the node. ast.search(:string).first.location.expression => #<Fast::Source::Range (sql) 7...18>

Returns:

  • (Fast::Node)

    the AST representation of the sql statement



50
51
52
# File 'lib/fast/sql.rb', line 50

def parse_sql(statement, buffer_name: "(sql)")
  SQL.parse(statement, buffer_name: buffer_name)
end

.parse_sql_file(file) ⇒ Fast::Node

Shortcut to parse a sql file

Examples:

Fast.parse_sql_file('spec/fixtures/sql/select.sql')

Returns:

  • (Fast::Node)

    the AST representation of the sql statements from a file



12
13
14
# File 'lib/fast/sql.rb', line 12

def parse_sql_file(file)
  SQL.parse_file(file)
end

.parser_ast(content, buffer_name: '(string)') ⇒ Object



84
85
86
# File 'lib/fast.rb', line 84

def parser_ast(content, buffer_name: '(string)')
  prism_ast(content, buffer_name: buffer_name)
end

.parser_ast_from_file(file) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/fast.rb', line 88

def parser_ast_from_file(file)
  @parser_cache ||= {}
  @parser_cache[file] ||=
    begin
      method =
        if file.end_with?('.sql')
          require_relative 'fast/sql' unless respond_to?(:parse_sql)
          :parse_sql
        else
          :parser_ast
        end
      Fast.public_send(method, IO.read(file), buffer_name: file)
    end
end

.parser_classObject

Raises:

  • (NoMethodError)


123
124
125
# File 'lib/fast.rb', line 123

def parser_class
  raise NoMethodError, 'Fast.parser_class was removed; Fast now parses Ruby with Prism'
end

.parser_const_nameObject

Raises:

  • (NoMethodError)


131
132
133
# File 'lib/fast.rb', line 131

def parser_const_name
  raise NoMethodError, 'Fast.parser_const_name was removed; Fast now parses Ruby with Prism'
end

.parser_require_pathObject

Raises:

  • (NoMethodError)


127
128
129
# File 'lib/fast.rb', line 127

def parser_require_path
  raise NoMethodError, 'Fast.parser_require_path was removed; Fast now parses Ruby with Prism'
end

.parser_version_supported?(const_name) ⇒ Boolean

Returns:

  • (Boolean)

Raises:

  • (NoMethodError)


135
136
137
# File 'lib/fast.rb', line 135

def parser_version_supported?(const_name)
  raise NoMethodError, "Fast.parser_version_supported?(#{const_name.inspect}) was removed; Fast now parses Ruby with Prism"
end

.prism_ast(content, buffer_name: '(string)') ⇒ Object

Raises:



70
71
72
73
74
75
76
77
78
# File 'lib/fast.rb', line 70

def prism_ast(content, buffer_name: '(string)')
  require_relative 'fast/prism_adapter'
  result = Fast::PrismAdapter.parse(content, buffer_name: buffer_name)
  return result if result

  prism_errors = Prism.parse(content).errors
  message = prism_errors.map(&:message).uniq.join("\n")
  raise SyntaxError, message
end

.render_markdown_for_terminal(line) ⇒ Object



50
51
52
53
54
55
# File 'lib/fast/shortcut.rb', line 50

def render_markdown_for_terminal(line)
  require 'tty-markdown'
  TTY::Markdown.parse(line)
rescue LoadError
  line
end

.replace(pattern, ast, source = nil, &replacement) ⇒ String

Replaces content based on a pattern.

Examples:

Fast.replace?(Fast.ast("a = 1"),"lvasgn") do |node|
  replace(node.location.name, 'variable_renamed')
end # => variable_renamed = 1

Parameters:

  • ast (Fast::Node)

    with the current AST to search.

  • pattern (String)

    with the expression to be targeting nodes.

  • replacement (Proc)

    gives the [Rewriter] context in the block.

Returns:

  • (String)

    with the new source code after apply the replacement

See Also:



20
21
22
23
24
# File 'lib/fast/rewriter.rb', line 20

def replace(pattern, ast, source = nil, &replacement)
  rewritten = rewriter_for(pattern, ast, source, &replacement).rewrite!
  Fast.validate_ruby!(rewritten, buffer_name: ast.buffer_name) if rewritten
  rewritten
end

.replace_file(pattern, file, &replacement) ⇒ Object

Replaces the source of an ast_from_file with and the same source if the pattern does not match.



38
39
40
41
# File 'lib/fast/rewriter.rb', line 38

def replace_file(pattern, file, &replacement)
  ast = ast_from_file(file)
  replace(pattern, ast, IO.read(file), &replacement)
end

.replace_sql(pattern, ast, &replacement) ⇒ Object

Fast.replace_sql('ival', Fast.parse_sql('select 1'), &->(node){ replace(node.location.expression, '2') }) # => "select 2"

Returns:

  • string with the sql content updated in case the pattern matches.

See Also:

  • SQLRewriter


26
27
28
# File 'lib/fast/sql.rb', line 26

def replace_sql(pattern, ast, &replacement)
  SQL.replace(pattern, ast, &replacement)
end

.replace_sql_file(pattern, file, &replacement) ⇒ Object

Returns string with the sql content updated in case the pattern matches.

Returns:

  • string with the sql content updated in case the pattern matches.



31
32
33
# File 'lib/fast/sql.rb', line 31

def replace_sql_file(pattern, file, &replacement)
  SQL.replace_file(pattern, file, &replacement)
end

.report(result, show_link: false, show_permalink: false, show_sexp: false, file: nil, headless: false, bodyless: false, colorize: true, level: nil) ⇒ Object

Combines highlight with files printing file name in the head with the source line.

Examples:

Fast.report(result, file: 'file.rb')

Parameters:

  • result (Fast::Node)
  • show_sexp (Boolean) (defaults to: false)

    Show string expression instead of source

  • file (String) (defaults to: nil)

    Show the file name and result line before content

  • headless (Boolean) (defaults to: false)

    Skip printing the file name and line before content

  • level (Integer) (defaults to: nil)

    Skip exploring deep branches of AST when showing sexp



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/fast/cli.rb', line 85

def report(result, show_link: false, show_permalink: false, show_sexp: false, file: nil, headless: false, bodyless: false, colorize: true, level: nil) # rubocop:disable Metrics/ParameterLists
  if file
    if result.is_a?(Symbol) && !result.respond_to?(:loc)
      result.extend(SymbolExtension)
    end
    line = result.loc.expression.line if Fast.ast_node?(result) && result.respond_to?(:loc)
    if show_link
      puts(result.link)
    elsif show_permalink
      puts(result.permalink)
    elsif !headless
      puts(highlight("# #{file}:#{line}", colorize: colorize))
    end
  end
  puts(highlight(result, show_sexp: show_sexp, colorize: colorize, level: level)) unless bodyless
end

.rewrite_file(pattern, file, &replacement) ⇒ Object

Combines #replace_file output overriding the file if the output is different from the original file content.



45
46
47
48
49
# File 'lib/fast/rewriter.rb', line 45

def rewrite_file(pattern, file, &replacement)
  previous_content = IO.read(file)
  content = replace_file(pattern, file, &replacement)
  File.open(file, 'w+') { |f| f.puts content } if content != previous_content
end

.rewriter_for(pattern, ast, source = nil, &replacement) ⇒ Fast::Rewriter

Returns:



27
28
29
30
31
32
33
34
# File 'lib/fast/rewriter.rb', line 27

def rewriter_for(pattern, ast, source = nil, &replacement)
  rewriter = Rewriter.new
  rewriter.source = source
  rewriter.ast = ast
  rewriter.search = pattern
  rewriter.replacement = replacement
  rewriter
end

.ruby_files_from(*files) ⇒ Array<String>

When the argument is a folder, it recursively fetches all .rb files from it.

Parameters:

  • files

    can be file paths or directories.

Returns:

  • (Array<String>)

    with all ruby files from arguments.



337
338
339
340
341
342
343
344
345
346
347
# File 'lib/fast.rb', line 337

def ruby_files_from(*files)
  dir_filter = File.method(:directory?)
  directories = files.select(&dir_filter)

  if directories.any?
    files -= directories
    files |= directories.flat_map { |dir| Dir["#{dir}/**/*.rb"] }
    files.uniq!
  end
  files.reject(&dir_filter)
end

.scan(locations, command_name: '.scan', level: nil) ⇒ Object



118
119
120
121
# File 'lib/fast.rb', line 118

def scan(locations, command_name: '.scan', level: nil)
  require_relative 'fast/scan'
  Scan.new(locations, command_name: command_name, level: level)
end

.search(pattern, node, *args) { ... } ⇒ Object

Search recursively into a node and its children. If the node matches with the pattern it returns the node, otherwise it recursively collect possible children nodes

Yields:

  • node and capture if block given



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/fast.rb', line 267

def search(pattern, node, *args)
  return [] if node.nil?

  if (match = match?(pattern, node, *args))
    yield node, match if block_given?
    match != true ? [node, match] : [node]
  else
    case node
    when Array
      node.flat_map { |child| search(pattern, child, *args) }
    else
      node.each_child_node
        .flat_map { |child| search(pattern, child, *args) }
        .compact.flatten
    end
  end
end

.search_all(pattern, locations = ['.'], parallel: true, on_result: nil) ⇒ Hash<String,Array<Fast::Node>>

Search with pattern on a directory or multiple files

Parameters:

  • pattern (String)
  • *locations (Array<String>)

    where to search. Default is '.'

Returns:

  • (Hash<String,Array<Fast::Node>>)

    with files and results



191
192
193
194
# File 'lib/fast.rb', line 191

def search_all(pattern, locations = ['.'], parallel: true, on_result: nil)
  group_results(build_grouped_search(:search_file, pattern, on_result),
                locations, parallel: parallel)
end

.search_file(pattern, file) ⇒ Array<Fast::Node>

Search with pattern directly on file

Returns:

  • (Array<Fast::Node>)

    that matches the pattern



175
176
177
178
179
180
181
182
183
184
185
# File 'lib/fast.rb', line 175

def search_file(pattern, file)
  node = ast_from_file(file)
  return [] unless node

  case node
  when Array
    node.map { |n| search(pattern, n) }.flatten.compact
  else
    search pattern, node
  end
end

.shortcut(identifier, *args, &block) ⇒ Object

Store predefined searches with default paths through shortcuts. define your Fastfile in you root folder or

Examples:

Shortcut for finding validations in rails models

Fast.shortcut(:validations, "(send nil {validate validates})", "app/models")


21
22
23
# File 'lib/fast/shortcut.rb', line 21

def shortcut(identifier, *args, &block)
  shortcuts[identifier] = Shortcut.new(*args, &block)
end

.shortcutsHash<String,Shortcut>

Stores shortcuts in a simple hash where the key is the identifier and the value is the object itself.

Returns:

  • (Hash<String,Shortcut>)

    as a dictionary.



28
29
30
# File 'lib/fast/shortcut.rb', line 28

def shortcuts
  @shortcuts ||= {}
end

.sql_rewriter_for(pattern, ast, &replacement) ⇒ Fast::SQLRewriter

Returns which can be used to rewrite the SQL.

Returns:

  • (Fast::SQLRewriter)

    which can be used to rewrite the SQL

See Also:

  • SQLRewriter


18
19
20
# File 'lib/fast/sql.rb', line 18

def sql_rewriter_for(pattern, ast, &replacement)
  SQL.rewriter_for(pattern, ast, &replacement)
end

.summary(code_or_ast, file: nil, command_name: '.summary', level: nil) ⇒ Object



113
114
115
116
# File 'lib/fast.rb', line 113

def summary(code_or_ast, file: nil, command_name: '.summary', level: nil)
  require_relative 'fast/summary'
  Summary.new(code_or_ast, file: file, command_name: command_name, level: level)
end

.validate_ruby!(content, buffer_name: '(string)') ⇒ Object



147
148
149
150
# File 'lib/fast.rb', line 147

def validate_ruby!(content, buffer_name: '(string)')
  prism_ast(content, buffer_name: buffer_name)
  true
end

.wrap_source_range(node) ⇒ Object

Fixes initial spaces to print the line since the beginning and fixes end of the expression including heredoc strings.



50
51
52
53
54
55
56
57
# File 'lib/fast/cli.rb', line 50

def wrap_source_range(node)
  expression = node.loc.expression
  Fast::Source.range(
    expression.source_buffer,
    first_position_from_expression(node),
    last_position_from_expression(node) || expression.end_pos
  )
end