Skip to content

Commit

Permalink
fix(parser): optimized class field parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
KFlash committed May 12, 2019
1 parent 4c61090 commit 2c1bf99
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 75 deletions.
2 changes: 1 addition & 1 deletion src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const enum PropertyKind {
Setter = 1 << 9,
Extends = 1 << 10,
Literal = 1 << 11,
PrivatField = 1 << 12,
PrivateField = 1 << 12,
GetSet = Getter | Setter
}

Expand Down
10 changes: 8 additions & 2 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,10 @@ export const enum Errors {
InvalidTernaryYield,
InvalidArrowPostfix,
InvalidObjLitKeyStar,
DeletePrivateField
DeletePrivateField,
InvalidStaticClassFieldConstructor,
InvalidClassFieldConstructor,
InvalidClassFieldArgEval
}

/*@internal*/
Expand Down Expand Up @@ -300,7 +303,10 @@ export const errorMessages: {
[Errors.InvalidTernaryYield]: 'Can not have a `yield` expression on the left side of a ternary',
[Errors.InvalidArrowPostfix]: 'An arrow function can not have a postfix update operator',
[Errors.InvalidObjLitKeyStar]: 'Invalid object literal key character after generator star',
[Errors.DeletePrivateField]: 'Private fields can not be deleted'
[Errors.DeletePrivateField]: 'Private fields can not be deleted',
[Errors.InvalidClassFieldConstructor]: 'Classes may not have a field called constructor',
[Errors.InvalidStaticClassFieldConstructor]: 'Classes may not have a private element named constructor',
[Errors.InvalidClassFieldArgEval]: 'A class field initializer may not contain arguments'
};

