Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e2cbeef

Browse files
authored
feat(eslint-plugin): [prefer-regexp-exec] add autofix (typescript-eslint#3207)
1 parent a5836be commit e2cbeef

File tree

6 files changed

+632
-99
lines changed

6 files changed

+632
-99
lines changed

packages/eslint-plugin/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int
162162
| [`@typescript-eslint/prefer-readonly`](./docs/rules/prefer-readonly.md) | Requires that private members are marked as `readonly` if they're never modified outside of the constructor | | :wrench: | :thought_balloon: |
163163
| [`@typescript-eslint/prefer-readonly-parameter-types`](./docs/rules/prefer-readonly-parameter-types.md) | Requires that function parameters are typed as readonly to prevent accidental mutation of inputs | | | :thought_balloon: |
164164
| [`@typescript-eslint/prefer-reduce-type-parameter`](./docs/rules/prefer-reduce-type-parameter.md) | Prefer using type parameter when calling `Array#reduce` instead of casting | | :wrench: | :thought_balloon: |
165-
| [`@typescript-eslint/prefer-regexp-exec`](./docs/rules/prefer-regexp-exec.md) | Enforce that `RegExp#exec` is used instead of `String#match` if no global flag is provided | :white_check_mark: | | :thought_balloon: |
165+
| [`@typescript-eslint/prefer-regexp-exec`](./docs/rules/prefer-regexp-exec.md) | Enforce that `RegExp#exec` is used instead of `String#match` if no global flag is provided | :white_check_mark: | :wrench: | :thought_balloon: |
166166
| [`@typescript-eslint/prefer-string-starts-ends-with`](./docs/rules/prefer-string-starts-ends-with.md) | Enforce the use of `String#startsWith` and `String#endsWith` instead of other equivalent methods of checking substrings | | :wrench: | :thought_balloon: |
167167
| [`@typescript-eslint/prefer-ts-expect-error`](./docs/rules/prefer-ts-expect-error.md) | Recommends using `@ts-expect-error` over `@ts-ignore` | | :wrench: | |
168168
| [`@typescript-eslint/promise-function-async`](./docs/rules/promise-function-async.md) | Requires any function or method that returns a Promise to be marked async | | :wrench: | :thought_balloon: |

packages/eslint-plugin/src/rules/prefer-regexp-exec.ts

Lines changed: 80 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import { TSESTree } from '@typescript-eslint/experimental-utils';
1+
import {
2+
AST_NODE_TYPES,
3+
TSESTree,
4+
} from '@typescript-eslint/experimental-utils';
25
import {
36
createRule,
47
getParserServices,
58
getStaticValue,
69
getTypeName,
10+
getWrappingFixer,
711
} from '../util';
812

913
export default createRule({
@@ -12,6 +16,7 @@ export default createRule({
1216

1317
meta: {
1418
type: 'suggestion',
19+
fixable: 'code',
1520
docs: {
1621
description:
1722
'Enforce that `RegExp#exec` is used instead of `String#match` if no global flag is provided',
@@ -27,44 +32,103 @@ export default createRule({
2732

2833
create(context) {
2934
const globalScope = context.getScope();
30-
const service = getParserServices(context);
31-
const typeChecker = service.program.getTypeChecker();
35+
const parserServices = getParserServices(context);
36+
const typeChecker = parserServices.program.getTypeChecker();
37+
const sourceCode = context.getSourceCode();
3238

3339
/**
3440
* Check if a given node is a string.
3541
* @param node The node to check.
3642
*/
37-
function isStringType(node: TSESTree.LeftHandSideExpression): boolean {
43+
function isStringType(node: TSESTree.Expression): boolean {
3844
const objectType = typeChecker.getTypeAtLocation(
39-
service.esTreeNodeToTSNodeMap.get(node),
45+
parserServices.esTreeNodeToTSNodeMap.get(node),
4046
);
4147
return getTypeName(typeChecker, objectType) === 'string';
4248
}
4349

50+
/**
51+
* Check if a given node is a RegExp.
52+
* @param node The node to check.
53+
*/
54+
function isRegExpType(node: TSESTree.Expression): boolean {
55+
const objectType = typeChecker.getTypeAtLocation(
56+
parserServices.esTreeNodeToTSNodeMap.get(node),
57+
);
58+
return getTypeName(typeChecker, objectType) === 'RegExp';
59+
}
60+
4461
return {
4562
"CallExpression[arguments.length=1] > MemberExpression.callee[property.name='match'][computed=false]"(
46-
node: TSESTree.MemberExpression,
63+
memberNode: TSESTree.MemberExpression,
4764
): void {
48-
const callNode = node.parent as TSESTree.CallExpression;
49-
const arg = callNode.arguments[0];
50-
const evaluated = getStaticValue(arg, globalScope);
65+
const objectNode = memberNode.object;
66+
const callNode = memberNode.parent as TSESTree.CallExpression;
67+
const argumentNode = callNode.arguments[0];
68+
const argumentValue = getStaticValue(argumentNode, globalScope);
69+
70+
if (!isStringType(objectNode)) {
71+
return;
72+
}
5173

5274
// Don't report regular expressions with global flag.
5375
if (
54-
evaluated &&
55-
evaluated.value instanceof RegExp &&
56-
evaluated.value.flags.includes('g')
76+
argumentValue &&
77+
argumentValue.value instanceof RegExp &&
78+
argumentValue.value.flags.includes('g')
5779
) {
5880
return;
5981
}
6082

61-
if (isStringType(node.object)) {
62-
context.report({
63-
node: callNode,
83+
if (
84+
argumentNode.type === AST_NODE_TYPES.Literal &&
85+
typeof argumentNode.value == 'string'
86+
) {
87+
const regExp = RegExp(argumentNode.value);
88+
return context.report({
89+
node: memberNode.property,
6490
messageId: 'regExpExecOverStringMatch',
91+
fix: getWrappingFixer({
92+
sourceCode,
93+
node: callNode,
94+
innerNode: [objectNode],
95+
wrap: objectCode => `${regExp.toString()}.exec(${objectCode})`,
96+
}),
97+
});
98+
}
99+
100+
if (isRegExpType(argumentNode)) {
101+
return context.report({
102+
node: memberNode.property,
103+
messageId: 'regExpExecOverStringMatch',
104+
fix: getWrappingFixer({
105+
sourceCode,
106+
node: callNode,
107+
innerNode: [objectNode, argumentNode],
108+
wrap: (objectCode, argumentCode) =>
109+
`${argumentCode}.exec(${objectCode})`,
110+
}),
65111
});
66-
return;
67112
}
113+
114+
if (isStringType(argumentNode)) {
115+
return context.report({
116+
node: memberNode.property,
117+
messageId: 'regExpExecOverStringMatch',
118+
fix: getWrappingFixer({
119+
sourceCode,
120+
node: callNode,
121+
innerNode: [objectNode, argumentNode],
122+
wrap: (objectCode, argumentCode) =>
123+
`RegExp(${argumentCode}).exec(${objectCode})`,
124+
}),
125+
});
126+
}
127+
128+
return context.report({
129+
node: memberNode.property,
130+
messageId: 'regExpExecOverStringMatch',
131+
});
68132
},
69133
};
70134
},

0 commit comments

Comments
 (0)