Class: Fast::Experiment

Inherits:
Object
  • Object
show all
Defined in:
lib/fast/experiment.rb

Overview

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:

Examples:

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

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 Also:

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, &block) ⇒ Experiment

Returns a new instance of Experiment.



95
96
97
98
99
# File 'lib/fast/experiment.rb', line 95

def initialize(name, &block)
  @name = name
  puts "\nStarting experiment: #{name}"
  instance_exec(&block)
end

Instance Attribute Details

#expressionObject (readonly)

Returns the value of attribute expression.



93
94
95
# File 'lib/fast/experiment.rb', line 93

def expression
  @expression
end

#filesArray<String>

Returns with files from #lookup expression.

Returns:

  • (Array<String>)

    with files from #lookup expression.



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

def files
  @files ||= Fast.ruby_files_from(@files_or_folders)
end

#files_or_foldersObject (readonly)

Returns the value of attribute files_or_folders.



93
94
95
# File 'lib/fast/experiment.rb', line 93

def files_or_folders
  @files_or_folders
end

#nameObject (readonly)

Returns the value of attribute name.



93
94
95
# File 'lib/fast/experiment.rb', line 93

def name
  @name
end

#ok_ifObject (readonly)

Returns the value of attribute ok_if.



93
94
95
# File 'lib/fast/experiment.rb', line 93

def ok_if
  @ok_if
end

#replacementObject (readonly)

Returns the value of attribute replacement.



93
94
95
# File 'lib/fast/experiment.rb', line 93

def replacement
  @replacement
end

Instance Method Details

#edit(&block) ⇒ Object

instance context of a [Fast::Rewriter]

Parameters:

  • block

    yields the node that matches and return the block in the



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

def edit(&block)
  @replacement = block
end

#lookup(files_or_folders) ⇒ Object

Parameters:

  • files_or_folders (String)

    that will be combined to find the #files



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

def lookup(files_or_folders)
  @files_or_folders = files_or_folders
end

#policy(&block) ⇒ Object

It calls the block after the replacement and use the result to drive the Fast::ExperimentFile#ok_experiments and Fast::ExperimentFile#fail_experiments.

Parameters:

  • block

    yields a temporary file with the content replaced in the current round.



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

def policy(&block)
  @ok_if = block
end

#runvoid

This method returns an undefined value.

Iterates over all #files to #run_with them.



137
138
139
# File 'lib/fast/experiment.rb', line 137

def run
  files.map(&method(:run_with))
end

#run_with(file) ⇒ Object

It combines current experiment with Fast::ExperimentFile#run

Parameters:

  • file (String)

    to be analyzed by the experiment



103
104
105
# File 'lib/fast/experiment.rb', line 103

def run_with(file)
  ExperimentFile.new(file, self).run
end

#search(expression) ⇒ Object

Parameters:

  • expression (String)

    with the node pattern to target nodes



108
109
110
# File 'lib/fast/experiment.rb', line 108

def search(expression)
  @expression = expression
end