Skip to content

Commit

Permalink
[parser] Disallow duplicate and undeclared private names (#10456)
Browse files Browse the repository at this point in the history
* [parser] Add private names tracking to Scope

- Disallow duplicate private names
- Disallow undeclared private names

* Update tests

* Test all possible duplications

* Test undeclared private names

* Better error message for top-level private names

* Fix flow

* Update test262 whitelist

* Update fixtures

* Update flow whitelist

* Remove old output.json

* Move ClassScopeHandler to a separate class

* Make the code readable
  • Loading branch information
nicolo-ribaudo committed Jan 10, 2020
1 parent 9f148a1 commit 771c730
Show file tree
Hide file tree
Showing 223 changed files with 16,948 additions and 259 deletions.
Expand Up @@ -28,9 +28,9 @@ class Foo {
foo = 0; bar = 1;

#foo;
#foo = 1;
static #foo;
static #foo = Foo.#foo;
#foo2 = 1;
static #foo3;
static #foo4 = Foo.#foo;
}

class A1 {
Expand Down
Expand Up @@ -27,9 +27,9 @@ class Foo {
foo = 0;
bar = 1;
#foo;
#foo = 1;
static #foo;
static #foo = Foo.#foo;
#foo2 = 1;
static #foo3;
static #foo4 = Foo.#foo;
}

class A1 {
Expand Down Expand Up @@ -69,4 +69,4 @@ class A6 {
class A7 {
static get static() {}

}
}
Expand Up @@ -6,13 +6,13 @@ class Foo {
set foo(bar) {}

async #foo() {}
#foo() {}
get #foo() {}
set #foo(bar) {}
* #foo() {}
async * #foo() {}
get #bar() {}
set #baz(taz) {}
#foo2() {}
get #foo3() {}
set #foo3(bar) {}
* #foo4() {}
async * #foo5() {}
get #bar6() {}
set #baz6(taz) {}

static async foo() {}
static foo() {}
Expand All @@ -23,13 +23,13 @@ class Foo {
static * foo() {}
static async * foo() {}

static #foo() {}
static async #foo() {}
static #foo7() {}
static async #foo8() {}
static ["foo"]() {}
static get #foo() {}
static set #foo(taz) {}
static * #foo() {}
static async * #foo() {}
static get #foo9() {}
static set #foo9(taz) {}
static * #foo10() {}
static async * #foo11() {}

get
() {}
Expand Down
Expand Up @@ -11,19 +11,19 @@ class Foo {

async #foo() {}

#foo() {}
#foo2() {}

get #foo() {}
get #foo3() {}

set #foo(bar) {}
set #foo3(bar) {}

*#foo() {}
*#foo4() {}

async *#foo() {}
async *#foo5() {}

get #bar() {}
get #bar6() {}

set #baz(taz) {}
set #baz6(taz) {}

static async foo() {}

Expand All @@ -41,19 +41,19 @@ class Foo {

static async *foo() {}

static #foo() {}
static #foo7() {}

static async #foo() {}
static async #foo8() {}

static ["foo"]() {}

static get #foo() {}
static get #foo9() {}

static set #foo(taz) {}
static set #foo9(taz) {}

static *#foo() {}
static *#foo10() {}

static async *#foo() {}
static async *#foo11() {}

get() {}

Expand Down
2 changes: 2 additions & 0 deletions packages/babel-parser/src/parser/base.js
Expand Up @@ -4,12 +4,14 @@ import type { Options } from "../options";
import type State from "../tokenizer/state";
import type { PluginsMap } from "./index";
import type ScopeHandler from "../util/scope";
import type ClassScopeHandler from "../util/class-scope";

export default class BaseParser {
// Properties set by constructor in index.js
options: Options;
inModule: boolean;
scope: ScopeHandler<*>;
classScope: ClassScopeHandler;
plugins: PluginsMap;
filename: ?string;
sawUnambiguousESM: boolean = false;
Expand Down
16 changes: 11 additions & 5 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -612,15 +612,21 @@ export default class ExpressionParser extends LValParser {
? this.parseIdentifier(true)
: this.parseMaybePrivateName();
node.computed = computed;
if (
node.property.type === "PrivateName" &&
node.object.type === "Super"
) {
this.raise(startPos, "Private fields can't be accessed on super");

if (node.property.type === "PrivateName") {
if (node.object.type === "Super") {
this.raise(startPos, "Private fields can't be accessed on super");
}
this.classScope.usePrivateName(
node.property.id.name,
node.property.start,
);
}

if (computed) {
this.expect(tt.bracketR);
}

if (state.optionalChainMember) {
node.optional = optional;
return this.finishNode(node, "OptionalMemberExpression");
Expand Down
2 changes: 2 additions & 0 deletions packages/babel-parser/src/parser/index.js
Expand Up @@ -7,6 +7,7 @@ import { getOptions } from "../options";
import StatementParser from "./statement";
import { SCOPE_ASYNC, SCOPE_PROGRAM } from "../util/scopeflags";
import ScopeHandler from "../util/scope";
import ClassScopeHandler from "../util/class-scope";

export type PluginsMap = Map<string, { [string]: any }>;

Expand All @@ -25,6 +26,7 @@ export default class Parser extends StatementParser {
this.options = options;
this.inModule = this.options.sourceType === "module";
this.scope = new ScopeHandler(this.raise.bind(this), this.inModule);
this.classScope = new ClassScopeHandler(this.raise.bind(this));
this.plugins = pluginsMap(this.options.plugins);
this.filename = options.sourceFilename;
}
Expand Down
51 changes: 38 additions & 13 deletions packages/babel-parser/src/parser/statement.js
Expand Up @@ -20,6 +20,11 @@ import {
SCOPE_OTHER,
SCOPE_SIMPLE_CATCH,
SCOPE_SUPER,
CLASS_ELEMENT_OTHER,
CLASS_ELEMENT_INSTANCE_GETTER,
CLASS_ELEMENT_INSTANCE_SETTER,
CLASS_ELEMENT_STATIC_GETTER,
CLASS_ELEMENT_STATIC_SETTER,
type BindingTypes,
} from "../util/scopeflags";

Expand Down Expand Up @@ -1171,7 +1176,7 @@ export default class StatementParser extends ExpressionParser {
}

parseClassBody(constructorAllowsSuper: boolean): N.ClassBody {
this.state.classLevel++;
this.classScope.enter();

const state = { hadConstructor: false };
let decorators: N.Decorator[] = [];
Expand Down Expand Up @@ -1231,7 +1236,7 @@ export default class StatementParser extends ExpressionParser {
);
}

this.state.classLevel--;
this.classScope.exit();

return this.finishNode(classBody, "ClassBody");
}
Expand Down Expand Up @@ -1514,7 +1519,15 @@ export default class StatementParser extends ExpressionParser {
prop: N.ClassPrivateProperty,
) {
this.expectPlugin("classPrivateProperties", prop.key.start);
classBody.body.push(this.parseClassPrivateProperty(prop));

const node = this.parseClassPrivateProperty(prop);
classBody.body.push(node);

this.classScope.declarePrivateName(
node.key.id.name,
CLASS_ELEMENT_OTHER,
node.key.start,
);
}

pushClassMethod(
Expand Down Expand Up @@ -1545,17 +1558,29 @@ export default class StatementParser extends ExpressionParser {
isAsync: boolean,
): void {
this.expectPlugin("classPrivateMethods", method.key.start);
classBody.body.push(
this.parseMethod(
method,
isGenerator,
isAsync,
/* isConstructor */ false,
false,
"ClassPrivateMethod",
true,
),

const node = this.parseMethod(
method,
isGenerator,
isAsync,
/* isConstructor */ false,
false,
"ClassPrivateMethod",
true,
);
classBody.body.push(node);

const kind =
node.kind === "get"
? node.static
? CLASS_ELEMENT_STATIC_GETTER
: CLASS_ELEMENT_INSTANCE_GETTER
: node.kind === "set"
? node.static
? CLASS_ELEMENT_STATIC_SETTER
: CLASS_ELEMENT_INSTANCE_SETTER
: CLASS_ELEMENT_OTHER;
this.classScope.declarePrivateName(node.key.id.name, kind, node.key.start);
}

// Overridden in typescript.js
Expand Down
10 changes: 2 additions & 8 deletions packages/babel-parser/src/tokenizer/index.js
Expand Up @@ -391,14 +391,8 @@ export default class Tokenizer extends LocationParser {
}

if (
(this.hasPlugin("classPrivateProperties") ||
this.hasPlugin("classPrivateMethods")) &&
this.state.classLevel > 0
) {
++this.state.pos;
this.finishToken(tt.hash);
return;
} else if (
this.hasPlugin("classPrivateProperties") ||
this.hasPlugin("classPrivateMethods") ||
this.getPluginOption("pipelineOperator", "proposal") === "smart"
) {
this.finishOp(tt.hash, 1);
Expand Down
3 changes: 0 additions & 3 deletions packages/babel-parser/src/tokenizer/state.js
Expand Up @@ -77,9 +77,6 @@ export default class State {
soloAwait: boolean = false;
inFSharpPipelineDirectBody: boolean = false;

// Check whether we are in a (nested) class or not.
classLevel: number = 0;

// Labels in scope.
labels: Array<{
kind: ?("loop" | "switch"),
Expand Down

0 comments on commit 771c730

Please sign in to comment.