# frozen_string_literal: true module RuboCop module Cop module Style # Checks for octal, hex, binary, and decimal literals using # uppercase prefixes and corrects them to lowercase prefix # or no prefix (in case of decimals). # # @example EnforcedOctalStyle: zero_with_o (default) # # bad - missing octal prefix # num = 01234 # # # bad - uppercase prefix # num = 0O1234 # num = 0X12AB # num = 0B10101 # # # bad - redundant decimal prefix # num = 0D1234 # num = 0d1234 # # # good # num = 0o1234 # num = 0x12AB # num = 0b10101 # num = 1234 # # @example EnforcedOctalStyle: zero_only # # bad # num = 0o1234 # num = 0O1234 # # # good # num = 01234 class NumericLiteralPrefix < Base include IntegerNode extend AutoCorrector OCTAL_ZERO_ONLY_REGEX = /^0[Oo][0-7]+$/.freeze OCTAL_REGEX = /^0O?[0-7]+$/.freeze HEX_REGEX = /^0X[0-9A-F]+$/.freeze BINARY_REGEX = /^0B[01]+$/.freeze DECIMAL_REGEX = /^0[dD][0-9]+$/.freeze OCTAL_ZERO_ONLY_MSG = 'Use 0 for octal literals.' OCTAL_MSG = 'Use 0o for octal literals.' HEX_MSG = 'Use 0x for hexadecimal literals.' BINARY_MSG = 'Use 0b for binary literals.' DECIMAL_MSG = 'Do not use prefixes for decimal literals.' def on_int(node) type = literal_type(node) return unless type add_offense(node) do |corrector| corrector.replace(node, send(:"format_#{type}", node.source)) end end private def message(node) self.class.const_get(:"#{literal_type(node).upcase}_MSG") end def literal_type(node) literal = integer_part(node) octal_literal_type(literal) || hex_bin_dec_literal_type(literal) end def octal_literal_type(literal) if OCTAL_ZERO_ONLY_REGEX.match?(literal) && octal_zero_only? :octal_zero_only elsif OCTAL_REGEX.match?(literal) && !octal_zero_only? :octal end end def hex_bin_dec_literal_type(literal) case literal when HEX_REGEX :hex when BINARY_REGEX :binary when DECIMAL_REGEX :decimal end end def octal_zero_only? cop_config['EnforcedOctalStyle'] == 'zero_only' end def format_octal(source) source.sub(/^0O?/, '0o') end def format_octal_zero_only(source) source.sub(/^0[Oo]?/, '0') end def format_hex(source) source.sub(/^0X/, '0x') end def format_binary(source) source.sub(/^0B/, '0b') end def format_decimal(source) source.sub(/^0[dD]/, '') end end end end end