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

Add options to escape-case and number-literal-case #2559

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion docs/rules/escape-case.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<!-- end auto-generated rule header -->
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` -->

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

## Fail

Expand All @@ -26,3 +26,29 @@ const foo = '\uD834';
const foo = '\u{1D306}';
const foo = '\cA';
```

## Options

Type: `string`\
Default: `'uppercase'`

- `'uppercase'` (default)
- Always use escape sequence values with uppercase characters.
- `'lowercase'`
- Always use escape sequence values with lowercase characters.

```js
// eslint unicorn/escape-case: ["error", "lowercase"]

// Fail
const foo = '\xA9';
const foo = '\uD834';
const foo = '\u{1D306}';
const foo = '\cA';

// Pass
const foo = '\xa9';
const foo = '\ud834';
const foo = '\u{1d306}';
const foo = '\ca';
```
95 changes: 94 additions & 1 deletion docs/rules/number-literal-case.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<!-- end auto-generated rule header -->
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` -->

Differentiating the casing of the identifier and value clearly separates them and makes your code more readable.
Differentiating the casing of the identifier and value clearly separates them and makes your code more readable. The default style is:

- 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).
- Lowercase `e` for exponential notation.
Expand Down Expand Up @@ -64,3 +64,96 @@ const foo = 0xFFn;
```js
const foo = 2e+5;
```

## Options

Type: `object`

Default options:

```js
{
'unicorn/number-literal-case': [
'error',
{
hexadecimalValue: 'uppercase',
radixIdentifier: 'lowercase',
exponentialNotation: 'lowercase'
}
]
}
```

### hexadecimalValue

Type: `'uppercase' | 'lowercase' | 'ignore'`\
Default: `'uppercase'`

Specify whether the hexadecimal number value (ABCDEF) should be in `uppercase`, `lowercase`, or `ignore` the check. Defaults to `'uppercase'`.

Example:
```js
// eslint unicorn/number-literal-case: ["error", {"hexadecimalValue": "lowercase"}]

// Fail
const foo = 0XFF;
const foo = 0xFF;
const foo = 0XFFn;
const foo = 0xFFn;

// Pass
const foo = 0Xff;
const foo = 0xff;
const foo = 0Xffn;
const foo = 0xffn;
```

### radixIdentifier

Type: `'uppercase' | 'lowercase' | 'ignore'`\
Default: `'lowercase'`

Specify whether the radix indentifer (`0x`, `0o`, `0b`) should be in `uppercase`, `lowercase`, or `ignore` the check. Defaults to `'lowercase'`.

Example:
```js
// eslint unicorn/number-literal-case: ["error", {"radixIdentifier": "uppercase"}]

// Fail
const foo = 0xFF;
const foo = 0o76;
const foo = 0b10;
const foo = 0xFFn;
const foo = 0o76n;
const foo = 0b10n;

// Pass
const foo = 0XFF;
const foo = 0O76;
const foo = 0B10;
const foo = 0XFFn;
const foo = 0O76n;
const foo = 0B10n;
```

### exponentialNotation

Type: `'uppercase' | 'lowercase' | 'ignore'`\
Default: `'lowercase'`

Specify whether the exponential notation (`e`) should be in `uppercase`, `lowercase`, or `ignore` the check. Defaults to `'lowercase'`.

Example:
```js
// eslint unicorn/number-literal-case: ["error", {"exponentialNotation": "uppercase"}]

// Fail
const foo = 2e-5;
const foo = 2e+5;
const foo = 2e99;

