2 * @fileoverview Rule to flag wrapping non-iife in parens
3 * @author Gyandeep Singh
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const astUtils = require("./utils/ast-utils");
14 //------------------------------------------------------------------------------
16 //------------------------------------------------------------------------------
19 * Property name if it can be computed statically, otherwise the list of the tokens of the key node.
20 * @typedef {string|Token[]} Key
24 * Accessor nodes with the same key.
25 * @typedef {Object} AccessorData
26 * @property {Key} key Accessor's key
27 * @property {ASTNode[]} getters List of getter nodes.
28 * @property {ASTNode[]} setters List of setter nodes.
31 //------------------------------------------------------------------------------
33 //------------------------------------------------------------------------------
36 * Checks whether or not the given lists represent the equal tokens in the same order.
37 * Tokens are compared by their properties, not by instance.
38 * @param {Token[]} left First list of tokens.
39 * @param {Token[]} right Second list of tokens.
40 * @returns {boolean} `true` if the lists have same tokens.
42 function areEqualTokenLists(left, right) {
43 if (left.length !== right.length) {
47 for (let i = 0; i < left.length; i++) {
48 const leftToken = left[i],
49 rightToken = right[i];
51 if (leftToken.type !== rightToken.type || leftToken.value !== rightToken.value) {
60 * Checks whether or not the given keys are equal.
61 * @param {Key} left First key.
62 * @param {Key} right Second key.
63 * @returns {boolean} `true` if the keys are equal.
65 function areEqualKeys(left, right) {
66 if (typeof left === "string" && typeof right === "string") {
68 // Statically computed names.
69 return left === right;
71 if (Array.isArray(left) && Array.isArray(right)) {
74 return areEqualTokenLists(left, right);
81 * Checks whether or not a given node is of an accessor kind ('get' or 'set').
82 * @param {ASTNode} node A node to check.
83 * @returns {boolean} `true` if the node is of an accessor kind.
85 function isAccessorKind(node) {
86 return node.kind === "get" || node.kind === "set";
90 * Checks whether or not a given node is an `Identifier` node which was named a given name.
91 * @param {ASTNode} node A node to check.
92 * @param {string} name An expected name of the node.
93 * @returns {boolean} `true` if the node is an `Identifier` node which was named as expected.
95 function isIdentifier(node, name) {
96 return node.type === "Identifier" && node.name === name;
100 * Checks whether or not a given node is an argument of a specified method call.
101 * @param {ASTNode} node A node to check.
102 * @param {number} index An expected index of the node in arguments.
103 * @param {string} object An expected name of the object of the method.
104 * @param {string} property An expected name of the method.
105 * @returns {boolean} `true` if the node is an argument of the specified method call.
107 function isArgumentOfMethodCall(node, index, object, property) {
108 const parent = node.parent;
111 parent.type === "CallExpression" &&
112 parent.callee.type === "MemberExpression" &&
113 parent.callee.computed === false &&
114 isIdentifier(parent.callee.object, object) &&
115 isIdentifier(parent.callee.property, property) &&
116 parent.arguments[index] === node
121 * Checks whether or not a given node is a property descriptor.
122 * @param {ASTNode} node A node to check.
123 * @returns {boolean} `true` if the node is a property descriptor.
125 function isPropertyDescriptor(node) {
127 // Object.defineProperty(obj, "foo", {set: ...})
128 if (isArgumentOfMethodCall(node, 2, "Object", "defineProperty") ||
129 isArgumentOfMethodCall(node, 2, "Reflect", "defineProperty")
135 * Object.defineProperties(obj, {foo: {set: ...}})
136 * Object.create(proto, {foo: {set: ...}})
138 const grandparent = node.parent.parent;
140 return grandparent.type === "ObjectExpression" && (
141 isArgumentOfMethodCall(grandparent, 1, "Object", "create") ||
142 isArgumentOfMethodCall(grandparent, 1, "Object", "defineProperties")
146 //------------------------------------------------------------------------------
148 //------------------------------------------------------------------------------
155 description: "enforce getter and setter pairs in objects and classes",
156 category: "Best Practices",
158 url: "https://eslint.org/docs/rules/accessor-pairs"
172 enforceForClassMembers: {
177 additionalProperties: false
181 missingGetterInPropertyDescriptor: "Getter is not present in property descriptor.",
182 missingSetterInPropertyDescriptor: "Setter is not present in property descriptor.",
183 missingGetterInObjectLiteral: "Getter is not present for {{ name }}.",
184 missingSetterInObjectLiteral: "Setter is not present for {{ name }}.",
185 missingGetterInClass: "Getter is not present for class {{ name }}.",
186 missingSetterInClass: "Setter is not present for class {{ name }}."
190 const config = context.options[0] || {};
191 const checkGetWithoutSet = config.getWithoutSet === true;
192 const checkSetWithoutGet = config.setWithoutGet !== false;
193 const enforceForClassMembers = config.enforceForClassMembers === true;
194 const sourceCode = context.getSourceCode();
197 * Reports the given node.
198 * @param {ASTNode} node The node to report.
199 * @param {string} messageKind "missingGetter" or "missingSetter".
203 function report(node, messageKind) {
204 if (node.type === "Property") {
207 messageId: `${messageKind}InObjectLiteral`,
208 loc: astUtils.getFunctionHeadLoc(node.value, sourceCode),
209 data: { name: astUtils.getFunctionNameWithKind(node.value) }
211 } else if (node.type === "MethodDefinition") {
214 messageId: `${messageKind}InClass`,
215 loc: astUtils.getFunctionHeadLoc(node.value, sourceCode),
216 data: { name: astUtils.getFunctionNameWithKind(node.value) }
221 messageId: `${messageKind}InPropertyDescriptor`
227 * Reports each of the nodes in the given list using the same messageId.
228 * @param {ASTNode[]} nodes Nodes to report.
229 * @param {string} messageKind "missingGetter" or "missingSetter".
233 function reportList(nodes, messageKind) {
234 for (const node of nodes) {
235 report(node, messageKind);
240 * Creates a new `AccessorData` object for the given getter or setter node.
241 * @param {ASTNode} node A getter or setter node.
242 * @returns {AccessorData} New `AccessorData` object that contains the given node.
245 function createAccessorData(node) {
246 const name = astUtils.getStaticPropertyName(node);
247 const key = (name !== null) ? name : sourceCode.getTokens(node.key);
251 getters: node.kind === "get" ? [node] : [],
252 setters: node.kind === "set" ? [node] : []
257 * Merges the given `AccessorData` object into the given accessors list.
258 * @param {AccessorData[]} accessors The list to merge into.
259 * @param {AccessorData} accessorData The object to merge.
260 * @returns {AccessorData[]} The same instance with the merged object.
263 function mergeAccessorData(accessors, accessorData) {
264 const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key));
266 if (equalKeyElement) {
267 equalKeyElement.getters.push(...accessorData.getters);
268 equalKeyElement.setters.push(...accessorData.setters);
270 accessors.push(accessorData);
277 * Checks accessor pairs in the given list of nodes.
278 * @param {ASTNode[]} nodes The list to check.
282 function checkList(nodes) {
283 const accessors = nodes
284 .filter(isAccessorKind)
285 .map(createAccessorData)
286 .reduce(mergeAccessorData, []);
288 for (const { getters, setters } of accessors) {
289 if (checkSetWithoutGet && setters.length && !getters.length) {
290 reportList(setters, "missingGetter");
292 if (checkGetWithoutSet && getters.length && !setters.length) {
293 reportList(getters, "missingSetter");
299 * Checks accessor pairs in an object literal.
300 * @param {ASTNode} node `ObjectExpression` node to check.
304 function checkObjectLiteral(node) {
305 checkList(node.properties.filter(p => p.type === "Property"));
309 * Checks accessor pairs in a property descriptor.
310 * @param {ASTNode} node Property descriptor `ObjectExpression` node to check.
314 function checkPropertyDescriptor(node) {
315 const namesToCheck = node.properties
316 .filter(p => p.type === "Property" && p.kind === "init" && !p.computed)
317 .map(({ key }) => key.name);
319 const hasGetter = namesToCheck.includes("get");
320 const hasSetter = namesToCheck.includes("set");
322 if (checkSetWithoutGet && hasSetter && !hasGetter) {
323 report(node, "missingGetter");
325 if (checkGetWithoutSet && hasGetter && !hasSetter) {
326 report(node, "missingSetter");
331 * Checks the given object expression as an object literal and as a possible property descriptor.
332 * @param {ASTNode} node `ObjectExpression` node to check.
336 function checkObjectExpression(node) {
337 checkObjectLiteral(node);
338 if (isPropertyDescriptor(node)) {
339 checkPropertyDescriptor(node);
344 * Checks the given class body.
345 * @param {ASTNode} node `ClassBody` node to check.
349 function checkClassBody(node) {
350 const methodDefinitions = node.body.filter(m => m.type === "MethodDefinition");
352 checkList(methodDefinitions.filter(m => m.static));
353 checkList(methodDefinitions.filter(m => !m.static));
356 const listeners = {};
358 if (checkSetWithoutGet || checkGetWithoutSet) {
359 listeners.ObjectExpression = checkObjectExpression;
360 if (enforceForClassMembers) {
361 listeners.ClassBody = checkClassBody;