Permalink
Cannot retrieve contributors at this time
346 lines (300 sloc)
11.7 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/accessor-pairs.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 Rule to enforce getter and setter pairs in objects and classes. | |
* @author Gyandeep Singh | |
*/ | |
"use strict"; | |
//------------------------------------------------------------------------------ | |
// Requirements | |
//------------------------------------------------------------------------------ | |
const astUtils = require("./utils/ast-utils"); | |
//------------------------------------------------------------------------------ | |
// Typedefs | |
//------------------------------------------------------------------------------ | |
/** | |
* Property name if it can be computed statically, otherwise the list of the tokens of the key node. | |
* @typedef {string|Token[]} Key | |
*/ | |
/** | |
* Accessor nodes with the same key. | |
* @typedef {Object} AccessorData | |
* @property {Key} key Accessor's key | |
* @property {ASTNode[]} getters List of getter nodes. | |
* @property {ASTNode[]} setters List of setter nodes. | |
*/ | |
//------------------------------------------------------------------------------ | |
// Helpers | |
//------------------------------------------------------------------------------ | |
/** | |
* Checks whether or not the given lists represent the equal tokens in the same order. | |
* Tokens are compared by their properties, not by instance. | |
* @param {Token[]} left First list of tokens. | |
* @param {Token[]} right Second list of tokens. | |
* @returns {boolean} `true` if the lists have same tokens. | |
*/ | |
function areEqualTokenLists(left, right) { | |
if (left.length !== right.length) { | |
return false; | |
} | |
for (let i = 0; i < left.length; i++) { | |
const leftToken = left[i], | |
rightToken = right[i]; | |
if (leftToken.type !== rightToken.type || leftToken.value !== rightToken.value) { | |
return false; | |
} | |
} | |
return true; | |
} | |
/** | |
* Checks whether or not the given keys are equal. | |
* @param {Key} left First key. | |
* @param {Key} right Second key. | |
* @returns {boolean} `true` if the keys are equal. | |
*/ | |
function areEqualKeys(left, right) { | |
if (typeof left === "string" && typeof right === "string") { | |
// Statically computed names. | |
return left === right; | |
} | |
if (Array.isArray(left) && Array.isArray(right)) { | |
// Token lists. | |
return areEqualTokenLists(left, right); | |
} | |
return false; | |
} | |
/** | |
* Checks whether or not a given node is of an accessor kind ('get' or 'set'). | |
* @param {ASTNode} node A node to check. | |
* @returns {boolean} `true` if the node is of an accessor kind. | |
*/ | |
function isAccessorKind(node) { | |
return node.kind === "get" || node.kind === "set"; | |
} | |
/** | |
* Checks whether or not a given node is an argument of a specified method call. | |
* @param {ASTNode} node A node to check. | |
* @param {number} index An expected index of the node in arguments. | |
* @param {string} object An expected name of the object of the method. | |
* @param {string} property An expected name of the method. | |
* @returns {boolean} `true` if the node is an argument of the specified method call. | |
*/ | |
function isArgumentOfMethodCall(node, index, object, property) { | |
const parent = node.parent; | |
return ( | |
parent.type === "CallExpression" && | |
astUtils.isSpecificMemberAccess(parent.callee, object, property) && | |
parent.arguments[index] === node | |
); | |
} | |
/** | |
* Checks whether or not a given node is a property descriptor. | |
* @param {ASTNode} node A node to check. | |
* @returns {boolean} `true` if the node is a property descriptor. | |
*/ | |
function isPropertyDescriptor(node) { | |
// Object.defineProperty(obj, "foo", {set: ...}) | |
if (isArgumentOfMethodCall(node, 2, "Object", "defineProperty") || | |
isArgumentOfMethodCall(node, 2, "Reflect", "defineProperty") | |
) { | |
return true; | |
} | |
/* | |
* Object.defineProperties(obj, {foo: {set: ...}}) | |
* Object.create(proto, {foo: {set: ...}}) | |
*/ | |
const grandparent = node.parent.parent; | |
return grandparent.type === "ObjectExpression" && ( | |
isArgumentOfMethodCall(grandparent, 1, "Object", "create") || | |
isArgumentOfMethodCall(grandparent, 1, "Object", "defineProperties") | |
); | |
} | |
//------------------------------------------------------------------------------ | |
// Rule Definition | |
//------------------------------------------------------------------------------ | |
/** @type {import('../shared/types').Rule} */ | |
module.exports = { | |
meta: { | |
type: "suggestion", | |
docs: { | |
description: "Enforce getter and setter pairs in objects and classes", | |
recommended: false, | |
url: "https://eslint.org/docs/latest/rules/accessor-pairs" | |
}, | |
schema: [{ | |
type: "object", | |
properties: { | |
getWithoutSet: { | |
type: "boolean", | |
default: false | |
}, | |
setWithoutGet: { | |
type: "boolean", | |
default: true | |
}, | |
enforceForClassMembers: { | |
type: "boolean", | |
default: true | |
} | |
}, | |
additionalProperties: false | |
}], | |
messages: { | |
missingGetterInPropertyDescriptor: "Getter is not present in property descriptor.", | |
missingSetterInPropertyDescriptor: "Setter is not present in property descriptor.", | |
missingGetterInObjectLiteral: "Getter is not present for {{ name }}.", | |
missingSetterInObjectLiteral: "Setter is not present for {{ name }}.", | |
missingGetterInClass: "Getter is not present for class {{ name }}.", | |
missingSetterInClass: "Setter is not present for class {{ name }}." | |
} | |
}, | |
create(context) { | |
const config = context.options[0] || {}; | |
const checkGetWithoutSet = config.getWithoutSet === true; | |
const checkSetWithoutGet = config.setWithoutGet !== false; | |
const enforceForClassMembers = config.enforceForClassMembers !== false; | |
const sourceCode = context.sourceCode; | |
/** | |
* Reports the given node. | |
* @param {ASTNode} node The node to report. | |
* @param {string} messageKind "missingGetter" or "missingSetter". | |
* @returns {void} | |
* @private | |
*/ | |
function report(node, messageKind) { | |
if (node.type === "Property") { | |
context.report({ | |
node, | |
messageId: `${messageKind}InObjectLiteral`, | |
loc: astUtils.getFunctionHeadLoc(node.value, sourceCode), | |
data: { name: astUtils.getFunctionNameWithKind(node.value) } | |
}); | |
} else if (node.type === "MethodDefinition") { | |
context.report({ | |
node, | |
messageId: `${messageKind}InClass`, | |
loc: astUtils.getFunctionHeadLoc(node.value, sourceCode), | |
data: { name: astUtils.getFunctionNameWithKind(node.value) } | |
}); | |
} else { | |
context.report({ | |
node, | |
messageId: `${messageKind}InPropertyDescriptor` | |
}); | |
} | |
} | |
/** | |
* Reports each of the nodes in the given list using the same messageId. | |
* @param {ASTNode[]} nodes Nodes to report. | |
* @param {string} messageKind "missingGetter" or "missingSetter". | |
* @returns {void} | |
* @private | |
*/ | |
function reportList(nodes, messageKind) { | |
for (const node of nodes) { | |
report(node, messageKind); | |
} | |
} | |
/** | |
* Checks accessor pairs in the given list of nodes. | |
* @param {ASTNode[]} nodes The list to check. | |
* @returns {void} | |
* @private | |
*/ | |
function checkList(nodes) { | |
const accessors = []; | |
let found = false; | |
for (let i = 0; i < nodes.length; i++) { | |
const node = nodes[i]; | |
if (isAccessorKind(node)) { | |
// Creates a new `AccessorData` object for the given getter or setter node. | |
const name = astUtils.getStaticPropertyName(node); | |
const key = (name !== null) ? name : sourceCode.getTokens(node.key); | |
// Merges the given `AccessorData` object into the given accessors list. | |
for (let j = 0; j < accessors.length; j++) { | |
const accessor = accessors[j]; | |
if (areEqualKeys(accessor.key, key)) { | |
accessor.getters.push(...node.kind === "get" ? [node] : []); | |
accessor.setters.push(...node.kind === "set" ? [node] : []); | |
found = true; | |
break; | |
} | |
} | |
if (!found) { | |
accessors.push({ | |
key, | |
getters: node.kind === "get" ? [node] : [], | |
setters: node.kind === "set" ? [node] : [] | |
}); | |
} | |
found = false; | |
} | |
} | |
for (const { getters, setters } of accessors) { | |
if (checkSetWithoutGet && setters.length && !getters.length) { | |
reportList(setters, "missingGetter"); | |
} | |
if (checkGetWithoutSet && getters.length && !setters.length) { | |
reportList(getters, "missingSetter"); | |
} | |
} | |
} | |
/** | |
* Checks accessor pairs in an object literal. | |
* @param {ASTNode} node `ObjectExpression` node to check. | |
* @returns {void} | |
* @private | |
*/ | |
function checkObjectLiteral(node) { | |
checkList(node.properties.filter(p => p.type === "Property")); | |
} | |
/** | |
* Checks accessor pairs in a property descriptor. | |
* @param {ASTNode} node Property descriptor `ObjectExpression` node to check. | |
* @returns {void} | |
* @private | |
*/ | |
function checkPropertyDescriptor(node) { | |
const namesToCheck = new Set(node.properties | |
.filter(p => p.type === "Property" && p.kind === "init" && !p.computed) | |
.map(({ key }) => key.name)); | |
const hasGetter = namesToCheck.has("get"); | |
const hasSetter = namesToCheck.has("set"); | |
if (checkSetWithoutGet && hasSetter && !hasGetter) { | |
report(node, "missingGetter"); | |
} | |
if (checkGetWithoutSet && hasGetter && !hasSetter) { | |
report(node, "missingSetter"); | |
} | |
} | |
/** | |
* Checks the given object expression as an object literal and as a possible property descriptor. | |
* @param {ASTNode} node `ObjectExpression` node to check. | |
* @returns {void} | |
* @private | |
*/ | |
function checkObjectExpression(node) { | |
checkObjectLiteral(node); | |
if (isPropertyDescriptor(node)) { | |
checkPropertyDescriptor(node); | |
} | |
} | |
/** | |
* Checks the given class body. | |
* @param {ASTNode} node `ClassBody` node to check. | |
* @returns {void} | |
* @private | |
*/ | |
function checkClassBody(node) { | |
const methodDefinitions = node.body.filter(m => m.type === "MethodDefinition"); | |
checkList(methodDefinitions.filter(m => m.static)); | |
checkList(methodDefinitions.filter(m => !m.static)); | |
} | |
const listeners = {}; | |
if (checkSetWithoutGet || checkGetWithoutSet) { | |
listeners.ObjectExpression = checkObjectExpression; | |
if (enforceForClassMembers) { | |
listeners.ClassBody = checkClassBody; | |
} | |
} | |
return listeners; | |
} | |
}; |