class Fast::Experiment

Fast experiment allow the user to combine single replacements and make multiple changes at the same time. Defining a policy is possible to check if the experiment was successfull and keep changing the file using a specific search.

The experiment have a combination algorithm that recursively check what combinations work with what combinations. It can delay years and because of that it tries a first replacement targeting all the cases in a single file.

You can define experiments and build experimental files to improve some code in an automated way. Let's create a hook to check if a `before` or `after` block is useless in a specific spec:

@example Remove useless before or after block RSpec hooks

#  Let's say you want to experimentally remove some before or after block
#  in specs to check if some of them are weak or useless:
#    RSpec.describe "something" do
#      before { @a = 1 }
#      before { @b = 1 }
#      it { expect(@b).to be_eq(1) }
#    end
#
#  The variable `@a` is not useful for the test, if I remove the block it
#  should continue passing.
#
#    RSpec.describe "something" do
#      before { @b = 1 }
#      it { expect(@b).to be_eq(1) }
#    end
#
#  But removing the next `before` block will fail:
#    RSpec.describe "something" do
#      before { @a = 1 }
#      it { expect(@b).to be_eq(1) }
#    end
#  And the experiments will have a policy to check if `rspec` run without
#  fail and only execute successfull replacements.
Fast.experiment("RSpec/RemoveUselessBeforeAfterHook") do
  lookup 'spec' # all files in the spec folder
  search "(block (send nil {before after}))"
  edit {|node| remove(node.loc.expression) }
  policy {|new_file| system("rspec --fail-fast #{new_file}") }
end

@example Replace FactoryBot create with build_stubbed method

# Let's say you want to try to automate some replacement of
# `FactoryBot.create` to use `FactoryBot.build_stubbed`.
# For specs let's consider the example we want to refactor:
#   let(:person) { create(:person, :with_email) }
# And the intent is replace to use `build_stubbed` instead of `create`:
#   let(:person) { build_stubbed(:person, :with_email) }
Fast.experiment('RSpec/ReplaceCreateWithBuildStubbed') do
  lookup 'spec'
  search '(block (send nil let (sym _)) (args) $(send nil create))'
  edit { |_, (create)| replace(create.loc.selector, 'build_stubbed') }
  policy { |new_file| system("rspec --format progress --fail-fast #{new_file}") }
end

@see asciinema.org/a/177283

Attributes

expression[R]
files[W]
files_or_folders[R]
name[R]
ok_if[R]
replacement[R]

Public Class Methods

new(name, &block) click to toggle source
# File lib/fast/experiment.rb, line 95
def initialize(name, &block)
  @name = name
  puts "\nStarting experiment: #{name}"
  instance_exec(&block)
end

Public Instance Methods

edit(&block) click to toggle source

@param block yields the node that matches and return the block in the instance context of a [Fast::Rewriter]

# File lib/fast/experiment.rb, line 114
def edit(&block)
  @replacement = block
end
files() click to toggle source

@return [Array<String>] with files from {#lookup} expression.

# File lib/fast/experiment.rb, line 131
def files
  @files ||= Fast.ruby_files_from(@files_or_folders)
end
lookup(files_or_folders) click to toggle source

@param [String] files_or_folders that will be combined to find the {#files}

# File lib/fast/experiment.rb, line 119
def lookup(files_or_folders)
  @files_or_folders = files_or_folders
end
policy(&block) click to toggle source

It calls the block after the replacement and use the result to drive the {Fast::ExperimentFile#ok_experiments} and {Fast::ExperimentFile#fail_experiments}. @param block yields a temporary file with the content replaced in the current round.

# File lib/fast/experiment.rb, line 126
def policy(&block)
  @ok_if = block
end
run() click to toggle source

Iterates over all {#files} to {#run_with} them. @return [void]

# File lib/fast/experiment.rb, line 137
def run
  files.map(&method(:run_with))
end
run_with(file) click to toggle source

It combines current experiment with {ExperimentFile#run} @param [String] file to be analyzed by the experiment

# File lib/fast/experiment.rb, line 103
def run_with(file)
  ExperimentFile.new(file, self).run
end