| /** | |
| * @fileoverview Rule to flag use of duplicate keys in an object. | |
| * @author Ian Christian Myers | |
| */ | |
| "use strict"; | |
| //------------------------------------------------------------------------------ | |
| // Requirements | |
| //------------------------------------------------------------------------------ | |
| const astUtils = require("./utils/ast-utils"); | |
| //------------------------------------------------------------------------------ | |
| // Helpers | |
| //------------------------------------------------------------------------------ | |
| const GET_KIND = /^(?:init|get)$/u; | |
| const SET_KIND = /^(?:init|set)$/u; | |
| /** | |
| * The class which stores properties' information of an object. | |
| */ | |
| class ObjectInfo { | |
| /** | |
| * @param {ObjectInfo|null} upper - The information of the outer object. | |
| * @param {ASTNode} node - The ObjectExpression node of this information. | |
| */ | |
| constructor(upper, node) { | |
| this.upper = upper; | |
| this.node = node; | |
| this.properties = new Map(); | |
| } | |
| /** | |
| * Gets the information of the given Property node. | |
| * @param {ASTNode} node - The Property node to get. | |
| * @returns {{get: boolean, set: boolean}} The information of the property. | |
| */ | |
| getPropertyInfo(node) { | |
| const name = astUtils.getStaticPropertyName(node); | |
| if (!this.properties.has(name)) { | |
| this.properties.set(name, { get: false, set: false }); | |
| } | |
| return this.properties.get(name); | |
| } | |
| /** | |
| * Checks whether the given property has been defined already or not. | |
| * @param {ASTNode} node - The Property node to check. | |
| * @returns {boolean} `true` if the property has been defined. | |
| */ | |
| isPropertyDefined(node) { | |
| const entry = this.getPropertyInfo(node); | |
| return ( | |
| (GET_KIND.test(node.kind) && entry.get) || | |
| (SET_KIND.test(node.kind) && entry.set) | |
| ); | |
| } | |
| /** | |
| * Defines the given property. | |
| * @param {ASTNode} node - The Property node to define. | |
| * @returns {void} | |
| */ | |
| defineProperty(node) { | |
| const entry = this.getPropertyInfo(node); | |
| if (GET_KIND.test(node.kind)) { | |
| entry.get = true; | |
| } | |
| if (SET_KIND.test(node.kind)) { | |
| entry.set = true; | |
| } | |
| } | |
| } | |
| //------------------------------------------------------------------------------ | |
| // Rule Definition | |
| //------------------------------------------------------------------------------ | |
| module.exports = { | |
| meta: { | |
| type: "problem", | |
| docs: { | |
| description: "disallow duplicate keys in object literals", | |
| category: "Possible Errors", | |
| recommended: true, | |
| url: "https://eslint.org/docs/rules/no-dupe-keys" | |
| }, | |
| schema: [], | |
| messages: { | |
| unexpected: "Duplicate key '{{name}}'." | |
| } | |
| }, | |
| create(context) { | |
| let info = null; | |
| return { | |
| ObjectExpression(node) { | |
| info = new ObjectInfo(info, node); | |
| }, | |
| "ObjectExpression:exit"() { | |
| info = info.upper; | |
| }, | |
| Property(node) { | |
| const name = astUtils.getStaticPropertyName(node); | |
| // Skip destructuring. | |
| if (node.parent.type !== "ObjectExpression") { | |
| return; | |
| } | |
| // Skip if the name is not static. | |
| if (!name) { | |
| return; | |
| } | |
| // Reports if the name is defined already. | |
| if (info.isPropertyDefined(node)) { | |
| context.report({ | |
| node: info.node, | |
| loc: node.key.loc, | |
| messageId: "unexpected", | |
| data: { name } | |
| }); | |
| } | |
| // Update info. | |
| info.defineProperty(node); | |
| } | |
| }; | |
| } | |
| }; |