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.



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

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

Instance Attribute Details

#autocleanObject

Returns the value of attribute autoclean.



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

def autoclean
  @autoclean
end

#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.



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

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

#autoclean?Boolean

Returns:

  • (Boolean)


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

def autoclean?
  !!@autoclean
end

#edit(&block) ⇒ Object

instance context of a [Fast::Rewriter]

Parameters:

  • block

    yields the node that matches and return the block in the



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

def edit(&block)
  @replacement = block
end

#lookup(files_or_folders) ⇒ Object

Parameters:

  • files_or_folders (String)

    that will be combined to find the #files



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

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.



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

def policy(&block)
  @ok_if = block
end

#runvoid

This method returns an undefined value.

Iterates over all #files to #run_with them.



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

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



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

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

#search(expression) ⇒ Object

Parameters:

  • expression (String)

    with the node pattern to target nodes



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

def search(expression)
  @expression = expression
end