Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

numeric-separators-style: Add onlyIfContainsSeparator option #916

Merged
merged 17 commits into from Jan 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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:
noftaly marked this conversation as resolved.
Show resolved Hide resolved

```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