Skip to content

Commit 0f6048c

Browse files
otomadsindresorhus
andauthored
Add options to escape-case and number-literal-case (#2559)
Co-authored-by: Sindre Sorhus <[email protected]>
1 parent 46b3628 commit 0f6048c

File tree

7 files changed

+478
-96
lines changed

7 files changed

+478
-96
lines changed

docs/rules/escape-case.md

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Require escape sequences to use uppercase values
1+
# Require escape sequences to use uppercase or lowercase values
22

33
💼 This rule is enabled in the ✅ `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#recommended-config).
44

@@ -7,22 +7,88 @@
77
<!-- end auto-generated rule header -->
88
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` -->
99

10-
Enforces defining escape sequence values with uppercase characters rather than lowercase ones. This promotes readability by making the escaped value more distinguishable from the identifier.
10+
Enforces a consistent escaped value style by defining escape sequence values with uppercase or lowercase characters. The default style is uppercase, which promotes readability by making the escaped value more distinguishable from the identifier.
1111

12-
## Fail
12+
## Examples
1313

1414
```js
15+
//
1516
const foo = '\xa9';
17+
18+
//
19+
const foo = '\xA9';
20+
```
21+
22+
```js
23+
//
1624
const foo = '\ud834';
25+
26+
//
27+
const foo = '\uD834';
28+
```
29+
30+
```js
31+
//
1732
const foo = '\u{1d306}';
33+
34+
//
35+
const foo = '\u{1D306}';
36+
```
37+
38+
```js
39+
//
1840
const foo = '\ca';
41+
42+
//
43+
const foo = '\cA';
1944
```
2045

21-
## Pass
46+
## Options
47+
48+
Type: `string`\
49+
Default: `'uppercase'`
50+
51+
- `'uppercase'` (default)
52+
- Always use escape sequence values with uppercase characters.
53+
- `'lowercase'`
54+
- Always use escape sequence values with lowercase characters.
55+
56+
Example:
2257

2358
```js
59+
{
60+
'unicorn/escape-case': ['error', 'lowercase']
61+
}
62+
```
63+
64+
```js
65+
//
2466
const foo = '\xA9';
67+
68+
//
69+
const foo = '\xa9';
70+
```
71+
72+
```js
73+
//
2574
const foo = '\uD834';
75+
76+
//
77+
const foo = '\ud834';
78+
```
79+
80+
```js
81+
//
2682
const foo = '\u{1D306}';
83+
84+
//
85+
const foo = '\u{1d306}';
86+
```
87+
88+
```js
89+
//
2790
const foo = '\cA';
91+
92+
//
93+
const foo = '\ca';
2894
```

docs/rules/number-literal-case.md

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,58 +9,98 @@
99

1010
Differentiating the casing of the identifier and value clearly separates them and makes your code more readable.
1111

12-
- Lowercase identifier and uppercase value for [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type) and [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#BigInt_type).
12+
- Lowercase radix identifier `0x` `0o` `0b` for [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type) and [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#BigInt_type).
13+
- Uppercase or lowercase hexadecimal value for [`Number`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type) and [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#BigInt_type).
1314
- Lowercase `e` for exponential notation.
1415

1516
## Fail
1617

1718
[Hexadecimal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Hexadecimal)
1819

1920
```js
21+
//
2022
const foo = 0XFF;
2123
const foo = 0xff;
2224
const foo = 0Xff;
2325
const foo = 0Xffn;
26+
27+
//
28+
const foo = 0xFF;
29+
const foo = 0xFFn;
2430
```
2531

2632
[Binary](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Binary)
2733

2834
```js
35+
//
2936
const foo = 0B10;
3037
const foo = 0B10n;
38+
39+
//
40+
const foo = 0b10;
41+
const foo = 0b10n;
3142
```
3243

3344
[Octal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Octal)
3445

3546
```js
47+
//
3648
const foo = 0O76;
3749
const foo = 0O76n;
50+
51+
//
52+
const foo = 0o76;
53+
const foo = 0o76n;
3854
```
3955

40-
Exponential notation
56+
[Exponential notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Exponential)
4157

4258
```js
59+
//
4360
const foo = 2E-5;
61+
const foo = 2E+5;
62+
const foo = 2E5;
63+
64+
//
65+
const foo = 2e-5;
66+
const foo = 2e+5;
67+
const foo = 2e5;
4468
```
4569

46-
## Pass
70+
## Options
4771

48-
```js
49-
const foo = 0xFF;
50-
```
72+
Type: `object`
5173

52-
```js
53-
const foo = 0b10;
54-
```
74+
### hexadecimalValue
75+
76+
Type: `'uppercase' | 'lowercase'`\
77+
Default: `'uppercase'`
78+
79+
Specify whether the hexadecimal number value (ABCDEF) should be in `uppercase` or `lowercase`.
80+
81+
Note: `0x` is always lowercase and not controlled by this option to maintain readable code.
82+
83+
Example:
5584

5685
```js
57-
const foo = 0o76;
86+
{
87+
'unicorn/number-literal-case': [
88+
'error',
89+
{
90+
hexadecimalValue: 'lowercase',
91+
}
92+
]
93+
}
5894
```
5995

6096
```js
97+
//
98+
const foo = 0XFF;
99+
const foo = 0xFF;
100+
const foo = 0XFFn;
61101
const foo = 0xFFn;
62-
```
63102

64-
```js
65-
const foo = 2e+5;
103+
//
104+
const foo = 0xff;
105+
const foo = 0xffn;
66106
```

readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export default [
6767
| [custom-error-definition](docs/rules/custom-error-definition.md) | Enforce correct `Error` subclassing. | | 🔧 | |
6868
| [empty-brace-spaces](docs/rules/empty-brace-spaces.md) | Enforce no spaces between braces. || 🔧 | |
6969
| [error-message](docs/rules/error-message.md) | Enforce passing a `message` value when creating a built-in error. || | |
70-
| [escape-case](docs/rules/escape-case.md) | Require escape sequences to use uppercase values. || 🔧 | |
70+
| [escape-case](docs/rules/escape-case.md) | Require escape sequences to use uppercase or lowercase values. || 🔧 | |
7171
| [expiring-todo-comments](docs/rules/expiring-todo-comments.md) | Add expiration conditions to TODO comments. || | |
7272
| [explicit-length-check](docs/rules/explicit-length-check.md) | Enforce explicitly comparing the `length` or `size` property of a value. || 🔧 | 💡 |
7373
| [filename-case](docs/rules/filename-case.md) | Enforce a case style for filenames. || | |

rules/escape-case.js

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,37 @@
11
import {replaceTemplateElement} from './fix/index.js';
22
import {isRegexLiteral, isStringLiteral, isTaggedTemplateLiteral} from './ast/index.js';
33

4-
const MESSAGE_ID = 'escape-case';
4+
const MESSAGE_ID_UPPERCASE = 'escape-uppercase';
5+
const MESSAGE_ID_LOWERCASE = 'escape-lowercase';
56
const messages = {
6-
[MESSAGE_ID]: 'Use uppercase characters for the value of the escape sequence.',
7+
[MESSAGE_ID_UPPERCASE]: 'Use uppercase characters for the value of the escape sequence.',
8+
[MESSAGE_ID_LOWERCASE]: 'Use lowercase characters for the value of the escape sequence.',
79
};
810

9-
const escapeWithLowercase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+})/g;
10-
const escapePatternWithLowercase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+}|c[a-z])/g;
11-
const getProblem = ({node, original, regex = escapeWithLowercase, fix}) => {
12-
const fixed = original.replace(regex, data => data.slice(0, 1) + data.slice(1).toUpperCase());
11+
const escapeCase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+})/g;
12+
const escapePatternCase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+}|c[A-Za-z])/g;
13+
const getProblem = ({node, original, regex = escapeCase, lowercase, fix}) => {
14+
const fixed = original.replace(regex, data => data[0] + data.slice(1)[lowercase ? 'toLowerCase' : 'toUpperCase']());
1315

1416
if (fixed !== original) {
1517
return {
1618
node,
17-
messageId: MESSAGE_ID,
19+
messageId: lowercase ? MESSAGE_ID_LOWERCASE : MESSAGE_ID_UPPERCASE,
1820
fix: fixer => fix ? fix(fixer, fixed) : fixer.replaceText(node, fixed),
1921
};
2022
}
2123
};
2224

