diff --git a/docs/rules/numeric-separators-style.md b/docs/rules/numeric-separators-style.md index 843f75d8d9..a15986233e 100644 --- a/docs/rules/numeric-separators-style.md +++ b/docs/rules/numeric-separators-style.md @@ -33,6 +33,23 @@ If you want a custom group size for a specific number type, you can specify it h There are four number types; [`hexadecimal`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Hexadecimal), [`binary`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Binary), [`octal`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Octal) and [`number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type). Each of them can be associated with an object containing the following options: +**`onlyIfContainsSeparator`** + +Type: `boolean`\ +Default: `false` + +Check if the group sizes are valid **only** if there are groups separated with an `_`. +You can set it at top-level, or override for each specific number type. + +Example: + +```js +// eslint unicorn/numeric-separators-style: ["error", {"onlyIfContainsSeparator": true, "binary": {"onlyIfContainsSeparator": false}] +const number = 100000; // Pass, this number does not contains separators +const binary = 0b101010001; // Fail, `binary` type don't require separators +const hexadecimal = 0xD_EED_BEE_F; // Fail, it contain separators and it's incorrectly grouped +``` + **`minimumDigits`** Type: `number` @@ -95,6 +112,7 @@ const foo = 0o12_7777; ```js { + onlyIfContainsSeparator: false, hexadecimal: { minimumDigits: 0, groupLength: 2 diff --git a/rules/numeric-separators-style.js b/rules/numeric-separators-style.js index 5411820476..5810e99ae5 100644 --- a/rules/numeric-separators-style.js +++ b/rules/numeric-separators-style.js @@ -1,5 +1,5 @@ 'use strict'; -const {defaultsDeep, fromPairs} = require('lodash'); +const {fromPairs} = require('lodash'); const getDocumentationUrl = require('./utils/get-documentation-url'); const MESSAGE_ID = 'numeric-separators-style'; @@ -47,12 +47,7 @@ function formatNumber(value, options) { return formatted; } -function format(value, options) { - const { - prefix = '', - data - } = value.match(/^(?0[box])?(?.*)$/i).groups; - +function format(value, {prefix, data}, options) { const formatOption = options[prefix.toLowerCase()]; if (prefix) { @@ -70,18 +65,44 @@ function format(value, options) { } const defaultOptions = { - hexadecimal: {minimumDigits: 0, groupLength: 2}, binary: {minimumDigits: 0, groupLength: 4}, octal: {minimumDigits: 0, groupLength: 4}, + hexadecimal: {minimumDigits: 0, groupLength: 2}, number: {minimumDigits: 5, groupLength: 3} }; const create = context => { - const rawOptions = defaultsDeep({}, context.options[0], defaultOptions); + const { + onlyIfContainsSeparator, + binary, + octal, + hexadecimal, + number + } = { + onlyIfContainsSeparator: false, + ...context.options[0] + }; + const options = { - '0b': rawOptions.binary, - '0o': rawOptions.octal, - '0x': rawOptions.hexadecimal, - '': rawOptions.number + '0b': { + onlyIfContainsSeparator, + ...defaultOptions.binary, + ...binary + }, + '0o': { + onlyIfContainsSeparator, + ...defaultOptions.octal, + ...octal + }, + '0x': { + onlyIfContainsSeparator, + ...defaultOptions.hexadecimal, + ...hexadecimal + }, + '': { + onlyIfContainsSeparator, + ...defaultOptions.number, + ...number + } }; return { @@ -101,7 +122,15 @@ const create = context => { return; } - const formatted = format(number.replace(/_/g, ''), options) + suffix; + const strippedNumber = number.replace(/_/g, ''); + const {prefix = '', data} = strippedNumber.match(/^(?0[box])?(?.*)$/i).groups; + + const {onlyIfContainsSeparator} = options[prefix.toLowerCase()]; + if (onlyIfContainsSeparator && !raw.includes('_')) { + return; + } + + const formatted = format(strippedNumber, {prefix, data}, options) + suffix; if (raw !== formatted) { context.report({ @@ -117,6 +146,9 @@ const create = context => { const formatOptionsSchema = ({minimumDigits, groupLength}) => ({ type: 'object', properties: { + onlyIfContainsSeparator: { + type: 'boolean' + }, minimumDigits: { type: 'integer', minimum: 0, @@ -133,9 +165,15 @@ const formatOptionsSchema = ({minimumDigits, groupLength}) => ({ const schema = [{ type: 'object', - properties: fromPairs( - Object.entries(defaultOptions).map(([type, options]) => [type, formatOptionsSchema(options)]) - ), + properties: { + ...fromPairs( + Object.entries(defaultOptions).map(([type, options]) => [type, formatOptionsSchema(options)]) + ), + onlyIfContainsSeparator: { + type: 'boolean', + default: false + } + }, additionalProperties: false }]; diff --git a/test/numeric-separators-style.js b/test/numeric-separators-style.js index 784377401d..62d2dcdab0 100644 --- a/test/numeric-separators-style.js +++ b/test/numeric-separators-style.js @@ -1,3 +1,4 @@ +import {outdent} from 'outdent'; import {test} from './utils/test.js'; const error = { @@ -104,6 +105,123 @@ test({ { code: 'const foo = 0b111', options: [{number: {minimumDigits: 3, groupLength: 1}}] + }, + { + code: outdent` + const binary = 0b10101010; + const octal = 0o76543210; + const hexadecimal = 0xfedcba97; + const number = 12345678.12345678e12345678; + `, + options: [{ + onlyIfContainsSeparator: true + }] + }, + { + code: outdent` + const binary = 0b1010_1010; + const octal = 0o76543210; + const hexadecimal = 0xfedcba97; + const number = 12345678.12345678e12345678; + `, + options: [{ + onlyIfContainsSeparator: true, + binary: { + onlyIfContainsSeparator: false + } + }] + }, + { + code: outdent` + const binary = 0b10_10_10_10; + const octal = 0o76543210; + const hexadecimal = 0xfedcba97; + const number = 12345678.12345678e12345678; + `, + options: [{ + onlyIfContainsSeparator: true, + binary: { + onlyIfContainsSeparator: false, + groupLength: 2 + } + }] + }, + { + code: outdent` + const binary = 0b10101010; + const octal = 0o7654_3210; + const hexadecimal = 0xfe_dc_ba_97; + const number = 12_345_678.123_456_78e12_345_678; + `, + options: [{ + binary: { + onlyIfContainsSeparator: true + } + }] + }, + { + code: 'const foo = 12345', + options: [{number: {onlyIfContainsSeparator: true}}] + }, + { + code: 'const foo = 12345678', + options: [{number: {onlyIfContainsSeparator: true}}] + }, + { + code: 'const foo = 12_345', + options: [{number: {onlyIfContainsSeparator: true}}] + }, + { + code: 'const foo = 1789.123_432_42', + options: [{number: {onlyIfContainsSeparator: true}}] + }, + { + code: 'const foo = -100_000e+100_000', + options: [{number: {onlyIfContainsSeparator: true}}] + }, + { + code: 'const foo = -100000e+100000', + options: [{number: {onlyIfContainsSeparator: true}}] + }, + { + code: 'const foo = -282_932 - (1938 / 10_000) * .1 + 18.100_000_2', + options: [{number: {onlyIfContainsSeparator: true}}] + }, + { + code: 'const foo = 0xA_B_C_D_E', + options: [{hexadecimal: {onlyIfContainsSeparator: true, groupLength: 1}}] + }, + { + code: 'const foo = 0o7777', + options: [{octal: {onlyIfContainsSeparator: true, minimumDigits: 4}}] + }, + { + code: 'const foo = 0xABCDEF012', + options: [{hexadecimal: {onlyIfContainsSeparator: true}}] + }, + { + code: 'const foo = 0o777777', + options: [{octal: {onlyIfContainsSeparator: true, minimumDigits: 3}}] + }, + { + code: 'const foo = 0o777777', + options: [{octal: {onlyIfContainsSeparator: true, minimumDigits: 3, groupLength: 2}}] + }, + { + code: 'const foo = 0o777_777', + options: [{octal: {onlyIfContainsSeparator: true, minimumDigits: 2, groupLength: 3}}] + }, + { + code: 'const foo = 0b01010101', + options: [{onlyIfContainsSeparator: true, binary: {onlyIfContainsSeparator: true}}] + }, + { + code: 'const foo = 0b0101_0101', + options: [{onlyIfContainsSeparator: false, binary: {onlyIfContainsSeparator: true}}] + }, + { + code: 'const foo = 0b0101_0101', + options: [{onlyIfContainsSeparator: false, binary: {onlyIfContainsSeparator: false}}] } ], invalid: [ @@ -327,6 +445,93 @@ test({ options: [{number: {minimumDigits: 3, groupLength: 2}}], errors: [error], output: 'const foo = 0b111' + }, + { + code: 'const foo = -100000e+100000', + options: [{number: {onlyIfContainsSeparator: false}}], + errors: [error], + output: 'const foo = -100_000e+100_000' + }, + { + code: outdent` + const binary = 0b10_101010; + const octal = 0o76_543210; + const hexadecimal = 0xfe_dcba97; + const number = 12_345678.12345678e12345678; + `, + output: outdent` + const binary = 0b1010_1010; + const octal = 0o7654_3210; + const hexadecimal = 0xfe_dc_ba_97; + const number = 12_345_678.123_456_78e12_345_678; + `, + options: [{ + onlyIfContainsSeparator: true + }], + errors: 4 + }, + { + code: outdent` + const binary = 0b10101010; + const octal = 0o76_543210; + const hexadecimal = 0xfe_dcba97; + const number = 12_345678.12345678e12345678; + `, + output: outdent` + const binary = 0b1010_1010; + const octal = 0o7654_3210; + const hexadecimal = 0xfe_dc_ba_97; + const number = 12_345_678.123_456_78e12_345_678; + `, + options: [{ + onlyIfContainsSeparator: true, + binary: { + onlyIfContainsSeparator: false + } + }], + errors: 4 + }, + { + code: outdent` + const binary = 0b10101010; + const octal = 0o76_543210; + const hexadecimal = 0xfe_dcba97; + const number = 12_345678.12345678e12345678; + `, + output: outdent` + const binary = 0b10_10_10_10; + const octal = 0o7654_3210; + const hexadecimal = 0xfe_dc_ba_97; + const number = 12_345_678.123_456_78e12_345_678; + `, + options: [{ + onlyIfContainsSeparator: true, + binary: { + onlyIfContainsSeparator: false, + groupLength: 2 + } + }], + errors: 4 + }, + { + code: outdent` + const binary = 0b10_101010; + const octal = 0o76543210; + const hexadecimal = 0xfedcba97; + const number = 12345678.12345678e12345678; + `, + output: outdent` + const binary = 0b1010_1010; + const octal = 0o7654_3210; + const hexadecimal = 0xfe_dc_ba_97; + const number = 12_345_678.123_456_78e12_345_678; + `, + options: [{ + binary: { + onlyIfContainsSeparator: true + } + }], + errors: 4 } ] });