rubocop-airbnb 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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-airbnb.yml +96 -0
- data/config/rubocop-bundler.yml +8 -0
- data/config/rubocop-gemspec.yml +9 -0
- data/config/rubocop-layout.yml +514 -0
- data/config/rubocop-lint.yml +315 -0
- data/config/rubocop-metrics.yml +47 -0
- data/config/rubocop-naming.yml +68 -0
- data/config/rubocop-performance.yml +143 -0
- data/config/rubocop-rails.yml +193 -0
- data/config/rubocop-rspec.yml +281 -0
- data/config/rubocop-security.yml +13 -0
- data/config/rubocop-style.yml +953 -0
- data/lib/rubocop-airbnb.rb +11 -0
- data/lib/rubocop/airbnb.rb +16 -0
- data/lib/rubocop/airbnb/inflections.rb +14 -0
- data/lib/rubocop/airbnb/inject.rb +20 -0
- data/lib/rubocop/airbnb/rails_autoloading.rb +55 -0
- data/lib/rubocop/airbnb/version.rb +8 -0
- data/lib/rubocop/cop/airbnb/class_name.rb +47 -0
- data/lib/rubocop/cop/airbnb/class_or_module_declared_in_wrong_file.rb +138 -0
- data/lib/rubocop/cop/airbnb/const_assigned_in_wrong_file.rb +74 -0
- data/lib/rubocop/cop/airbnb/continuation_slash.rb +25 -0
- data/lib/rubocop/cop/airbnb/default_scope.rb +20 -0
- data/lib/rubocop/cop/airbnb/factory_attr_references_class.rb +74 -0
- data/lib/rubocop/cop/airbnb/factory_class_use_string.rb +39 -0
- data/lib/rubocop/cop/airbnb/mass_assignment_accessible_modifier.rb +18 -0
- data/lib/rubocop/cop/airbnb/module_method_in_wrong_file.rb +104 -0
- data/lib/rubocop/cop/airbnb/no_timeout.rb +19 -0
- data/lib/rubocop/cop/airbnb/opt_arg_parameters.rb +38 -0
- data/lib/rubocop/cop/airbnb/phrase_bundle_keys.rb +67 -0
- data/lib/rubocop/cop/airbnb/risky_activerecord_invocation.rb +63 -0
- data/lib/rubocop/cop/airbnb/rspec_describe_or_context_under_namespace.rb +114 -0
- data/lib/rubocop/cop/airbnb/rspec_environment_modification.rb +58 -0
- data/lib/rubocop/cop/airbnb/simple_modifier_conditional.rb +23 -0
- data/lib/rubocop/cop/airbnb/spec_constant_assignment.rb +55 -0
- data/lib/rubocop/cop/airbnb/unsafe_yaml_marshal.rb +47 -0
- data/rubocop-airbnb.gemspec +32 -0
- data/spec/rubocop/cop/airbnb/class_name_spec.rb +78 -0
- data/spec/rubocop/cop/airbnb/class_or_module_declared_in_wrong_file_spec.rb +174 -0
- data/spec/rubocop/cop/airbnb/const_assigned_in_wrong_file_spec.rb +178 -0
- data/spec/rubocop/cop/airbnb/continuation_slash_spec.rb +162 -0
- data/spec/rubocop/cop/airbnb/default_scope_spec.rb +38 -0
- data/spec/rubocop/cop/airbnb/factory_attr_references_class_spec.rb +160 -0
- data/spec/rubocop/cop/airbnb/factory_class_use_string_spec.rb +26 -0
- data/spec/rubocop/cop/airbnb/mass_assignment_accessible_modifier_spec.rb +28 -0
- data/spec/rubocop/cop/airbnb/module_method_in_wrong_file_spec.rb +181 -0
- data/spec/rubocop/cop/airbnb/no_timeout_spec.rb +30 -0
- data/spec/rubocop/cop/airbnb/opt_arg_parameter_spec.rb +103 -0
- data/spec/rubocop/cop/airbnb/phrase_bundle_keys_spec.rb +74 -0
- data/spec/rubocop/cop/airbnb/risky_activerecord_invocation_spec.rb +54 -0
- data/spec/rubocop/cop/airbnb/rspec_describe_or_context_under_namespace_spec.rb +284 -0
- data/spec/rubocop/cop/airbnb/rspec_environment_modification_spec.rb +64 -0
- data/spec/rubocop/cop/airbnb/simple_modifier_conditional_spec.rb +122 -0
- data/spec/rubocop/cop/airbnb/spec_constant_assignment_spec.rb +80 -0
- data/spec/rubocop/cop/airbnb/unsafe_yaml_marshal_spec.rb +50 -0
- data/spec/spec_helper.rb +35 -0
- metadata +150 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
describe RuboCop::Cop::Airbnb::FactoryClassUseString do
|
2
|
+
subject(:cop) { described_class.new }
|
3
|
+
|
4
|
+
it 'rejects with :class => Model' do
|
5
|
+
source = [
|
6
|
+
'factory :help_answer, :class => Help::Answer do',
|
7
|
+
' text { Faker::Company.name }',
|
8
|
+
'end',
|
9
|
+
].join("\n")
|
10
|
+
inspect_source(source)
|
11
|
+
|
12
|
+
expect(cop.offenses.size).to eq(1)
|
13
|
+
expect(cop.offenses.map(&:line)).to eq([1])
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'passes with :class => "Model"' do
|
17
|
+
source = [
|
18
|
+
'factory :help_answer, :class => "Help::Answer" do',
|
19
|
+
' text { Faker::Company.name }',
|
20
|
+
'end',
|
21
|
+
].join("\n")
|
22
|
+
inspect_source(source)
|
23
|
+
|
24
|
+
expect(cop.offenses).to be_empty
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
describe RuboCop::Cop::Airbnb::MassAssignmentAccessibleModifier do
|
2
|
+
subject(:cop) { described_class.new }
|
3
|
+
|
4
|
+
it 'rejects when accessible= is called' do
|
5
|
+
source = [
|
6
|
+
'def some_method',
|
7
|
+
' user = User.new',
|
8
|
+
' user.accessible = [:first_name, :last_name]',
|
9
|
+
' user.update_attributes(:first_name => "Walter", :last_name => "Udoing")',
|
10
|
+
'end',
|
11
|
+
].join("\n")
|
12
|
+
inspect_source(source)
|
13
|
+
expect(cop.offenses.size).to eq(1)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'accepts when accessible= is not called' do
|
17
|
+
source = [
|
18
|
+
'def some_method',
|
19
|
+
' user = User.new',
|
20
|
+
' user.first_name = "Walter"',
|
21
|
+
' user.last_name = "Udoing"',
|
22
|
+
' user.save!',
|
23
|
+
'end',
|
24
|
+
].join("\n")
|
25
|
+
inspect_source(source)
|
26
|
+
expect(cop.offenses).to be_empty
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
describe RuboCop::Cop::Airbnb::ModuleMethodInWrongFile 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 module method is in file with non-matching name' do
|
28
|
+
source = [
|
29
|
+
'module Hello',
|
30
|
+
' module Foo',
|
31
|
+
' def baz', # error is here if this file is named bar.rb
|
32
|
+
' 42',
|
33
|
+
' end',
|
34
|
+
' end',
|
35
|
+
'end',
|
36
|
+
].join("\n")
|
37
|
+
|
38
|
+
File.open "#{models_dir}/bar.rb", "w" do |file|
|
39
|
+
inspect_source(source, file)
|
40
|
+
end
|
41
|
+
|
42
|
+
expect(cop.offenses.size).to eq(1)
|
43
|
+
expect(cop.offenses.map(&:line).sort).to eq([3])
|
44
|
+
expect(cop.offenses.first.message).to include("Method baz should be defined in hello/foo.rb.")
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'accepts if module method is in file with matching name' do
|
48
|
+
source = [
|
49
|
+
'module Foo',
|
50
|
+
' def baz',
|
51
|
+
' 42',
|
52
|
+
' end',
|
53
|
+
'end',
|
54
|
+
].join("\n")
|
55
|
+
|
56
|
+
File.open "#{models_dir}/foo.rb", "w" do |file|
|
57
|
+
inspect_source(source, file)
|
58
|
+
end
|
59
|
+
|
60
|
+
expect(cop.offenses).to be_empty
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'rejects with "self." static methods and a non-matching name' do
|
64
|
+
source = [
|
65
|
+
'module Foo',
|
66
|
+
' def self.baz',
|
67
|
+
' 42',
|
68
|
+
' end',
|
69
|
+
'end',
|
70
|
+
].join("\n")
|
71
|
+
|
72
|
+
File.open "#{models_dir}/bar.rb", "w" do |file|
|
73
|
+
inspect_source(source, file)
|
74
|
+
end
|
75
|
+
|
76
|
+
expect(cop.offenses.size).to eq(1)
|
77
|
+
expect(cop.offenses.map(&:line).sort).to eq([2])
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'accepts with "self." static methods and a matching name' do
|
81
|
+
source = [
|
82
|
+
'module Foo',
|
83
|
+
' def self.baz',
|
84
|
+
' 42',
|
85
|
+
' end',
|
86
|
+
'end',
|
87
|
+
].join("\n")
|
88
|
+
|
89
|
+
File.open "#{models_dir}/foo.rb", "w" do |file|
|
90
|
+
inspect_source(source, file)
|
91
|
+
end
|
92
|
+
|
93
|
+
expect(cop.offenses).to be_empty
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'rejects with "<<" static methods and a non-matching name' do
|
97
|
+
source = [
|
98
|
+
'module Foo',
|
99
|
+
' class << self',
|
100
|
+
' def baz',
|
101
|
+
' 42',
|
102
|
+
' end',
|
103
|
+
' end',
|
104
|
+
'end',
|
105
|
+
].join("\n")
|
106
|
+
|
107
|
+
File.open "#{models_dir}/bar.rb", "w" do |file|
|
108
|
+
inspect_source(source, file)
|
109
|
+
end
|
110
|
+
|
111
|
+
expect(cop.offenses.size).to eq(1)
|
112
|
+
expect(cop.offenses.map(&:line).sort).to eq([3])
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'accepts with "<<" static methods and a matching name' do
|
116
|
+
source = [
|
117
|
+
'module Foo',
|
118
|
+
' class << self',
|
119
|
+
' def baz',
|
120
|
+
' 42',
|
121
|
+
' end',
|
122
|
+
' end',
|
123
|
+
'end',
|
124
|
+
].join("\n")
|
125
|
+
|
126
|
+
File.open "#{models_dir}/foo.rb", "w" do |file|
|
127
|
+
inspect_source(source, file)
|
128
|
+
end
|
129
|
+
|
130
|
+
expect(cop.offenses).to be_empty
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'accepts methods defined in a nested class' do
|
134
|
+
source = [
|
135
|
+
'module Foo',
|
136
|
+
' class Bar',
|
137
|
+
' def qux',
|
138
|
+
' end',
|
139
|
+
' end',
|
140
|
+
'end',
|
141
|
+
].join("\n")
|
142
|
+
|
143
|
+
File.open "#{models_dir}/foo.rb", "w" do |file|
|
144
|
+
inspect_source(source, file)
|
145
|
+
end
|
146
|
+
|
147
|
+
expect(cop.offenses).to be_empty
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'accepts methods where the containing class uses an acronym' do
|
151
|
+
source = [
|
152
|
+
'module CSVFoo',
|
153
|
+
' def baz',
|
154
|
+
' 42',
|
155
|
+
' end',
|
156
|
+
'end',
|
157
|
+
].join("\n")
|
158
|
+
|
159
|
+
File.open "#{models_dir}/csv_foo.rb", "w" do |file|
|
160
|
+
inspect_source(source, file)
|
161
|
+
end
|
162
|
+
|
163
|
+
expect(cop.offenses).to be_empty
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'ignores rake tasks' do
|
167
|
+
source = [
|
168
|
+
'module Hello',
|
169
|
+
' def baz', # error is here if this file is named bar.rb
|
170
|
+
' 42',
|
171
|
+
' end',
|
172
|
+
'end',
|
173
|
+
].join("\n")
|
174
|
+
|
175
|
+
File.open "#{models_dir}/bar.rake", "w" do |file|
|
176
|
+
inspect_source(source, file)
|
177
|
+
end
|
178
|
+
|
179
|
+
expect(cop.offenses).to be_empty
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
describe RuboCop::Cop::Airbnb::NoTimeout do
|
2
|
+
subject(:cop) { described_class.new }
|
3
|
+
|
4
|
+
context 'send' do
|
5
|
+
it 'rejects Timeout.timeout' do
|
6
|
+
source = [
|
7
|
+
'def some_method(a)',
|
8
|
+
' Timeout.timeout(5) do',
|
9
|
+
' some_other_method(a)',
|
10
|
+
' end',
|
11
|
+
'end',
|
12
|
+
].join("\n")
|
13
|
+
inspect_source(source)
|
14
|
+
expect(cop.offenses.size).to eql(1)
|
15
|
+
expect(cop.offenses.first.message).to start_with('Do not use Timeout.timeout')
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'accepts foo.timeout' do
|
19
|
+
source = [
|
20
|
+
'def some_method(a)',
|
21
|
+
' foo.timeout do',
|
22
|
+
' some_other_method(a)',
|
23
|
+
' end',
|
24
|
+
'end',
|
25
|
+
].join("\n")
|
26
|
+
inspect_source(source)
|
27
|
+
expect(cop.offenses).to be_empty
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
describe RuboCop::Cop::Airbnb::OptArgParameters do
|
2
|
+
subject(:cop) { described_class.new }
|
3
|
+
|
4
|
+
it 'allows method with no parameters' do
|
5
|
+
source = [
|
6
|
+
'def my_method',
|
7
|
+
'end',
|
8
|
+
].join("\n")
|
9
|
+
|
10
|
+
inspect_source(source)
|
11
|
+
expect(cop.offenses).to be_empty
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'allows method with one parameter' do
|
15
|
+
source = [
|
16
|
+
'def my_method(params)',
|
17
|
+
'end',
|
18
|
+
].join("\n")
|
19
|
+
|
20
|
+
inspect_source(source)
|
21
|
+
expect(cop.offenses).to be_empty
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'allows method with one parameter with a default hash value' do
|
25
|
+
source = [
|
26
|
+
'def my_method(params = {})',
|
27
|
+
'end',
|
28
|
+
].join("\n")
|
29
|
+
|
30
|
+
inspect_source(source)
|
31
|
+
expect(cop.offenses).to be_empty
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'allows method named default parameters' do
|
35
|
+
source = [
|
36
|
+
'def my_method(a, b, c: 5, d: 6)',
|
37
|
+
'end',
|
38
|
+
].join("\n")
|
39
|
+
|
40
|
+
inspect_source(source)
|
41
|
+
expect(cop.offenses).to be_empty
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'allows method with multiple parameter with a default hash value' do
|
45
|
+
source = [
|
46
|
+
'def my_method(a, b, c, params = {})',
|
47
|
+
'end',
|
48
|
+
].join("\n")
|
49
|
+
|
50
|
+
inspect_source(source)
|
51
|
+
expect(cop.offenses).to be_empty
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'allows method with default parameter before block parameter' do
|
55
|
+
source = [
|
56
|
+
'def my_method(a, b, c, params = {}, &block)',
|
57
|
+
'end',
|
58
|
+
].join("\n")
|
59
|
+
|
60
|
+
inspect_source(source)
|
61
|
+
expect(cop.offenses).to be_empty
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'rejects method with a default parameter other than the last non-block parameter' do
|
65
|
+
source = [
|
66
|
+
'def my_method(a, b = nil, c, &block)',
|
67
|
+
'end',
|
68
|
+
].join("\n")
|
69
|
+
|
70
|
+
inspect_source(source)
|
71
|
+
expect(cop.offenses.size).to eq(1)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'rejects method with a default parameter other than the last parameter' do
|
75
|
+
source = [
|
76
|
+
'def my_method(a, b = 5, c = nil, params = {})',
|
77
|
+
'end',
|
78
|
+
].join("\n")
|
79
|
+
|
80
|
+
inspect_source(source)
|
81
|
+
expect(cop.offenses.size).to eq(2)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'rejects method where last parameter has a default value of nil' do
|
85
|
+
source = [
|
86
|
+
'def my_method(a, b, c, params = nil)',
|
87
|
+
'end',
|
88
|
+
].join("\n")
|
89
|
+
|
90
|
+
inspect_source(source)
|
91
|
+
expect(cop.offenses.size).to eq(1)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'rejects method where last parameter has a default value of a constant' do
|
95
|
+
source = [
|
96
|
+
'def my_method(a, b, c, params = Constants::A_CONSTANT)',
|
97
|
+
'end',
|
98
|
+
].join("\n")
|
99
|
+
|
100
|
+
inspect_source(source)
|
101
|
+
expect(cop.offenses.size).to eq(1)
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
describe RuboCop::Cop::Airbnb::PhraseBundleKeys do
|
2
|
+
subject(:cop) { described_class.new }
|
3
|
+
|
4
|
+
it 'generates offenses for mismatched keys in PhraseBundle classes' do
|
5
|
+
source = <<EOS
|
6
|
+
# encoding: UTF-8
|
7
|
+
module PhraseBundles
|
8
|
+
class Host < PhraseBundle
|
9
|
+
|
10
|
+
def phrases
|
11
|
+
{
|
12
|
+
"shortened_key" => t(
|
13
|
+
"my_real_translation_key",
|
14
|
+
default: 'Does not matter',
|
15
|
+
),
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
EOS
|
21
|
+
|
22
|
+
inspect_source(source)
|
23
|
+
expect(cop.offenses.size).to eq(1)
|
24
|
+
expect(cop.offenses.map(&:line).sort).to eq([7])
|
25
|
+
expect(cop.messages).to eq(
|
26
|
+
['Phrase bundle keys should match their translation keys.']
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'does not generate offenses for matching keys in PhraseBundle classes' do
|
31
|
+
source = <<EOS
|
32
|
+
# encoding: UTF-8
|
33
|
+
module PhraseBundles
|
34
|
+
class Host < PhraseBundle
|
35
|
+
|
36
|
+
def phrases
|
37
|
+
{
|
38
|
+
"my_real_translation_key" => t(
|
39
|
+
"my_real_translation_key",
|
40
|
+
default: 'Does not matter',
|
41
|
+
),
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
EOS
|
47
|
+
|
48
|
+
inspect_source(source)
|
49
|
+
inspect_source(source)
|
50
|
+
expect(cop.offenses).to be_empty
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'passes on t calls that are not in PhraseBundle classes' do
|
54
|
+
source = <<EOS
|
55
|
+
# encoding: UTF-8
|
56
|
+
module PhraseBundles
|
57
|
+
class Host
|
58
|
+
|
59
|
+
def phrases
|
60
|
+
{
|
61
|
+
"shortened_key" => t(
|
62
|
+
"my_real_translation_key",
|
63
|
+
default: 'Does not matter',
|
64
|
+
),
|
65
|
+
}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
EOS
|
70
|
+
|
71
|
+
inspect_source(source)
|
72
|
+
expect(cop.offenses).to be_empty
|
73
|
+
end
|
74
|
+
end
|