Skip to content

Commit 8d32574

Browse files
noftalysindresorhusfisker
authored
numeric-separators-style: Add checkOnlyIfSeparator option (#916)
Co-authored-by: Sindre Sorhus <[email protected]> Co-authored-by: fisker <[email protected]>
1 parent 373d43d commit 8d32574

File tree

3 files changed

+278
-17
lines changed

3 files changed

+278
-17
lines changed

docs/rules/numeric-separators-style.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,23 @@ If you want a custom group size for a specific number type, you can specify it h
3333

3434
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:
3535

36+
**`onlyIfContainsSeparator`**
37+
38+
Type: `boolean`\
39+
Default: `false`
40+
41+
Check if the group sizes are valid **only** if there are groups separated with an `_`.
42+
You can set it at top-level, or override for each specific number type.
43+
44+
Example:
45+
46+
```js
47+
// eslint unicorn/numeric-separators-style: ["error", {"onlyIfContainsSeparator": true, "binary": {"onlyIfContainsSeparator": false}]
48+
const number = 100000; // Pass, this number does not contains separators
49+
const binary = 0b101010001; // Fail, `binary` type don't require separators
50+
const hexadecimal = 0xD_EED_BEE_F; // Fail, it contain separators and it's incorrectly grouped
51+
```
52+
3653
**`minimumDigits`**
3754

3855
Type: `number`
@@ -95,6 +112,7 @@ const foo = 0o12_7777;
95112

96113
```js
97114
{
115+
onlyIfContainsSeparator: false,
98116
hexadecimal: {
99117
minimumDigits: 0,
100118
groupLength: 2

rules/numeric-separators-style.js

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'use strict';
2-
const {defaultsDeep, fromPairs} = require('lodash');
2+
const {fromPairs} = require('lodash');
33
const getDocumentationUrl = require('./utils/get-documentation-url');
44

55
const MESSAGE_ID = 'numeric-separators-style';
@@ -47,12 +47,7 @@ function formatNumber(value, options) {
4747
return formatted;
4848
}
4949

50-
function format(value, options) {
51-
const {
52-
prefix = '',
53-
data
54-
} = value.match(/^(?<prefix>0[box])?(?<data>.*)$/i).groups;
55-
50+
function format(value, {prefix, data}, options) {
5651
const formatOption = options[prefix.toLowerCase()];
5752

5853
if (prefix) {
@@ -70,18 +65,44 @@ function format(value, options) {
7065
}
7166

7267
const defaultOptions = {
73-
hexadecimal: {minimumDigits: 0, groupLength: 2},
7468
binary: {minimumDigits: 0, groupLength: 4},
7569
octal: {minimumDigits: 0, groupLength: 4},
70+
hexadecimal: {minimumDigits: 0, groupLength: 2},
7671
number: {minimumDigits: 5, groupLength: 3}
7772
};
7873
const create = context => {
79-
const rawOptions = defaultsDeep({}, context.options[0], defaultOptions);
74+
const {
75+
onlyIfContainsSeparator,
76+
binary,
77+
octal,
78+
hexadecimal,
79+
number
80+
} = {
81+
onlyIfContainsSeparator: false,
82+
...context.options[0]
83+
};
84+
8085
const options = {
81-
'0b': rawOptions.binary,
82-
'0o': rawOptions.octal,
83-
'0x': rawOptions.hexadecimal,
84-
'': rawOptions.number
86+
'0b': {
87+
onlyIfContainsSeparator,
88+
...defaultOptions.binary,
89+
...binary
90+
},
91+
'0o': {
92+
onlyIfContainsSeparator,
93+
...defaultOptions.octal,
94+
...octal
95+
},
96+
'0x': {
97+
onlyIfContainsSeparator,
98+
...defaultOptions.hexadecimal,
99+
...hexadecimal
100+
},
101+
'': {
102+
onlyIfContainsSeparator,
103+
...defaultOptions.number,
104+
...number
105+
}
85106
};
86107

87108
return {
@@ -101,7 +122,15 @@ const create = context => {
101122
return;
102123
}
103124

104-
const formatted = format(number.replace(/_/g, ''), options) + suffix;
125+
const strippedNumber = number.replace(/_/g, '');
126+
const {prefix = '', data} = strippedNumber.match(/^(?<prefix>0[box])?(?<data>.*)$/i).groups;
127+
128+
const {onlyIfContainsSeparator} = options[prefix.toLowerCase()];
129+
if (onlyIfContainsSeparator && !raw.includes('_')) {
130+
return;
131+
}
132+
133+
const formatted = format(strippedNumber, {prefix, data}, options) + suffix;
105134

106135
if (raw !== formatted) {
107136
context.report({
@@ -117,6 +146,9 @@ const create = context => {
117146
const formatOptionsSchema = ({minimumDigits, groupLength}) => ({
118147
type: 'object',
119148
properties: {
149+
onlyIfContainsSeparator: {
150+
type: 'boolean'
151+
},
120152
minimumDigits: {
121153
type: 'integer',
122154
minimum: 0,
@@ -133,9 +165,15 @@ const formatOptionsSchema = ({minimumDigits, groupLength}) => ({
133165

134166
const schema = [{
135167
type: 'object',
136-
properties: fromPairs(
137-
Object.entries(defaultOptions).map(([type, options]) => [type, formatOptionsSchema(options)])
138-
),
168+
properties: {
169+
...fromPairs(
170+
Object.entries(defaultOptions).map(([type, options]) => [type, formatOptionsSchema(options)])
171+
),
172+
onlyIfContainsSeparator: {
173+
type: 'boolean',
174+
default: false
175+
}
176+
},
139177
additionalProperties: false
140178
}];
141179

test/numeric-separators-style.js

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {outdent} from 'outdent';
12
import {test} from './utils/test.js';
23

34
const error = {
@@ -104,6 +105,123 @@ test({
104105
{
105106
code: 'const foo = 0b111',
106107
options: [{number: {minimumDigits: 3, groupLength: 1}}]
108+
},
109+
{
110+
code: outdent`
111+
const binary = 0b10101010;
112+
const octal = 0o76543210;
113+
const hexadecimal = 0xfedcba97;
114+
const number = 12345678.12345678e12345678;
115+
`,
116+
options: [{
117+
onlyIfContainsSeparator: true
118+
}]
119+
},
120+
{
121+
code: outdent`
122+
const binary = 0b1010_1010;
123+
const octal = 0o76543210;
124+
const hexadecimal = 0xfedcba97;
125+
const number = 12345678.12345678e12345678;
126+
`,
127+
options: [{
128+
onlyIfContainsSeparator: true,
129+
binary: {
130+
onlyIfContainsSeparator: false
131+
}
132+
}]
133+
},
134+
{
135+
code: outdent`
136+
const binary = 0b10_10_10_10;
137+
const octal = 0o76543210;
138+
const hexadecimal = 0xfedcba97;
139+
const number = 12345678.12345678e12345678;
140+
`,
141+
options: [{
142+
onlyIfContainsSeparator: true,
143+
binary: {
144+
onlyIfContainsSeparator: false,
145+
groupLength: 2
146+
}
147+
}]
148+
},
149+
{
150+
code: outdent`
151+
const binary = 0b10101010;
152+
const octal = 0o7654_3210;
153+
const hexadecimal = 0xfe_dc_ba_97;
154+
const number = 12_345_678.123_456_78e12_345_678;
155+
`,
156+
options: [{
157+
binary: {
158+
onlyIfContainsSeparator: true
159+
}
160+
}]
161+
},
162+
{
163+
code: 'const foo = 12345',
164+
options: [{number: {onlyIfContainsSeparator: true}}]
165+
},
166+
{
167+
code: 'const foo = 12345678',
168+
options: [{number: {onlyIfContainsSeparator: true}}]
169+
},
170+
{
171+
code: 'const foo = 12_345',
172+
options: [{number: {onlyIfContainsSeparator: true}}]
173+
},
174+
{
175+
code: 'const foo = 1789.123_432_42',
176+
options: [{number: {onlyIfContainsSeparator: true}}]
177+
},
178+
{
179+
code: 'const foo = -100_000e+100_000',
180+
options: [{number: {onlyIfContainsSeparator: true}}]
181+
},
182+
{
183+
code: 'const foo = -100000e+100000',
184+
options: [{number: {onlyIfContainsSeparator: true}}]
185+
},
186+
{
187+
code: 'const foo = -282_932 - (1938 / 10_000) * .1 + 18.100_000_2',
188+
options: [{number: {onlyIfContainsSeparator: true}}]
189+
},
190+
{
191+
code: 'const foo = 0xA_B_C_D_E',
192+
options: [{hexadecimal: {onlyIfContainsSeparator: true, groupLength: 1}}]
193+
},
194+
{
195+
code: 'const foo = 0o7777',
196+
options: [{octal: {onlyIfContainsSeparator: true, minimumDigits: 4}}]
197+
},
198+
{
199+
code: 'const foo = 0xABCDEF012',
200+
options: [{hexadecimal: {onlyIfContainsSeparator: true}}]
201+
},
202+
{
203+
code: 'const foo = 0o777777',
204+
options: [{octal: {onlyIfContainsSeparator: true, minimumDigits: 3}}]
205+
},
206+
{
207+
code: 'const foo = 0o777777',
208+
options: [{octal: {onlyIfContainsSeparator: true, minimumDigits: 3, groupLength: 2}}]
209+
},
210+
{
211+
code: 'const foo = 0o777_777',
212+
options: [{octal: {onlyIfContainsSeparator: true, minimumDigits: 2, groupLength: 3}}]
213+
},
214+
{
215+
code: 'const foo = 0b01010101',
216+
options: [{onlyIfContainsSeparator: true, binary: {onlyIfContainsSeparator: true}}]
217+
},
218+
{
219+
code: 'const foo = 0b0101_0101',
220+
options: [{onlyIfContainsSeparator: false, binary: {onlyIfContainsSeparator: true}}]
221+
},
222+
{
223+
code: 'const foo = 0b0101_0101',
224+
options: [{onlyIfContainsSeparator: false, binary: {onlyIfContainsSeparator: false}}]
107225
}
108226
],
109227
invalid: [
@@ -327,6 +445,93 @@ test({
327445
options: [{number: {minimumDigits: 3, groupLength: 2}}],
328446
errors: [error],
329447
output: 'const foo = 0b111'
448+
},
449+
{
450+
code: 'const foo = -100000e+100000',
451+
options: [{number: {onlyIfContainsSeparator: false}}],
452+
errors: [error],
453+
output: 'const foo = -100_000e+100_000'
454+
},
455+
{
456+
code: outdent`
457+
const binary = 0b10_101010;
458+
const octal = 0o76_543210;
459+
const hexadecimal = 0xfe_dcba97;
460+
const number = 12_345678.12345678e12345678;
461+
`,
462+
output: outdent`
463+
const binary = 0b1010_1010;
464+
const octal = 0o7654_3210;
465+
const hexadecimal = 0xfe_dc_ba_97;
466+
const number = 12_345_678.123_456_78e12_345_678;
467+
`,
468+
options: [{
469+
onlyIfContainsSeparator: true
470+
}],
471+
errors: 4
472+
},
473+
{
474+
code: outdent`
475+
const binary = 0b10101010;
476+
const octal = 0o76_543210;
477+
const hexadecimal = 0xfe_dcba97;
478+
const number = 12_345678.12345678e12345678;
479+
`,
480+
output: outdent`
481+
const binary = 0b1010_1010;
482+
const octal = 0o7654_3210;
483+
const hexadecimal = 0xfe_dc_ba_97;
484+
const number = 12_345_678.123_456_78e12_345_678;
485+
`,
486+
options: [{
487+
onlyIfContainsSeparator: true,
488+
binary: {
489+
onlyIfContainsSeparator: false
490+
}
491+
}],
492+
errors: 4
493+
},
494+
{
495+
code: outdent`
496+
const binary = 0b10101010;
497+
const octal = 0o76_543210;
498+
const hexadecimal = 0xfe_dcba97;
499+
const number = 12_345678.12345678e12345678;
500+
`,
501+
output: outdent`
502+
const binary = 0b10_10_10_10;
503+
const octal = 0o7654_3210;
504+
const hexadecimal = 0xfe_dc_ba_97;
505+
const number = 12_345_678.123_456_78e12_345_678;
506+
`,
507+
options: [{
508+
onlyIfContainsSeparator: true,
509+
binary: {
510+
onlyIfContainsSeparator: false,
511+
groupLength: 2
512+
}
513+
}],
514+
errors: 4
515+
},
516+
{
517+
code: outdent`
518+
const binary = 0b10_101010;
519+
const octal = 0o76543210;
520+
const hexadecimal = 0xfedcba97;
521+
const number = 12345678.12345678e12345678;
522+
`,
523+
output: outdent`
524+
const binary = 0b1010_1010;
525+
const octal = 0o7654_3210;
526+
const hexadecimal = 0xfe_dc_ba_97;
527+
const number = 12_345_678.123_456_78e12_345_678;
528+
`,
529+
options: [{
530+
binary: {
531+
onlyIfContainsSeparator: true
532+
}
533+
}],
534+
errors: 4
330535
}
331536
]
332537
});

0 commit comments

Comments
 (0)