2325
/** @param {import('eslint').Rule.RuleContext} context */
2426
const create = context => {
27+
const lowercase = context.options[0] === 'lowercase';
28+
2529
context.on('Literal', node => {
2630
if (isStringLiteral(node)) {
2731
return getProblem({
2832
node,
2933
original: node.raw,
34+
lowercase,
3035
});
3136
}
3237
});
@@ -36,7 +41,8 @@ const create = context => {
3641
return getProblem({
3742
node,
3843
original: node.raw,
39-
regex: escapePatternWithLowercase,
44+
regex: escapePatternCase,
45+
lowercase,
4046
});
4147
}
4248
});
@@ -49,21 +55,30 @@ const create = context => {
4955
return getProblem({
5056
node,
5157
original: node.value.raw,
58+
lowercase,
5259
fix: (fixer, fixed) => replaceTemplateElement(fixer, node, fixed),
5360
});
5461
});
5562
};
5663

64+
const schema = [
65+
{
66+
enum: ['uppercase', 'lowercase'],
67+
},
68+
];
69+
5770
/** @type {import('eslint').Rule.RuleModule} */
5871
const config = {
5972
create,
6073
meta: {
6174
type: 'suggestion',
6275
docs: {
63-
description: 'Require escape sequences to use uppercase values.',
76+
description: 'Require escape sequences to use uppercase or lowercase values.',
6477
recommended: true,
6578
},
6679
fixable: 'code',
80+
schema,
81+
defaultOptions: ['uppercase'],
6782
messages,
6883
},
6984
};

