no-dupe-class-members.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. /**
  2. * @fileoverview A rule to disallow duplicate name in class members.
  3. * @author Toru Nagashima
  4. */
  5. "use strict";
  6. const astUtils = require("./utils/ast-utils");
  7. //------------------------------------------------------------------------------
  8. // Rule Definition
  9. //------------------------------------------------------------------------------
  10. /** @type {import('../types').Rule.RuleModule} */
  11. module.exports = {
  12. meta: {
  13. type: "problem",
  14. dialects: ["javascript", "typescript"],
  15. language: "javascript",
  16. docs: {
  17. description: "Disallow duplicate class members",
  18. recommended: true,
  19. url: "https://eslint.org/docs/latest/rules/no-dupe-class-members",
  20. },
  21. schema: [],
  22. messages: {
  23. unexpected: "Duplicate name '{{name}}'.",
  24. },
  25. },
  26. create(context) {
  27. let stack = [];
  28. /**
  29. * Gets state of a given member name.
  30. * @param {string} name A name of a member.
  31. * @param {boolean} isStatic A flag which specifies that is a static member.
  32. * @returns {Object} A state of a given member name.
  33. * - retv.init {boolean} A flag which shows the name is declared as normal member.
  34. * - retv.get {boolean} A flag which shows the name is declared as getter.
  35. * - retv.set {boolean} A flag which shows the name is declared as setter.
  36. */
  37. function getState(name, isStatic) {
  38. const stateMap = stack.at(-1);
  39. const key = `$${name}`; // to avoid "__proto__".
  40. if (!stateMap[key]) {
  41. stateMap[key] = {
  42. nonStatic: { init: false, get: false, set: false },
  43. static: { init: false, get: false, set: false },
  44. };
  45. }
  46. return stateMap[key][isStatic ? "static" : "nonStatic"];
  47. }
  48. return {
  49. // Initializes the stack of state of member declarations.
  50. Program() {
  51. stack = [];
  52. },
  53. // Initializes state of member declarations for the class.
  54. ClassBody() {
  55. stack.push(Object.create(null));
  56. },
  57. // Disposes the state for the class.
  58. "ClassBody:exit"() {
  59. stack.pop();
  60. },
  61. // Reports the node if its name has been declared already.
  62. "MethodDefinition, PropertyDefinition"(node) {
  63. if (
  64. node.value &&
  65. node.value.type === "TSEmptyBodyFunctionExpression"
  66. ) {
  67. return;
  68. }
  69. const name = astUtils.getStaticPropertyName(node);
  70. const kind =
  71. node.type === "MethodDefinition" ? node.kind : "field";
  72. if (name === null || kind === "constructor") {
  73. return;
  74. }
  75. const state = getState(name, node.static);
  76. let isDuplicate;
  77. if (kind === "get") {
  78. isDuplicate = state.init || state.get;
  79. state.get = true;
  80. } else if (kind === "set") {
  81. isDuplicate = state.init || state.set;
  82. state.set = true;
  83. } else {
  84. isDuplicate = state.init || state.get || state.set;
  85. state.init = true;
  86. }
  87. if (isDuplicate) {
  88. context.report({
  89. loc: node.key.loc,
  90. messageId: "unexpected",
  91. data: { name },
  92. });
  93. }
  94. },
  95. };
  96. },
  97. };