Skip to content

Instantly share code, notes, and snippets.

@jonatas
Last active July 27, 2020 02:28
Show Gist options
  • Save jonatas/a88489805cf053cd5bf9163b3ba9005c to your computer and use it in GitHub Desktop.
Save jonatas/a88489805cf053cd5bf9163b3ba9005c to your computer and use it in GitHub Desktop.
Fastfile compatible with RuboCop Node Pattern
# List all shortcut with comments
Fast.shortcut :shortcuts do
fast_files.each do |file|
lines = File.readlines(file).map{|line|line.chomp.gsub(/\s*#/,'').strip}
result = capture_file('(send _ :shortcut $(sym _) ...)', file)
result = [result] unless result.is_a?Array
result.each do |capture|
target = capture.loc.expression
puts "fast .#{target.source[1..-1].ljust(30)} # #{lines[target.line-2]}"
end
end
end
# Experimental switch from `create` to `build_stubbed`
Fast.shortcut(:exp_build_stubbed) do
require 'fast/experiment'
Fast.experiment('FactoryBot/UseBuildStubbed') do
lookup ARGV.last
search '(send nil? :create ...)'
edit { |node| replace(node.loc.selector, 'build_stubbed') }
policy { |new_file| system("bin/spring rspec --fail-fast #{new_file}") }
end.run
end
# Experimental replace `let(_)` with `let_it_be` case it calls `create` inside the block
Fast.shortcut(:exp_let_it_be) do
require 'fast/experiment'
Fast.experiment('FactoryBot/LetItBe') do
lookup ARGV.last
search '(block (send nil? :let (sym _)) (args ...) (send nil? :create ...))'
edit { |node| replace(node.children.first.loc.selector, 'let_it_be') }
policy { |new_file| system("bin/spring rspec --fail-fast #{new_file}") }
end.run
end
# Experimental remove `let` that are not referenced in the spec
Fast.shortcut(:exp_remove_let) do
require 'fast/experiment'
Kernel.class_eval do
file = ARGV.last
defined_lets = Fast.capture_file('(block (send nil? :let (sym $_) ...))', file).uniq
@unreferenced= defined_lets.select do |identifier|
Fast.search_file("(send nil? #{identifier})", file).empty?
end
def unreferenced_let?(identifier)
@unreferenced.include? identifier
end
end
Fast.experiment('RSpec/RemoveUnreferencedLet') do
lookup ARGV.last
search '(block (send nil? let (sym #unreferenced_let?)))'
edit { |node| remove(node.loc.expression) }
policy { |new_file| system("bin/spring rspec --fail-fast #{new_file}") }
end.run
end
# Experimental remove `before` or `after` blocks.
Fast.shortcut(:exp_remove_before_after) do
require 'fast/experiment'
Fast.experiment('RSpec/RemoveBeforeAfter') do
lookup ARGV.last
search '(block (send nil? {before after}))'
edit { |node| remove(node.loc.expression) }
policy { |new_file| system("bin/spring rspec --fail-fast #{new_file}") }
end.run
end
# Show validations from models
Fast.shortcut(:validations, "(send nil? {:validate :validates} ... )", "app/models", '--headless')
# Show unused shared contexts
Fast.shortcut(:unused_shared_contexts) do
specs = ruby_files_from('spec')
support_files = specs.grep %r{spec/support/}
flat_capture = -> (search, files) { capture_all(search, files)&.values&.flatten&.uniq }
defined_contexts = flat_capture.('(block (send ... :shared_context (str $_) ...))', support_files)
used = flat_capture.("(send nil? {:it_behaves_like :include_context} (str $_) ...)", specs)
unused = defined_contexts - used
if unused.any?
puts "Unused shared contexts", unused
else
puts "Good job! all the #{defined_contexts.size} contexts are used!"
end
end
#Fast.shortcut(:<, "(class ... (const nil? #{ARGV[ARGV.index('.<')+1]})", 'lib')
Fast.shortcut :flaky_allow do
pattern_with_captures = <<~FAST
(send (send nil? :allow (const nil? $_)) to
(send (send nil? :receive (sym $_)) !:with))
FAST
pattern = expression(pattern_with_captures.tr('$',''))
search_all(pattern, ruby_files_from('spec')).each do |file, results|
results.each do |n|
clazz, method = capture(n, pattern_with_captures)
if klazz = Object.const_get(clazz.to_s) rescue next
if klazz.respond_to?(method)
params = klazz.method(method).parameters
if params.any?{|e|e.first == :req}
code = n.loc.expression
range = [code.first_line, code.last_line].uniq.join(",")
boom_message = "💥 #{clazz}.#{method} does not include the parameters!"
puts boom_message, "#{file}:#{range}", code.source
end
end
end
end
end
end
# RSpec context iterating with multiple roles roles
Fast.shortcut(:context_roles, '(block (send nil? :context ?(str _) (hash (pair (sym roles) (array ...) ))))', 'spec')
# Show RSpec message chains
Fast.shortcut(:message_chains, '^^(send nil? :receive_message_chain)', 'spec')
# Show RSpec nested assertions with .and
Fast.shortcut(:nested_assertions, '^^(send ... and)', 'spec')
# Show calls to RSpec shared examples
Fast.shortcut(:shared_examples, <<~FAST, 'spec')
{
(block ( send ... :shared_examples ...))
(send nil? :it_behaves_like ...)
}
FAST
# Show calls to RSpec its examples
Fast.shortcut(:its, '(block ( send nil? :its ...))', 'spec')
# Show calls to RSpec stubbed classes
Fast.shortcut(:stub_class, '(block ( send nil? :stub_class ...))', 'spec')
# Show `allow / message chain that uses ` .and_return
Fast.shortcut(:and_return, '^(send _ :and_return ...)', 'spec')
# Extract all identifiers from a ruby file
Fast.shortcut(:identifiers) do
Kernel.class_eval do
def capture_source identifier
case identifier
when Symbol, String
identifier
when Parser::AST::Node
identifier.loc.expression.source
end
end
end
@pattern = <<~FAST
{
({class def sym str ivasgn lvasgn} $#capture_source ...)
({const send casgn} {nil _} $#capture_source ...)
}
FAST
def recursive_capture ast
results = [*capture(@pattern, ast)]
results.each do |node|
case node
when Symbol, String
@captures << node
when Parser::AST::Node
@captures << node.loc.expression.source
node.children.grep(Parser::AST::Node).each(&method(:recursive_capture))
end
end
if ast.respond_to?(:children)
(ast.children.grep(Parser::AST::Node) - results).each(&method(:recursive_capture))
end
end
require 'set'
ARGV[1..-1].select do |folder|
ruby_files_from(folder).each do |file|
@captures = Set.new
recursive_capture ast_from_file(file)
results =
@captures.map do |identifier|
identifier.to_s.strip.tr('._/-:', ' ').gsub(/(.+?)([A-Z])/, '\1 \2').downcase
end#.reject do |word|
#word =~ /\w[\?=]$/ || !word.index(' ')
#end
puts results
end
end
end
# Search all references about some word
Fast.shortcut(:ref) do
Kernel.class_eval do
def matches_args? identifier
search = ARGV.last
regex = Regexp.new(search, Regexp::IGNORECASE)
case identifier
when Symbol, String
regex.match?(identifier) || identifier.to_s.include?(search)
when Parser::AST::Node
regex.match?(identifier.to_sexp)
end
end
end
pattern = <<~FAST
{
({class def sym str} #matches_args? ...)
({const send} {nil? _} #matches_args? ...)
}
FAST
Fast::Cli.run!([pattern, '.', '--parallel'])
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment