Skip to content

Commit

Permalink
Make Style/NumericLiteralPrefix configurable for octal literals
Browse files Browse the repository at this point in the history
Can use `EnforcedOctalStyle` => `zero_only` to use only `0` for
octal literals instead of `0o` or `0O`.
  • Loading branch information
tejasbubane committed Jun 15, 2016
1 parent 8842ba0 commit 9ffa30d
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 39 deletions.
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,12 @@ Style/MultilineOperationIndentation:
Style/NumericLiterals:
MinDigits: 5

Style/NumericLiteralPrefix:
EnforcedOctalStyle: zero_with_o
SupportedOctalStyles:
- zero_with_o
- zero_only

Style/OptionHash:
# A list of parameter names that will be flagged by this cop.
SuspiciousParamNames:
Expand Down
3 changes: 1 addition & 2 deletions config/enabled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -543,8 +543,7 @@ Style/NumericLiterals:
Enabled: true

Style/NumericLiteralPrefix:
Description: >-
Use smallcase prefixes for literals.
Description: 'Use smallcase prefixes for numeric literals.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#numeric-literal-prefixes'
Enabled: true

Expand Down
52 changes: 36 additions & 16 deletions lib/rubocop/cop/style/numeric_literal_prefix.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,23 @@
module RuboCop
module Cop
module Style
# This cop checks for octal, hex and binary literals using
# This cop checks for octal, hex, binary and decimal literals using
# uppercase prefixes and corrects them to lowercase prefix
# or no prefix (in case of decimals).
# eg. for octal use `0o` instead of `0` or `0O`.
#
# Can be configured to use `0` only for octal literals using
# `EnforcedOctalStyle` => `zero_only`
class NumericLiteralPrefix < Cop
include IntegerNode

OCTAL_ZERO_ONLY_REGEX = /^0[Oo][0-7]+$/
OCTAL_REGEX = /^0O?[0-7]+$/
HEX_REGEX = /^0X[0-9A-F]+$/
BINARY_REGEX = /^0B[01]+$/
DECIMAL_REGEX = /^0[dD][0-9]+$/

OCTAL_ZERO_ONLY_MSG = 'Use 0 for octal literals.'.freeze
OCTAL_MSG = 'Use 0o for octal literals.'.freeze
HEX_MSG = 'Use 0x for hexadecimal literals.'.freeze
BINARY_MSG = 'Use 0b for binary literals.'.freeze
Expand All @@ -36,37 +41,52 @@ def on_int(node)
def autocorrect(node)
lambda do |corrector|
type = literal_type(node)
corrector.replace(node.source_range, send(:"format_#{type}", node))
corrector.replace(node.source_range,
send(:"format_#{type}", node.source))
end
end

def literal_type(node)
case integer_part(node)
when OCTAL_REGEX
'octal'.freeze
literal = integer_part(node)

if literal =~ OCTAL_ZERO_ONLY_REGEX && octal_zero_only?
return :octal_zero_only
elsif literal =~ OCTAL_REGEX && !octal_zero_only?
return :octal
end

case literal
when HEX_REGEX
'hex'.freeze
:hex
when BINARY_REGEX
'binary'.freeze
:binary
when DECIMAL_REGEX
'decimal'.freeze
:decimal
end
end

def format_octal(node)
node.source.sub(/^0O?/, '0o')
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(node)
node.source.sub(/^0X/, '0x')
def format_hex(source)
source.sub(/^0X/, '0x')
end

def format_binary(node)
node.source.sub(/^0B/, '0b')
def format_binary(source)
source.sub(/^0B/, '0b')
end

def format_decimal(node)
node.source.sub(/^0[dD]/, '')
def format_decimal(source)
source.sub(/^0[dD]/, '')
end
end
end
Expand Down
91 changes: 70 additions & 21 deletions spec/rubocop/cop/style/numeric_literal_prefix_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,72 @@

require 'spec_helper'

describe RuboCop::Cop::Style::NumericLiteralPrefix do
subject(:cop) { described_class.new }
describe RuboCop::Cop::Style::NumericLiteralPrefix, :config do
subject(:cop) { described_class.new(config) }

context 'octal literals' do
it 'registers an offense for prefixes `0` and `0O`' do
inspect_source(cop, ['a = 01234',
'b(0O123)'])
expect(cop.offenses.size).to eq(2)
end

it 'does not register offense for lowercase prefix' do
inspect_source(cop, ['a = 0o101',
'b = 0o567'])
expect(cop.messages).to be_empty
end

it 'autocorrects a octal literal starting with 0' do
corrected = autocorrect_source(cop, ['a = 01234'])
expect(corrected).to eq 'a = 0o1234'
context 'when config is zero_with_o' do
let(:cop_config) do
{
'EnforcedOctalStyle' => 'zero_with_o'
}
end

it 'registers an offense for prefixes `0` and `0O`' do
inspect_source(cop, ['a = 01234',
'b(0O1234)'])
expect(cop.offenses.size).to eq(2)
expect(cop.messages.uniq).to eq(['Use 0o for octal literals.'])
expect(cop.highlights).to eq(%w(01234 0O1234))
end

it 'does not register offense for lowercase prefix' do
inspect_source(cop, ['a = 0o101',
'b = 0o567'])
expect(cop.messages).to be_empty
end

it 'autocorrects an octal literal starting with 0' do
corrected = autocorrect_source(cop, ['a = 01234'])
expect(corrected).to eq('a = 0o1234')
end

it 'autocorrects an octal literal starting with 0O' do
corrected = autocorrect_source(cop, ['b(0O1234, a)'])
expect(corrected).to eq('b(0o1234, a)')
end
end

it 'autocorrects a octal literal starting with 0O' do
corrected = autocorrect_source(cop, ['b(0O1234, a)'])
expect(corrected).to eq 'b(0o1234, a)'
context 'when config is zero_only' do
let(:cop_config) do
{
'EnforcedOctalStyle' => 'zero_only'
}
end

it 'registers an offense for prefix `0O` and `0o`' do
inspect_source(cop, ['a = 0O1234',
'b(0o1234)'])
expect(cop.offenses.size).to eq(2)
expect(cop.messages.uniq).to eq(['Use 0 for octal literals.'])
expect(cop.highlights).to eq(%w(0O1234 0o1234))
end

it 'does not register offense for prefix `0`' do
inspect_source(cop, 'b = 0567')
expect(cop.messages).to be_empty
end

it 'autocorrects an octal literal starting with 0O or 0o' do
corrected = autocorrect_source(cop, ['a = 0O1234',
'b(0o1234)'])
expect(corrected).to eq "a = 01234\nb(01234)"
end

it 'does not autocorrect an octal literal starting with 0' do
corrected = autocorrect_source(cop, ['b(01234, a)'])
expect(corrected).to eq 'b(01234, a)'
end
end
end

Expand All @@ -35,6 +77,8 @@
inspect_source(cop, ['a = 0X1AC',
'b(0XABC)'])
expect(cop.offenses.size).to eq(2)
expect(cop.messages.uniq).to eq(['Use 0x for hexadecimal literals.'])
expect(cop.highlights).to eq(%w(0X1AC 0XABC))
end

it 'does not register offense for lowercase prefix' do
Expand All @@ -53,6 +97,8 @@
inspect_source(cop, ['a = 0B10101',
'b(0B111)'])
expect(cop.offenses.size).to eq(2)
expect(cop.messages.uniq).to eq(['Use 0b for binary literals.'])
expect(cop.highlights).to eq(%w(0B10101 0B111))
end

it 'does not register offense for lowercase prefix' do
Expand All @@ -69,8 +115,11 @@
context 'decimal literals' do
it 'registers an offense for prefixes' do
inspect_source(cop, ['a = 0d1234',
'b(0D123)'])
'b(0D1234)'])
expect(cop.offenses.size).to eq(2)
expect(cop.messages.uniq)
.to eq(['Do not use prefixes for decimal literals.'])
expect(cop.highlights).to eq(%w(0d1234 0D1234))
end

it 'does not register offense for no prefix' do
Expand Down

0 comments on commit 9ffa30d

Please sign in to comment.