rubocop-airbnb 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2 -0
  3. data/Gemfile +7 -0
  4. data/LICENSE.md +9 -0
  5. data/README.md +68 -0
  6. data/config/default.yml +39 -0
  7. data/config/rubocop-airbnb.yml +96 -0
  8. data/config/rubocop-bundler.yml +8 -0
  9. data/config/rubocop-gemspec.yml +9 -0
  10. data/config/rubocop-layout.yml +514 -0
  11. data/config/rubocop-lint.yml +315 -0
  12. data/config/rubocop-metrics.yml +47 -0
  13. data/config/rubocop-naming.yml +68 -0
  14. data/config/rubocop-performance.yml +143 -0
  15. data/config/rubocop-rails.yml +193 -0
  16. data/config/rubocop-rspec.yml +281 -0
  17. data/config/rubocop-security.yml +13 -0
  18. data/config/rubocop-style.yml +953 -0
  19. data/lib/rubocop-airbnb.rb +11 -0
  20. data/lib/rubocop/airbnb.rb +16 -0
  21. data/lib/rubocop/airbnb/inflections.rb +14 -0
  22. data/lib/rubocop/airbnb/inject.rb +20 -0
  23. data/lib/rubocop/airbnb/rails_autoloading.rb +55 -0
  24. data/lib/rubocop/airbnb/version.rb +8 -0
  25. data/lib/rubocop/cop/airbnb/class_name.rb +47 -0
  26. data/lib/rubocop/cop/airbnb/class_or_module_declared_in_wrong_file.rb +138 -0
  27. data/lib/rubocop/cop/airbnb/const_assigned_in_wrong_file.rb +74 -0
  28. data/lib/rubocop/cop/airbnb/continuation_slash.rb +25 -0
  29. data/lib/rubocop/cop/airbnb/default_scope.rb +20 -0
  30. data/lib/rubocop/cop/airbnb/factory_attr_references_class.rb +74 -0
  31. data/lib/rubocop/cop/airbnb/factory_class_use_string.rb +39 -0
  32. data/lib/rubocop/cop/airbnb/mass_assignment_accessible_modifier.rb +18 -0
  33. data/lib/rubocop/cop/airbnb/module_method_in_wrong_file.rb +104 -0
  34. data/lib/rubocop/cop/airbnb/no_timeout.rb +19 -0
  35. data/lib/rubocop/cop/airbnb/opt_arg_parameters.rb +38 -0
  36. data/lib/rubocop/cop/airbnb/phrase_bundle_keys.rb +67 -0
  37. data/lib/rubocop/cop/airbnb/risky_activerecord_invocation.rb +63 -0
  38. data/lib/rubocop/cop/airbnb/rspec_describe_or_context_under_namespace.rb +114 -0
  39. data/lib/rubocop/cop/airbnb/rspec_environment_modification.rb +58 -0
  40. data/lib/rubocop/cop/airbnb/simple_modifier_conditional.rb +23 -0
  41. data/lib/rubocop/cop/airbnb/spec_constant_assignment.rb +55 -0
  42. data/lib/rubocop/cop/airbnb/unsafe_yaml_marshal.rb +47 -0
  43. data/rubocop-airbnb.gemspec +32 -0
  44. data/spec/rubocop/cop/airbnb/class_name_spec.rb +78 -0
  45. data/spec/rubocop/cop/airbnb/class_or_module_declared_in_wrong_file_spec.rb +174 -0
  46. data/spec/rubocop/cop/airbnb/const_assigned_in_wrong_file_spec.rb +178 -0
  47. data/spec/rubocop/cop/airbnb/continuation_slash_spec.rb +162 -0
  48. data/spec/rubocop/cop/airbnb/default_scope_spec.rb +38 -0
  49. data/spec/rubocop/cop/airbnb/factory_attr_references_class_spec.rb +160 -0
  50. data/spec/rubocop/cop/airbnb/factory_class_use_string_spec.rb +26 -0
  51. data/spec/rubocop/cop/airbnb/mass_assignment_accessible_modifier_spec.rb +28 -0
  52. data/spec/rubocop/cop/airbnb/module_method_in_wrong_file_spec.rb +181 -0
  53. data/spec/rubocop/cop/airbnb/no_timeout_spec.rb +30 -0
  54. data/spec/rubocop/cop/airbnb/opt_arg_parameter_spec.rb +103 -0
  55. data/spec/rubocop/cop/airbnb/phrase_bundle_keys_spec.rb +74 -0
  56. data/spec/rubocop/cop/airbnb/risky_activerecord_invocation_spec.rb +54 -0
  57. data/spec/rubocop/cop/airbnb/rspec_describe_or_context_under_namespace_spec.rb +284 -0
  58. data/spec/rubocop/cop/airbnb/rspec_environment_modification_spec.rb +64 -0
  59. data/spec/rubocop/cop/airbnb/simple_modifier_conditional_spec.rb +122 -0
  60. data/spec/rubocop/cop/airbnb/spec_constant_assignment_spec.rb +80 -0
  61. data/spec/rubocop/cop/airbnb/unsafe_yaml_marshal_spec.rb +50 -0
  62. data/spec/spec_helper.rb +35 -0
  63. metadata +150 -0
