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