Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(eslint-plugin): add prefer-regexp-exec rule (#305)
- Loading branch information
1 parent
eb613ca
commit f61d421
Showing
6 changed files
with
361 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# Enforce to use `RegExp#exec` over `String#match` (prefer-regexp-exec) | ||
|
||
`RegExp#exec` is faster than `String#match` and both work the same when not using the `/g` flag. | ||
|
||
## Rule Details | ||
|
||
This rule is aimed at enforcing the more performant way of applying regular expressions on strings. | ||
|
||
From [`String#match` on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match): | ||
|
||
> If the regular expression does not include the g flag, returns the same result as RegExp.exec(). | ||
From [Stack Overflow](https://stackoverflow.com/questions/9214754/what-is-the-difference-between-regexp-s-exec-function-and-string-s-match-fun) | ||
|
||
> `RegExp.prototype.exec` is a lot faster than `String.prototype.match`, but that’s because they are not exactly the same thing, they are different. | ||
Examples of **incorrect** code for this rule: | ||
|
||
```ts | ||
'something'.match(/thing/); | ||
|
||
'some things are just things'.match(/thing/); | ||
|
||
const text = 'something'; | ||
const search = /thing/; | ||
text.match(search); | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```ts | ||
/thing/.exec('something'); | ||
|
||
'some things are just things'.match(/thing/g); | ||
|
||
const text = 'something'; | ||
const search = /thing/; | ||
search.exec(text); | ||
``` | ||
|
||
## Options | ||
|
||
There are no options. | ||
|
||
```json | ||
{ | ||
"@typescript-eslint/prefer-regexp-exec": "error" | ||
} | ||
``` | ||
|
||
## When Not To Use It | ||
|
||
If you prefer consistent use of `String#match` for both, with `g` flag and without it, you can turn this rule off. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { TSESTree } from '@typescript-eslint/typescript-estree'; | ||
import { createRule, getParserServices, getTypeName } from '../util'; | ||
import { getStaticValue } from 'eslint-utils'; | ||
|
||
export default createRule({ | ||
name: 'prefer-regexp-exec', | ||
defaultOptions: [], | ||
|
||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
description: | ||
'Prefer RegExp#exec() over String#match() if no global flag is provided.', | ||
category: 'Best Practices', | ||
recommended: false, | ||
}, | ||
messages: { | ||
regExpExecOverStringMatch: 'Use the `RegExp#exec()` method instead.', | ||
}, | ||
schema: [], | ||
}, | ||
|
||
create(context) { | ||
const globalScope = context.getScope(); | ||
const service = getParserServices(context); | ||
const typeChecker = service.program.getTypeChecker(); | ||
|
||
/** | ||
* Check if a given node is a string. | ||
* @param node The node to check. | ||
*/ | ||
function isStringType(node: TSESTree.Node): boolean { | ||
const objectType = typeChecker.getTypeAtLocation( | ||
service.esTreeNodeToTSNodeMap.get(node), | ||
); | ||
return getTypeName(typeChecker, objectType) === 'string'; | ||
} | ||
|
||
return { | ||
"CallExpression[arguments.length=1] > MemberExpression.callee[property.name='match'][computed=false]"( | ||
node: TSESTree.MemberExpression, | ||
) { | ||
const callNode = node.parent as TSESTree.CallExpression; | ||
const arg = callNode.arguments[0]; | ||
const evaluated = getStaticValue(arg, globalScope); | ||
|
||
// Don't report regular expressions with global flag. | ||
if ( | ||
evaluated && | ||
evaluated.value instanceof RegExp && | ||
evaluated.value.flags.includes('g') | ||
) { | ||
return; | ||
} | ||
|
||
if (isStringType(node.object)) { | ||
context.report({ | ||
node: callNode, | ||
messageId: 'regExpExecOverStringMatch', | ||
}); | ||
return; | ||
} | ||
}, | ||
}; | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.