no-dupe-keys.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. /**
  2. * @fileoverview Rule to flag use of duplicate keys in an object.
  3. * @author Ian Christian Myers
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Helpers
  12. //------------------------------------------------------------------------------
  13. const GET_KIND = /^(?:init|get)$/u;
  14. const SET_KIND = /^(?:init|set)$/u;
  15. /**
  16. * The class which stores properties' information of an object.
  17. */
  18. class ObjectInfo {
  19. /**
  20. * @param {ObjectInfo|null} upper The information of the outer object.
  21. * @param {ASTNode} node The ObjectExpression node of this information.
  22. */
  23. constructor(upper, node) {
  24. this.upper = upper;
  25. this.node = node;
  26. this.properties = new Map();
  27. }
  28. /**
  29. * Gets the information of the given Property node.
  30. * @param {ASTNode} node The Property node to get.
  31. * @returns {{get: boolean, set: boolean}} The information of the property.
  32. */
  33. getPropertyInfo(node) {
  34. const name = astUtils.getStaticPropertyName(node);
  35. if (!this.properties.has(name)) {
  36. this.properties.set(name, { get: false, set: false });
  37. }
  38. return this.properties.get(name);
  39. }
  40. /**
  41. * Checks whether the given property has been defined already or not.
  42. * @param {ASTNode} node The Property node to check.
  43. * @returns {boolean} `true` if the property has been defined.
  44. */
  45. isPropertyDefined(node) {
  46. const entry = this.getPropertyInfo(node);
  47. return (
  48. (GET_KIND.test(node.kind) && entry.get) ||
  49. (SET_KIND.test(node.kind) && entry.set)
  50. );
  51. }
  52. /**
  53. * Defines the given property.
  54. * @param {ASTNode} node The Property node to define.
  55. * @returns {void}
  56. */
  57. defineProperty(node) {
  58. const entry = this.getPropertyInfo(node);
  59. if (GET_KIND.test(node.kind)) {
  60. entry.get = true;
  61. }
  62. if (SET_KIND.test(node.kind)) {
  63. entry.set = true;
  64. }
  65. }
  66. }
  67. //------------------------------------------------------------------------------
  68. // Rule Definition
  69. //------------------------------------------------------------------------------
  70. /** @type {import('../shared/types').Rule} */
  71. module.exports = {
  72. meta: {
  73. type: "problem",
  74. docs: {
  75. description: "Disallow duplicate keys in object literals",
  76. recommended: true,
  77. url: "https://eslint.org/docs/rules/no-dupe-keys"
  78. },
  79. schema: [],
  80. messages: {
  81. unexpected: "Duplicate key '{{name}}'."
  82. }
  83. },
  84. create(context) {
  85. let info = null;
  86. return {
  87. ObjectExpression(node) {
  88. info = new ObjectInfo(info, node);
  89. },
  90. "ObjectExpression:exit"() {
  91. info = info.upper;
  92. },
  93. Property(node) {
  94. const name = astUtils.getStaticPropertyName(node);
  95. // Skip destructuring.
  96. if (node.parent.type !== "ObjectExpression") {
  97. return;
  98. }
  99. // Skip if the name is not static.
  100. if (name === null) {
  101. return;
  102. }
  103. // Reports if the name is defined already.
  104. if (info.isPropertyDefined(node)) {
  105. context.report({
  106. node: info.node,
  107. loc: node.key.loc,
  108. messageId: "unexpected",
  109. data: { name }
  110. });
  111. }
  112. // Update info.
  113. info.defineProperty(node);
  114. }
  115. };
  116. }
  117. };