Permalink
Cannot retrieve contributors at this time
301 lines (265 sloc)
10.9 KB
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
codeql-action/node_modules/eslint/lib/rules/prefer-destructuring.js
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @fileoverview Prefer destructuring from arrays and objects | |
* @author Alex LaFroscia | |
*/ | |
"use strict"; | |
//------------------------------------------------------------------------------ | |
// Requirements | |
//------------------------------------------------------------------------------ | |
const astUtils = require("./utils/ast-utils"); | |
//------------------------------------------------------------------------------ | |
// Helpers | |
//------------------------------------------------------------------------------ | |
const PRECEDENCE_OF_ASSIGNMENT_EXPR = astUtils.getPrecedence({ type: "AssignmentExpression" }); | |
//------------------------------------------------------------------------------ | |
// Rule Definition | |
//------------------------------------------------------------------------------ | |
/** @type {import('../shared/types').Rule} */ | |
module.exports = { | |
meta: { | |
type: "suggestion", | |
docs: { | |
description: "Require destructuring from arrays and/or objects", | |
recommended: false, | |
url: "https://eslint.org/docs/latest/rules/prefer-destructuring" | |
}, | |
fixable: "code", | |
schema: [ | |
{ | |
/* | |
* old support {array: Boolean, object: Boolean} | |
* new support {VariableDeclarator: {}, AssignmentExpression: {}} | |
*/ | |
oneOf: [ | |
{ | |
type: "object", | |
properties: { | |
VariableDeclarator: { | |
type: "object", | |
properties: { | |
array: { | |
type: "boolean" | |
}, | |
object: { | |
type: "boolean" | |
} | |
}, | |
additionalProperties: false | |
}, | |
AssignmentExpression: { | |
type: "object", | |
properties: { | |
array: { | |
type: "boolean" | |
}, | |
object: { | |
type: "boolean" | |
} | |
}, | |
additionalProperties: false | |
} | |
}, | |
additionalProperties: false | |
}, | |
{ | |
type: "object", | |
properties: { | |
array: { | |
type: "boolean" | |
}, | |
object: { | |
type: "boolean" | |
} | |
}, | |
additionalProperties: false | |
} | |
] | |
}, | |
{ | |
type: "object", | |
properties: { | |
enforceForRenamedProperties: { | |
type: "boolean" | |
} | |
}, | |
additionalProperties: false | |
} | |
], | |
messages: { | |
preferDestructuring: "Use {{type}} destructuring." | |
} | |
}, | |
create(context) { | |
const enabledTypes = context.options[0]; | |
const enforceForRenamedProperties = context.options[1] && context.options[1].enforceForRenamedProperties; | |
let normalizedOptions = { | |
VariableDeclarator: { array: true, object: true }, | |
AssignmentExpression: { array: true, object: true } | |
}; | |
if (enabledTypes) { | |
normalizedOptions = typeof enabledTypes.array !== "undefined" || typeof enabledTypes.object !== "undefined" | |
? { VariableDeclarator: enabledTypes, AssignmentExpression: enabledTypes } | |
: enabledTypes; | |
} | |
//-------------------------------------------------------------------------- | |
// Helpers | |
//-------------------------------------------------------------------------- | |
/** | |
* Checks if destructuring type should be checked. | |
* @param {string} nodeType "AssignmentExpression" or "VariableDeclarator" | |
* @param {string} destructuringType "array" or "object" | |
* @returns {boolean} `true` if the destructuring type should be checked for the given node | |
*/ | |
function shouldCheck(nodeType, destructuringType) { | |
return normalizedOptions && | |
normalizedOptions[nodeType] && | |
normalizedOptions[nodeType][destructuringType]; | |
} | |
/** | |
* Determines if the given node is accessing an array index | |
* | |
* This is used to differentiate array index access from object property | |
* access. | |
* @param {ASTNode} node the node to evaluate | |
* @returns {boolean} whether or not the node is an integer | |
*/ | |
function isArrayIndexAccess(node) { | |
return Number.isInteger(node.property.value); | |
} | |
/** | |
* Report that the given node should use destructuring | |
* @param {ASTNode} reportNode the node to report | |
* @param {string} type the type of destructuring that should have been done | |
* @param {Function|null} fix the fix function or null to pass to context.report | |
* @returns {void} | |
*/ | |
function report(reportNode, type, fix) { | |
context.report({ | |
node: reportNode, | |
messageId: "preferDestructuring", | |
data: { type }, | |
fix | |
}); | |
} | |
/** | |
* Determines if a node should be fixed into object destructuring | |
* | |
* The fixer only fixes the simplest case of object destructuring, | |
* like: `let x = a.x`; | |
* | |
* Assignment expression is not fixed. | |
* Array destructuring is not fixed. | |
* Renamed property is not fixed. | |
* @param {ASTNode} node the node to evaluate | |
* @returns {boolean} whether or not the node should be fixed | |
*/ | |
function shouldFix(node) { | |
return node.type === "VariableDeclarator" && | |
node.id.type === "Identifier" && | |
node.init.type === "MemberExpression" && | |
!node.init.computed && | |
node.init.property.type === "Identifier" && | |
node.id.name === node.init.property.name; | |
} | |
/** | |
* Fix a node into object destructuring. | |
* This function only handles the simplest case of object destructuring, | |
* see {@link shouldFix}. | |
* @param {SourceCodeFixer} fixer the fixer object | |
* @param {ASTNode} node the node to be fixed. | |
* @returns {Object} a fix for the node | |
*/ | |
function fixIntoObjectDestructuring(fixer, node) { | |
const rightNode = node.init; | |
const sourceCode = context.sourceCode; | |
// Don't fix if that would remove any comments. Only comments inside `rightNode.object` can be preserved. | |
if (sourceCode.getCommentsInside(node).length > sourceCode.getCommentsInside(rightNode.object).length) { | |
return null; | |
} | |
let objectText = sourceCode.getText(rightNode.object); | |
if (astUtils.getPrecedence(rightNode.object) < PRECEDENCE_OF_ASSIGNMENT_EXPR) { | |
objectText = `(${objectText})`; | |
} | |
return fixer.replaceText( | |
node, | |
`{${rightNode.property.name}} = ${objectText}` | |
); | |
} | |
/** | |
* Check that the `prefer-destructuring` rules are followed based on the | |
* given left- and right-hand side of the assignment. | |
* | |
* Pulled out into a separate method so that VariableDeclarators and | |
* AssignmentExpressions can share the same verification logic. | |
* @param {ASTNode} leftNode the left-hand side of the assignment | |
* @param {ASTNode} rightNode the right-hand side of the assignment | |
* @param {ASTNode} reportNode the node to report the error on | |
* @returns {void} | |
*/ | |
function performCheck(leftNode, rightNode, reportNode) { | |
if ( | |
rightNode.type !== "MemberExpression" || | |
rightNode.object.type === "Super" || | |
rightNode.property.type === "PrivateIdentifier" | |
) { | |
return; | |
} | |
if (isArrayIndexAccess(rightNode)) { | |
if (shouldCheck(reportNode.type, "array")) { | |
report(reportNode, "array", null); | |
} | |
return; | |
} | |
const fix = shouldFix(reportNode) | |
? fixer => fixIntoObjectDestructuring(fixer, reportNode) | |
: null; | |
if (shouldCheck(reportNode.type, "object") && enforceForRenamedProperties) { | |
report(reportNode, "object", fix); | |
return; | |
} | |
if (shouldCheck(reportNode.type, "object")) { | |
const property = rightNode.property; | |
if ( | |
(property.type === "Literal" && leftNode.name === property.value) || | |
(property.type === "Identifier" && leftNode.name === property.name && !rightNode.computed) | |
) { | |
report(reportNode, "object", fix); | |
} | |
} | |
} | |
/** | |
* Check if a given variable declarator is coming from an property access | |
* that should be using destructuring instead | |
* @param {ASTNode} node the variable declarator to check | |
* @returns {void} | |
*/ | |
function checkVariableDeclarator(node) { | |
// Skip if variable is declared without assignment | |
if (!node.init) { | |
return; | |
} | |
// We only care about member expressions past this point | |
if (node.init.type !== "MemberExpression") { | |
return; | |
} | |
performCheck(node.id, node.init, node); | |
} | |
/** | |
* Run the `prefer-destructuring` check on an AssignmentExpression | |
* @param {ASTNode} node the AssignmentExpression node | |
* @returns {void} | |
*/ | |
function checkAssignmentExpression(node) { | |
if (node.operator === "=") { | |
performCheck(node.left, node.right, node); | |
} | |
} | |
//-------------------------------------------------------------------------- | |
// Public | |
//-------------------------------------------------------------------------- | |
return { | |
VariableDeclarator: checkVariableDeclarator, | |
AssignmentExpression: checkAssignmentExpression | |
}; | |
} | |
}; |