eslint 8.4.0 → 8.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/eslint.js +7 -1
- package/lib/cli-engine/cli-engine.js +4 -1
- package/lib/eslint/eslint.js +1 -0
- package/lib/linter/apply-disable-directives.js +20 -16
- package/lib/linter/linter.js +8 -6
- package/lib/rule-tester/rule-tester.js +19 -5
- package/lib/rules/camelcase.js +7 -1
- package/lib/rules/id-match.js +35 -1
- package/lib/rules/index.js +1 -0
- package/lib/rules/keyword-spacing.js +32 -0
- package/lib/rules/max-lines-per-function.js +2 -20
- package/lib/rules/no-constant-condition.js +23 -4
- package/lib/rules/no-invalid-this.js +50 -53
- package/lib/rules/no-restricted-exports.js +9 -3
- package/lib/rules/no-restricted-imports.js +24 -7
- package/lib/rules/no-restricted-modules.js +2 -1
- package/lib/rules/no-self-assign.js +1 -1
- package/lib/rules/no-useless-rename.js +8 -4
- package/lib/rules/prefer-object-has-own.js +112 -0
- package/lib/rules/prefer-regex-literals.js +217 -1
- package/lib/rules/prefer-template.js +1 -1
- package/lib/rules/quotes.js +12 -1
- package/lib/rules/utils/ast-utils.js +21 -1
- package/messages/no-config-found.js +1 -1
- package/package.json +7 -7
- package/lib/init/autoconfig.js +0 -351
- package/lib/init/config-file.js +0 -144
- package/lib/init/config-initializer.js +0 -709
- package/lib/init/config-rule.js +0 -316
- package/lib/init/npm-utils.js +0 -179
- package/lib/init/source-code-utils.js +0 -110
@@ -11,6 +11,21 @@
|
|
11
11
|
|
12
12
|
const astUtils = require("./utils/ast-utils");
|
13
13
|
|
14
|
+
//------------------------------------------------------------------------------
|
15
|
+
// Helpers
|
16
|
+
//------------------------------------------------------------------------------
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Determines if the given code path is a code path with lexical `this` binding.
|
20
|
+
* That is, if `this` within the code path refers to `this` of surrounding code path.
|
21
|
+
* @param {CodePath} codePath Code path.
|
22
|
+
* @param {ASTNode} node Node that started the code path.
|
23
|
+
* @returns {boolean} `true` if it is a code path with lexical `this` binding.
|
24
|
+
*/
|
25
|
+
function isCodePathWithLexicalThis(codePath, node) {
|
26
|
+
return codePath.origin === "function" && node.type === "ArrowFunctionExpression";
|
27
|
+
}
|
28
|
+
|
14
29
|
//------------------------------------------------------------------------------
|
15
30
|
// Rule Definition
|
16
31
|
//------------------------------------------------------------------------------
|
@@ -72,71 +87,53 @@ module.exports = {
|
|
72
87
|
return current;
|
73
88
|
};
|
74
89
|
|
75
|
-
|
76
|
-
* Pushs new checking context into the stack.
|
77
|
-
*
|
78
|
-
* The checking context is not initialized yet.
|
79
|
-
* Because most functions don't have `this` keyword.
|
80
|
-
* When `this` keyword was found, the checking context is initialized.
|
81
|
-
* @param {ASTNode} node A function node that was entered.
|
82
|
-
* @returns {void}
|
83
|
-
*/
|
84
|
-
function enterFunction(node) {
|
85
|
-
|
86
|
-
// `this` can be invalid only under strict mode.
|
87
|
-
stack.push({
|
88
|
-
init: !context.getScope().isStrict,
|
89
|
-
node,
|
90
|
-
valid: true
|
91
|
-
});
|
92
|
-
}
|
90
|
+
return {
|
93
91
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
function exitFunction() {
|
99
|
-
stack.pop();
|
100
|
-
}
|
92
|
+
onCodePathStart(codePath, node) {
|
93
|
+
if (isCodePathWithLexicalThis(codePath, node)) {
|
94
|
+
return;
|
95
|
+
}
|
101
96
|
|
102
|
-
|
97
|
+
if (codePath.origin === "program") {
|
98
|
+
const scope = context.getScope();
|
99
|
+
const features = context.parserOptions.ecmaFeatures || {};
|
100
|
+
|
101
|
+
stack.push({
|
102
|
+
init: true,
|
103
|
+
node,
|
104
|
+
valid: !(
|
105
|
+
scope.isStrict ||
|
106
|
+
node.sourceType === "module" ||
|
107
|
+
(features.globalReturn && scope.childScopes[0].isStrict)
|
108
|
+
)
|
109
|
+
});
|
103
110
|
|
104
|
-
|
105
|
-
|
106
|
-
* Modules is always strict mode.
|
107
|
-
*/
|
108
|
-
Program(node) {
|
109
|
-
const scope = context.getScope(),
|
110
|
-
features = context.parserOptions.ecmaFeatures || {};
|
111
|
+
return;
|
112
|
+
}
|
111
113
|
|
114
|
+
/*
|
115
|
+
* `init: false` means that `valid` isn't determined yet.
|
116
|
+
* Most functions don't use `this`, and the calculation for `valid`
|
117
|
+
* is relatively costly, so we'll calculate it lazily when the first
|
118
|
+
* `this` within the function is traversed. A special case are non-strict
|
119
|
+
* functions, because `this` refers to the global object and therefore is
|
120
|
+
* always valid, so we can set `init: true` right away.
|
121
|
+
*/
|
112
122
|
stack.push({
|
113
|
-
init:
|
123
|
+
init: !context.getScope().isStrict,
|
114
124
|
node,
|
115
|
-
valid:
|
116
|
-
scope.isStrict ||
|
117
|
-
node.sourceType === "module" ||
|
118
|
-
(features.globalReturn && scope.childScopes[0].isStrict)
|
119
|
-
)
|
125
|
+
valid: true
|
120
126
|
});
|
121
127
|
},
|
122
128
|
|
123
|
-
|
129
|
+
onCodePathEnd(codePath, node) {
|
130
|
+
if (isCodePathWithLexicalThis(codePath, node)) {
|
131
|
+
return;
|
132
|
+
}
|
133
|
+
|
124
134
|
stack.pop();
|
125
135
|
},
|
126
136
|
|
127
|
-
FunctionDeclaration: enterFunction,
|
128
|
-
"FunctionDeclaration:exit": exitFunction,
|
129
|
-
FunctionExpression: enterFunction,
|
130
|
-
"FunctionExpression:exit": exitFunction,
|
131
|
-
|
132
|
-
// Field initializers are implicit functions.
|
133
|
-
"PropertyDefinition > *.value": enterFunction,
|
134
|
-
"PropertyDefinition > *.value:exit": exitFunction,
|
135
|
-
|
136
|
-
// Class static blocks are implicit functions.
|
137
|
-
StaticBlock: enterFunction,
|
138
|
-
"StaticBlock:exit": exitFunction,
|
139
|
-
|
140
137
|
// Reports if `this` of the current context is invalid.
|
141
138
|
ThisExpression(node) {
|
142
139
|
const current = stack.getCurrent();
|
@@ -5,6 +5,12 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
//------------------------------------------------------------------------------
|
9
|
+
// Requirements
|
10
|
+
//------------------------------------------------------------------------------
|
11
|
+
|
12
|
+
const astUtils = require("./utils/ast-utils");
|
13
|
+
|
8
14
|
//------------------------------------------------------------------------------
|
9
15
|
// Rule Definition
|
10
16
|
//------------------------------------------------------------------------------
|
@@ -44,12 +50,12 @@ module.exports = {
|
|
44
50
|
const restrictedNames = new Set(context.options[0] && context.options[0].restrictedNamedExports);
|
45
51
|
|
46
52
|
/**
|
47
|
-
* Checks and reports given exported
|
48
|
-
* @param {ASTNode} node exported `Identifier` node to check.
|
53
|
+
* Checks and reports given exported name.
|
54
|
+
* @param {ASTNode} node exported `Identifier` or string `Literal` node to check.
|
49
55
|
* @returns {void}
|
50
56
|
*/
|
51
57
|
function checkExportedName(node) {
|
52
|
-
const name = node
|
58
|
+
const name = astUtils.getModuleExportName(node);
|
53
59
|
|
54
60
|
if (restrictedNames.has(name)) {
|
55
61
|
context.report({
|
@@ -4,6 +4,12 @@
|
|
4
4
|
*/
|
5
5
|
"use strict";
|
6
6
|
|
7
|
+
//------------------------------------------------------------------------------
|
8
|
+
// Requirements
|
9
|
+
//------------------------------------------------------------------------------
|
10
|
+
|
11
|
+
const astUtils = require("./utils/ast-utils");
|
12
|
+
|
7
13
|
//------------------------------------------------------------------------------
|
8
14
|
// Rule Definition
|
9
15
|
//------------------------------------------------------------------------------
|
@@ -63,6 +69,9 @@ const arrayOfStringsOrObjectPatterns = {
|
|
63
69
|
message: {
|
64
70
|
type: "string",
|
65
71
|
minLength: 1
|
72
|
+
},
|
73
|
+
caseSensitive: {
|
74
|
+
type: "boolean"
|
66
75
|
}
|
67
76
|
},
|
68
77
|
additionalProperties: false,
|
@@ -142,10 +151,18 @@ module.exports = {
|
|
142
151
|
}, {});
|
143
152
|
|
144
153
|
// Handle patterns too, either as strings or groups
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
154
|
+
let restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
|
155
|
+
|
156
|
+
// standardize to array of objects if we have an array of strings
|
157
|
+
if (restrictedPatterns.length > 0 && typeof restrictedPatterns[0] === "string") {
|
158
|
+
restrictedPatterns = [{ group: restrictedPatterns }];
|
159
|
+
}
|
160
|
+
|
161
|
+
// relative paths are supported for this rule
|
162
|
+
const restrictedPatternGroups = restrictedPatterns.map(({ group, message, caseSensitive }) => ({
|
163
|
+
matcher: ignore({ allowRelativePaths: true, ignorecase: !caseSensitive }).add(group),
|
164
|
+
customMessage: message
|
165
|
+
}));
|
149
166
|
|
150
167
|
// if no imports are restricted we don't need to check
|
151
168
|
if (Object.keys(restrictedPaths).length === 0 && restrictedPatternGroups.length === 0) {
|
@@ -269,12 +286,12 @@ module.exports = {
|
|
269
286
|
} else if (specifier.type === "ImportNamespaceSpecifier") {
|
270
287
|
name = "*";
|
271
288
|
} else if (specifier.imported) {
|
272
|
-
name = specifier.imported
|
289
|
+
name = astUtils.getModuleExportName(specifier.imported);
|
273
290
|
} else if (specifier.local) {
|
274
|
-
name = specifier.local
|
291
|
+
name = astUtils.getModuleExportName(specifier.local);
|
275
292
|
}
|
276
293
|
|
277
|
-
if (name) {
|
294
|
+
if (typeof name === "string") {
|
278
295
|
if (importNames.has(name)) {
|
279
296
|
importNames.get(name).push(specifierData);
|
280
297
|
} else {
|
@@ -132,8 +132,10 @@ module.exports = {
|
|
132
132
|
return;
|
133
133
|
}
|
134
134
|
|
135
|
-
if (
|
136
|
-
|
135
|
+
if (
|
136
|
+
node.imported.range[0] !== node.local.range[0] &&
|
137
|
+
astUtils.getModuleExportName(node.imported) === node.local.name
|
138
|
+
) {
|
137
139
|
reportError(node, node.imported, "Import");
|
138
140
|
}
|
139
141
|
}
|
@@ -148,8 +150,10 @@ module.exports = {
|
|
148
150
|
return;
|
149
151
|
}
|
150
152
|
|
151
|
-
if (
|
152
|
-
|
153
|
+
if (
|
154
|
+
node.local.range[0] !== node.exported.range[0] &&
|
155
|
+
astUtils.getModuleExportName(node.local) === astUtils.getModuleExportName(node.exported)
|
156
|
+
) {
|
153
157
|
reportError(node, node.local, "Export");
|
154
158
|
}
|
155
159
|
|
@@ -0,0 +1,112 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview Prefers Object.hasOwn() instead of Object.prototype.hasOwnProperty.call()
|
3
|
+
* @author Nitin Kumar
|
4
|
+
* @author Gautam Arora
|
5
|
+
*/
|
6
|
+
|
7
|
+
"use strict";
|
8
|
+
|
9
|
+
//------------------------------------------------------------------------------
|
10
|
+
// Requirements
|
11
|
+
//------------------------------------------------------------------------------
|
12
|
+
|
13
|
+
const astUtils = require("./utils/ast-utils");
|
14
|
+
|
15
|
+
//------------------------------------------------------------------------------
|
16
|
+
// Helpers
|
17
|
+
//------------------------------------------------------------------------------
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Checks if the given node is considered to be an access to a property of `Object.prototype`.
|
21
|
+
* @param {ASTNode} node `MemberExpression` node to evaluate.
|
22
|
+
* @returns {boolean} `true` if `node.object` is `Object`, `Object.prototype`, or `{}` (empty 'ObjectExpression' node).
|
23
|
+
*/
|
24
|
+
function hasLeftHandObject(node) {
|
25
|
+
|
26
|
+
/*
|
27
|
+
* ({}).hasOwnProperty.call(obj, prop) - `true`
|
28
|
+
* ({ foo }.hasOwnProperty.call(obj, prop)) - `false`, object literal should be empty
|
29
|
+
*/
|
30
|
+
if (node.object.type === "ObjectExpression" && node.object.properties.length === 0) {
|
31
|
+
return true;
|
32
|
+
}
|
33
|
+
|
34
|
+
const objectNodeToCheck = node.object.type === "MemberExpression" && astUtils.getStaticPropertyName(node.object) === "prototype" ? node.object.object : node.object;
|
35
|
+
|
36
|
+
if (objectNodeToCheck.type === "Identifier" && objectNodeToCheck.name === "Object") {
|
37
|
+
return true;
|
38
|
+
}
|
39
|
+
|
40
|
+
return false;
|
41
|
+
}
|
42
|
+
|
43
|
+
//------------------------------------------------------------------------------
|
44
|
+
// Rule Definition
|
45
|
+
//------------------------------------------------------------------------------
|
46
|
+
|
47
|
+
/** @type {import('../shared/types').Rule} */
|
48
|
+
module.exports = {
|
49
|
+
meta: {
|
50
|
+
type: "suggestion",
|
51
|
+
docs: {
|
52
|
+
description:
|
53
|
+
"disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`",
|
54
|
+
recommended: false,
|
55
|
+
url: "https://eslint.org/docs/rules/prefer-object-has-own"
|
56
|
+
},
|
57
|
+
schema: [],
|
58
|
+
messages: {
|
59
|
+
useHasOwn: "Use 'Object.hasOwn()' instead of 'Object.prototype.hasOwnProperty.call()'."
|
60
|
+
},
|
61
|
+
fixable: "code"
|
62
|
+
},
|
63
|
+
create(context) {
|
64
|
+
return {
|
65
|
+
CallExpression(node) {
|
66
|
+
if (!(node.callee.type === "MemberExpression" && node.callee.object.type === "MemberExpression")) {
|
67
|
+
return;
|
68
|
+
}
|
69
|
+
|
70
|
+
const calleePropertyName = astUtils.getStaticPropertyName(node.callee);
|
71
|
+
const objectPropertyName = astUtils.getStaticPropertyName(node.callee.object);
|
72
|
+
const isObject = hasLeftHandObject(node.callee.object);
|
73
|
+
|
74
|
+
// check `Object` scope
|
75
|
+
const scope = context.getScope();
|
76
|
+
const variable = astUtils.getVariableByName(scope, "Object");
|
77
|
+
|
78
|
+
if (
|
79
|
+
calleePropertyName === "call" &&
|
80
|
+
objectPropertyName === "hasOwnProperty" &&
|
81
|
+
isObject &&
|
82
|
+
variable && variable.scope.type === "global"
|
83
|
+
) {
|
84
|
+
context.report({
|
85
|
+
node,
|
86
|
+
messageId: "useHasOwn",
|
87
|
+
fix(fixer) {
|
88
|
+
const sourceCode = context.getSourceCode();
|
89
|
+
|
90
|
+
if (sourceCode.getCommentsInside(node.callee).length > 0) {
|
91
|
+
return null;
|
92
|
+
}
|
93
|
+
|
94
|
+
const tokenJustBeforeNode = sourceCode.getTokenBefore(node.callee, { includeComments: true });
|
95
|
+
|
96
|
+
// for https://github.com/eslint/eslint/pull/15346#issuecomment-991417335
|
97
|
+
if (
|
98
|
+
tokenJustBeforeNode &&
|
99
|
+
tokenJustBeforeNode.range[1] === node.callee.range[0] &&
|
100
|
+
!astUtils.canTokensBeAdjacent(tokenJustBeforeNode, "Object.hasOwn")
|
101
|
+
) {
|
102
|
+
return fixer.replaceText(node.callee, " Object.hasOwn");
|
103
|
+
}
|
104
|
+
|
105
|
+
return fixer.replaceText(node.callee, "Object.hasOwn");
|
106
|
+
}
|
107
|
+
});
|
108
|
+
}
|
109
|
+
}
|
110
|
+
};
|
111
|
+
}
|
112
|
+
};
|
@@ -11,11 +11,15 @@
|
|
11
11
|
|
12
12
|
const astUtils = require("./utils/ast-utils");
|
13
13
|
const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("eslint-utils");
|
14
|
+
const { RegExpValidator, visitRegExpAST, RegExpParser } = require("regexpp");
|
15
|
+
const { canTokensBeAdjacent } = require("./utils/ast-utils");
|
14
16
|
|
15
17
|
//------------------------------------------------------------------------------
|
16
18
|
// Helpers
|
17
19
|
//------------------------------------------------------------------------------
|
18
20
|
|
21
|
+
const REGEXPP_LATEST_ECMA_VERSION = 2022;
|
22
|
+
|
19
23
|
/**
|
20
24
|
* Determines whether the given node is a string literal.
|
21
25
|
* @param {ASTNode} node Node to check.
|
@@ -43,6 +47,71 @@ function isStaticTemplateLiteral(node) {
|
|
43
47
|
return node.type === "TemplateLiteral" && node.expressions.length === 0;
|
44
48
|
}
|
45
49
|
|
50
|
+
const validPrecedingTokens = [
|
51
|
+
"(",
|
52
|
+
";",
|
53
|
+
"[",
|
54
|
+
",",
|
55
|
+
"=",
|
56
|
+
"+",
|
57
|
+
"*",
|
58
|
+
"-",
|
59
|
+
"?",
|
60
|
+
"~",
|
61
|
+
"%",
|
62
|
+
"**",
|
63
|
+
"!",
|
64
|
+
"typeof",
|
65
|
+
"instanceof",
|
66
|
+
"&&",
|
67
|
+
"||",
|
68
|
+
"??",
|
69
|
+
"return",
|
70
|
+
"...",
|
71
|
+
"delete",
|
72
|
+
"void",
|
73
|
+
"in",
|
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
|
+
"do",
|
108
|
+
"break",
|
109
|
+
"continue",
|
110
|
+
"debugger",
|
111
|
+
"case",
|
112
|
+
"throw"
|
113
|
+
];
|
114
|
+
|
46
115
|
|
47
116
|
//------------------------------------------------------------------------------
|
48
117
|
// Rule Definition
|
@@ -59,6 +128,8 @@ module.exports = {
|
|
59
128
|
url: "https://eslint.org/docs/rules/prefer-regex-literals"
|
60
129
|
},
|
61
130
|
|
131
|
+
hasSuggestions: true,
|
132
|
+
|
62
133
|
schema: [
|
63
134
|
{
|
64
135
|
type: "object",
|
@@ -74,6 +145,7 @@ module.exports = {
|
|
74
145
|
|
75
146
|
messages: {
|
76
147
|
unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor.",
|
148
|
+
replaceWithLiteral: "Replace with an equivalent regular expression literal.",
|
77
149
|
unexpectedRedundantRegExp: "Regular expression literal is unnecessarily wrapped within a 'RegExp' constructor.",
|
78
150
|
unexpectedRedundantRegExpWithFlags: "Use regular expression literal with flags instead of the 'RegExp' constructor."
|
79
151
|
}
|
@@ -81,6 +153,7 @@ module.exports = {
|
|
81
153
|
|
82
154
|
create(context) {
|
83
155
|
const [{ disallowRedundantWrapping = false } = {}] = context.options;
|
156
|
+
const sourceCode = context.getSourceCode();
|
84
157
|
|
85
158
|
/**
|
86
159
|
* Determines whether the given identifier node is a reference to a global variable.
|
@@ -107,6 +180,27 @@ module.exports = {
|
|
107
180
|
isStaticTemplateLiteral(node.quasi);
|
108
181
|
}
|
109
182
|
|
183
|
+
/**
|
184
|
+
* Gets the value of a string
|
185
|
+
* @param {ASTNode} node The node to get the string of.
|
186
|
+
* @returns {string|null} The value of the node.
|
187
|
+
*/
|
188
|
+
function getStringValue(node) {
|
189
|
+
if (isStringLiteral(node)) {
|
190
|
+
return node.value;
|
191
|
+
}
|
192
|
+
|
193
|
+
if (isStaticTemplateLiteral(node)) {
|
194
|
+
return node.quasis[0].value.cooked;
|
195
|
+
}
|
196
|
+
|
197
|
+
if (isStringRawTaggedStaticTemplateLiteral(node)) {
|
198
|
+
return node.quasi.quasis[0].value.raw;
|
199
|
+
}
|
200
|
+
|
201
|
+
return null;
|
202
|
+
}
|
203
|
+
|
110
204
|
/**
|
111
205
|
* Determines whether the given node is considered to be a static string by the logic of this rule.
|
112
206
|
* @param {ASTNode} node Node to check.
|
@@ -152,6 +246,53 @@ module.exports = {
|
|
152
246
|
return false;
|
153
247
|
}
|
154
248
|
|
249
|
+
/**
|
250
|
+
* Returns a ecmaVersion compatible for regexpp.
|
251
|
+
* @param {any} ecmaVersion The ecmaVersion to convert.
|
252
|
+
* @returns {import("regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp.
|
253
|
+
*/
|
254
|
+
function getRegexppEcmaVersion(ecmaVersion) {
|
255
|
+
if (typeof ecmaVersion !== "number" || ecmaVersion <= 5) {
|
256
|
+
return 5;
|
257
|
+
}
|
258
|
+
return Math.min(ecmaVersion + 2009, REGEXPP_LATEST_ECMA_VERSION);
|
259
|
+
}
|
260
|
+
|
261
|
+
/**
|
262
|
+
* Makes a character escaped or else returns null.
|
263
|
+
* @param {string} character The character to escape.
|
264
|
+
* @returns {string} The resulting escaped character.
|
265
|
+
*/
|
266
|
+
function resolveEscapes(character) {
|
267
|
+
switch (character) {
|
268
|
+
case "\n":
|
269
|
+
case "\\\n":
|
270
|
+
return "\\n";
|
271
|
+
|
272
|
+
case "\r":
|
273
|
+
case "\\\r":
|
274
|
+
return "\\r";
|
275
|
+
|
276
|
+
case "\t":
|
277
|
+
case "\\\t":
|
278
|
+
return "\\t";
|
279
|
+
|
280
|
+
case "\v":
|
281
|
+
case "\\\v":
|
282
|
+
return "\\v";
|
283
|
+
|
284
|
+
case "\f":
|
285
|
+
case "\\\f":
|
286
|
+
return "\\f";
|
287
|
+
|
288
|
+
case "/":
|
289
|
+
return "\\/";
|
290
|
+
|
291
|
+
default:
|
292
|
+
return null;
|
293
|
+
}
|
294
|
+
}
|
295
|
+
|
155
296
|
return {
|
156
297
|
Program() {
|
157
298
|
const scope = context.getScope();
|
@@ -171,7 +312,82 @@ module.exports = {
|
|
171
312
|
context.report({ node, messageId: "unexpectedRedundantRegExp" });
|
172
313
|
}
|
173
314
|
} else if (hasOnlyStaticStringArguments(node)) {
|
174
|
-
|
315
|
+
let regexContent = getStringValue(node.arguments[0]);
|
316
|
+
let noFix = false;
|
317
|
+
let flags;
|
318
|
+
|
319
|
+
if (node.arguments[1]) {
|
320
|
+
flags = getStringValue(node.arguments[1]);
|
321
|
+
}
|
322
|
+
|
323
|
+
const regexppEcmaVersion = getRegexppEcmaVersion(context.parserOptions.ecmaVersion);
|
324
|
+
const RegExpValidatorInstance = new RegExpValidator({ ecmaVersion: regexppEcmaVersion });
|
325
|
+
|
326
|
+
try {
|
327
|
+
RegExpValidatorInstance.validatePattern(regexContent, 0, regexContent.length, flags ? flags.includes("u") : false);
|
328
|
+
if (flags) {
|
329
|
+
RegExpValidatorInstance.validateFlags(flags);
|
330
|
+
}
|
331
|
+
} catch {
|
332
|
+
noFix = true;
|
333
|
+
}
|
334
|
+
|
335
|
+
const tokenBefore = sourceCode.getTokenBefore(node);
|
336
|
+
|
337
|
+
if (tokenBefore && !validPrecedingTokens.includes(tokenBefore.value)) {
|
338
|
+
noFix = true;
|
339
|
+
}
|
340
|
+
|
341
|
+
if (!/^[-a-zA-Z0-9\\[\](){} \t\r\n\v\f!@#$%^&*+^_=/~`.><?,'"|:;]*$/u.test(regexContent)) {
|
342
|
+
noFix = true;
|
343
|
+
}
|
344
|
+
|
345
|
+
if (sourceCode.getCommentsInside(node).length > 0) {
|
346
|
+
noFix = true;
|
347
|
+
}
|
348
|
+
|
349
|
+
if (regexContent && !noFix) {
|
350
|
+
let charIncrease = 0;
|
351
|
+
|
352
|
+
const ast = new RegExpParser({ ecmaVersion: regexppEcmaVersion }).parsePattern(regexContent, 0, regexContent.length, flags ? flags.includes("u") : false);
|
353
|
+
|
354
|
+
visitRegExpAST(ast, {
|
355
|
+
onCharacterEnter(characterNode) {
|
356
|
+
const escaped = resolveEscapes(characterNode.raw);
|
357
|
+
|
358
|
+
if (escaped) {
|
359
|
+
regexContent =
|
360
|
+
regexContent.slice(0, characterNode.start + charIncrease) +
|
361
|
+
escaped +
|
362
|
+
regexContent.slice(characterNode.end + charIncrease);
|
363
|
+
|
364
|
+
if (characterNode.raw.length === 1) {
|
365
|
+
charIncrease += 1;
|
366
|
+
}
|
367
|
+
}
|
368
|
+
}
|
369
|
+
});
|
370
|
+
}
|
371
|
+
|
372
|
+
const newRegExpValue = `/${regexContent || "(?:)"}/${flags || ""}`;
|
373
|
+
|
374
|
+
context.report({
|
375
|
+
node,
|
376
|
+
messageId: "unexpectedRegExp",
|
377
|
+
suggest: noFix ? [] : [{
|
378
|
+
messageId: "replaceWithLiteral",
|
379
|
+
fix(fixer) {
|
380
|
+
const tokenAfter = sourceCode.getTokenAfter(node);
|
381
|
+
|
382
|
+
return fixer.replaceText(
|
383
|
+
node,
|
384
|
+
(tokenBefore && !canTokensBeAdjacent(tokenBefore, newRegExpValue) && tokenBefore.range[1] === node.range[0] ? " " : "") +
|
385
|
+
newRegExpValue +
|
386
|
+
(tokenAfter && !canTokensBeAdjacent(newRegExpValue, tokenAfter) && node.range[1] === tokenAfter.range[0] ? " " : "")
|
387
|
+
);
|
388
|
+
}
|
389
|
+
}]
|
390
|
+
});
|
175
391
|
}
|
176
392
|
}
|
177
393
|
}
|
@@ -188,7 +188,7 @@ module.exports = {
|
|
188
188
|
return sourceCode.getText(currentNode);
|
189
189
|
}
|
190
190
|
|
191
|
-
if (isConcatenation(currentNode) && hasStringLiteral(currentNode)
|
191
|
+
if (isConcatenation(currentNode) && hasStringLiteral(currentNode)) {
|
192
192
|
const plusSign = sourceCode.getFirstTokenBetween(currentNode.left, currentNode.right, token => token.value === "+");
|
193
193
|
const textBeforePlus = getTextBetween(currentNode.left, plusSign);
|
194
194
|
const textAfterPlus = getTextBetween(plusSign, currentNode.right);
|