diff --git a/packages/babel-parser/src/parser/statement.js b/packages/babel-parser/src/parser/statement.js index 3e950d26578b..04008adace8e 100644 --- a/packages/babel-parser/src/parser/statement.js +++ b/packages/babel-parser/src/parser/statement.js @@ -1177,6 +1177,7 @@ export default class StatementParser extends ExpressionParser { this.state.classLevel++; const state = { hadConstructor: false }; + const privateNames: Set = new Set(); let decorators: N.Decorator[] = []; const classBody: N.ClassBody = this.startNode(); classBody.body = []; @@ -1212,7 +1213,13 @@ export default class StatementParser extends ExpressionParser { decorators = []; } - this.parseClassMember(classBody, member, state, constructorAllowsSuper); + this.parseClassMember( + classBody, + member, + state, + constructorAllowsSuper, + privateNames, + ); if ( member.kind === "constructor" && @@ -1244,6 +1251,7 @@ export default class StatementParser extends ExpressionParser { member: N.ClassMember, state: { hadConstructor: boolean }, constructorAllowsSuper: boolean, + privateNames: Set, ): void { let isStatic = false; const containsEsc = this.state.containsEsc; @@ -1291,6 +1299,7 @@ export default class StatementParser extends ExpressionParser { state, isStatic, constructorAllowsSuper, + privateNames, ); } @@ -1300,6 +1309,7 @@ export default class StatementParser extends ExpressionParser { state: { hadConstructor: boolean }, isStatic: boolean, constructorAllowsSuper: boolean, + privateNames: Set, ) { const publicMethod: $FlowSubtype = member; const privateMethod: $FlowSubtype = member; @@ -1318,7 +1328,13 @@ export default class StatementParser extends ExpressionParser { if (method.key.type === "PrivateName") { // Private generator method - this.pushClassPrivateMethod(classBody, privateMethod, true, false); + this.pushClassPrivateMethod( + classBody, + privateMethod, + true, + false, + privateNames, + ); return; } @@ -1350,7 +1366,13 @@ export default class StatementParser extends ExpressionParser { method.kind = "method"; if (isPrivate) { - this.pushClassPrivateMethod(classBody, privateMethod, false, false); + this.pushClassPrivateMethod( + classBody, + privateMethod, + false, + false, + privateNames, + ); return; } @@ -1385,7 +1407,7 @@ export default class StatementParser extends ExpressionParser { ); } else if (this.isClassProperty()) { if (isPrivate) { - this.pushClassPrivateProperty(classBody, privateProp); + this.pushClassPrivateProperty(classBody, privateProp, privateNames); } else { this.pushClassProperty(classBody, publicProp); } @@ -1409,6 +1431,7 @@ export default class StatementParser extends ExpressionParser { privateMethod, isGenerator, true, + privateNames, ); } else { if (this.isNonstaticConstructor(publicMethod)) { @@ -1441,7 +1464,13 @@ export default class StatementParser extends ExpressionParser { if (method.key.type === "PrivateName") { // private getter/setter - this.pushClassPrivateMethod(classBody, privateMethod, false, false); + this.pushClassPrivateMethod( + classBody, + privateMethod, + false, + false, + privateNames, + ); } else { if (this.isNonstaticConstructor(publicMethod)) { this.raise( @@ -1463,7 +1492,7 @@ export default class StatementParser extends ExpressionParser { } else if (this.isLineTerminator()) { // an uninitialized class property (due to ASI, since we don't otherwise recognize the next token) if (isPrivate) { - this.pushClassPrivateProperty(classBody, privateProp); + this.pushClassPrivateProperty(classBody, privateProp, privateNames); } else { this.pushClassProperty(classBody, publicProp); } @@ -1511,8 +1540,10 @@ export default class StatementParser extends ExpressionParser { pushClassPrivateProperty( classBody: N.ClassBody, prop: N.ClassPrivateProperty, + privateNames: Set, ) { this.expectPlugin("classPrivateProperties", prop.key.start); + this.assertNoDuplicatePrivateName(prop, privateNames); classBody.body.push(this.parseClassPrivateProperty(prop)); } @@ -1542,8 +1573,10 @@ export default class StatementParser extends ExpressionParser { method: N.ClassPrivateMethod, isGenerator: boolean, isAsync: boolean, + privateNames: Set, ): void { this.expectPlugin("classPrivateMethods", method.key.start); + this.assertNoDuplicatePrivateName(method, privateNames); classBody.body.push( this.parseMethod( method, @@ -1557,6 +1590,37 @@ export default class StatementParser extends ExpressionParser { ); } + assertNoDuplicatePrivateName( + methodOrProp: N.ClassPrivateMethod | N.ClassPrivateProperty, + privateNames: Set, + ) { + let isDuplicate = false; + const { name } = methodOrProp.key.id; + + if ( + methodOrProp.type === "ClassPrivateProperty" || + methodOrProp.kind === "method" + ) { + isDuplicate = privateNames.has(name); + } else { + // Accessors can have the same name for get and set + const { kind } = methodOrProp; + const otherKind = kind === "get" ? "set" : "get"; + + isDuplicate = privateNames.has(name); + if (isDuplicate && privateNames.has(`${otherKind} ${name}`)) { + isDuplicate = privateNames.has(`${kind} ${name}`); + } + + privateNames.add(`${kind} ${name}`); + } + privateNames.add(name); + + if (isDuplicate) { + this.raise(methodOrProp.key.start, `Duplicate private name #${name}`); + } + } + // Overridden in typescript.js parsePostMemberNameModifiers( // eslint-disable-next-line no-unused-vars diff --git a/packages/babel-parser/src/plugins/flow.js b/packages/babel-parser/src/plugins/flow.js index 024a21baa5ab..6371317844f4 100644 --- a/packages/babel-parser/src/plugins/flow.js +++ b/packages/babel-parser/src/plugins/flow.js @@ -2107,8 +2107,6 @@ export default (superClass: Class): Class => pushClassPrivateMethod( classBody: N.ClassBody, method: N.ClassPrivateMethod, - isGenerator: boolean, - isAsync: boolean, ): void { if ((method: $FlowFixMe).variance) { this.unexpected((method: $FlowFixMe).variance.start); @@ -2118,7 +2116,7 @@ export default (superClass: Class): Class => method.typeParameters = this.flowParseTypeParameterDeclaration(); } - super.pushClassPrivateMethod(classBody, method, isGenerator, isAsync); + super.pushClassPrivateMethod(...arguments); } // parse a the super class type parameters and implements diff --git a/packages/babel-parser/src/plugins/typescript/index.js b/packages/babel-parser/src/plugins/typescript/index.js index 5d6922d329eb..de95ff30a726 100644 --- a/packages/babel-parser/src/plugins/typescript/index.js +++ b/packages/babel-parser/src/plugins/typescript/index.js @@ -1795,16 +1795,11 @@ export default (superClass: Class): Class => return this.tsParseModifier(["public", "protected", "private"]); } - parseClassMember( - classBody: N.ClassBody, - member: any, - state: { hadConstructor: boolean }, - constructorAllowsSuper: boolean, - ): void { + parseClassMember(classBody: N.ClassBody, member: any): void { const accessibility = this.parseAccessModifier(); if (accessibility) member.accessibility = accessibility; - super.parseClassMember(classBody, member, state, constructorAllowsSuper); + super.parseClassMember(...arguments); } parseClassMemberWithIsStatic( @@ -1812,7 +1807,6 @@ export default (superClass: Class): Class => member: any, state: { hadConstructor: boolean }, isStatic: boolean, - constructorAllowsSuper: boolean, ): void { const methodOrProp: N.ClassMethod | N.ClassProperty = member; const prop: N.ClassProperty = member; @@ -1853,13 +1847,7 @@ export default (superClass: Class): Class => return; } - super.parseClassMemberWithIsStatic( - classBody, - member, - state, - isStatic, - constructorAllowsSuper, - ); + super.parseClassMemberWithIsStatic(...arguments); } parsePostMemberNameModifiers( @@ -2035,12 +2023,10 @@ export default (superClass: Class): Class => pushClassPrivateMethod( classBody: N.ClassBody, method: N.ClassPrivateMethod, - isGenerator: boolean, - isAsync: boolean, ): void { const typeParameters = this.tsTryParseTypeParameters(); if (typeParameters) method.typeParameters = typeParameters; - super.pushClassPrivateMethod(classBody, method, isGenerator, isAsync); + super.pushClassPrivateMethod(...arguments); } parseClassSuper(node: N.Class): void { diff --git a/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-accessor-method/input.js b/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-accessor-method/input.js new file mode 100644 index 000000000000..00d50846e973 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-accessor-method/input.js @@ -0,0 +1,4 @@ +class A { + get #x() {} + #x() {} +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-accessor-method/options.json b/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-accessor-method/options.json new file mode 100644 index 000000000000..d8cfeb86ac86 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-accessor-method/options.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + "classPrivateMethods", + "classPrivateProperties" + ], + "throws": "Duplicate private name #x (3:2)" +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-field-method/input.js b/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-field-method/input.js new file mode 100644 index 000000000000..6dde01adc90e --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-field-method/input.js @@ -0,0 +1,4 @@ +class A { + #x; + #x() {} +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-field-method/options.json b/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-field-method/options.json new file mode 100644 index 000000000000..d8cfeb86ac86 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-field-method/options.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + "classPrivateMethods", + "classPrivateProperties" + ], + "throws": "Duplicate private name #x (3:2)" +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-method-accessor/input.js b/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-method-accessor/input.js new file mode 100644 index 000000000000..b1d19924d288 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-method-accessor/input.js @@ -0,0 +1,4 @@ +class A { + #x() {} + get #x() {} +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-method-accessor/options.json b/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-method-accessor/options.json new file mode 100644 index 000000000000..b7d1abf9903b --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-method-accessor/options.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + "classPrivateMethods", + "classPrivateProperties" + ], + "throws": "Duplicate private name #x (3:6)" +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-method-field/input.js b/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-method-field/input.js new file mode 100644 index 000000000000..26122843af16 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-method-field/input.js @@ -0,0 +1,4 @@ +class A { + #x() {} + #x; +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-method-field/options.json b/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-method-field/options.json new file mode 100644 index 000000000000..d8cfeb86ac86 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-private-methods/duplicated-method-field/options.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + "classPrivateMethods", + "classPrivateProperties" + ], + "throws": "Duplicate private name #x (3:2)" +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/class-private-properties/duplicated-different-placement/input.js b/packages/babel-parser/test/fixtures/experimental/class-private-properties/duplicated-different-placement/input.js new file mode 100644 index 000000000000..df22b36e0ae8 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-private-properties/duplicated-different-placement/input.js @@ -0,0 +1,4 @@ +class A { + #x = 1; + static #x = 2; +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/class-private-properties/duplicated-different-placement/options.json b/packages/babel-parser/test/fixtures/experimental/class-private-properties/duplicated-different-placement/options.json new file mode 100644 index 000000000000..c446ed73a897 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-private-properties/duplicated-different-placement/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "classPrivateProperties" + ], + "throws": "Duplicate private name #x (3:9)" +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/class-private-properties/duplicated-same-placement/input.js b/packages/babel-parser/test/fixtures/experimental/class-private-properties/duplicated-same-placement/input.js new file mode 100644 index 000000000000..83ddd3d8a226 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-private-properties/duplicated-same-placement/input.js @@ -0,0 +1,4 @@ +class A { + #x = 1; + #x = 2; +} \ No newline at end of file diff --git a/packages/babel-parser/test/fixtures/experimental/class-private-properties/duplicated-same-placement/options.json b/packages/babel-parser/test/fixtures/experimental/class-private-properties/duplicated-same-placement/options.json new file mode 100644 index 000000000000..c7707470a4f3 --- /dev/null +++ b/packages/babel-parser/test/fixtures/experimental/class-private-properties/duplicated-same-placement/options.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "classPrivateProperties" + ], + "throws": "Duplicate private name #x (3:2)" +} \ No newline at end of file diff --git a/scripts/tests/test262/test262_whitelist.txt b/scripts/tests/test262/test262_whitelist.txt index 8f929fd51b80..e972f8e4a24b 100644 --- a/scripts/tests/test262/test262_whitelist.txt +++ b/scripts/tests/test262/test262_whitelist.txt @@ -396,8 +396,6 @@ language/expressions/async-arrow-function/early-errors-arrow-await-in-formals-de language/expressions/async-arrow-function/early-errors-arrow-await-in-formals-default.js(strict mode) language/expressions/async-arrow-function/early-errors-arrow-await-in-formals.js(default) language/expressions/async-arrow-function/early-errors-arrow-await-in-formals.js(strict mode) -language/expressions/class/elements/fields-duplicate-privatenames.js(default) -language/expressions/class/elements/fields-duplicate-privatenames.js(strict mode) language/expressions/class/elements/fields-literal-name-static-propname-constructor.js(default) language/expressions/class/elements/fields-literal-name-static-propname-constructor.js(strict mode) language/expressions/class/elements/fields-string-name-static-propname-constructor.js(default) @@ -412,32 +410,6 @@ language/expressions/class/elements/syntax/early-errors/grammar-private-environm language/expressions/class/elements/syntax/early-errors/grammar-private-environment-on-class-heritage.js(strict mode) language/expressions/class/elements/syntax/early-errors/grammar-private-field-on-object-destructuring.js(default) language/expressions/class/elements/syntax/early-errors/grammar-private-field-on-object-destructuring.js(strict mode) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-async-gen.js(default) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-async-gen.js(strict mode) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-async.js(default) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-async.js(strict mode) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-gen.js(default) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-gen.js(strict mode) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-get-field.js(default) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-get-field.js(strict mode) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-get-get.js(default) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-get-get.js(strict mode) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-field.js(default) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-field.js(strict mode) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-get.js(default) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-get.js(strict mode) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-meth.js(default) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-meth.js(strict mode) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-set.js(default) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-set.js(strict mode) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-staticfield.js(default) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-staticfield.js(strict mode) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-staticmeth.js(default) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-staticmeth.js(strict mode) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-set-field.js(default) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-set-field.js(strict mode) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-set-set.js(default) -language/expressions/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-set-set.js(strict mode) language/expressions/class/elements/syntax/early-errors/grammar-privatename-in-computed-property-missing.js(default) language/expressions/class/elements/syntax/early-errors/grammar-privatename-in-computed-property-missing.js(strict mode) language/expressions/class/elements/syntax/early-errors/invalid-names/field-init-call-expression-bad-reference.js(default) @@ -1028,8 +1000,6 @@ language/module-code/top-level-await/syntax/if-await-expr-boolean.js(default) language/module-code/top-level-await/syntax/if-await-expr-boolean.js(strict mode) language/module-code/top-level-await/syntax/try-await-expr.js(default) language/module-code/top-level-await/syntax/try-await-expr.js(strict mode) -language/statements/class/elements/fields-duplicate-privatenames.js(default) -language/statements/class/elements/fields-duplicate-privatenames.js(strict mode) language/statements/class/elements/fields-literal-name-static-propname-constructor.js(default) language/statements/class/elements/fields-literal-name-static-propname-constructor.js(strict mode) language/statements/class/elements/fields-string-name-static-propname-constructor.js(default) @@ -1056,32 +1026,6 @@ language/statements/class/elements/syntax/early-errors/grammar-private-environme language/statements/class/elements/syntax/early-errors/grammar-private-environment-on-class-heritage.js(strict mode) language/statements/class/elements/syntax/early-errors/grammar-private-field-on-object-destructuring.js(default) language/statements/class/elements/syntax/early-errors/grammar-private-field-on-object-destructuring.js(strict mode) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-async-gen.js(default) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-async-gen.js(strict mode) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-async.js(default) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-async.js(strict mode) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-gen.js(default) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-gen.js(strict mode) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-get-field.js(default) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-get-field.js(strict mode) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-get-get.js(default) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-get-get.js(strict mode) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-field.js(default) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-field.js(strict mode) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-get.js(default) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-get.js(strict mode) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-meth.js(default) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-meth.js(strict mode) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-set.js(default) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-set.js(strict mode) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-staticfield.js(default) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-staticfield.js(strict mode) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-staticmeth.js(default) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-meth-staticmeth.js(strict mode) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-set-field.js(default) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-set-field.js(strict mode) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-set-set.js(default) -language/statements/class/elements/syntax/early-errors/grammar-privatemeth-duplicate-set-set.js(strict mode) language/statements/class/elements/syntax/early-errors/grammar-privatename-in-computed-property-missing.js(default) language/statements/class/elements/syntax/early-errors/grammar-privatename-in-computed-property-missing.js(strict mode) language/statements/class/elements/syntax/early-errors/invalid-names/field-init-call-expression-bad-reference.js(default)