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,39 @@
1
+ module RuboCop
2
+ module Cop
3
+ module Airbnb
4
+ # Cop to tell developers to use :class => "MyClass" instead of :class => MyClass,
5
+ # because the latter slows down reloading zeus.
6
+ class FactoryClassUseString < Cop
7
+ MSG = 'Instead of :class => MyClass, use :class => "MyClass". ' \
8
+ "This enables faster spec startup time and faster Zeus reload time.".freeze
9
+
10
+ def on_send(node)
11
+ return unless node.command?(:factory)
12
+
13
+ class_pair = class_node(node)
14
+
15
+ if class_pair && !string_class_name?(class_pair)
16
+ add_offense(class_pair)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ # Return the descendant node that is a hash pair (:key => value) whose key
23
+ # is :class.
24
+ def class_node(node)
25
+ node.descendants.detect do |e|
26
+ e.is_a?(Parser::AST::Node) &&
27
+ e.pair_type? &&
28
+ e.children[0].children[0] == :class
29
+ end
30
+ end
31
+
32
+ # Given a hash pair :class_name => value, is the value a hardcoded string?
33
+ def string_class_name?(class_pair)
34
+ class_pair.children[1].str_type?
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,18 @@
1
+ module RuboCop
2
+ module Cop
3
+ module Airbnb
4
+ # Modifying Mass assignment restrictions eliminates the entire point of disabling
5
+ # mass assignment. It's a lazy, potentially dangerous approach that should be discouraged.
6
+ class MassAssignmentAccessibleModifier < Cop
7
+ MSG = 'Do no override and objects mass assignment restrictions.'.freeze
8
+
9
+ def on_send(node)
10
+ _receiver, method_name, *_args = *node
11
+
12
+ return unless method_name == :accessible=
13
+ add_offense(node, message: MSG)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,104 @@
1
+ require_relative '../../airbnb/inflections'
2
+ require_relative '../../airbnb/rails_autoloading'
3
+
4
+ module RuboCop
5
+ module Cop
6
+ module Airbnb
7
+ # This cop checks for methods defined in a module declaration, in a file that doesn't
8
+ # match the module name. The Rails autoloader can't find such a method, but sometimes
9
+ # people "get lucky" if the file happened to be loaded before the method was defined.
10
+ #
11
+ # @example
12
+ # # bad
13
+ #
14
+ # # foo/bar.rb
15
+ # module Foo
16
+ # class Bar
17
+ # end
18
+ #
19
+ # def baz
20
+ # 42
21
+ # end
22
+ # end
23
+ #
24
+ # # good
25
+ #
26
+ # # foo/bar.rb
27
+ # module Foo
28
+ # class Bar
29
+ # end
30
+ # end
31
+ #
32
+ # # foo.rb
33
+ # module Foo
34
+ # def baz
35
+ # 42
36
+ # end
37
+ # end
38
+ #
39
+ # Note that autoloading works fine if classes are defined in the file that defines
40
+ # the module. This is common usage for things like error classes, so we'll allow it:
41
+ #
42
+ # @example
43
+ # # good
44
+ #
45
+ # # foo.rb
46
+ # module Foo
47
+ # class Bar < StandardError
48
+ # def baz
49
+ # end
50
+ # end
51
+ # end
52
+ #
53
+ # # good
54
+ #
55
+ # # foo.rb
56
+ # class Foo
57
+ # class Bar # nested class
58
+ # def baz
59
+ # end
60
+ # end
61
+ # end
62
+ class ModuleMethodInWrongFile < Cop
63
+ include Inflections
64
+ include RailsAutoloading
65
+
66
+ MSG_TEMPLATE =
67
+ "In order for Rails autoloading to be able to find and load this file when " \
68
+ "someone calls this method, move the method definition to a file that defines " \
69
+ "the module. This file just uses the module as a namespace for another class " \
70
+ "or module. Method %s should be defined in %s.".freeze
71
+
72
+ def on_def(node)
73
+ method_name, args, body = *node
74
+ on_method_def(node, method_name, args, body)
75
+ end
76
+
77
+ alias on_defs on_def
78
+
79
+ private
80
+
81
+ def on_method_def(node, method_name, args, body)
82
+ path = node.source_range.source_buffer.name
83
+ return unless run_rails_autoloading_cops?(path)
84
+ return unless node.parent_module_name
85
+ # "#<Class:>" is the parent module name of a method being defined in an if/unless.
86
+ return if node.parent_module_name == "#<Class:>"
87
+
88
+ expected_dir = underscore(normalize_module_name(node.parent_module_name))
89
+ allowable_filenames = expected_dir.split("/").map { |file| "#{file}.rb" }
90
+ basename = File.basename(path)
91
+ if !allowable_filenames.include?(basename)
92
+ parent_module_names = split_modules(node.parent_module_name)
93
+ expected_parent_module_file =
94
+ "#{parent_module_names.map { |name| underscore(name) }.join("/")}.rb"
95
+ add_offense(
96
+ node,
97
+ message: MSG_TEMPLATE % [method_name, expected_parent_module_file]
98
+ )
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,19 @@
1
+ module RuboCop
2
+ module Cop
3
+ module Airbnb
4
+ class NoTimeout < Cop
5
+ MSG =
6
+ 'Do not use Timeout.timeout. In combination with Rails autoloading, ' \
7
+ 'timeout can cause Segmentation Faults in version of Ruby we use. ' \
8
+ 'It can also cause logic errors since it can raise in ' \
9
+ 'any callee scope. Use client library timeouts and monitoring to ' \
10
+ 'ensure proper timing behavior for web requests.'.freeze
11
+
12
+ def on_send(node)
13
+ return unless node.source.start_with?('Timeout.timeout')
14
+ add_offense(node, message: MSG)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+ module RuboCop
2
+ module Cop
3
+ module Airbnb
4
+ # Cop to enforce use of options hash over default arguments
5
+ # https://github.com/airbnb/ruby#no-default-args
6
+ class OptArgParameters < Cop
7
+ MSG =
8
+ 'Do not use default positional arguments. '\
9
+ 'Use keyword arguments or an options hash instead.'.freeze
10
+
11
+ def on_args(node)
12
+ *but_last, last_arg = *node
13
+
14
+ if last_arg && last_arg.blockarg_type?
15
+ last_arg = but_last.pop
16
+ end
17
+
18
+ but_last.each do |arg|
19
+ next unless arg.optarg_type?
20
+ add_offense(arg, message: MSG)
21
+ end
22
+ return if last_arg.nil?
23
+
24
+ return unless last_arg.optarg_type?
25
+
26
+ _arg_name, default_value = *last_arg
27
+ if default_value.hash_type?
28
+ # asserting default value is empty hash
29
+ *key_value_pairs = *default_value
30
+ return if key_value_pairs.empty?
31
+ end
32
+
33
+ add_offense(last_arg, message: MSG)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,67 @@
1
+ module RuboCop
2
+ module Cop
3
+ module Airbnb
4
+ # Prefer matching Phrase Bundle and t call keys inside of
5
+ # PhraseBundleClasses.
6
+ #
7
+ # @example
8
+ # # bad
9
+ # def phrases
10
+ # {
11
+ # "shortened_key" => t(
12
+ # "my_real_translation_key",
13
+ # default: 'Does not matter',
14
+ # ),
15
+ # }
16
+ # end
17
+ #
18
+ # # good
19
+ # def phrases
20
+ # {
21
+ # "my_real_translation_key" => t(
22
+ # "my_real_translation_key",
23
+ # default: 'Does not matter',
24
+ # ),
25
+ # }
26
+ # end
27
+ class PhraseBundleKeys < Cop
28
+ MESSAGE =
29
+ 'Phrase bundle keys should match their translation keys.'.freeze
30
+
31
+ def on_send(node)
32
+ parent = node.parent
33
+ if t_call?(node) && in_phrase_bundle_class?(node) && parent.pair_type?
34
+ hash_key = parent.children[0]
35
+ unless hash_key.children[0] == node.children[2].children[0]
36
+ add_offense(hash_key, message: MESSAGE)
37
+ end
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def in_phrase_bundle_class?(node)
44
+ if node.class_type? && !const_phrase_bundle_children(node).empty?
45
+ true
46
+ elsif node.parent
47
+ in_phrase_bundle_class?(node.parent)
48
+ else
49
+ false
50
+ end
51
+ end
52
+
53
+ def const_phrase_bundle_children(node)
54
+ node.children.select do |e|
55
+ e.is_a?(Parser::AST::Node) &&
56
+ e.const_type? &&
57
+ e.children[1] == :PhraseBundle
58
+ end
59
+ end
60
+
61
+ def t_call?(node)
62
+ node.children[1] == :t
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,63 @@
1
+ module RuboCop
2
+ module Cop
3
+ module Airbnb
4
+ # Disallow ActiveRecord calls that pass interpolated or added strings as an argument.
5
+ class RiskyActiverecordInvocation < Cop
6
+ VULNERABLE_AR_METHODS = [
7
+ :delete_all,
8
+ :destroy_all,
9
+ :exists?,
10
+ :execute,
11
+ :find_by_sql,
12
+ :group,
13
+ :having,
14
+ :insert,
15
+ :order,
16
+ :pluck,
17
+ :reorder,
18
+ :select,
19
+ :select_rows,
20
+ :select_values,
21
+ :select_all,
22
+ :update_all,
23
+ :where,
24
+ ].freeze
25
+ MSG = 'Passing a string computed by interpolation or addition to an ActiveRecord ' \
26
+ 'method is likely to lead to SQL injection. Use hash or parameterized syntax. For ' \
27
+ 'more information, see ' \
28
+ 'http://guides.rubyonrails.org/security.html#sql-injection-countermeasures and ' \
29
+ 'https://rails-sqli.org/rails3. If you have confirmed with Security that this is a ' \
30
+ 'safe usage of this style, disable this alert with ' \
31
+ '`# rubocop:disable Airbnb/RiskyActiverecordInvocation`.'.freeze
32
+ def on_send(node)
33
+ receiver, method_name, *_args = *node
34
+
35
+ return if receiver.nil?
36
+ return unless vulnerable_ar_method?(method_name)
37
+ if !includes_interpolation?(_args) && !includes_sum?(_args)
38
+ return
39
+ end
40
+
41
+ add_offense(node)
42
+ end
43
+
44
+ def vulnerable_ar_method?(method)
45
+ VULNERABLE_AR_METHODS.include?(method)
46
+ end
47
+
48
+ # Return true if the first arg is a :dstr that has non-:str components
49
+ def includes_interpolation?(args)
50
+ !args.first.nil? &&
51
+ args.first.type == :dstr &&
52
+ args.first.each_child_node.any? { |child| child.type != :str }
53
+ end
54
+
55
+ def includes_sum?(args)
56
+ !args.first.nil? &&
57
+ args.first.type == :send &&
58
+ args.first.method_name == :+
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,114 @@
1
+ module RuboCop
2
+ module Cop
3
+ module Airbnb
4
+ # This cop checks for Rspec describe or context method calls under a namespace.
5
+ # It can potentially cause autoloading to occur in a different order than it
6
+ # would have in development or production. This could cause flaky tests.
7
+ #
8
+ # @example
9
+ # # bad
10
+ #
11
+ # # spec/foo/bar_spec.rb
12
+ # module Foo
13
+ # describe Bar do
14
+ # end
15
+ # end
16
+ #
17
+ # # good
18
+ #
19
+ # # spec/foo/bar_spec.rb do
20
+ #
21
+ # describe Foo::Bar
22
+ # end
23
+ class RspecDescribeOrContextUnderNamespace < Cop
24
+ DESCRIBE_OR_CONTEXT_UNDER_NAMESPACE_MSG =
25
+ 'Declaring a `module` in a spec can break autoloading because subsequent references ' \
26
+ 'to it will not cause it to be loaded from the app. This could cause flaky tests.'.freeze
27
+
28
+ FIX_DESCRIBE_OR_CONTEXT_HELP_MSG =
29
+ 'Change `%{describe} %{klass} do` to `%{describe} %{module_name}::%{klass} do`.'.freeze
30
+
31
+ FIX_CODE_HELP_MSG =
32
+ 'Remove `module %{module_name}` and fix `%{module_name}::CONST` and ' \
33
+ '`%{module_name}.method` calls accordingly.'.freeze
34
+
35
+ def_node_matcher :describe_or_context?,
36
+ '(send {(const nil? :RSpec) nil?} {:describe :context} ...)'.freeze
37
+
38
+ def on_module(node)
39
+ path = node.source_range.source_buffer.name
40
+ return unless is_spec_file?(path)
41
+
42
+ matched_node = search_children_for_describe_or_context(node.children)
43
+ return unless matched_node
44
+
45
+ method_name = matched_node.method_name
46
+ module_name = get_module_name(node)
47
+ message = [DESCRIBE_OR_CONTEXT_UNDER_NAMESPACE_MSG]
48
+
49
+ described_class = get_described_class(matched_node)
50
+ method_parent = get_method_parent(matched_node)
51
+ parent_dot_method = method_parent ? "#{method_parent}.#{method_name}" : method_name
52
+ if described_class
53
+ message << FIX_DESCRIBE_OR_CONTEXT_HELP_MSG % {
54
+ describe: parent_dot_method,
55
+ klass: described_class,
56
+ module_name: module_name,
57
+ }
58
+ end
59
+
60
+ message << FIX_CODE_HELP_MSG % { module_name: module_name }
61
+ add_offense(node, message: message.join(' '))
62
+ end
63
+
64
+ def search_children_for_describe_or_context(nodes)
65
+ blocks = []
66
+ # match nodes for send describe or context
67
+ nodes.detect do |node|
68
+ next unless node
69
+
70
+ if is_block?(node)
71
+ blocks << node
72
+ next
73
+ end
74
+ return node if describe_or_context?(node)
75
+ end
76
+
77
+ # Process child nodes of block
78
+ blocks.each do |node|
79
+ matched_node = search_children_for_describe_or_context(node.children)
80
+ return matched_node if matched_node
81
+ end
82
+
83
+ nil
84
+ end
85
+
86
+ def is_spec_file?(path)
87
+ path.end_with?('_spec.rb')
88
+ end
89
+
90
+ def get_module_name(node)
91
+ const_node = node.children[0]
92
+ return unless const_node
93
+ const_node.const_name
94
+ end
95
+
96
+ def get_described_class(node)
97
+ const_node = node.children[2]
98
+ return unless const_node
99
+ const_node.const_name
100
+ end
101
+
102
+ def get_method_parent(node)
103
+ const_node = node.children[0]
104
+ return unless const_node
105
+ const_node.const_name
106
+ end
107
+
108
+ def is_block?(node)
109
+ node && [:block, :begin].include?(node.type)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end