@@ -0,0 +1,178 @@
1
+ describe RuboCop::Cop::Airbnb::ConstAssignedInWrongFile 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 const assignment is in a file with non-matching name' do
28
+ source = [
29
+ 'module Foo',
30
+ ' module Bar',
31
+ ' BAZ = 42',
32
+ ' end',
33
+ 'end',
34
+ ].join("\n")
35
+
36
+ File.open "#{models_dir}/qux.rb", "w" do |file|
37
+ inspect_source(source, file)
38
+ end
39
+
40
+ expect(cop.offenses.map(&:line)).to include(3)
41
+ expect(cop.offenses.map(&:message)).to include(%r{Const BAZ should be defined in foo/bar\.rb.})
42
+ end
43
+
44
+ it 'rejects if const assignment is in a file with matching name but wrong parent dir' do
45
+ source = [
46
+ 'module Foo',
47
+ ' module Bar',
48
+ ' BAZ = 42',
49
+ ' end',
50
+ 'end',
51
+ ].join("\n")
52
+
53
+ File.open "#{models_dir}/bar.rb", "w" do |file|
54
+ inspect_source(source, file)
55
+ end
56
+
57
+ expect(cop.offenses.map(&:line)).to include(3)
58
+ expect(cop.offenses.map(&:message)).to include(%r{Const BAZ should be defined in foo/bar\.rb.})
59
+ end
60
+
61
+ it 'accepts if const assignment is in a file with matching name and right parent dir' do
62
+ source = [
63
+ 'module Foo',
64
+ ' module Bar',
65
+ ' BAZ = 42',
66
+ ' end',
67
+ 'end',
68
+ ].join("\n")
69
+
70
+ FileUtils.mkdir "#{models_dir}/foo"
71
+ File.open "#{models_dir}/foo/bar.rb", "w" do |file|
72
+ inspect_source(source, file)
73
+ end
74
+
75
+ expect(cop.offenses).to be_empty
76
+ end
77
+
78
+ it 'accepts if const assignment is in a file with matching name and right parent dir' \
79
+ 'and parent modules were defined on a single line' do
80
+ source = [
81
+ 'module Foo::Bar',
82
+ ' BAZ = 42',
83
+ 'end',
84
+ ].join("\n")
85
+
86
+ FileUtils.mkdir "#{models_dir}/foo"
87
+ File.open "#{models_dir}/foo/bar.rb", "w" do |file|
88
+ inspect_source(source, file)
89
+ end
90
+
91
+ expect(cop.offenses).to be_empty
92
+ end
93
+
94
+ it 'accepts if const assignment is in a file whose name matches the const and right parent dir' do
95
+ source = [
96
+ 'module Foo',
97
+ ' module Bar',
98
+ ' BAZ = 42',
99
+ ' end',
100
+ 'end',
101
+ ].join("\n")
102
+
103
+ FileUtils.mkdir_p "#{models_dir}/foo/bar"
104
+ File.open "#{models_dir}/foo/bar/baz.rb", "w" do |file|
105
+ inspect_source(source, file)
106
+ end
107
+
108
+ expect(cop.offenses).to be_empty
109
+ end
110
+
111
+ it 'ignores if const assignment is assigning something in another scope' do
112
+ source = [
113
+ 'module Foo',
114
+ ' Bar::BAZ = 42',
115
+ 'end',
116
+ ].join("\n")
117
+
118
+ File.open "#{models_dir}/foo.rb", "w" do |file|
119
+ inspect_source(source, file)
120
+ end
121
+
122
+ expect(cop.offenses).to be_empty
123
+ end
124
+
125
+ it 'accepts const assignment where the containing class uses an acronym' do
126
+ source = [
127
+ 'module CSVFoo',
128
+ ' BAZ = 42',
129
+ 'end',
130
+ ].join("\n")
131
+
132
+ File.open "#{models_dir}/csv_foo.rb", "w" do |file|
133
+ inspect_source(source, file)
134
+ end
135
+
136
+ expect(cop.offenses).to be_empty
137
+ end
138
+
139
+ it 'suggests moving a global const into a namespace' do
140
+ source = [
141
+ 'FOO = 42',
142
+ ].join("\n")
143
+
144
+ File.open "#{models_dir}/bar.rb", "w" do |file|
145
+ inspect_source(source, file)
146
+ end
147
+
148
+ expect(cop.offenses.map(&:line)).to eq([1])
149
+ expect(cop.offenses.first.message).
150
+ to include('Const FOO should be moved into a namespace or defined in foo.rb.')
151
+ end
152
+
153
+ it 'ignores const assignment in global namespace in a rake task' do
154
+ source = [
155
+ 'FOO = 42',
156
+ ].join("\n")
157
+
158
+ File.open "#{models_dir}/foo.rake", "w" do |file|
159
+ inspect_source(source, file)
160
+ end
161
+
162
+ expect(cop.offenses).to be_empty
163
+ end
164
+
165
+ it 'ignores const assignment in a class in a rake task' do
166
+ source = [
167
+ 'class Baz',
168
+ ' FOO = 42',
169
+ 'end',
170
+ ].join("\n")
171
+
172
+ File.open "#{models_dir}/foo.rake", "w" do |file|
173
+ inspect_source(source, file)
174
+ end
175
+
176
+ expect(cop.offenses).to be_empty
177
+ end
178
+ end
@@ -0,0 +1,162 @@
1
+ describe RuboCop::Cop::Airbnb::ContinuationSlash do
2
+ subject(:cop) { described_class.new }
3
+
4
+ it 'rejects continuations used to continue a method call with trailing dot' do
5
+ source = [
6
+ 'User. \\',
7
+ ' first_name',
8
+ ].join("\n")
9
+ inspect_source(source)
10
+
11
+ expect(cop.offenses.size).to eq(1)
12
+ expect(cop.offenses.map(&:line).sort).to eq([1])
13
+ end
14
+
15
+ context 'on assignment' do
16
+ it 'rejects on constant assignment' do
17
+ source = [
18
+ 'CONSTANT = "I am a string that \\',
19
+ ' spans multiple lines"',
20
+ ].join("\n")
21
+ inspect_source(source)
22
+
23
+ expect(cop.offenses.size).to eq(1)
24
+ end
25
+
26
+ it 'rejects on local variable assignment' do
27
+ source = [
28
+ 'variable = "I am a string that \\',
29
+ ' spans multiple lines"',
30
+ ].join("\n")
31
+ inspect_source(source)
32
+
33
+ expect(cop.offenses.size).to eq(1)
34
+ end
35
+
36
+ it 'rejects on @var assignment' do
37
+ source = [
38
+ 'class SomeClass',
39
+ ' @class_var = "I am a string that \\',
40
+ ' spans multiple lines"',
41
+ 'end',
42
+ ].join("\n")
43
+ inspect_source(source)
44
+
45
+ expect(cop.offenses.size).to eq(1)
46
+ end
47
+
48
+ it 'rejects on @@var assignment' do
49
+ source = [
50
+ 'class SomeClass',
51
+ ' @@class_var = "I am a string that \\',
52
+ ' spans multiple lines"',
53
+ 'end',
54
+ ].join("\n")
55
+ inspect_source(source)
56
+
57
+ expect(cop.offenses.size).to eq(1)
58
+ end
59
+ end
60
+
61
+ context 'in conditional continuation' do
62
+ it 'rejects if with continuation \\ before operator' do
63
+ source = [
64
+ 'if true \\',
65
+ ' && false',
66
+ ' return false',
67
+ 'end',
68
+ ].join("\n")
69
+ inspect_source(source)
70
+
71
+ expect(cop.offenses.size).to eq(1)
72
+ end
73
+
74
+ it 'rejects unless with continuation \\' do
75
+ source = [
76
+ 'unless true \\',
77
+ ' && false',
78
+ ' return false',
79
+ 'end',
80
+ ].join("\n")
81
+ inspect_source(source)
82
+
83
+ expect(cop.offenses.size).to eq(1)
84
+ end
85
+
86
+ it 'rejects if with continuation \\ after operator' do
87
+ source = [
88
+ 'if true || \\',
89
+ ' false',
90
+ ' return false',
91
+ 'end',
92
+ ].join("\n")
93
+ inspect_source(source)
94
+
95
+ expect(cop.offenses.size).to eq(1)
96
+ end
97
+ end
98
+
99
+ context 'open string continuation' do
100
+ it 'rejects contination with space before \\' do
101
+ source = [
102
+ 'I18n.t("I am a string that \\',
103
+ ' spans multiple lines")',
104
+ ].join("\n")
105
+ inspect_source(source)
106
+
107
+ expect(cop.offenses.size).to eq(1)
108
+ end
109
+
110
+ it 'rejects contination with no space before \\' do
111
+ source = [
112
+ 'I18n.t(\'I am a string that \\',
113
+ ' spans multiple lines\')',
114
+ ].join("\n")
115
+ inspect_source(source)
116
+
117
+ expect(cop.offenses.size).to eq(1)
118
+ end
119
+ end
120
+
121
+ context 'closed string continuation' do
122
+ it 'allows double quote string with no space before \\' do
123
+ source = [
124
+ 'I18n.t("I am a string that "\\',
125
+ ' "spans multiple lines")',
126
+ ].join("\n")
127
+ inspect_source(source)
128
+
129
+ expect(cop.offenses).to be_empty
130
+ end
131
+
132
+ it 'allows double quote string with space before \\' do
133
+ source = [
134
+ 'I18n.t("I am a string that " \\',
135
+ ' "spans multiple lines")',
136
+ ].join("\n")
137
+ inspect_source(source)
138
+
139
+ expect(cop.offenses).to be_empty
140
+ end
141
+
142
+ it 'allows single quote string with no space before \\' do
143
+ source = [
144
+ 'I18n.t(\'I am a string that \'\\',
145
+ ' \'spans multiple lines\')',
146
+ ].join("\n")
147
+ inspect_source(source)
148
+
149
+ expect(cop.offenses).to be_empty
150
+ end
151
+
152
+ it 'allows single quote string with space before \\' do
153
+ source = [
154
+ 'I18n.t(\'I am a string that \' \\',
155
+ ' \'spans multiple lines\')',
156
+ ].join("\n")
157
+ inspect_source(source)
158
+
159
+ expect(cop.offenses).to be_empty
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,38 @@
1
+ describe RuboCop::Cop::Airbnb::DefaultScope do
2
+ subject(:cop) { described_class.new }
3
+
4
+ it 'rejects with default_scopes' do
5
+ source = [
6
+ '# encoding: UTF-8',
7
+ 'module SurveyQuestion',
8
+ ' class Host < PhraseBundle',
9
+ ' db_magic :connection => AIRMISC_MASTER,',
10
+ ' :slaves => AIRMISC_DB_SLAVES,',
11
+ ' :force_slave_reads => FORCE_SLAVE_READS',
12
+ '',
13
+ ' default_scope where(active: true)',
14
+ '',
15
+ ' end',
16
+ 'end',
17
+ ].join("\n")
18
+ inspect_source(source)
19
+
20
+ expect(cop.offenses.size).to eq(1)
21
+ expect(cop.offenses.map(&:line).sort).to eq([8])
22
+ end
23
+
24
+ it 'passes when there is no default_scope' do
25
+ source = [
26
+ '# encoding: UTF-8',
27
+ 'module SurveyQuestion',
28
+ ' class Host < PhraseBundle',
29
+ ' db_magic :connection => AIRMISC_MASTER,',
30
+ ' :slaves => AIRMISC_DB_SLAVES,',
31
+ ' :force_slave_reads => FORCE_SLAVE_READS',
32
+ ' end',
33
+ 'end',
34
+ ].join("\n")
35
+ inspect_source(source)
36
+ expect(cop.offenses).to be_empty
37
+ end
38
+ end
@@ -0,0 +1,160 @@
1
+ describe RuboCop::Cop::Airbnb::FactoryAttrReferencesClass do
2
+ subject(:cop) { described_class.new }
3
+
4
+ it 'rejects with `attr_name CONST_NAME` in a factory' do
5
+ source = [
6
+ 'factory :reservation2 do',
7
+ ' status Reservation2::STATUS_NEW',
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([2])
14
+ end
15
+
16
+ it 'passes with `attr_name { CONST_NAME }` in a factory' do
17
+ source = [
18
+ 'factory :reservation2 do',
19
+ ' status { Reservation2::STATUS_NEW }',
20
+ 'end',
21
+ ].join("\n")
22
+ inspect_source(source)
23
+
24
+ expect(cop.offenses).to be_empty
25
+ end
26
+
27
+ it 'rejects with `attr_name [CONST_NAME]`' do
28
+ source = [
29
+ 'factory :reservation2 do',
30
+ ' statuses [Reservation2::STATUS_NEW]',
31
+ 'end',
32
+ ].join("\n")
33
+ inspect_source(source)
34
+
35
+ expect(cop.offenses.size).to eq(1)
36
+ expect(cop.offenses.map(&:line)).to eq([2])
37
+ end
38
+
39
+ it 'passes with `attr_name { [CONST_NAME] }`' do
40
+ source = [
41
+ 'factory :reservation2 do',
42
+ ' statuses { [Reservation2::STATUS_NEW] }',
43
+ 'end',
44
+ ].join("\n")
45
+ inspect_source(source)
46
+
47
+ expect(cop.offenses).to be_empty
48
+ end
49
+
50
+ it 'rejects with `attr_name [[CONST_NAME]]`' do
51
+ source = [
52
+ 'factory :reservation2 do',
53
+ ' statuses [[Reservation2::STATUS_NEW]]',
54
+ 'end',
55
+ ].join("\n")
56
+ inspect_source(source)
57
+
58
+ expect(cop.offenses.size).to eq(1)
59
+ expect(cop.offenses.map(&:line)).to eq([2])
60
+ end
61
+
62
+ it 'passes with `attr_name { [[CONST_NAME]] }`' do
63
+ source = [
64
+ 'factory :reservation2 do',
65
+ ' statuses { [[Reservation2::STATUS_NEW]] }',
66
+ 'end',
67
+ ].join("\n")
68
+ inspect_source(source)
69
+
70
+ expect(cop.offenses).to be_empty
71
+ end
72
+
73
+ it 'rejects with `attr_name({ ConstName => something })' do
74
+ source = [
75
+ 'factory :reservation2 do',
76
+ ' status_names({ Reservation2::STATUS_NEW => "new" })',
77
+ 'end',
78
+ ].join("\n")
79
+ inspect_source(source)
80
+
81
+ expect(cop.offenses.size).to eq(1)
82
+ expect(cop.offenses.map(&:line)).to eq([2])
83
+ end
84
+
85
+ it 'passes with `attr_name { { ConstName => something } }`' do
86
+ source = [
87
+ 'factory :reservation2 do',
88
+ ' status_names { { Reservation2::STATUS_NEW => "new" } }',
89
+ 'end',
90
+ ].join("\n")
91
+ inspect_source(source)
92
+
93
+ expect(cop.offenses).to be_empty
94
+ end
95
+
96
+ it 'rejects with `attr_name ConstName[:symbol]`' do
97
+ source = [
98
+ 'factory :airlock_rule do',
99
+ ' stickiness Airlock::STICKINESS[:user]',
100
+ 'end',
101
+ ].join("\n")
102
+ inspect_source(source)
103
+
104
+ expect(cop.offenses.size).to eq(1)
105
+ expect(cop.offenses.map(&:line)).to eq([2])
106
+ end
107
+
108
+ it 'passes with `attr_name { ConstName[:symbol] }`' do
109
+ source = [
110
+ 'factory :airlock_rule do',
111
+ ' stickiness { Airlock::STICKINESS[:user] }',
112
+ 'end',
113
+ ].join("\n")
114
+ inspect_source(source)
115
+
116
+ expect(cop.offenses).to be_empty
117
+ end
118
+
119
+ it 'rejects even if the const is not the first attr' do
120
+ source = [
121
+ 'factory :reservation2 do',
122
+ ' trait :accepted do',
123
+ ' cancel_policy Hosting::CANCEL_FLEXIBLE',
124
+ ' status Reservation2::STATUS_NEW',
125
+ ' end',
126
+ 'end',
127
+ ].join("\n")
128
+ inspect_source(source)
129
+
130
+ expect(cop.offenses.size).to eq(2)
131
+ expect(cop.offenses.map(&:line)).to eq([3, 4])
132
+ end
133
+
134
+ it 'rejects with `attr_name CONST_NAME` in a trait' do
135
+ source = [
136
+ 'factory :reservation2 do',
137
+ ' trait :accepted do',
138
+ ' status Reservation2::STATUS_NEW',
139
+ ' end',
140
+ 'end',
141
+ ].join("\n")
142
+ inspect_source(source)
143
+
144
+ expect(cop.offenses.size).to eq(1)
145
+ expect(cop.offenses.map(&:line)).to eq([3])
146
+ end
147
+
148
+ it 'passes with `attr_name { CONST_NAME }` in a trait' do
149
+ source = [
150
+ 'factory :reservation2 do',
151
+ ' trait :accepted do',
152
+ ' status { Reservation2::STATUS_NEW }',
153
+ ' end',
154
+ 'end',
155
+ ].join("\n")
156
+ inspect_source(source)
157
+
158
+ expect(cop.offenses).to be_empty
159
+ end
160
+ end