rubocop-magic_numbers 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4db321fec029b7e924bda74c35c9f9dcfdd902173d31965fad3ca07507ef606b
4
+ data.tar.gz: f477370605f885adfd7a1353a6065c637226ff4b637001546b4b2e4109787bde
5
+ SHA512:
6
+ metadata.gz: 408cb1f1488a6e57063a57b7422dc74145334c3c296e7d3dd129e1e7447c5e309df13040b7aae1d258294b21026e81ff586e4c9754f7af2be31b916343690a10
7
+ data.tar.gz: 4020f1a2891c85d1f0ed3c13acffb47e10a3213d073335d907a1d53f2faeaf912b631a80b332a443b3e2e6fc8da93f54a6958ca5e72590c34dd66d2ef00bcf50
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # rubocop-magic_numbers
2
+
3
+ ![Ruby tests](https://github.com/bodacious/rubocop-magic_numbers/actions/workflows/ruby.yml/badge.svg)
4
+ ![RuboCop Lint](https://github.com/bodacious/rubocop-magic_numbers/actions/workflows/rubocop.yml/badge.svg)
5
+
6
+
7
+ `rubocop-magic_numbers` is a gem that detects the use of magic numbers within Ruby code and raises them as offenses.
8
+
9
+ Magic numbers are typically integers or floats that are used within code with no descriptive context to help other developers understand what they represent. To write cleaner and more maintainable code, it is recommended to assign the numbers to constants with helpful names.
10
+
11
+ ``` ruby
12
+ # BAD
13
+ def calculate_annual_earnings
14
+ pay_per_shift = 50.0
15
+ pay_per_shift * 5 * 52
16
+ end
17
+
18
+ # GOOD
19
+ PAY_PER_SHIFT = 50.0
20
+ SHIFTS_PER_WEEK = 5
21
+ WEEKS_PER_YEAR = 52
22
+ def calculate_annual_earnings
23
+ PAY_PER_SHIFT * SHIFTS_PER_WEEK * WEEKS_PER_YEAR
24
+ end
25
+ ```
26
+
27
+ ## Installation
28
+
29
+ Add this line to your application's Gemfile:
30
+
31
+ ```ruby
32
+ gem 'rubocop-magic_numbers', require: false, group: :development
33
+ ```
34
+
35
+ And then execute:
36
+
37
+ ```bash
38
+ $ bundle install
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ After installing the gem, `rubocop` should automatically detect and raise offenses for magic numbers within your code. You can customize the behavior of the gem by adding configurations to a `.rubocop.yml` file in your project's root directory.
44
+
45
+ Here are some examples of configurations you can use:
46
+
47
+ ```yaml
48
+ require:
49
+ - rubocop-magic_numbers
50
+
51
+ # TODO
52
+ # define configs here
53
+ ```
54
+
55
+ For more information on configuring `rubocop`, please refer to the [official documentation](https://docs.rubocop.org/rubocop/configuration.html).
56
+
57
+ ## Contributing
58
+
59
+ Bug reports and pull requests are welcome on GitHub at https://github.com/bodacious/rubocop-magic_numbers. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](https://www.contributor-covenant.org/) code of conduct.
60
+
61
+ ## License
62
+
63
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop/cop/cop'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module MagicNumbers
8
+ # Base class for all shared behaviour between these cops
9
+ class Base < ::RuboCop::Cop::Cop
10
+ CONFIG_ALL = 'All'
11
+ CONFIG_FLOAT = 'Float'
12
+ CONFIG_INTEGER = 'Integer'
13
+ CONFIG_NAME_FORBIDDEN_NUMERICS = 'ForbiddenNumerics'
14
+
15
+ DEFAULT_CONFIG = {
16
+ CONFIG_NAME_FORBIDDEN_NUMERICS => CONFIG_ALL
17
+ }.freeze
18
+
19
+ ILLEGAL_SCALAR_TYPES = {
20
+ CONFIG_ALL => %i[float int],
21
+ CONFIG_INTEGER => %i[int],
22
+ CONFIG_FLOAT => %i[float]
23
+ }.freeze
24
+
25
+ # The configuration for this cop, pre-set with defaults
26
+ #
27
+ # Returns Hash
28
+ def cop_config
29
+ DEFAULT_CONFIG.merge(super)
30
+ end
31
+
32
+ private
33
+
34
+ # The AST pattern for the configured ForbiddenNumerics types.
35
+ #
36
+ # Examples
37
+ #
38
+ # "{float int}"
39
+ #
40
+ # Returns String
41
+ def illegal_scalar_pattern
42
+ "{#{forbidden_numerics.join(' ')}}"
43
+ end
44
+
45
+ # The numeric types that are forbidden based on this cop's configuration
46
+ #
47
+ # Returns Array of Symbols
48
+ def forbidden_numerics
49
+ forbidden_numerics_key = cop_config[CONFIG_NAME_FORBIDDEN_NUMERICS]
50
+ ILLEGAL_SCALAR_TYPES[forbidden_numerics_key]
51
+ end
52
+
53
+ # Check if the given AST node matches the given pattern
54
+ #
55
+ # node - A RuboCop::AST::ProcessedSource
56
+ # pattern - A RuboCop::AST::NodePattern
57
+ #
58
+ # Returns Boolean
59
+ def node_matches_pattern?(node:, pattern:)
60
+ RuboCop::AST::NodePattern.new(pattern).match(node)
61
+ end
62
+
63
+ # Check if the given AST node is within a method definition
64
+ #
65
+ # node - A RuboCop::AST::ProcessedSource
66
+ #
67
+ # Returns Boolean
68
+ def node_within_method?(node)
69
+ node.ancestors.any?(&:def_type?)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module MagicNumbers
8
+ # Adds violations for magic numbers when used as the argument to a method
9
+ #
10
+ # BAD:
11
+ # object.bottles_on_the_wall(100)
12
+ #
13
+ # GOOD:
14
+ # object.bottles_on_the_wall(DEFAULT_BOTTLE_COUNT)
15
+ class NoArgument < Base
16
+ MAGIC_NUMBER_ARGUMENT_PATTERN = <<-PATTERN
17
+ (send
18
+ {
19
+ _
20
+ _
21
+ (%<illegal_scalar_pattern>s _)
22
+ | # This is a union of lhs and rhs literal
23
+ (%<illegal_scalar_pattern>s _)
24
+ _
25
+ _
26
+ }
27
+ )
28
+ PATTERN
29
+
30
+ ARGUMENT_MSG = 'Do not use magic number arguments to methods'
31
+
32
+ CONFIG_IGNORED_METHODS_NAME = 'IgnoredMethods'
33
+
34
+ # By default, don't raise an offense for magic numbers arguments
35
+ # for these methods
36
+ DEFAULT_CONFIG = {
37
+ CONFIG_IGNORED_METHODS_NAME => ['[]']
38
+ }.freeze
39
+
40
+ def cop_config
41
+ super.merge(DEFAULT_CONFIG)
42
+ end
43
+
44
+ def on_message_send(node)
45
+ return unless illegal_argument?(node)
46
+ return if ignored_method?(node)
47
+
48
+ add_offense(node, location: :expression, message: ARGUMENT_MSG)
49
+ end
50
+ alias on_send on_message_send # rubocop API method name
51
+
52
+ private
53
+
54
+ def ignored_method?(node)
55
+ return false unless node.respond_to?(:method_name)
56
+
57
+ ignored_methods.include?(node.method_name)
58
+ end
59
+
60
+ def ignored_methods
61
+ Array(cop_config[CONFIG_IGNORED_METHODS_NAME]).map(&:to_sym)
62
+ end
63
+
64
+ def illegal_argument?(node)
65
+ node_matches_pattern?(
66
+ node:,
67
+ pattern: format(
68
+ MAGIC_NUMBER_ARGUMENT_PATTERN,
69
+ illegal_scalar_pattern:
70
+ )
71
+ )
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module MagicNumbers
8
+ # Adds violations for magic numbers, aka assignments to variables with
9
+ # bare numbers, configurable by literal type. Can detect local, instance,
10
+ # global, and setter assignment, and works on multiple assignment.
11
+ #
12
+ # bad: hours = 24
13
+ #
14
+ # good: HOURS_IN_ONE_DAY = 24
15
+ class NoAssignment < Base
16
+ MAGIC_NUMBER_ARGUMENT_TO_SETTER_PATTERN = <<-PATTERN
17
+ (send
18
+ ({send self} ...)
19
+ $_
20
+ (%<illegal_scalar_pattern>s _)
21
+ )
22
+ PATTERN
23
+
24
+ MAGIC_NUMBER_MULTI_ASSIGN_PATTERN = <<-PATTERN
25
+ (masgn
26
+ (mlhs ({lvasgn ivasgn send} ...)+)
27
+ (array <(%<illegal_scalar_pattern>s _) ...>)
28
+ )
29
+ PATTERN
30
+ LOCAL_VARIABLE_ASSIGN_MSG = 'Do not use magic number local variables'
31
+ INSTANCE_VARIABLE_ASSIGN_MSG = 'Do not use magic number instance variables'
32
+ MULTIPLE_ASSIGN_MSG = 'Do not use magic numbers in multiple assignments'
33
+ PROPERTY_MSG = 'Do not use magic numbers to set properties'
34
+ DEFAULT_CONFIG = {
35
+ 'AllowedAssignments' => %w[class_variables global_variables]
36
+ }.freeze
37
+
38
+ def cop_config
39
+ DEFAULT_CONFIG.merge(super)
40
+ end
41
+
42
+ def on_local_variable_assignment(node)
43
+ return unless illegal_scalar_value?(node)
44
+ return unless node_within_method?(node)
45
+
46
+ add_offense(node, location: :expression, message: LOCAL_VARIABLE_ASSIGN_MSG)
47
+ end
48
+ alias on_lvasgn on_local_variable_assignment # rubocop API method name
49
+
50
+ def on_instance_variable_assignment(node)
51
+ return unless illegal_scalar_value?(node)
52
+ return unless node_within_method?(node)
53
+
54
+ add_offense(node, location: :expression, message: INSTANCE_VARIABLE_ASSIGN_MSG)
55
+ end
56
+ alias on_ivasgn on_instance_variable_assignment # rubocop API method name
57
+
58
+ def on_message_send(node)
59
+ return unless illegal_scalar_argument_to_setter?(node)
60
+ return unless node_within_method?(node)
61
+
62
+ add_offense(node, location: :expression, message: PROPERTY_MSG)
63
+ end
64
+ alias on_send on_message_send # rubocop API method name
65
+
66
+ def on_multiple_assign(node)
67
+ # multiassignment nodes aren't AsgnNode typed, so we need to have a
68
+ # special approach to deconstruct them and assess if they contain magic
69
+ # numbers amongst their assignments
70
+ return false unless illegal_multi_assign_right_hand_side?(node)
71
+
72
+ add_offense(node, location: :expression, message: MULTIPLE_ASSIGN_MSG)
73
+ end
74
+ alias on_masgn on_multiple_assign
75
+
76
+ private
77
+
78
+ def illegal_scalar_argument_to_setter?(node)
79
+ method = node_matches_pattern?(
80
+ node:,
81
+ pattern: format(
82
+ MAGIC_NUMBER_ARGUMENT_TO_SETTER_PATTERN,
83
+ illegal_scalar_pattern:
84
+ )
85
+ )
86
+
87
+ method&.end_with?('=')
88
+ end
89
+
90
+ def illegal_multi_assign_right_hand_side?(node)
91
+ node_matches_pattern?(
92
+ node:,
93
+ pattern: format(
94
+ MAGIC_NUMBER_MULTI_ASSIGN_PATTERN,
95
+ illegal_scalar_pattern:
96
+ )
97
+ )
98
+ end
99
+
100
+ def illegal_scalar_value?(node)
101
+ return false unless node.assignment?
102
+
103
+ # multiassignment nodes contain individual assignments in their AST
104
+ # representations, but they aren't aware of their values, so we need to
105
+ # allow for expressionless assignments
106
+ forbidden_numerics.include?(node.expression&.type)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module MagicNumbers
8
+ # Adds violations for magic numbers, when used as default values for
9
+ # arguments to methods
10
+ #
11
+ # BAD
12
+ # def on_the_wall(bottles = 100)
13
+ #
14
+ # GOOD
15
+ # def on_the_wall(bottles = DEFAULT_BOTTLE_COUNT)
16
+ class NoDefault < Base
17
+ MAGIC_NUMBER_OPTIONAL_ARGUMENT_PATTERN = <<-PATTERN
18
+ (def
19
+ _
20
+ (args
21
+ <({kwoptarg optarg}
22
+ _
23
+ (%<illegal_scalar_pattern>s _)
24
+ ) ...>
25
+ )
26
+ ...
27
+ )
28
+ PATTERN
29
+
30
+ DEFAULT_OPTIONAL_ARGUMENT_MSG = 'Do not use magic number optional ' \
31
+ 'argument defaults'
32
+
33
+ def on_method_defined(node)
34
+ return unless illegal_positional_default?(node)
35
+
36
+ add_offense(
37
+ node,
38
+ location: :expression,
39
+ message: DEFAULT_OPTIONAL_ARGUMENT_MSG
40
+ )
41
+ end
42
+ alias on_def on_method_defined # rubocop API method name
43
+
44
+ private
45
+
46
+ def illegal_positional_default?(node)
47
+ node_matches_pattern?(
48
+ node:,
49
+ pattern: format(
50
+ MAGIC_NUMBER_OPTIONAL_ARGUMENT_PATTERN,
51
+ illegal_scalar_pattern:
52
+ )
53
+ )
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module MagicNumbers
8
+ # Raises an offense if a method returns with a magic number
9
+ # Catches both explicit and implicit returns
10
+ class NoReturn < Base
11
+ MAGIC_NUMBER_RETURN_PATTERN = <<~PATTERN.chomp
12
+ (%<illegal_scalar_pattern>s _)
13
+ PATTERN
14
+ NO_EXPLICIT_RETURN_MSG = 'Do not return magic numbers from a method or proc'
15
+
16
+ CONFIG_NAME_ALLOWED_RETURNS = 'AllowedReturns'
17
+
18
+ RETURN_TYPE_IMPLICIT = 'Implicit'
19
+ RETURN_TYPE_EXPLICIT = 'Explicit'
20
+ RETURN_TYPE_NONE = 'None'
21
+
22
+ # Supported values are 'Explicit', 'Implicit', 'None'
23
+ DEFAULT_CONFIG = {
24
+ CONFIG_NAME_ALLOWED_RETURNS => [RETURN_TYPE_NONE]
25
+ }.freeze
26
+
27
+ def cop_config
28
+ super.merge(DEFAULT_CONFIG)
29
+ end
30
+
31
+ def on_method_defined(node)
32
+ return if allowed_returns.include?(RETURN_TYPE_IMPLICIT)
33
+ return unless implicit_return?(node.children.last)
34
+
35
+ add_offense(node.children.last, location: :expression, message: NO_EXPLICIT_RETURN_MSG)
36
+ end
37
+ alias on_def on_method_defined
38
+
39
+ def on_return(node)
40
+ return if allowed_returns.include?(RETURN_TYPE_EXPLICIT)
41
+ return unless forbidden_numerics.include?(node.children.first&.type)
42
+
43
+ add_offense(node.children.first, location: :expression, message: NO_EXPLICIT_RETURN_MSG)
44
+ end
45
+
46
+ private
47
+
48
+ def allowed_returns
49
+ Array(cop_config[CONFIG_NAME_ALLOWED_RETURNS])
50
+ end
51
+
52
+ def implicit_return?(node)
53
+ is_node_begin_type = node.is_a?(RuboCop::AST::Node) && node.begin_type?
54
+ return implicit_return?(node.children.last) if is_node_begin_type
55
+
56
+ pattern = format(MAGIC_NUMBER_RETURN_PATTERN, {
57
+ illegal_scalar_pattern:
58
+ })
59
+ node_matches_pattern?(node:, pattern:)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module MagicNumbers
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop/magic_numbers/version'
4
+ require 'rubocop/cop/magic_numbers/base'
5
+ require 'rubocop/cop/magic_numbers/no_argument'
6
+ require 'rubocop/cop/magic_numbers/no_assignment'
7
+ require 'rubocop/cop/magic_numbers/no_default'
8
+ require 'rubocop/cop/magic_numbers/no_return'
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubocop-magic_numbers
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Gavin Morrice
8
+ - Fell Sunderland
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2023-06-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: parser
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rubocop
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ description: rubocop/magic_numbers implements a rubocop cop for detecting the use
43
+ of bare numbers when linting
44
+ email:
45
+ - gavin@gavinmorrice.com
46
+ - fell@meetcleo.com
47
+ executables: []
48
+ extensions: []
49
+ extra_rdoc_files: []
50
+ files:
51
+ - README.md
52
+ - lib/rubocop/cop/magic_numbers/base.rb
53
+ - lib/rubocop/cop/magic_numbers/no_argument.rb
54
+ - lib/rubocop/cop/magic_numbers/no_assignment.rb
55
+ - lib/rubocop/cop/magic_numbers/no_default.rb
56
+ - lib/rubocop/cop/magic_numbers/no_return.rb
57
+ - lib/rubocop/magic_numbers.rb
58
+ - lib/rubocop/magic_numbers/version.rb
59
+ homepage: https://github.com/Bodacious/rubocop-magic_numbers
60
+ licenses:
61
+ - MIT
62
+ metadata:
63
+ rubygems_mfa_required: 'true'
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 3.2.0
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubygems_version: 3.4.6
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: rubocop/magic_numbers implements a rubocop cop for detecting the use of bare
83
+ numbers when linting
84
+ test_files: []