You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
142 lines
3.9 KiB
142 lines
3.9 KiB
/** |
|
* @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 |
|
//------------------------------------------------------------------------------ |
|
|
|
/** @type {import('../shared/types').Rule} */ |
|
module.exports = { |
|
meta: { |
|
type: "problem", |
|
|
|
docs: { |
|
description: "Disallow duplicate keys in object literals", |
|
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 === null) { |
|
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); |
|
} |
|
}; |
|
} |
|
};
|
|
|