]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
1 | /** |
2 | * @fileoverview Rule to flag use of duplicate keys in an object. | |
3 | * @author Ian Christian Myers | |
4 | */ | |
5 | ||
6 | "use strict"; | |
7 | ||
8 | //------------------------------------------------------------------------------ | |
9 | // Requirements | |
10 | //------------------------------------------------------------------------------ | |
11 | ||
12 | const astUtils = require("./utils/ast-utils"); | |
13 | ||
14 | //------------------------------------------------------------------------------ | |
15 | // Helpers | |
16 | //------------------------------------------------------------------------------ | |
17 | ||
18 | const GET_KIND = /^(?:init|get)$/u; | |
19 | const SET_KIND = /^(?:init|set)$/u; | |
20 | ||
21 | /** | |
22 | * The class which stores properties' information of an object. | |
23 | */ | |
24 | class ObjectInfo { | |
25 | ||
eb39fafa DC |
26 | /** |
27 | * @param {ObjectInfo|null} upper The information of the outer object. | |
28 | * @param {ASTNode} node The ObjectExpression node of this information. | |
29 | */ | |
30 | constructor(upper, node) { | |
31 | this.upper = upper; | |
32 | this.node = node; | |
33 | this.properties = new Map(); | |
34 | } | |
35 | ||
36 | /** | |
37 | * Gets the information of the given Property node. | |
38 | * @param {ASTNode} node The Property node to get. | |
39 | * @returns {{get: boolean, set: boolean}} The information of the property. | |
40 | */ | |
41 | getPropertyInfo(node) { | |
42 | const name = astUtils.getStaticPropertyName(node); | |
43 | ||
44 | if (!this.properties.has(name)) { | |
45 | this.properties.set(name, { get: false, set: false }); | |
46 | } | |
47 | return this.properties.get(name); | |
48 | } | |
49 | ||
50 | /** | |
51 | * Checks whether the given property has been defined already or not. | |
52 | * @param {ASTNode} node The Property node to check. | |
53 | * @returns {boolean} `true` if the property has been defined. | |
54 | */ | |
55 | isPropertyDefined(node) { | |
56 | const entry = this.getPropertyInfo(node); | |
57 | ||
58 | return ( | |
59 | (GET_KIND.test(node.kind) && entry.get) || | |
60 | (SET_KIND.test(node.kind) && entry.set) | |
61 | ); | |
62 | } | |
63 | ||
64 | /** | |
65 | * Defines the given property. | |
66 | * @param {ASTNode} node The Property node to define. | |
67 | * @returns {void} | |
68 | */ | |
69 | defineProperty(node) { | |
70 | const entry = this.getPropertyInfo(node); | |
71 | ||
72 | if (GET_KIND.test(node.kind)) { | |
73 | entry.get = true; | |
74 | } | |
75 | if (SET_KIND.test(node.kind)) { | |
76 | entry.set = true; | |
77 | } | |
78 | } | |
79 | } | |
80 | ||
81 | //------------------------------------------------------------------------------ | |
82 | // Rule Definition | |
83 | //------------------------------------------------------------------------------ | |
84 | ||
34eeec05 | 85 | /** @type {import('../shared/types').Rule} */ |
eb39fafa DC |
86 | module.exports = { |
87 | meta: { | |
88 | type: "problem", | |
89 | ||
90 | docs: { | |
8f9d1d4d | 91 | description: "Disallow duplicate keys in object literals", |
eb39fafa DC |
92 | recommended: true, |
93 | url: "https://eslint.org/docs/rules/no-dupe-keys" | |
94 | }, | |
95 | ||
96 | schema: [], | |
97 | ||
98 | messages: { | |
99 | unexpected: "Duplicate key '{{name}}'." | |
100 | } | |
101 | }, | |
102 | ||
103 | create(context) { | |
104 | let info = null; | |
105 | ||
106 | return { | |
107 | ObjectExpression(node) { | |
108 | info = new ObjectInfo(info, node); | |
109 | }, | |
110 | "ObjectExpression:exit"() { | |
111 | info = info.upper; | |
112 | }, | |
113 | ||
114 | Property(node) { | |
115 | const name = astUtils.getStaticPropertyName(node); | |
116 | ||
117 | // Skip destructuring. | |
118 | if (node.parent.type !== "ObjectExpression") { | |
119 | return; | |
120 | } | |
121 | ||
122 | // Skip if the name is not static. | |
123 | if (name === null) { | |
124 | return; | |
125 | } | |
126 | ||
127 | // Reports if the name is defined already. | |
128 | if (info.isPropertyDefined(node)) { | |
129 | context.report({ | |
130 | node: info.node, | |
131 | loc: node.key.loc, | |
132 | messageId: "unexpected", | |
133 | data: { name } | |
134 | }); | |
135 | } | |
136 | ||
137 | // Update info. | |
138 | info.defineProperty(node); | |
139 | } | |
140 | }; | |
141 | } | |
142 | }; |