]>
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 | ||
85 | module.exports = { | |
86 | meta: { | |
87 | type: "problem", | |
88 | ||
89 | docs: { | |
90 | description: "disallow duplicate keys in object literals", | |
eb39fafa DC |
91 | recommended: true, |
92 | url: "https://eslint.org/docs/rules/no-dupe-keys" | |
93 | }, | |
94 | ||
95 | schema: [], | |
96 | ||
97 | messages: { | |
98 | unexpected: "Duplicate key '{{name}}'." | |
99 | } | |
100 | }, | |
101 | ||
102 | create(context) { | |
103 | let info = null; | |
104 | ||
105 | return { | |
106 | ObjectExpression(node) { | |
107 | info = new ObjectInfo(info, node); | |
108 | }, | |
109 | "ObjectExpression:exit"() { | |
110 | info = info.upper; | |
111 | }, | |
112 | ||
113 | Property(node) { | |
114 | const name = astUtils.getStaticPropertyName(node); | |
115 | ||
116 | // Skip destructuring. | |
117 | if (node.parent.type !== "ObjectExpression") { | |
118 | return; | |
119 | } | |
120 | ||
121 | // Skip if the name is not static. | |
122 | if (name === null) { | |
123 | return; | |
124 | } | |
125 | ||
126 | // Reports if the name is defined already. | |
127 | if (info.isPropertyDefined(node)) { | |
128 | context.report({ | |
129 | node: info.node, | |
130 | loc: node.key.loc, | |
131 | messageId: "unexpected", | |
132 | data: { name } | |
133 | }); | |
134 | } | |
135 | ||
136 | // Update info. | |
137 | info.defineProperty(node); | |
138 | } | |
139 | }; | |
140 | } | |
141 | }; |