-
# encoding: utf-8
-
-
1
require 'rubocop'
-
-
1
require 'rubocop/rspec/version'
-
1
require 'rubocop/rspec/inject'
-
-
1
Rubocop::RSpec::Inject.defaults!
-
-
# We are housing ourselves directly into RuboCop's module structure. This is
-
# less than ideal, but until RuboCop directly supports plugins, we can avoid
-
# breaking too many assumptions.
-
1
require 'rubocop/cop/rspec/unit_spec_naming'
-
# encoding: utf-8
-
-
1
module Rubocop
-
1
module Cop
-
1
module RSpec
-
# This cop checks that RSpec unit tests conform to a consistent naming
-
# scheme - both for the describe call, and the file path.
-
#
-
# Disabled by default. Generally, you want to scope it to your project's
-
# unit spec paths:
-
#
-
# UnitSpecNaming:
-
# Enabled: true
-
# Include:
-
# - 'spec/rubocop/*'
-
#
-
1
class UnitSpecNaming < Cop
-
1
DESCRIBE_CLASS_MSG = 'The first argument to describe should be the ' \
-
'class or module being tested.'
-
-
1
METHOD_STRING_MSG = 'The second argument to describe should be the ' \
-
"method being tested. '#instance' or '.class'"
-
-
1
CLASS_SPEC_MSG = 'Class unit spec should have a path ending with %s'
-
-
1
METHOD_SPEC_MSG = 'Unit spec should have a path matching %s'
-
-
1
METHOD_STRING_MATCHER = /^[\#\.].+/
-
-
1
def on_send(node)
-
20
return unless top_level_describe? node
-
17
_receiver, _method_name, *args = *node
-
# Ignore non-string args (RSpec metadata)
-
26
args = [args.first] + args[1..-1].select { |a| a.type == :str }
-
-
17
if cop_config['EnforceDescribeStatement']
-
6
enforce_describe_statement(node, args)
-
end
-
-
17
if offences.size == 0 && cop_config['EnforceFilenames']
-
11
enforce_filename(node, args)
-
end
-
end
-
-
1
private
-
-
1
def enforce_describe_statement(node, args)
-
6
check_described_class(node, args.first)
-
6
check_described_method(node, args[1])
-
end
-
-
1
def enforce_filename(node, args)
-
11
path_parts = const_name(args.first).split('::').map do |part|
-
17
camel_to_underscore(part)
-
end
-
-
11
if !args[1]
-
5
check_class_spec(node, path_parts)
-
else
-
6
method_str = args[1].children.first if args[1]
-
6
path_parts << 'class_methods' if method_str.start_with? '.'
-
6
check_method_spec(node, path_parts, method_str)
-
end
-
end
-
-
1
def check_described_class(node, first_arg)
-
6
if !first_arg || first_arg.type != :const
-
2
add_offence(first_arg || node, :expression, DESCRIBE_CLASS_MSG)
-
end
-
end
-
-
1
def check_described_method(node, second_arg)
-
6
return unless second_arg
-
-
3
unless METHOD_STRING_MATCHER =~ second_arg.children.first
-
1
add_offence(second_arg, :expression, METHOD_STRING_MSG)
-
end
-
end
-
-
1
def check_class_spec(node, path_parts)
-
5
spec_path = File.join(path_parts) + '_spec.rb'
-
5
unless source_filename.end_with? spec_path
-
1
add_offence(node, :expression, format(CLASS_SPEC_MSG, spec_path))
-
end
-
end
-
-
1
def check_method_spec(node, path_parts, method_str)
-
6
matcher_parts = path_parts.dup
-
# Strip out symbols; it's not worth enforcing a vocabulary for them.
-
6
matcher_parts << method_str[1..-1].gsub(/\W+/, '*') + '_spec.rb'
-
-
6
glob_matcher = File.join(matcher_parts)
-
6
unless source_filename =~ regexp_from_glob(glob_matcher)
-
1
message = format(METHOD_SPEC_MSG, glob_matcher)
-
1
add_offence(node, :expression, message)
-
end
-
end
-
-
1
def top_level_describe?(node)
-
20
_receiver, method_name, *_args = *node
-
20
return false unless method_name == :describe
-
-
19
root_node = processed_source.ast
-
19
top_level_nodes = describe_statement_children(root_node)
-
# If we have no top level describe statements, we need to check any
-
# blocks on the top level (e.g. after a require).
-
19
if top_level_nodes.size == 0
-
2
top_level_nodes = node_children(root_node).map do |child|
-
3
describe_statement_children(child) if child.type == :block
-
end.flatten.compact
-
end
-
-
19
top_level_nodes.include? node
-
end
-
-
1
def describe_statement_children(node)
-
20
node_children(node).select do |element|
-
41
element.type == :send && element.children[1] == :describe
-
end
-
end
-
-
1
def source_filename
-
11
processed_source.buffer.name
-
end
-
-
1
def camel_to_underscore(string)
-
17
string.dup.tap do |result|
-
17
result.gsub!(/([^A-Z])([A-Z]+)/, '\\1_\\2')
-
17
result.gsub!(/([A-Z]+)([A-Z][^A-Z]+)/, '\\1_\\2')
-
17
result.downcase!
-
end
-
end
-
-
1
def regexp_from_glob(glob)
-
6
Regexp.new(glob.gsub('.', '\\.').gsub('*', '.*') + '$')
-
end
-
-
1
def node_children(node)
-
86
node.children.select { |e| e.is_a? Parser::AST::Node }
-
end
-
end
-
end
-
end
-
end
-
1
require 'yaml'
-
-
1
module Rubocop
-
1
module RSpec
-
# Because RuboCop doesn't yet support plugins, we have to monkey patch in a
-
# bit of our configuration.
-
1
module Inject
-
1
DEFAULT_FILE = File.expand_path(
-
'../../../../config/default.yml', __FILE__
-
)
-
-
1
def self.defaults!
-
1
hash = YAML.load_file(DEFAULT_FILE)
-
1
puts "configuration from #{path}" if ConfigLoader.debug?
-
1
config = ConfigLoader.merge_with_default(hash, DEFAULT_FILE)
-
-
1
ConfigLoader.instance_variable_set(:@default_configuration, config)
-
end
-
end
-
end
-
end