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