Skip to content

Commit

Permalink
Rename regex-shorthand to better-regex (#522)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <[email protected]>
  • Loading branch information
fisker and sindresorhus authored Feb 13, 2020
1 parent fdd96cb commit a5e5405
Show file tree
Hide file tree
Showing 9 changed files with 366 additions and 464 deletions.
36 changes: 36 additions & 0 deletions docs/rules/better-regex.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Improve regexes by making them shorter, consistent, and safer

This rule is fixable.

## Fail

```js
const regex = /[0-9]/;
const regex = /[^0-9]/;
const regex = /[a-zA-Z0-9_]/;
const regex = /[a-z0-9_]/i;
const regex = /[^a-zA-Z0-9_]/;
const regex = /[^a-z0-9_]/i;
const regex = /[0-9]\.[a-zA-Z0-9_]\-[^0-9]/i;
```

## Pass

```js
const regex = /\d/;
const regex = /\D/;
const regex = /\w/;
const regex = /\w/i;
const regex = /\W/;
const regex = /\W/i;
const regex = /\d\.\w\-\D/i;
```

## Options

### sortCharacterClasses

Type: `boolean`\
Default: `true`

Disables optimizations that affect the sorting of character classes. For example, preserves the order of the characters in `[AaQqTt]` rather than sorting it to `[AQTaqt]`.
37 changes: 1 addition & 36 deletions docs/rules/regex-shorthand.md
Original file line number Diff line number Diff line change
@@ -1,36 +1 @@
# Enforce the use of regex shorthands to improve readability

This rule is fixable.

## Fail

```js
const regex = /[0-9]/;
const regex = /[^0-9]/;
const regex = /[a-zA-Z0-9_]/;
const regex = /[a-z0-9_]/i;
const regex = /[^a-zA-Z0-9_]/;
const regex = /[^a-z0-9_]/i;
const regex = /[0-9]\.[a-zA-Z0-9_]\-[^0-9]/i;
```

## Pass

```js
const regex = /\d/;
const regex = /\D/;
const regex = /\w/;
const regex = /\w/i;
const regex = /\W/;
const regex = /\W/i;
const regex = /\d\.\w\-\D/i;
```

## Options

### sortCharacterClasses

Type: `boolean`\
Default: `true`

Disables optimizations that affect the sorting of character classes. For example, preserves the order of the characters in `[AaQqTt]` rather than sorting it to `[AQTaqt]`.
This rule was renamed to [`better-regex`](better-regex.md).
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
'unicorn'
],
rules: {
'unicorn/better-regex': 'error',
'unicorn/catch-error-name': 'error',
'unicorn/consistent-function-scoping': 'error',
'unicorn/custom-error-definition': 'off',
Expand Down Expand Up @@ -63,7 +64,6 @@ module.exports = {
'unicorn/prefer-trim-start-end': 'error',
'unicorn/prefer-type-error': 'error',
'unicorn/prevent-abbreviations': 'error',
'unicorn/regex-shorthand': 'error',
'unicorn/throw-new-error': 'error'
}
}
Expand Down
5 changes: 3 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Configure it in `package.json`.
"unicorn"
],
"rules": {
"unicorn/better-regex": "error",
"unicorn/catch-error-name": "error",
"unicorn/consistent-function-scoping": "error",
"unicorn/custom-error-definition": "off",
Expand Down Expand Up @@ -78,7 +79,6 @@ Configure it in `package.json`.
"unicorn/prefer-trim-start-end": "error",
"unicorn/prefer-type-error": "error",
"unicorn/prevent-abbreviations": "error",
"unicorn/regex-shorthand": "error",
"unicorn/throw-new-error": "error"
}
}
Expand All @@ -87,6 +87,7 @@ Configure it in `package.json`.

## Rules

- [better-regex](docs/rules/better-regex.md) - Improve regexes by making them shorter, consistent, and safer. *(fixable)*
- [catch-error-name](docs/rules/catch-error-name.md) - Enforce a specific parameter name in catch clauses.
- [consistent-function-scoping](docs/rules/consistent-function-scoping.md) - Move function definitions to the highest possible scope.
- [custom-error-definition](docs/rules/custom-error-definition.md) - Enforce correct `Error` subclassing. *(fixable)*
Expand Down Expand Up @@ -131,12 +132,12 @@ Configure it in `package.json`.
- [prefer-trim-start-end](docs/rules/prefer-trim-start-end.md) - Prefer `String#trimStart()` / `String#trimEnd()` over `String#trimLeft()` / `String#trimRight()`. *(fixable)*
- [prefer-type-error](docs/rules/prefer-type-error.md) - Enforce throwing `TypeError` in type checking conditions. *(fixable)*
- [prevent-abbreviations](docs/rules/prevent-abbreviations.md) - Prevent abbreviations. *(partly fixable)*
- [regex-shorthand](docs/rules/regex-shorthand.md) - Enforce the use of regex shorthands to improve readability. *(fixable)*
- [throw-new-error](docs/rules/throw-new-error.md) - Require `new` when throwing an error. *(fixable)*

