forked from sindresorhus/eslint-plugin-unicorn
/
prefer-string-replace-all.js
107 lines (89 loc) · 2.46 KB
/
prefer-string-replace-all.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
'use strict';
const {getStaticValue} = require('eslint-utils');
const quoteString = require('./utils/quote-string.js');
const {methodCallSelector} = require('./selectors/index.js');
const {isRegexLiteral, isNewExpression} = require('./ast/index.js');
const MESSAGE_ID = 'prefer-string-replace-all';
const messages = {
[MESSAGE_ID]: 'Prefer `String#replaceAll()` over `String#replace()`.',
};
const selector = methodCallSelector({
method: 'replace',
argumentsLength: 2,
});
const canReplacePatternWithString = node =>
isRegexLiteral(node)
&& node.regex.flags.replace('u', '') === 'g'
&& !/[$()*+.?[\\\]^{|}]/.test(node.regex.pattern.replace(/\\[$()*+.?[\\\]^{|}]/g, ''));
const isRegExpWithGlobalFlag = (node, scope) => {
if (isRegexLiteral(node)) {
return node.regex.flags.includes('g');
}
if (
isNewExpression(node, {name: 'RegExp'})
&& node.arguments[0]?.type !== 'SpreadElement'
&& node.arguments[1]?.type === 'Literal'
&& typeof node.arguments[1].value === 'string'
) {
return node.arguments[1].value.includes('g');
}
const staticResult = getStaticValue(node, scope);
// Don't know if there is `g` flag
if (!staticResult) {
return false;
}
const {value} = staticResult;
return (
Object.prototype.toString.call(value) === '[object RegExp]'
&& value.global
);
};
function removeEscapeCharacters(regexString) {
let fixedString = regexString;
let index = 0;
do {
index = fixedString.indexOf('\\', index);
if (index >= 0) {
fixedString = fixedString.slice(0, index) + fixedString.slice(index + 1);
index++;
}
} while (index >= 0);
return fixedString;
}
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[selector](node) {
const {
arguments: [pattern],
callee: {property},
} = node;
if (!isRegExpWithGlobalFlag(pattern, context.getScope())) {
return;
}
return {
node: property,
messageId: MESSAGE_ID,
/** @param {import('eslint').Rule.RuleFixer} fixer */
* fix(fixer) {
yield fixer.insertTextAfter(property, 'All');
if (!canReplacePatternWithString(pattern)) {
return;
}
const string = removeEscapeCharacters(pattern.regex.pattern);
yield fixer.replaceText(pattern, quoteString(string));
},
};
},
});
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
description: 'Prefer `String#replaceAll()` over regex searches with the global flag.',
},
fixable: 'code',
messages,
},
};