rules/number-literal-case.js

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,33 @@ const messages = {
66
[MESSAGE_ID]: 'Invalid number literal casing.',
77
};
88

9-
const fix = raw => {
9+
/**
10+
@param {string} raw
11+
@param {Options} options
12+
*/
13+
const fix = (raw, {hexadecimalValue}) => {
1014
let fixed = raw.toLowerCase();
1115
if (fixed.startsWith('0x')) {
12-
fixed = '0x' + fixed.slice(2).toUpperCase();
16+
fixed = '0x' + fixed.slice(2)[hexadecimalValue === 'lowercase' ? 'toLowerCase' : 'toUpperCase']();
1317
}
1418

1519
return fixed;
1620
};
1721

1822
/** @param {import('eslint').Rule.RuleContext} context */
19-
const create = () => ({
23+
const create = context => ({
2024
Literal(node) {
2125
const {raw} = node;
2226

27+
/** @type {Options} */
28+
const options = context.options[0] ?? {};
29+
options.hexadecimalValue ??= 'uppercase';
30+
2331
let fixed = raw;
2432
if (isNumberLiteral(node)) {
25-
fixed = fix(raw);
33+
fixed = fix(raw, options);
2634
} else if (isBigIntLiteral(node)) {
27-
fixed = fix(raw.slice(0, -1)) + 'n';
35+
fixed = fix(raw.slice(0, -1), options) + 'n';
2836
}
2937

3038
if (raw !== fixed) {
@@ -37,6 +45,22 @@ const create = () => ({
3745
},
3846
});
3947

48+
/** @typedef {Record<keyof typeof schema[0]["properties"], typeof caseEnum["enum"][number]>} Options */
49+
50+
const caseEnum = /** @type {const} */ ({
51+
enum: ['uppercase', 'lowercase'],
52+
});
53+
54+
const schema = [
55+
{
56+
type: 'object',
57+
additionalProperties: false,
58+
properties: {
59+
hexadecimalValue: caseEnum,
60+
},
61+
},
62+
];
63+
4064
/** @type {import('eslint').Rule.RuleModule} */
4165
const config = {
4266
create: checkVueTemplate(create),
@@ -47,6 +71,10 @@ const config = {
4771
recommended: true,
4872
},
4973
fixable: 'code',
74+
schema,
75+
defaultOptions: [{
76+
hexadecimalValue: 'uppercase',
77+
}],
5078
messages,
5179
},
5280
};

0 commit comments

Comments
 (0)