rubocop-airbnb 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.
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