-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
/
no-array-constructor.js
133 lines (110 loc) · 4.38 KB
/
no-array-constructor.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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/**
* @fileoverview Disallow construction of dense arrays using the Array constructor
* @author Matt DuVall <http://www.mattduvall.com/>
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const {
getVariableByName,
isClosingParenToken,
isOpeningParenToken,
isStartOfExpressionStatement,
needsPrecedingSemicolon
} = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "Disallow `Array` constructors",
recommended: false,
url: "https://eslint.org/docs/latest/rules/no-array-constructor"
},
hasSuggestions: true,
schema: [],
messages: {
preferLiteral: "The array literal notation [] is preferable.",
useLiteral: "Replace with an array literal.",
useLiteralAfterSemicolon: "Replace with an array literal, add preceding semicolon."
}
},
create(context) {
const sourceCode = context.sourceCode;
/**
* Gets the text between the calling parentheses of a CallExpression or NewExpression.
* @param {ASTNode} node A CallExpression or NewExpression node.
* @returns {string} The text between the calling parentheses, or an empty string if there are none.
*/
function getArgumentsText(node) {
const lastToken = sourceCode.getLastToken(node);
if (!isClosingParenToken(lastToken)) {
return "";
}
let firstToken = node.callee;
do {
firstToken = sourceCode.getTokenAfter(firstToken);
if (!firstToken || firstToken === lastToken) {
return "";
}
} while (!isOpeningParenToken(firstToken));
return sourceCode.text.slice(firstToken.range[1], lastToken.range[0]);
}
/**
* Disallow construction of dense arrays using the Array constructor
* @param {ASTNode} node node to evaluate
* @returns {void}
* @private
*/
function check(node) {
if (
node.callee.type !== "Identifier" ||
node.callee.name !== "Array" ||
node.arguments.length === 1 &&
node.arguments[0].type !== "SpreadElement") {
return;
}
const variable = getVariableByName(sourceCode.getScope(node), "Array");
/*
* Check if `Array` is a predefined global variable: predefined globals have no declarations,
* meaning that the `identifiers` list of the variable object is empty.
*/
if (variable && variable.identifiers.length === 0) {
const argsText = getArgumentsText(node);
let fixText;
let messageId;
/*
* Check if the suggested change should include a preceding semicolon or not.
* Due to JavaScript's ASI rules, a missing semicolon may be inserted automatically
* before an expression like `Array()` or `new Array()`, but not when the expression
* is changed into an array literal like `[]`.
*/
if (isStartOfExpressionStatement(node) && needsPrecedingSemicolon(sourceCode, node)) {
fixText = `;[${argsText}]`;
messageId = "useLiteralAfterSemicolon";
} else {
fixText = `[${argsText}]`;
messageId = "useLiteral";
}
context.report({
node,
messageId: "preferLiteral",
suggest: [
{
messageId,
fix: fixer => fixer.replaceText(node, fixText)
}
]
});
}
}
return {
CallExpression: check,
NewExpression: check
};
}
};