Last active
July 27, 2020 02:28
-
-
Save jonatas/a88489805cf053cd5bf9163b3ba9005c to your computer and use it in GitHub Desktop.
Fastfile compatible with RuboCop Node Pattern
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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