export class ParseError extends SyntaxError {
Expand Down
47 changes: 27 additions & 20 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2928,8 +2928,8 @@ export function parseFunctionDeclaration(
params: parseFormalParametersOrFormalList(parser, context | Context.InArgList, BindingType.ArgumentList),
body: parseFunctionBody(
parser,
(context | Context.TopLevel | Context.InGlobal | Context.InSwitchOrIteration) ^
(Context.InGlobal | Context.TopLevel | Context.InSwitchOrIteration),
(context | Context.TopLevel | Context.InGlobal | Context.InSwitchOrIteration | Context.InClass) ^
(Context.InGlobal | Context.TopLevel | Context.InSwitchOrIteration | Context.InClass),
BindingOrigin.Declaration,
firstRestricted
),
Expand Down Expand Up @@ -2980,8 +2980,8 @@ export function parseFunctionExpression(
const params = parseFormalParametersOrFormalList(parser, context | Context.InArgList, BindingType.ArgumentList);
const body = parseFunctionBody(
parser,
(context | Context.InGlobal | Context.TopLevel | Context.InSwitchOrIteration) ^
(Context.InGlobal | Context.TopLevel | Context.InSwitchOrIteration),
(context | Context.InGlobal | Context.TopLevel | Context.InSwitchOrIteration | Context.InClass) ^
(Context.InGlobal | Context.TopLevel | Context.InSwitchOrIteration | Context.InClass),
0,
firstRestricted
);
Expand Down Expand Up @@ -5080,6 +5080,7 @@ export function parseClassBody(
if (parser.token === Token.RightBrace) report(parser, Errors.TrailingDecorators);
if (consumeOpt(parser, context, Token.Semicolon)) {
if (decorators.length > 0) report(parser, Errors.InvalidDecoratorSemicolon);
continue;
}
}

Expand Down Expand Up @@ -5163,17 +5164,16 @@ function parseClassElementList(
kind |= PropertyKind.Generator;
nextToken(parser, context);
} else if (context & Context.OptionsNext && parser.token === Token.PrivateField) {
kind |= PropertyKind.PrivatField;
} else if (context & Context.OptionsNext && parser.token === Token.RightBrace) {
return parseFieldDefinition(parser, context, key, kind, decorators);
kind |= PropertyKind.PrivateField;
} else if (context & Context.OptionsNext && (parser.token & Token.IsClassField) === Token.IsClassField) {
kind |= PropertyKind.ClassField;
} else {
report(parser, Errors.UnexpectedToken, KeywordDescTable[parser.token & Token.Type]);
}

if (kind & (PropertyKind.Generator | PropertyKind.Async | PropertyKind.GetSet)) {
if (kind & PropertyKind.Generator) {
if (parser.token === Token.LeftParen) report(parser, Errors.Unexpected);
}
if (kind & PropertyKind.Generator && parser.token === Token.LeftParen) report(parser, Errors.Unexpected);

if (parser.token & Token.IsIdentifier) {
key = parseIdentifier(parser, context);
} else if ((parser.token & Token.IsStringOrNumber) === Token.IsStringOrNumber) {
Expand All @@ -5183,23 +5183,29 @@ function parseClassElementList(
key = parseComputedPropertyName(parser, context);
} else if (context & Context.OptionsNext && parser.token === Token.PrivateField) {
key = parsePrivateName(parser, context);
} else if ((context & Context.OptionsNext) === 0 && parser.token === Token.RightBrace) {
} else if (parser.token === Token.RightBrace) {
report(parser, Errors.NoIdentOrDynamicName);
}
} else if (kind & PropertyKind.Computed) {
key = parseComputedPropertyName(parser, context);
} else if (kind & PropertyKind.PrivatField) {
} else if (kind & PropertyKind.PrivateField) {
key = parsePrivateName(parser, context);
context = context | Context.InClass;
if (parser.token !== Token.LeftParen) return parseFieldDefinition(parser, context, key, kind, decorators);
} else if (kind & PropertyKind.ClassField) {
context = context | Context.InClass;
if (parser.token !== Token.LeftParen) return parseFieldDefinition(parser, context, key, kind, decorators);
}

if (parser.tokenValue === 'constructor') {
if ((kind & PropertyKind.Static) === 0) {
if (
(kind & PropertyKind.Computed) === 0 &&
kind & (PropertyKind.Getter | PropertyKind.Setter | PropertyKind.Async | PropertyKind.Generator)
)
kind & (PropertyKind.GetSet | PropertyKind.Async | PropertyKind.ClassField | PropertyKind.Generator)
) {
report(parser, Errors.InvalidConstructor, 'accessor');
}

if ((context & Context.SuperCall) === 0 && (kind & PropertyKind.Computed) === 0) {
if (parser.flags & Flags.HasConstructor) report(parser, Errors.DuplicateConstructor);
else parser.flags |= Flags.HasConstructor;
Expand All @@ -5218,6 +5224,7 @@ function parseClassElementList(

// Note: This is temporary until this reach Stage 4
if (context & Context.OptionsNext && parser.token !== Token.LeftParen) {
if (parser.tokenValue === 'constructor') report(parser, Errors.InvalidClassFieldConstructor);
return parseFieldDefinition(parser, context, key, kind, decorators);
}

Expand Down Expand Up @@ -5268,7 +5275,7 @@ function parsePrivateName(parser: ParserState, context: Context): ESTree.Private
// #IdentifierName
nextToken(parser, context); // skip: '#'
const { tokenValue: name } = parser;
if (name === 'constructor') report(parser, Errors.UnexpectedStrictReserved);
if (name === 'constructor') report(parser, Errors.InvalidStaticClassFieldConstructor);
nextToken(parser, context);

return {
Expand All @@ -5294,8 +5301,11 @@ export function parseFieldDefinition(
state: PropertyKind,
decorators: ESTree.Decorator[] | null
): ESTree.FieldDefinition {
// FieldDefinition[?Yield, ?Await];
// static FieldDefinition[?Yield, ?Await];
// ClassElement :
// MethodDefinition
// static MethodDefinition
// FieldDefinition ;
// ;
let value: ESTree.Expression | null = null;
if (state & PropertyKind.Static && parser.tokenValue === 'constructor') report(parser, Errors.Unexpected);
if (state & PropertyKind.Generator) report(parser, Errors.Unexpected);
Expand All @@ -5305,9 +5315,6 @@ export function parseFieldDefinition(
report(parser, Errors.StrictEvalArguments);
value = parseExpression(parser, context | Context.InClass, /* assignable */ 1);
}

consumeOpt(parser, context, Token.Semicolon);

return {
type: 'FieldDefinition',
key,
Expand Down
62 changes: 33 additions & 29 deletions test/parser/next/private_methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,37 @@ describe('Next - Private methods', () => {
['var o = { #m() {} };', Context.OptionsNext],
['var o = { async #m() {} };', Context.OptionsNext],
['var o = { *#m() {} };', Context.OptionsNext],
//['class C { #x = true ? {} : arguments; }', Context.OptionsNext],
['class A { constructor = 4 }', Context.OptionsNext],
['class A { #constructor = 4 }', Context.OptionsNext],
['class A { a = () => super() }', Context.OptionsNext],
['class A { # a }', Context.OptionsNext],
['class A { #a; a() { this.# a } }', Context.OptionsNext],
['class A { #x; g = this.f; x = delete (g().#m); }', Context.OptionsNext],
['class A { #x; x = delete (this.#m); }', Context.OptionsNext],
['class A { #x; g = this.f; x = delete (g().#m); }', Context.OptionsNext],
['class A { #x; x = delete (g().#m); async *#m() {} }', Context.OptionsNext],
['class A { #x; x() { var g = this.f; delete g().#x; } }', Context.OptionsNext],
['class A { #x; x() { delete ((this.#m )); } async #m() {} }', Context.OptionsNext],
['class A { # x }', Context.OptionsNext],
['class A { #\\u0000; }', Context.OptionsNext],
['class A { #\\u200D_ZWJ;; }', Context.OptionsNext],
['class A { * # m() {} }', Context.OptionsNext],
['class A { # x; }', Context.OptionsNext],
['class A { #x; m() { this.# x; }}', Context.OptionsNext],
['class A { # m() {} }', Context.OptionsNext],
['class A { static get # m() {} }', Context.OptionsNext],
['class A { * method() { super(); } }', Context.OptionsNext],
['class A { static async * prototype() {} }', Context.OptionsNext],
['class A { static async #method() { super(); } }', Context.OptionsNext],
['class A { method() { this.\\u0023field; } }', Context.OptionsNext],
['class C { static #x = super(); }', Context.OptionsNext],
['class C{ #method() { super.#x(); } }', Context.OptionsNext],
['class C { async \\u0023m() {} } }', Context.OptionsNext],
['class C { static async *#gen() { var await; } }', Context.OptionsNext],
['class C { static async *#gen() { void await; } }', Context.OptionsNext],
['class C { static async *#gen() { var yield; } }', Context.OptionsNext],
['class C { static async *#gen() { void yield; } }', Context.OptionsNext],
['class C { static async *#gen() { yield: ; } }', Context.OptionsNext],
['class C { static async *#gen() { void \\u0061wait; } }', Context.OptionsNext],
//['class C {\n#x\n\n#y\n}', Context.OptionsNext],
['class C { async \\u0023m() {} } }', Context.OptionsNext],
['class C { #method() { a() { foo().\\u0023field; } } }', Context.OptionsNext],
['class C { #method() { \\u0023field; } }', Context.OptionsNext],
Expand All @@ -44,18 +64,10 @@ describe('Next - Private methods', () => {
'#a =',
'#*a = 0',
'#*a',
//'#get a',
//'#yield a',
//'#async a = 0',
//'#async a',
// "#a; #a",
// "#a = 1; #a",
// "#a; #a = 1;",
'#constructor',
'static #constructor',
'#constructor = function() {}',
'# a = 0',

'#0 = 0;',
'#0;',
"#'a' = 0;",
Expand All @@ -71,9 +83,6 @@ describe('Next - Private methods', () => {
'foo() { delete f.#a }',
'foo() { delete f.x.#a }',
'foo() { delete f.x().#a }',
//'#a b',
//'#a = 0 b',
// ASI requires that the next token is not part of any legal production
'#a = 0\n *b(){}',
"#a = 0\n ['b'](){}",
'const { x: x } = this;',
Expand All @@ -89,22 +98,16 @@ describe('Next - Private methods', () => {
'# m() {}',
'static get # m() {}',
'async #*a() { }',
//'static #prototype() {}',
'static #prototype() {}',
'#x = () => /*{ initializer }*/;',
'#x = /*{ initializer }*/;',
'#x = false ? {} : /*{ initializer }*/;',
'#x = typeof /*{ initializer }*/;',
' static #x = /*{ initializer }*/;',
// '#x = () => arguments;',
// '#x = () => super();',
// '#x = super();',
// '#x = true ? {} : arguments;',
// '#x = true ? {} : super();',
// '#x = typeof arguments;',
// 'static #x = arguments;',

'#\u0000;',
'#\u200D_ZWJ;'
'static #x = /*{ initializer }*/;',
'#x = () => super();',
'#x = super();',
'#\\u0000;',
'#\\u200D_ZWJ;'
]) {
it(`class C { ${arg} }`, () => {
t.throws(() => {
Expand Down Expand Up @@ -158,7 +161,7 @@ describe('Next - Private methods', () => {
"#a;\n ['b'](){}",
'#a;\n get;',
'#get;\n *a(){}',
// '#a;\n static;',
'#a;\n static;',
'#a = function t() { arguments; }',
'#a = () => function() { arguments; }',
'#yield;',
Expand All @@ -168,7 +171,7 @@ describe('Next - Private methods', () => {
'#async = 0;',
'#async;',
'#async = 0;',
'#async;\n a(){}', // a field named async, and a method named a.
'#async;\n a(){}',
'#async;\n a;',
'#await;',
'* #foo() {}',
Expand All @@ -187,7 +190,8 @@ describe('Next - Private methods', () => {
'foo() { this.#m, (() => this)().#m }',
'foo() { this.#m, (() => this)().#m }',
'foo() { this.#m, (() => this)().#m }',
'foo() { this.#m, (() => this)().#m }'
'foo() { this.#m, (() => this)().#m }',
'method() { super.#x(); }'
]) {
it(`class C { ${arg} }`, () => {
t.doesNotThrow(() => {
Expand Down
28 changes: 5 additions & 23 deletions test/parser/next/public-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,42 +17,24 @@ describe('Next - Public fields', () => {
for (const arg of [
'static a : 0',
'static a =',
// "static constructor",
// "static prototype",
'static constructor',
'static prototype',
'static *a = 0',
'static *a',
// "static get a",
//"static get\n a",
// "static yield a",
// "static async a = 0",
// "static async a",
// "static a = arguments",
// "static a = () => arguments",
// "static a = () => { arguments }",
'static a = arguments[0]',
// "static a = delete arguments[0]",
// "static a = f(arguments)",
// "static a = () => () => arguments",
// "static a b",
// "static a = 0 b",
'static c = [1] = [c]',
'static a = 0\n *b(){}',
"static a = 0\n ['b'](){}",
'a : 0',
'a =',
// "constructor",
'constructor',
'*a = 0',
'*a',
// "get a",
// "yield a",
// "async a = 0",
// "async a",
'c = [1] = [c]',
'a = 0\n *b(){}',
"a = 0\n ['b'](){}",
'static prototype',
'static constructor'
//"get\n a",
]) {
it(`class C { ${arg} }`, () => {
t.throws(() => {
Expand Down Expand Up @@ -105,8 +87,8 @@ describe('Next - Public fields', () => {
"['a'];\n ['b'](){}",
'a;\n get;',
'get;\n *a(){}',
// 'a = function t() { arguments; }',
// 'a = () => function() { arguments; }',
'a = function t() { arguments; }',
'a = () => function() { arguments; }',
'async;',
'async = await;',
'yield;',
Expand Down

0 comments on commit 2c1bf99

Please sign in to comment.