## Deprecated Rules

- [prefer-exponentiation-operator](docs/rules/prefer-exponentiation-operator.md) - Use the built-in ESLint [`prefer-exponentiation-operator`](https://eslint.org/docs/rules/prefer-exponentiation-operator) rule instead.
- [regex-shorthand](docs/rules/regex-shorthand.md) - Renamed to [`better-regex`](docs/rules/better-regex.md).

## Recommended config

Expand Down
106 changes: 106 additions & 0 deletions rules/better-regex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
'use strict';
const cleanRegexp = require('clean-regexp');
const {optimize} = require('regexp-tree');
const getDocumentationUrl = require('./utils/get-documentation-url');
const quoteString = require('./utils/quote-string');

const message = '{{original}} can be optimized to {{optimized}}';

const create = context => {
const {sortCharacterClasses} = context.options[0] || {};

const blacklist = [];

if (sortCharacterClasses === false) {
blacklist.push('charClassClassrangesMerge');
}

return {
'Literal[regex]': node => {
const {raw: original, regex} = node;

// Regular Expressions with `u` flag are not well handled by `regexp-tree`
// https://github.com/DmitrySoshnikov/regexp-tree/issues/162
if (regex.flags.includes('u')) {
return;
}

let optimized = original;

try {
optimized = optimize(original, undefined, {blacklist}).toString();
} catch (_) {}

if (original === optimized) {
return;
}

context.report({
node,
message,
data: {
original,
optimized
},
fix: fixer => fixer.replaceText(node, optimized)
});
},
'NewExpression[callee.name="RegExp"]': node => {
const arguments_ = node.arguments;

if (arguments_.length === 0 || arguments_[0].type !== 'Literal') {
return;
}

const hasRegExp = arguments_[0].regex;

if (hasRegExp) {
return;
}

const oldPattern = arguments_[0].value;
const flags = arguments_[1] && arguments_[1].type === 'Literal' ? arguments_[1].value : '';

const newPattern = cleanRegexp(oldPattern, flags);

if (oldPattern !== newPattern) {
// Escape backslash
const fixed = quoteString((newPattern || '').replace(/\\/g, '\\\\'));

context.report({
node,
message,
data: {
original: oldPattern,
optimized: newPattern
},
fix: fixer => fixer.replaceText(arguments_[0], fixed)
});
}
}
};
};

const schema = [
{
type: 'object',
properties: {
sortCharacterClasses: {
type: 'boolean',
default: true
}
}
}
];

module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
schema
}
};
98 changes: 6 additions & 92 deletions rules/regex-shorthand.js
Original file line number Diff line number Diff line change
@@ -1,102 +1,16 @@
'use strict';
const cleanRegexp = require('clean-regexp');
const {optimize} = require('regexp-tree');
const getDocumentationUrl = require('./utils/get-documentation-url');
const quoteString = require('./utils/quote-string');

const message = 'Use regex shorthands to improve readability.';

const create = context => {
const {sortCharacterClasses} = context.options[0] || {};

const blacklist = [];

if (sortCharacterClasses === false) {
blacklist.push('charClassClassrangesMerge');
}

return {
'Literal[regex]': node => {
const {raw: original, regex} = node;

// Regular Expressions with `u` flag are not well handled by `regexp-tree`
// https://github.com/DmitrySoshnikov/regexp-tree/issues/162
if (regex.flags.includes('u')) {
return;
}

let optimized = original;

try {
optimized = optimize(original, undefined, {blacklist}).toString();
} catch (_) {}

if (original === optimized) {
return;
}

context.report({
node,
message: '{{original}} can be optimized to {{optimized}}',
data: {
original,
optimized
},
fix: fixer => fixer.replaceText(node, optimized)
});
},
'NewExpression[callee.name="RegExp"]': node => {
const arguments_ = node.arguments;

if (arguments_.length === 0 || arguments_[0].type !== 'Literal') {
return;
}

const hasRegExp = arguments_[0].regex;

if (hasRegExp) {
return;
}

const oldPattern = arguments_[0].value;
const flags = arguments_[1] && arguments_[1].type === 'Literal' ? arguments_[1].value : '';

const newPattern = cleanRegexp(oldPattern, flags);

if (oldPattern !== newPattern) {
// Escape backslash
const fixed = quoteString((newPattern || '').replace(/\\/g, '\\\\'));

context.report({
node,
message,
fix: fixer => fixer.replaceText(arguments_[0], fixed)
});
}
}
};
};

const schema = [
{
type: 'object',
properties: {
sortCharacterClasses: {
type: 'boolean',
default: true
}
}
}
];

module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
url: getDocumentationUrl(__filename)
},
fixable: 'code',
schema
}
fixable: 'code'
},
deprecated: true,
replacedBy: [
'unicorn/better-regex'
]
};
Loading

0 comments on commit a5e5405

Please sign in to comment.