Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 70 additions & 4 deletions docs/rules/escape-case.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Require escape sequences to use uppercase values
# Require escape sequences to use uppercase or lowercase values

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

Expand All @@ -7,22 +7,88 @@
<!-- 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
## Examples

```js
// ❌
const foo = '\xa9';

// ✅
const foo = '\xA9';
```

```js
// ❌
const foo = '\ud834';

// ✅
const foo = '\uD834';
```

```js
// ❌
const foo = '\u{1d306}';

// ✅
const foo = '\u{1D306}';
```

```js
// ❌
const foo = '\ca';

// ✅
const foo = '\cA';
```

## Pass
## Options

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

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

Example:

```js
{
'unicorn/escape-case': ['error', 'lowercase']
}
```

```js
// ❌
const foo = '\xA9';

// ✅
const foo = '\xa9';
```

```js
// ❌
const foo = '\uD834';

// ✅
const foo = '\ud834';
```

```js
// ❌
const foo = '\u{1D306}';

// ✅
const foo = '\u{1d306}';
```

```js
// ❌
const foo = '\cA';

// ✅
const foo = '\ca';
```
66 changes: 53 additions & 13 deletions docs/rules/number-literal-case.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,58 +9,98 @@

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

- 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 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).
- 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).
- Lowercase `e` for exponential notation.

## Fail

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

```js
// ❌
const foo = 0XFF;
const foo = 0xff;
const foo = 0Xff;
const foo = 0Xffn;

// ✅
const foo = 0xFF;
const foo = 0xFFn;
```

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

```js
// ❌
const foo = 0B10;
const foo = 0B10n;

// ✅
const foo = 0b10;
const foo = 0b10n;
```

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

```js
// ❌
const foo = 0O76;
const foo = 0O76n;

// ✅
const foo = 0o76;
const foo = 0o76n;
```

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

```js
// ❌
const foo = 2E-5;
const foo = 2E+5;
const foo = 2E5;

// ✅
const foo = 2e-5;
const foo = 2e+5;
const foo = 2e5;
```

## Pass
## Options

```js
const foo = 0xFF;
```
Type: `object`

```js
const foo = 0b10;
```
### hexadecimalValue

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

Specify whether the hexadecimal number value (ABCDEF) should be in `uppercase` or `lowercase`.

Note: `0x` is always lowercase and not controlled by this option to maintain readable code.

Example:

```js
const foo = 0o76;
{
'unicorn/number-literal-case': [
'error',
{
hexadecimalValue: 'lowercase',
}
]
}
```

```js
// ❌
const foo = 0XFF;
const foo = 0xFF;
const foo = 0XFFn;
const foo = 0xFFn;
```

```js
const foo = 2e+5;
// ✅
const foo = 0xff;
const foo = 0xffn;
```
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default [
| [custom-error-definition](docs/rules/custom-error-definition.md) | Enforce correct `Error` subclassing. | | 🔧 | |
| [empty-brace-spaces](docs/rules/empty-brace-spaces.md) | Enforce no spaces between braces. | ✅ | 🔧 | |
| [error-message](docs/rules/error-message.md) | Enforce passing a `message` value when creating a built-in error. | ✅ | | |
| [escape-case](docs/rules/escape-case.md) | Require escape sequences to use uppercase values. | ✅ | 🔧 | |
| [escape-case](docs/rules/escape-case.md) | Require escape sequences to use uppercase or lowercase values. | ✅ | 🔧 | |
| [expiring-todo-comments](docs/rules/expiring-todo-comments.md) | Add expiration conditions to TODO comments. | ✅ | | |
| [explicit-length-check](docs/rules/explicit-length-check.md) | Enforce explicitly comparing the `length` or `size` property of a value. | ✅ | 🔧 | 💡 |
| [filename-case](docs/rules/filename-case.md) | Enforce a case style for filenames. | ✅ | | |
Expand Down
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
38 changes: 33 additions & 5 deletions rules/number-literal-case.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,33 @@ const messages = {
[MESSAGE_ID]: 'Invalid number literal casing.',
};

const fix = raw => {
/**
@param {string} raw
@param {Options} options
*/
const fix = (raw, {hexadecimalValue}) => {
let fixed = raw.toLowerCase();
if (fixed.startsWith('0x')) {
fixed = '0x' + fixed.slice(2).toUpperCase();
fixed = '0x' + fixed.slice(2)[hexadecimalValue === 'lowercase' ? 'toLowerCase' : 'toUpperCase']();
}

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';

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 +45,22 @@ const create = () => ({
},
});

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

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

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

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