Skip to content

Commit

Permalink
[parser] Disallow duplicate private names
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolo-ribaudo committed Sep 17, 2019
1 parent 99f4f6c commit cec80d4
Show file tree
Hide file tree
Showing 16 changed files with 139 additions and 83 deletions.
76 changes: 70 additions & 6 deletions packages/babel-parser/src/parser/statement.js
Expand Up @@ -1177,6 +1177,7 @@ export default class StatementParser extends ExpressionParser {
this.state.classLevel++;

const state = { hadConstructor: false };
const privateNames: Set<string> = new Set();
let decorators: N.Decorator[] = [];
const classBody: N.ClassBody = this.startNode();
classBody.body = [];
Expand Down Expand Up @@ -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" &&
Expand Down Expand Up @@ -1244,6 +1251,7 @@ export default class StatementParser extends ExpressionParser {
member: N.ClassMember,
state: { hadConstructor: boolean },
constructorAllowsSuper: boolean,
privateNames: Set<string>,
): void {
let isStatic = false;
const containsEsc = this.state.containsEsc;
Expand Down Expand Up @@ -1291,6 +1299,7 @@ export default class StatementParser extends ExpressionParser {
state,
isStatic,
constructorAllowsSuper,
privateNames,
);
}

Expand All @@ -1300,6 +1309,7 @@ export default class StatementParser extends ExpressionParser {
state: { hadConstructor: boolean },
isStatic: boolean,
constructorAllowsSuper: boolean,
privateNames: Set<string>,
) {
const publicMethod: $FlowSubtype<N.ClassMethod> = member;
const privateMethod: $FlowSubtype<N.ClassPrivateMethod> = member;
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
}
Expand All @@ -1409,6 +1431,7 @@ export default class StatementParser extends ExpressionParser {
privateMethod,
isGenerator,
true,
privateNames,
);
} else {
if (this.isNonstaticConstructor(publicMethod)) {
Expand Down Expand Up @@ -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(
Expand All @@ -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);
}
Expand Down Expand Up @@ -1511,8 +1540,10 @@ export default class StatementParser extends ExpressionParser {
pushClassPrivateProperty(
classBody: N.ClassBody,
prop: N.ClassPrivateProperty,
privateNames: Set<string>,
) {
this.expectPlugin("classPrivateProperties", prop.key.start);
this.assertNoDuplicatePrivateName(prop, privateNames);
classBody.body.push(this.parseClassPrivateProperty(prop));
}

Expand Down Expand Up @@ -1542,8 +1573,10 @@ export default class StatementParser extends ExpressionParser {
method: N.ClassPrivateMethod,
isGenerator: boolean,
isAsync: boolean,
privateNames: Set<string>,
): void {
this.expectPlugin("classPrivateMethods", method.key.start);
this.assertNoDuplicatePrivateName(method, privateNames);
classBody.body.push(
this.parseMethod(
method,
Expand All @@ -1557,6 +1590,37 @@ export default class StatementParser extends ExpressionParser {
);
}

assertNoDuplicatePrivateName(
methodOrProp: N.ClassPrivateMethod | N.ClassPrivateProperty,
privateNames: Set<string>,
) {
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
Expand Down
4 changes: 1 addition & 3 deletions packages/babel-parser/src/plugins/flow.js
Expand Up @@ -2107,8 +2107,6 @@ export default (superClass: Class<Parser>): Class<Parser> =>
pushClassPrivateMethod(
classBody: N.ClassBody,
method: N.ClassPrivateMethod,
isGenerator: boolean,
isAsync: boolean,
): void {
if ((method: $FlowFixMe).variance) {
this.unexpected((method: $FlowFixMe).variance.start);
Expand All @@ -2118,7 +2116,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
method.typeParameters = this.flowParseTypeParameterDeclaration();
}

super.pushClassPrivateMethod(classBody, method, isGenerator, isAsync);
super.pushClassPrivateMethod(...arguments);
}

// parse a the super class type parameters and implements
Expand Down
22 changes: 4 additions & 18 deletions packages/babel-parser/src/plugins/typescript/index.js
Expand Up @@ -1795,24 +1795,18 @@ export default (superClass: Class<Parser>): Class<Parser> =>
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(
classBody: N.ClassBody,
member: any,
state: { hadConstructor: boolean },
isStatic: boolean,
constructorAllowsSuper: boolean,
): void {
const methodOrProp: N.ClassMethod | N.ClassProperty = member;
const prop: N.ClassProperty = member;
Expand Down Expand Up @@ -1853,13 +1847,7 @@ export default (superClass: Class<Parser>): Class<Parser> =>
return;
}

super.parseClassMemberWithIsStatic(
classBody,
member,
state,
isStatic,
constructorAllowsSuper,
);
super.parseClassMemberWithIsStatic(...arguments);
}

parsePostMemberNameModifiers(
Expand Down Expand Up @@ -2035,12 +2023,10 @@ export default (superClass: Class<Parser>): Class<Parser> =>
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 {
Expand Down
@@ -0,0 +1,4 @@
class A {
get #x() {}
#x() {}
}
@@ -0,0 +1,7 @@
{
"plugins": [
"classPrivateMethods",
"classPrivateProperties"
],
"throws": "Duplicate private name #x (3:2)"
}
@@ -0,0 +1,4 @@
class A {
#x;
#x() {}
}
@@ -0,0 +1,7 @@
{
"plugins": [
"classPrivateMethods",
"classPrivateProperties"
],
"throws": "Duplicate private name #x (3:2)"
}
@@ -0,0 +1,4 @@
class A {
#x() {}
get #x() {}
}
@@ -0,0 +1,7 @@
{
"plugins": [
"classPrivateMethods",
"classPrivateProperties"
],
"throws": "Duplicate private name #x (3:6)"
}
@@ -0,0 +1,4 @@
class A {
#x() {}
#x;
}
@@ -0,0 +1,7 @@
{
"plugins": [
"classPrivateMethods",
"classPrivateProperties"
],
"throws": "Duplicate private name #x (3:2)"
}
@@ -0,0 +1,4 @@
class A {
#x = 1;
static #x = 2;
}
@@ -0,0 +1,6 @@
{
"plugins": [
"classPrivateProperties"
],
"throws": "Duplicate private name #x (3:9)"
}
@@ -0,0 +1,4 @@
class A {
#x = 1;
#x = 2;
}
@@ -0,0 +1,6 @@
{
"plugins": [
"classPrivateProperties"
],
"throws": "Duplicate private name #x (3:2)"
}

0 comments on commit cec80d4

Please sign in to comment.