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