Skip to content

Commit

Permalink
numeric-separators-style: Add checkOnlyIfSeparator option (#916)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
Co-authored-by: fisker <lionkay@gmail.com>
  • Loading branch information
3 people committed Jan 19, 2021
1 parent 373d43d commit 8d32574
Show file tree
Hide file tree
Showing 3 changed files with 278 additions and 17 deletions.
18 changes: 18 additions & 0 deletions docs/rules/numeric-separators-style.md
Expand Up @@ -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`
Expand Down Expand Up @@ -95,6 +112,7 @@ const foo = 0o12_7777;

```js
{
onlyIfContainsSeparator: false,
hexadecimal: {
minimumDigits: 0,
groupLength: 2
Expand Down
72 changes: 55 additions & 17 deletions 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';
Expand Down Expand Up @@ -47,12 +47,7 @@ function formatNumber(value, options) {
return formatted;
}

function format(value, options) {
const {
prefix = '',
data
} = value.match(/^(?<prefix>0[box])?(?<data>.*)$/i).groups;

function format(value, {prefix, data}, options) {
const formatOption = options[prefix.toLowerCase()];

if (prefix) {
Expand All @@ -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 {
Expand All @@ -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(/^(?<prefix>0[box])?(?<data>.*)$/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({
Expand All @@ -117,6 +146,9 @@ const create = context => {
const formatOptionsSchema = ({minimumDigits, groupLength}) => ({
type: 'object',
properties: {
onlyIfContainsSeparator: {
type: 'boolean'
},
minimumDigits: {
type: 'integer',
minimum: 0,
Expand All @@ -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
}];

Expand Down
205 changes: 205 additions & 0 deletions test/numeric-separators-style.js
@@ -1,3 +1,4 @@
import {outdent} from 'outdent';
import {test} from './utils/test.js';

const error = {
Expand Down Expand Up @@ -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: [
Expand Down Expand Up @@ -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
}
]
});
Expand Down

0 comments on commit 8d32574

Please sign in to comment.