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,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