rubocop-highlands 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +7 -0
- data/LICENSE.md +9 -0
- data/README.md +68 -0
- data/config/default.yml +39 -0
- data/config/rubocop-bundler.yml +8 -0
- data/config/rubocop-gemspec.yml +9 -0
- data/config/rubocop-highlands.yml +100 -0
- data/config/rubocop-layout.yml +560 -0
- data/config/rubocop-lint.yml +301 -0
- data/config/rubocop-metrics.yml +47 -0
- data/config/rubocop-naming.yml +85 -0
- data/config/rubocop-performance.yml +141 -0
- data/config/rubocop-rails.yml +208 -0
- data/config/rubocop-rspec.yml +331 -0
- data/config/rubocop-security.yml +17 -0
- data/config/rubocop-style.yml +983 -0
- data/lib/rubocop-highlands.rb +11 -0
- data/lib/rubocop/cop/highlands/class_name.rb +47 -0
- data/lib/rubocop/cop/highlands/class_or_module_declared_in_wrong_file.rb +138 -0
- data/lib/rubocop/cop/highlands/const_assigned_in_wrong_file.rb +74 -0
- data/lib/rubocop/cop/highlands/continuation_slash.rb +25 -0
- data/lib/rubocop/cop/highlands/default_scope.rb +20 -0
- data/lib/rubocop/cop/highlands/factory_attr_references_class.rb +74 -0
- data/lib/rubocop/cop/highlands/factory_class_use_string.rb +39 -0
- data/lib/rubocop/cop/highlands/mass_assignment_accessible_modifier.rb +18 -0
- data/lib/rubocop/cop/highlands/module_method_in_wrong_file.rb +104 -0
- data/lib/rubocop/cop/highlands/no_timeout.rb +19 -0
- data/lib/rubocop/cop/highlands/opt_arg_parameters.rb +38 -0
- data/lib/rubocop/cop/highlands/phrase_bundle_keys.rb +67 -0
- data/lib/rubocop/cop/highlands/risky_activerecord_invocation.rb +63 -0
- data/lib/rubocop/cop/highlands/rspec_describe_or_context_under_namespace.rb +114 -0
- data/lib/rubocop/cop/highlands/rspec_environment_modification.rb +58 -0
- data/lib/rubocop/cop/highlands/simple_modifier_conditional.rb +23 -0
- data/lib/rubocop/cop/highlands/simple_unless.rb +19 -0
- data/lib/rubocop/cop/highlands/spec_constant_assignment.rb +55 -0
- data/lib/rubocop/cop/highlands/unsafe_yaml_marshal.rb +47 -0
- data/lib/rubocop/highlands.rb +16 -0
- data/lib/rubocop/highlands/inflections.rb +14 -0
- data/lib/rubocop/highlands/inject.rb +20 -0
- data/lib/rubocop/highlands/rails_autoloading.rb +55 -0
- data/lib/rubocop/highlands/version.rb +8 -0
- data/rubocop-highlands.gemspec +31 -0
- data/spec/rubocop/cop/highlands/class_name_spec.rb +78 -0
- data/spec/rubocop/cop/highlands/class_or_module_declared_in_wrong_file_spec.rb +174 -0
- data/spec/rubocop/cop/highlands/const_assigned_in_wrong_file_spec.rb +178 -0
- data/spec/rubocop/cop/highlands/continuation_slash_spec.rb +162 -0
- data/spec/rubocop/cop/highlands/default_scope_spec.rb +38 -0
- data/spec/rubocop/cop/highlands/factory_attr_references_class_spec.rb +160 -0
- data/spec/rubocop/cop/highlands/factory_class_use_string_spec.rb +26 -0
- data/spec/rubocop/cop/highlands/mass_assignment_accessible_modifier_spec.rb +28 -0
- data/spec/rubocop/cop/highlands/module_method_in_wrong_file_spec.rb +181 -0
- data/spec/rubocop/cop/highlands/no_timeout_spec.rb +30 -0
- data/spec/rubocop/cop/highlands/opt_arg_parameter_spec.rb +103 -0
- data/spec/rubocop/cop/highlands/phrase_bundle_keys_spec.rb +74 -0
- data/spec/rubocop/cop/highlands/risky_activerecord_invocation_spec.rb +54 -0
- data/spec/rubocop/cop/highlands/rspec_describe_or_context_under_namespace_spec.rb +284 -0
- data/spec/rubocop/cop/highlands/rspec_environment_modification_spec.rb +64 -0
- data/spec/rubocop/cop/highlands/simple_modifier_conditional_spec.rb +122 -0
- data/spec/rubocop/cop/highlands/simple_unless_spec.rb +36 -0
- data/spec/rubocop/cop/highlands/spec_constant_assignment_spec.rb +80 -0
- data/spec/rubocop/cop/highlands/unsafe_yaml_marshal_spec.rb +50 -0
- data/spec/spec_helper.rb +35 -0
- metadata +151 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module Highlands
|
|
4
|
+
# Cop to tackle prevent more complicated modifier if/unless statements
|
|
5
|
+
# https://github.com/highlands/ruby#only-simple-if-unless
|
|
6
|
+
class SimpleModifierConditional < Cop
|
|
7
|
+
MSG = 'Modifier if/unless usage is okay when the body is simple, ' \
|
|
8
|
+
'the condition is simple, and the whole thing fits on one line. ' \
|
|
9
|
+
'Otherwise, avoid modifier if/unless.'.freeze
|
|
10
|
+
|
|
11
|
+
def_node_matcher :multiple_conditionals?, '(if ({and or :^} ...) ...)'
|
|
12
|
+
|
|
13
|
+
def on_if(node)
|
|
14
|
+
return unless node.modifier_form?
|
|
15
|
+
|
|
16
|
+
if multiple_conditionals?(node) || node.multiline?
|
|
17
|
+
add_offense(node)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module Highlands
|
|
4
|
+
# Cop to tackle prevent unless statements with multiple conditions
|
|
5
|
+
# https://github.com/highlands/ruby#unless-with-multiple-conditions
|
|
6
|
+
class SimpleUnless < Cop
|
|
7
|
+
MSG = 'Unless usage is okay when there is only one conditional'.freeze
|
|
8
|
+
|
|
9
|
+
def_node_matcher :multiple_conditionals?, '(if ({and or :^} ...) ...)'
|
|
10
|
+
|
|
11
|
+
def on_if(node)
|
|
12
|
+
return unless node.unless?
|
|
13
|
+
|
|
14
|
+
add_offense(node) if multiple_conditionals?(node)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require 'rubocop-rspec'
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module Highlands
|
|
6
|
+
# This cop checks for constant assignment inside of specs
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# # bad
|
|
10
|
+
# describe Something do
|
|
11
|
+
# PAYLOAD = [1, 2, 3]
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# # good
|
|
15
|
+
# describe Something do
|
|
16
|
+
# let(:payload) { [1, 2, 3] }
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# # bad
|
|
20
|
+
# describe Something do
|
|
21
|
+
# MyClass::PAYLOAD = [1, 2, 3]
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# # good
|
|
25
|
+
# describe Something do
|
|
26
|
+
# before { stub_const('MyClass::PAYLOAD', [1, 2, 3])
|
|
27
|
+
# end
|
|
28
|
+
class SpecConstantAssignment < Cop
|
|
29
|
+
include RuboCop::RSpec::TopLevelDescribe
|
|
30
|
+
MESSAGE = "Defining constants inside of specs can cause spurious behavior. " \
|
|
31
|
+
"It is almost always preferable to use `let` statements, "\
|
|
32
|
+
"anonymous class/module definitions, or stub_const".freeze
|
|
33
|
+
|
|
34
|
+
def on_casgn(node)
|
|
35
|
+
return unless in_spec_file?(node)
|
|
36
|
+
parent_module_name = node.parent_module_name
|
|
37
|
+
if node.parent_module_name && parent_module_name != 'Object'
|
|
38
|
+
return
|
|
39
|
+
end
|
|
40
|
+
add_offense(node, message: MESSAGE)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def in_spec_file?(node)
|
|
46
|
+
filename = node.location.expression.source_buffer.name
|
|
47
|
+
|
|
48
|
+
# For tests, the input is a string
|
|
49
|
+
return true if filename == "(string)"
|
|
50
|
+
filename.include?("/spec/")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module RuboCop
|
|
2
|
+
module Cop
|
|
3
|
+
module Highlands
|
|
4
|
+
# Disallow use of YAML/Marshal methods that can trigger RCE on untrusted input
|
|
5
|
+
class UnsafeYamlMarshal < Cop
|
|
6
|
+
MSG = 'Using unsafe YAML parsing methods on untrusted input can lead ' \
|
|
7
|
+
'to remote code execution. Use `safe_load`, `parse`, `parse_file`, or ' \
|
|
8
|
+
'`parse_stream` instead'.freeze
|
|
9
|
+
|
|
10
|
+
def on_send(node)
|
|
11
|
+
receiver, method_name, *_args = *node
|
|
12
|
+
|
|
13
|
+
return if receiver.nil?
|
|
14
|
+
return unless receiver.const_type?
|
|
15
|
+
|
|
16
|
+
check_yaml(node, receiver, method_name, *_args)
|
|
17
|
+
check_marshal(node, receiver, method_name, *_args)
|
|
18
|
+
rescue => e
|
|
19
|
+
puts e
|
|
20
|
+
puts e.backtrace
|
|
21
|
+
raise
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def check_yaml(node, receiver, method_name, *_args)
|
|
25
|
+
return unless ['YAML', 'Psych'].include?(receiver.const_name)
|
|
26
|
+
return unless [:load, :load_documents, :load_file, :load_stream].include?(method_name)
|
|
27
|
+
|
|
28
|
+
message = "Using `#{receiver.const_name}.#{method_name}` on untrusted input can lead " \
|
|
29
|
+
"to remote code execution. Use `safe_load`, `parse`, `parse_file`, or " \
|
|
30
|
+
"`parse_stream` instead"
|
|
31
|
+
|
|
32
|
+
add_offense(node, message: message)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def check_marshal(node, receiver, method_name, *_args)
|
|
36
|
+
return unless receiver.const_name == 'Marshal'
|
|
37
|
+
return unless method_name == :load
|
|
38
|
+
|
|
39
|
+
message = 'Using `Marshal.load` on untrusted input can lead to remote code execution. ' \
|
|
40
|
+
'Restructure your code to not use Marshal'
|
|
41
|
+
|
|
42
|
+
add_offense(node, message: message)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'pathname'
|
|
2
|
+
require 'psych'
|
|
3
|
+
|
|
4
|
+
Dir.glob(File.expand_path('cop/**/*.rb', File.dirname(__FILE__))).map(&method(:require))
|
|
5
|
+
|
|
6
|
+
module RuboCop
|
|
7
|
+
# RuboCop Highlands project namespace
|
|
8
|
+
module Highlands
|
|
9
|
+
PROJECT_ROOT =
|
|
10
|
+
Pathname.new(__FILE__).parent.parent.parent.expand_path.freeze
|
|
11
|
+
CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
|
|
12
|
+
CONFIG = Psych.safe_load(CONFIG_DEFAULT.read).freeze
|
|
13
|
+
|
|
14
|
+
private_constant(*constants(false))
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# String inflections copied over from ActiveSupport
|
|
2
|
+
module Inflections
|
|
3
|
+
# Convert Foo::BarBaz to foo/bar_baz.
|
|
4
|
+
# Copied from ActiveSupport.
|
|
5
|
+
def underscore(camel_cased_word)
|
|
6
|
+
word = camel_cased_word.to_s.dup
|
|
7
|
+
word.gsub!(/::/, '/')
|
|
8
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
9
|
+
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
|
10
|
+
word.tr!("-", "_")
|
|
11
|
+
word.downcase!
|
|
12
|
+
word
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Straight up ripped from the custom Rspec rubocop
|
|
2
|
+
# https://github.com/nevir/rubocop-rspec/blob/master/lib/rubocop/rspec/inject.rb
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Highlands
|
|
7
|
+
# Because RuboCop doesn't yet support plugins, we have to monkey patch in a
|
|
8
|
+
# bit of our configuration.
|
|
9
|
+
module Inject
|
|
10
|
+
def self.defaults!
|
|
11
|
+
path = CONFIG_DEFAULT.to_s
|
|
12
|
+
hash = ConfigLoader.load_file(path).to_hash
|
|
13
|
+
config = Config.new(hash, path)
|
|
14
|
+
puts "configuration from #{path}" if ConfigLoader.debug?
|
|
15
|
+
config = ConfigLoader.merge_with_default(config, path)
|
|
16
|
+
ConfigLoader.instance_variable_set(:@default_configuration, config)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# These methods are useful for Rubocop rules related to Rails autoloading.
|
|
2
|
+
module RailsAutoloading
|
|
3
|
+
def run_rails_autoloading_cops?(path)
|
|
4
|
+
return false unless config["Rails".freeze]
|
|
5
|
+
return false unless config["Rails".freeze]["Enabled".freeze]
|
|
6
|
+
|
|
7
|
+
# Ignore rake tasks
|
|
8
|
+
return false unless path.end_with?(".rb")
|
|
9
|
+
|
|
10
|
+
true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Given "foo/bar/baz", return:
|
|
14
|
+
# [
|
|
15
|
+
# %r{/foo.rb$},
|
|
16
|
+
# %r{/foo/bar.rb$},
|
|
17
|
+
# %r{/foo/bar/baz.rb$},
|
|
18
|
+
# %r{/foo/bar/baz/}, # <= only if allow_dir = true
|
|
19
|
+
# ]
|
|
20
|
+
def allowable_paths_for(expected_dir, options = {})
|
|
21
|
+
options = { allow_dir: false }.merge(options)
|
|
22
|
+
allowable_paths = []
|
|
23
|
+
next_slash = expected_dir.index("/")
|
|
24
|
+
while next_slash
|
|
25
|
+
allowable_paths << %r{/#{expected_dir[0...next_slash]}.rb$}
|
|
26
|
+
next_slash = expected_dir.index("/", next_slash + 1)
|
|
27
|
+
end
|
|
28
|
+
allowable_paths << %r{#{expected_dir}.rb$}
|
|
29
|
+
allowable_paths << %r{/#{expected_dir}/} if options[:allow_dir]
|
|
30
|
+
allowable_paths
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def normalize_module_name(module_name)
|
|
34
|
+
return '' if module_name.nil?
|
|
35
|
+
normalized_name = module_name.gsub(/#<Class:|>/, "")
|
|
36
|
+
normalized_name = "" if normalized_name == "Object"
|
|
37
|
+
normalized_name
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# module_name looks like one of these:
|
|
41
|
+
# Foo::Bar for an instance method
|
|
42
|
+
# #<Class:Foo::Bar> for a class method.
|
|
43
|
+
# For either case we return ["Foo", "Bar"]
|
|
44
|
+
def split_modules(module_name)
|
|
45
|
+
normalize_module_name(module_name).split("::")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def full_const_name(parent_module_name, const_name)
|
|
49
|
+
if parent_module_name == "".freeze
|
|
50
|
+
"#{const_name}"
|
|
51
|
+
else
|
|
52
|
+
"#{parent_module_name}::#{const_name}"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
|
|
2
|
+
require 'rubocop/highlands/version'
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |spec|
|
|
5
|
+
spec.name = 'rubocop-highlands'
|
|
6
|
+
spec.summary = 'Custom code style checking for Church of the Highlands.'
|
|
7
|
+
spec.description = <<-EOF
|
|
8
|
+
A plugin for RuboCop code style enforcing & linting tool. It includes Rubocop configuration
|
|
9
|
+
used at Church of the Highlands and a few custom rules that have caused internal issues at
|
|
10
|
+
Church of the Highlands but are not supported by core Rubocop.
|
|
11
|
+
EOF
|
|
12
|
+
spec.authors = ['Church of the Highlands - Digital Team']
|
|
13
|
+
spec.email = ['info@churchofthehighlands.com']
|
|
14
|
+
spec.homepage = 'https://github.com/highlands/ruby'
|
|
15
|
+
spec.license = 'MIT'
|
|
16
|
+
spec.version = RuboCop::Highlands::VERSION
|
|
17
|
+
spec.platform = Gem::Platform::RUBY
|
|
18
|
+
spec.required_ruby_version = '>= 2.1'
|
|
19
|
+
|
|
20
|
+
spec.require_paths = ['lib']
|
|
21
|
+
spec.files = Dir[
|
|
22
|
+
'{config,lib,spec}/**/*',
|
|
23
|
+
'*.md',
|
|
24
|
+
'*.gemspec',
|
|
25
|
+
'Gemfile',
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
spec.add_dependency('rubocop', '~> 0.58.0')
|
|
29
|
+
spec.add_dependency('rubocop-rspec', '~> 1.30.0')
|
|
30
|
+
spec.add_development_dependency('rspec', '~> 3.5')
|
|
31
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
describe RuboCop::Cop::Highlands::ClassName do
|
|
2
|
+
subject(:cop) { described_class.new }
|
|
3
|
+
|
|
4
|
+
describe "belongs_to" do
|
|
5
|
+
it 'rejects with Model.name' do
|
|
6
|
+
source = [
|
|
7
|
+
'class Coupon',
|
|
8
|
+
' belongs_to :user, :class_name => User.name',
|
|
9
|
+
'end',
|
|
10
|
+
].join("\n")
|
|
11
|
+
inspect_source(source)
|
|
12
|
+
|
|
13
|
+
expect(cop.offenses.size).to eq(1)
|
|
14
|
+
expect(cop.offenses.map(&:line).sort).to eq([2])
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'passes with "Model"' do
|
|
18
|
+
source = [
|
|
19
|
+
'class Coupon',
|
|
20
|
+
' belongs_to :user, :class_name => "User"',
|
|
21
|
+
'end',
|
|
22
|
+
].join("\n")
|
|
23
|
+
inspect_source(source)
|
|
24
|
+
|
|
25
|
+
expect(cop.offenses).to be_empty
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe "has_many" do
|
|
30
|
+
it 'rejects with Model.name' do
|
|
31
|
+
source = [
|
|
32
|
+
'class Coupon',
|
|
33
|
+
' has_many :reservations, :class_name => Reservation2.name',
|
|
34
|
+
'end',
|
|
35
|
+
].join("\n")
|
|
36
|
+
inspect_source(source)
|
|
37
|
+
|
|
38
|
+
expect(cop.offenses.size).to eq(1)
|
|
39
|
+
expect(cop.offenses.map(&:line)).to eq([2])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'passes with "Model"' do
|
|
43
|
+
source = [
|
|
44
|
+
'class Coupon',
|
|
45
|
+
' has_many :reservations, :class_name => "Reservation2"',
|
|
46
|
+
'end',
|
|
47
|
+
].join("\n")
|
|
48
|
+
inspect_source(source)
|
|
49
|
+
|
|
50
|
+
expect(cop.offenses).to be_empty
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe "has_one" do
|
|
55
|
+
it 'rejects with Model.name' do
|
|
56
|
+
source = [
|
|
57
|
+
'class Coupon',
|
|
58
|
+
' has_one :loss, :class_name => Payments::Loss.name',
|
|
59
|
+
'end',
|
|
60
|
+
].join("\n")
|
|
61
|
+
inspect_source(source)
|
|
62
|
+
|
|
63
|
+
expect(cop.offenses.size).to eq(1)
|
|
64
|
+
expect(cop.offenses.map(&:line)).to eq([2])
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'passes with "Model"' do
|
|
68
|
+
source = [
|
|
69
|
+
'class Coupon',
|
|
70
|
+
' has_one :loss, :class_name => "Payments::Loss"',
|
|
71
|
+
'end',
|
|
72
|
+
].join("\n")
|
|
73
|
+
inspect_source(source)
|
|
74
|
+
|
|
75
|
+
expect(cop.offenses).to be_empty
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
describe RuboCop::Cop::Highlands::ClassOrModuleDeclaredInWrongFile do
|
|
2
|
+
subject(:cop) { described_class.new(config) }
|
|
3
|
+
|
|
4
|
+
let(:config) do
|
|
5
|
+
RuboCop::Config.new(
|
|
6
|
+
{
|
|
7
|
+
"Rails" => {
|
|
8
|
+
"Enabled" => true,
|
|
9
|
+
},
|
|
10
|
+
}
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Put source in a directory under /tmp because this cop cares about the filename
|
|
15
|
+
# but not the parent dir name.
|
|
16
|
+
let(:tmpdir) { Dir.mktmpdir }
|
|
17
|
+
let(:models_dir) do
|
|
18
|
+
gemfile = File.new("#{tmpdir}/Gemfile", "w")
|
|
19
|
+
gemfile.close
|
|
20
|
+
FileUtils.mkdir_p("#{tmpdir}/app/models").first
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
after do
|
|
24
|
+
FileUtils.rm_rf tmpdir
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'rejects if class declaration is in a file with non-matching name' do
|
|
28
|
+
source = [
|
|
29
|
+
'module Foo',
|
|
30
|
+
' module Bar',
|
|
31
|
+
' class Baz',
|
|
32
|
+
' end',
|
|
33
|
+
' end',
|
|
34
|
+
'end',
|
|
35
|
+
].join("\n")
|
|
36
|
+
|
|
37
|
+
File.open "#{models_dir}/qux.rb", "w" do |file|
|
|
38
|
+
inspect_source(source, file)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
expect(cop.offenses.size).to eq(3)
|
|
42
|
+
expect(cop.offenses.map(&:line).sort).to eq([1, 2, 3])
|
|
43
|
+
expect(cop.offenses.first.message).to include('Module Foo should be defined in foo.rb.')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'rejects if class declaration is in a file with matching name but wrong parent dir' do
|
|
47
|
+
source = [
|
|
48
|
+
'module Foo',
|
|
49
|
+
' module Bar',
|
|
50
|
+
' class Baz',
|
|
51
|
+
' end',
|
|
52
|
+
' end',
|
|
53
|
+
'end',
|
|
54
|
+
].join("\n")
|
|
55
|
+
|
|
56
|
+
File.open "#{models_dir}/baz.rb", "w" do |file|
|
|
57
|
+
inspect_source(source, file)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
expect(cop.offenses.size).to eq(3)
|
|
61
|
+
expect(cop.offenses.map(&:line).sort).to eq([1, 2, 3])
|
|
62
|
+
expect(cop.offenses.last.message).to include('Class Baz should be defined in foo/bar/baz.rb.')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'accepts if class declaration is in a file with matching name and right parent dir' do
|
|
66
|
+
source = [
|
|
67
|
+
'module Foo',
|
|
68
|
+
' module Bar',
|
|
69
|
+
' class Baz',
|
|
70
|
+
' end',
|
|
71
|
+
' end',
|
|
72
|
+
'end',
|
|
73
|
+
].join("\n")
|
|
74
|
+
|
|
75
|
+
FileUtils.mkdir_p "#{models_dir}/foo/bar"
|
|
76
|
+
File.open "#{models_dir}/foo/bar/baz.rb", "w" do |file|
|
|
77
|
+
inspect_source(source, file)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
expect(cop.offenses).to be_empty
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'rejects if class declaration is in wrong dir and parent module uses ::' do
|
|
84
|
+
source = [
|
|
85
|
+
'module Foo::Bar',
|
|
86
|
+
' class Baz',
|
|
87
|
+
' end',
|
|
88
|
+
'end',
|
|
89
|
+
].join("\n")
|
|
90
|
+
|
|
91
|
+
FileUtils.mkdir_p "#{models_dir}/bar"
|
|
92
|
+
File.open "#{models_dir}/bar/baz.rb", "w" do |file|
|
|
93
|
+
inspect_source(source, file)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
expect(cop.offenses.map(&:line).sort).to eq([1, 2])
|
|
97
|
+
expect(cop.offenses.last.message).to include('Class Baz should be defined in foo/bar/baz.rb.')
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'accepts if class declaration is in a file with matching name and parent module uses ::' do
|
|
101
|
+
source = [
|
|
102
|
+
'module Foo::Bar',
|
|
103
|
+
' class Baz',
|
|
104
|
+
' end',
|
|
105
|
+
'end',
|
|
106
|
+
].join("\n")
|
|
107
|
+
|
|
108
|
+
FileUtils.mkdir_p "#{models_dir}/foo/bar"
|
|
109
|
+
File.open "#{models_dir}/foo/bar/baz.rb", "w" do |file|
|
|
110
|
+
inspect_source(source, file)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
expect(cop.offenses).to be_empty
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'accepts class declaration where the containing class uses an acronym' do
|
|
117
|
+
source = [
|
|
118
|
+
'module CSVFoo',
|
|
119
|
+
' class Baz',
|
|
120
|
+
' end',
|
|
121
|
+
'end',
|
|
122
|
+
].join("\n")
|
|
123
|
+
|
|
124
|
+
FileUtils.mkdir_p "#{models_dir}/csv_foo"
|
|
125
|
+
File.open "#{models_dir}/csv_foo/baz.rb", "w" do |file|
|
|
126
|
+
inspect_source(source, file)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
expect(cop.offenses).to be_empty
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'ignores class/module declaration in a rake task' do
|
|
133
|
+
source = [
|
|
134
|
+
'class Baz',
|
|
135
|
+
'end',
|
|
136
|
+
].join("\n")
|
|
137
|
+
|
|
138
|
+
File.open "#{models_dir}/foo.rake", "w" do |file|
|
|
139
|
+
inspect_source(source, file)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
expect(cop.offenses).to be_empty
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it 'suggests moving error classes into the file that defines the owning scope' do
|
|
146
|
+
source = [
|
|
147
|
+
'module Foo',
|
|
148
|
+
' class BarError < StandardError; end',
|
|
149
|
+
'end',
|
|
150
|
+
].join("\n")
|
|
151
|
+
|
|
152
|
+
File.open "#{models_dir}/bar.rb", "w" do |file|
|
|
153
|
+
inspect_source(source, file)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
expect(cop.offenses.map(&:line)).to include(2)
|
|
157
|
+
expect(cop.offenses.map(&:message)).to include(%r{Class BarError should be defined in foo\.rb.})
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'recognizes error class based on the superclass name' do
|
|
161
|
+
source = [
|
|
162
|
+
'module Foo',
|
|
163
|
+
' class Bar < StandardError; end',
|
|
164
|
+
'end',
|
|
165
|
+
].join("\n")
|
|
166
|
+
|
|
167
|
+
File.open "#{models_dir}/bar.rb", "w" do |file|
|
|
168
|
+
inspect_source(source, file)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
expect(cop.offenses.map(&:line)).to include(2)
|
|
172
|
+
expect(cop.offenses.map(&:message)).to include(%r{Class Bar should be defined in foo\.rb.})
|
|
173
|
+
end
|
|
174
|
+
end
|