/
numeric_literals.rb
105 lines (90 loc) · 2.75 KB
/
numeric_literals.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# This cop checks for big numeric literals without _ between groups
# of digits in them.
#
# @example
#
# # bad
# 1000000
# 1_00_000
# 1_0000
#
# # good
# 1_000_000
# 1000
#
# @example Strict: false (default)
#
# # good
# 10_000_00 # typical representation of $10,000 in cents
#
# @example Strict: true
#
# # bad
# 10_000_00 # typical representation of $10,000 in cents
#
class NumericLiterals < Base
include IntegerNode
extend AutoCorrector
MSG = 'Use underscores(_) as thousands separator and separate every 3 digits with them.'
DELIMITER_REGEXP = /[eE.]/.freeze
# The parameter is called MinDigits (meaning the minimum number of
# digits for which an offense can be registered), but essentially it's
# a Max parameter (the maximum number of something that's allowed).
exclude_limit 'MinDigits'
def on_int(node)
check(node)
end
def on_float(node)
check(node)
end
private
def check(node)
int = integer_part(node)
# TODO: handle non-decimal literals as well
return if int.start_with?('0')
return unless int.size >= min_digits
case int
when /^\d+$/
register_offense(node) { self.min_digits = int.size + 1 }
when /\d{4}/, short_group_regex
register_offense(node) { self.config_to_allow_offenses = { 'Enabled' => false } }
end
end
def register_offense(node, &_block)
add_offense(node) do |corrector|
yield
corrector.replace(node, format_number(node))
end
end
def short_group_regex
cop_config['Strict'] ? /_\d{1,2}(_|$)/ : /_\d{1,2}_/
end
def format_number(node)
source = node.source.gsub(/\s+/, '')
int_part, additional_part = source.split(DELIMITER_REGEXP, 2)
formatted_int = format_int_part(int_part)
delimiter = source[DELIMITER_REGEXP]
if additional_part
formatted_int + delimiter + additional_part
else
formatted_int
end
end
# @param int_part [String]
def format_int_part(int_part)
int_part = Integer(int_part)
formatted_int = int_part.abs.to_s.reverse.gsub(/...(?=.)/, '\&_').reverse
formatted_int.insert(0, '-') if int_part.negative?
formatted_int
end
def min_digits
cop_config['MinDigits']
end
end
end
end
end