@mysticatea/eslint-plugin
Version:
Additional ESLint rules.
162 lines (141 loc) • 5.31 kB
JavaScript
/**
* @fileoverview Rule to disallow unnecessary spread operators.
* @author Toru Nagashima
*/
"use strict"
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const FUNC_TYPE = /^(?:FunctionDeclaration|(?:New|Call|(?:Arrow)?Function)Expression)$/u
const PROPERTY_PATTERN = /^(?:Experimental)?(Rest|Spread)Property$/u
/**
* Checks whether the given token is a comma.
*
* @param {Token} token - The token to check.
* @returns {boolean} `true` if the token is a comma.
*/
function isCommaToken(token) {
return token.type === "Punctuator" && token.value === ","
}
/**
* Check whether a given node is a hole.
* @param {ASTNode|null} element The node to check.
* @returns {boolean} `true` if the node is a hole.
*/
function isHole(element) {
return element == null
}
/**
* Gets the last token of the given node's elements.
* This skips trailing commas.
*
* @param {SourceCode} sourceCode - The source code object to get tokens.
* @param {ASTNode} node - The node to get. This is one of ArrayExpression,
* ArrayPattern, ObjectExpression, and ObjectPattern.
* @returns {Token} The last element token.
*/
function getLastElementToken(sourceCode, node) {
const token = sourceCode.getLastToken(node, 1)
if (isCommaToken(token)) {
return sourceCode.getTokenBefore(token)
}
return token
}
/**
* Defines a fixer function.
*
* @param {SourceCode} sourceCode - The source code object to get tokens.
* @param {ASTNode} node - A node to fix.
* @returns {function} A fixer function.
*/
function defineFixer(sourceCode, node) {
return fixer => {
const child = node.argument
// If the inner array includes holes, do nothing.
if (child.elements != null && child.elements.some(isHole)) {
return null
}
// Remove this element if it's empty.
if ((child.elements || child.properties).length === 0) {
const next = sourceCode.getTokenAfter(node)
if (isCommaToken(next)) {
return fixer.removeRange([node.range[0], next.range[1]])
}
const prev = sourceCode.getTokenBefore(node)
if (isCommaToken(prev)) {
return fixer.removeRange([prev.range[0], node.range[1]])
}
return fixer.remove(node)
}
// Unwrap.
const first = sourceCode.getFirstToken(child, 1)
const last = getLastElementToken(sourceCode, child)
const replaceText = sourceCode.text.slice(first.range[0], last.range[1])
return fixer.replaceText(node, replaceText)
}
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: "Disallow unnecessary spread operators.",
category: "Best Practices",
recommended: false,
url:
"https://github.com/mysticatea/eslint-plugin/blob/v13.0.0/docs/rules/no-useless-rest-spread.md",
},
fixable: "code",
schema: [],
type: "suggestion",
},
create(context) {
const sourceCode = context.getSourceCode()
/**
* Verify the given SpreadElement or RestElement.
* @param {ASTNode} node The node to verify.
* @returns {void}
*/
function verify(node) {
const nodeType = node.type.replace(
PROPERTY_PATTERN,
t => `${t}Element`
)
const parentType = node.parent.type
const argumentType = node.argument.type
const isArray = argumentType.startsWith("Array")
const isObject = !isArray && argumentType.startsWith("Object")
const isRedundant =
((isArray || isObject) && argumentType === parentType) ||
(isArray && FUNC_TYPE.test(parentType))
if (isRedundant) {
const isRestParameter =
nodeType === "RestElement" && argumentType !== parentType
const type1 = nodeType === "RestElement" ? "rest" : "spread"
const type2 =
/*eslint-disable @mysticatea/prettier */
isRestParameter ? "parameter" :
isArray ? "element" :
/* otherwise */ "property"
/*eslint-enable @mysticatea/prettier */
context.report({
node,
message: "Redundant {{type1}} {{type2}}.",
data: { type1, type2 },
fix: defineFixer(sourceCode, node),
})
}
}
return {
SpreadElement: verify,
RestElement: verify,
// Legacy for espree and babel-eslint.
// SpreadProperty and RestProperty were replaced by SpreadElement and RestElement.
SpreadProperty: verify,
RestProperty: verify,
ExperimentalSpreadProperty: verify,
ExperimentalRestProperty: verify,
}
},
}