// Pass
const foo = 2E-5;
const foo = 2E+5;
const foo = 2E99;
```
33 changes: 24 additions & 9 deletions rules/escape-case.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
import {replaceTemplateElement} from './fix/index.js';
import {isRegexLiteral, isStringLiteral, isTaggedTemplateLiteral} from './ast/index.js';

const MESSAGE_ID = 'escape-case';
const MESSAGE_ID_UPPERCASE = 'escape-uppercase';
const MESSAGE_ID_LOWERCASE = 'escape-lowercase';
const messages = {
[MESSAGE_ID]: 'Use uppercase characters for the value of the escape sequence.',
[MESSAGE_ID_UPPERCASE]: 'Use uppercase characters for the value of the escape sequence.',
[MESSAGE_ID_LOWERCASE]: 'Use lowercase characters for the value of the escape sequence.',
};

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

if (fixed !== original) {
return {
node,
messageId: MESSAGE_ID,
messageId: lowercase ? MESSAGE_ID_LOWERCASE : MESSAGE_ID_UPPERCASE,
fix: fixer => fix ? fix(fixer, fixed) : fixer.replaceText(node, fixed),
};
}
};

/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
const lowercase = context.options[0] === 'lowercase';

context.on('Literal', node => {
if (isStringLiteral(node)) {
return getProblem({
node,
original: node.raw,
lowercase,
});
}
});
Expand All @@ -36,7 +41,8 @@ const create = context => {
return getProblem({
node,
original: node.raw,
regex: escapePatternWithLowercase,
regex: escapePatternCase,
lowercase,
});
}
});
Expand All @@ -49,21 +55,30 @@ const create = context => {
return getProblem({
node,
original: node.value.raw,
lowercase,
fix: (fixer, fixed) => replaceTemplateElement(fixer, node, fixed),
});
});
};

const schema = [
{
enum: ['uppercase', 'lowercase'],
},
];

/** @type {import('eslint').Rule.RuleModule} */
const config = {
create,
meta: {
type: 'suggestion',
docs: {
description: 'Require escape sequences to use uppercase values.',
description: 'Require escape sequences to use uppercase or lowercase values.',
recommended: true,
},
fixable: 'code',
schema,
defaultOptions: ['uppercase'],
messages,
},
};
Expand Down
79 changes: 72 additions & 7 deletions rules/number-literal-case.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,62 @@
[MESSAGE_ID]: 'Invalid number literal casing.',
};

const fix = raw => {
let fixed = raw.toLowerCase();
if (fixed.startsWith('0x')) {
fixed = '0x' + fixed.slice(2).toUpperCase();
/**
@param {string} raw
@param {Options[keyof Options]} option
*/
const convertCase = (raw, option) => {
if (option === 'uppercase') {
return raw.toUpperCase();
}

if (option === 'lowercase') {
return raw.toLowerCase();
}

return raw;
};

/**
@param {string} raw
@param {Options} options
*/
const fix = (raw, options) => {
let fixed = raw;
let isSpecialBase = false; // Indicates that the number is hexadecimal, octal, or binary.
fixed = fixed.replace(/^(0[xob])(.*)/i, (_, radix, value) => {

Check failure on line 32 in rules/number-literal-case.js

View workflow job for this annotation

GitHub Actions / run-rules-on-codebase

/^(0[xob])(.*)/i can be optimized to /^(0[box])(.*)/i
isSpecialBase = true;
radix = convertCase(radix, options.radixIdentifier);
if (radix.toLowerCase() === '0x') {
value = convertCase(value, options.hexadecimalValue);
}

return radix + value;
});

if (!isSpecialBase) {
fixed = fixed.replaceAll(/e/ig, expo => convertCase(expo, options.exponentialNotation));

Check failure on line 43 in rules/number-literal-case.js

View workflow job for this annotation

GitHub Actions / run-rules-on-codebase

/e/ig can be optimized to /e/gi
}

return fixed;
};

/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
const create = context => ({
Literal(node) {
const {raw} = node;

/** @type {Options} */
const options = context.options[0] ?? {};
options.hexadecimalValue ??= 'uppercase';
options.radixIdentifier ??= 'lowercase';
options.exponentialNotation ??= 'lowercase';

let fixed = raw;
if (isNumberLiteral(node)) {
fixed = fix(raw);
fixed = fix(raw, options);
} else if (isBigIntLiteral(node)) {
fixed = fix(raw.slice(0, -1)) + 'n';
fixed = fix(raw.slice(0, -1), options) + 'n';
}

if (raw !== fixed) {
Expand All @@ -37,6 +74,28 @@
},
});

/** @typedef {Record<keyof typeof schema[0]["properties"], typeof caseEnum["enum"][number]>} Options */

const caseEnum = /** @type {const} */ ({
enum: [
'uppercase',
'lowercase',
'ignore',
],
});

const schema = [
{
type: 'object',
additionalProperties: false,
properties: {
hexadecimalValue: caseEnum,
radixIdentifier: caseEnum,
exponentialNotation: caseEnum,
},
},
];

/** @type {import('eslint').Rule.RuleModule} */
const config = {
create: checkVueTemplate(create),
Expand All @@ -47,6 +106,12 @@
recommended: true,
},
fixable: 'code',
schema,
defaultOptions: [{
hexadecimalValue: 'uppercase',
radixIdentifier: 'lowercase',
exponentialNotation: 'lowercase',
}],
messages,
},
};
Expand Down
Loading
Loading