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
!== false;
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
;