/** * @fileoverview Disallow construction of dense arrays using the Array constructor * @author Matt DuVall */ 'use strict'; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ const { getVariableByName, isClosingParenToken, isOpeningParenToken, isStartOfExpressionStatement, needsPrecedingSemicolon, } = require('./utils/ast-utils'); //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ /** @type {import('../types').Rule.RuleModule} */ module.exports = { meta: { dialects: ['javascript', 'typescript'], language: 'javascript', 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.typeArguments || (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, }; }, };