From f18f5b467eac24c5c0528a5b72edeaec68d91132 Mon Sep 17 00:00:00 2001 From: Kenny F Date: Fri, 26 Jul 2019 06:56:44 +0800 Subject: [PATCH] fix(parser): fixed bunch of edge cases --- package.json | 2 +- src/common.ts | 76 +++++++++++++++++------------- src/errors.ts | 3 +- src/lexer/common.ts | 4 +- src/meriyah.ts | 2 +- src/parser.ts | 76 +++++++++++------------------- test/parser/declarations/let.ts | 4 ++ test/parser/expressions/arrow.ts | 18 +++++++ test/parser/expressions/class.ts | 3 ++ test/parser/expressions/group.ts | 5 ++ test/parser/expressions/yield.ts | 66 ++++++++++++++++++++++++++ test/parser/lexical/arrow.ts | 2 + test/parser/module/import.ts | 4 ++ test/parser/statements/do-while.ts | 56 ++++++++++++++++++++++ 14 files changed, 234 insertions(+), 87 deletions(-) diff --git a/package.json b/package.json index e42be614..12dce6b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meriyah", - "version": "1.4.2", + "version": "1.4.4", "description": "A 100% compliant, self-hosted javascript parser with high focus on both performance and stability", "main": "dist/meriyah.umd.js", "module": "dist/meriyah.esm.js", diff --git a/src/common.ts b/src/common.ts index 3520326b..d852dc1d 100644 --- a/src/common.ts +++ b/src/common.ts @@ -36,7 +36,7 @@ export const enum Context { AllowNewTarget = 1 << 26, DisallowIn = 1 << 27, OptionsIdentifierPattern = 1 << 28, - OptionsSpecDeviation = 1 << 29, + OptionsSpecDeviation = 1 << 29 } export const enum PropertyKind { @@ -60,7 +60,7 @@ export const enum PropertyKind { export const enum BindingKind { None = 0, ArgumentList = 1 << 0, - EmptyBinding = 1 << 1, + EmptyBinding = 1 << 1, Variable = 1 << 2, Let = 1 << 3, Const = 1 << 4, @@ -68,7 +68,7 @@ export const enum BindingKind { FunctionLexical = 1 << 6, FunctionStatement = 1 << 7, CatchPattern = 1 << 8, - CatchIdentifier = 1 << 9, + CatchIdentifier = 1 << 9, CatchIdentifierOrPattern = CatchIdentifier | CatchPattern, LexicalOrFunction = Variable | FunctionLexical, LexicalBinding = Let | Const | FunctionLexical | FunctionStatement | Class @@ -83,7 +83,7 @@ export const enum BindingOrigin { Export = 1 << 4, Other = 1 << 5, BlockStatement = 1 << 7, - TopLevel = 1 << 8, + TopLevel = 1 << 8 } export const enum AssignmentKind { @@ -114,7 +114,7 @@ export const enum Flags { Octals = 1 << 6, SimpleParameterList = 1 << 7, HasStrictReserved = 1 << 8, - StrictEvalArguments = 1 << 9, + StrictEvalArguments = 1 << 9 } export const enum HoistedClassFlags { @@ -216,8 +216,11 @@ export interface ParserState { */ export function matchOrInsertSemicolon(parser: ParserState, context: Context, specDeviation?: number): void { - if ((parser.flags & Flags.NewLine) === 0 && (parser.token & Token.IsAutoSemicolon) !== Token.IsAutoSemicolon - && !specDeviation) { + if ( + (parser.flags & Flags.NewLine) === 0 && + (parser.token & Token.IsAutoSemicolon) !== Token.IsAutoSemicolon && + !specDeviation + ) { report(parser, Errors.UnexpectedToken, KeywordDescTable[parser.token & Token.Type]); } consumeOpt(parser, context, Token.Semicolon); @@ -353,9 +356,9 @@ export function validateBindingIdentifier( report(parser, Errors.KeywordNotId); } - // The BoundNames of LexicalDeclaration and ForDeclaration must not - // contain 'let'. (CatchParameter is the only lexical binding form - // without this restriction.) + // The BoundNames of LexicalDeclaration and ForDeclaration must not + // contain 'let'. (CatchParameter is the only lexical binding form + // without this restriction.) if (type & (BindingKind.Let | BindingKind.Const) && t === Token.LetKeyword) { report(parser, Errors.InvalidLetConstBinding); } @@ -503,8 +506,10 @@ export function recordScopeError(parser: ParserState, type: Errors): ScopeError const { index, line, column } = parser; return { type, - index, line, column - } + index, + line, + column + }; } /** @@ -531,7 +536,6 @@ export function addChildScope(parent: any, type: ScopeKind): ScopeState { }; } - /** * Adds either a var binding or a block scoped binding. * @@ -542,21 +546,23 @@ export function addChildScope(parent: any, type: ScopeKind): ScopeState { * @param type Binding kind * @param origin Binding Origin */ -export function addVarOrBlock(parser: ParserState, +export function addVarOrBlock( + parser: ParserState, context: Context, scope: ScopeState, name: string, type: BindingKind, - origin: BindingOrigin) { - if (type & BindingKind.Variable) { - addVarName(parser, context, scope, name, type); - } else { - addBlockName(parser, context, scope, name, type, BindingOrigin.Other); - } - if (origin & BindingOrigin.Export) { - updateExportsList(parser, parser.tokenValue); - addBindingToExports(parser, parser.tokenValue); - } + origin: BindingOrigin +) { + if (type & BindingKind.Variable) { + addVarName(parser, context, scope, name, type); + } else { + addBlockName(parser, context, scope, name, type, BindingOrigin.Other); + } + if (origin & BindingOrigin.Export) { + updateExportsList(parser, parser.tokenValue); + addBindingToExports(parser, parser.tokenValue); + } } /** @@ -575,15 +581,15 @@ export function addVarName( name: string, type: BindingKind ): void { - let currentScope: any = scope; while (currentScope && (currentScope.type & ScopeKind.FuncRoot) === 0) { - const value: ScopeKind = currentScope['#' + name]; if (value & BindingKind.LexicalBinding) { - if (context & Context.OptionsWebCompat && (context & Context.Strict) === 0 && + if ( + context & Context.OptionsWebCompat && + (context & Context.Strict) === 0 && ((type & BindingKind.FunctionStatement && value & BindingKind.LexicalOrFunction) || (value & BindingKind.FunctionStatement && type & BindingKind.LexicalOrFunction)) ) { @@ -592,12 +598,16 @@ export function addVarName( } } if (currentScope === scope) { - if (value & BindingKind.ArgumentList && type & BindingKind.ArgumentList) { - currentScope.scopeError = recordScopeError(parser, Errors.Unexpected); - } + if (value & BindingKind.ArgumentList && type & BindingKind.ArgumentList) { + currentScope.scopeError = recordScopeError(parser, Errors.Unexpected); + } } if (value & (BindingKind.CatchIdentifier | BindingKind.CatchPattern)) { - if (((value & BindingKind.CatchIdentifier) === 0 || (context & Context.OptionsWebCompat) === 0) || context & Context.Strict) { + if ( + (value & BindingKind.CatchIdentifier) === 0 || + (context & Context.OptionsWebCompat) === 0 || + context & Context.Strict + ) { report(parser, Errors.DuplicateBinding, name); } } @@ -626,7 +636,6 @@ export function addBlockName( type: BindingKind, origin: BindingOrigin ) { - const value = (scope as any)['#' + name]; if (value && (value & BindingKind.EmptyBinding) === 0) { @@ -656,7 +665,8 @@ export function addBlockName( } if (scope.type & ScopeKind.CatchBody) { - if ((scope as any).parent['#' + name] & BindingKind.CatchIdentifierOrPattern) report(parser, Errors.ShadowedCatchClause, name); + if ((scope as any).parent['#' + name] & BindingKind.CatchIdentifierOrPattern) + report(parser, Errors.ShadowedCatchClause, name); } (scope as any)['#' + name] = type; diff --git a/src/errors.ts b/src/errors.ts index 3f17a48c..532400bb 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -217,7 +217,8 @@ export const errorMessages: { [Errors.AccessorWrongArgs]: '%0 functions must have exactly %1 argument%2', [Errors.BadSetterRestParameter]: 'Setter function argument must not be a rest parameter', [Errors.DeclNoName]: '%0 declaration must have a name in this context', - [Errors.StrictFunctionName]: 'Function name may not be eval or arguments in strict mode', + [Errors.StrictFunctionName]: + 'Function name may not contain any reserved words or be eval or arguments in strict mode', [Errors.RestMissingArg]: 'The rest operator is missing an argument', [Errors.CantAssignToInit]: 'Cannot assign to lhs, not destructible with this initializer', [Errors.InvalidGeneratorGetter]: 'A getter cannot be a generator', diff --git a/src/lexer/common.ts b/src/lexer/common.ts index b55a6e05..52e9c57d 100644 --- a/src/lexer/common.ts +++ b/src/lexer/common.ts @@ -6,9 +6,7 @@ import { report, Errors } from '../errors'; export const enum LexerState { None = 0, NewLine = 1 << 0, - SameLine = 1 << 1, - LastIsCR = 1 << 2, - InJSXMode = 1 << 3 + LastIsCR = 1 << 2 } export const enum NumberKind { diff --git a/src/meriyah.ts b/src/meriyah.ts index 57d1e233..84c9289c 100644 --- a/src/meriyah.ts +++ b/src/meriyah.ts @@ -27,4 +27,4 @@ export function parse(source: string, options?: Options): ESTree.Program { export { ESTree, Options }; // Export current version -export const version = '1.4.2'; +export const version = '1.4.4'; diff --git a/src/parser.ts b/src/parser.ts index b0c107bb..d43d0d87 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1090,6 +1090,8 @@ export function parseAsyncArrowOrAsyncFunctionDeclaration( (context | Context.DisallowIn) ^ Context.DisallowIn, expr, 1, + BindingKind.ArgumentList, + BindingOrigin.None, asyncNewLine, start, line, @@ -1900,6 +1902,8 @@ export function parseLetIdentOrVarDeclarationStatement( if (context & Context.Strict) report(parser, Errors.UnexpectedLetStrictReserved); + if (parser.token === Token.LetKeyword) report(parser, Errors.InvalidLetBoundName); + /** LabelledStatement[Yield, Await, Return]: * * ExpressionStatement | LabelledStatement :: @@ -2086,7 +2090,7 @@ export function parseVariableDeclarationList( * * @param parser Parser object * @param context Context masks - * * @param start Start pos of node + * @param start Start pos of node * @param start Start pos of node * @param line * @param column @@ -2158,10 +2162,9 @@ function parseVariableDeclaration( * * @param parser Parser object * @param context Context masks - * @param start Start pos of node -* @param start Start pos of node -* @param line -* @param column + * @param start Start pos of node + * @param line + * @param column */ export function parseForStatement( @@ -2329,9 +2332,7 @@ export function parseForStatement( }); } - if (forAwait) { - report(parser, Errors.InvalidForAwait); - } + if (forAwait) report(parser, Errors.InvalidForAwait); if (!isVarDecl) { if (destructible & DestructuringKind.MustDestruct && parser.token !== Token.Assign) { @@ -2732,6 +2733,8 @@ function parseExportDeclaration( (context | Context.DisallowIn) ^ Context.DisallowIn, declaration, 1, + BindingKind.ArgumentList, + BindingOrigin.None, flags, idxBeforeAsync, lineBeforeAsync, @@ -7179,6 +7182,8 @@ export function parseAsyncExpression( (context | Context.DisallowIn) ^ Context.DisallowIn, expr, assignable, + BindingKind.ArgumentList, + BindingOrigin.None, flags, start, line, @@ -7210,13 +7215,21 @@ export function parseAsyncExpression( * @param context Context masks * @param callee ESTree AST node * @param assignable - * @param asyncNewLine + * @param kind Binding kind + * @param origin Binding origin + * @param flags Mutual parser flags + * @param start Start pos of node + * @param line Line pos of node + * @param column Column pos of node */ + export function parseAsyncArrowOrCallExpression( parser: ParserState, context: Context, callee: ESTree.Identifier | void, assignable: 0 | 1, + kind: BindingKind, + origin: BindingOrigin, flags: Flags, start: number, line: number, @@ -7253,20 +7266,9 @@ export function parseAsyncArrowOrCallExpression( if (token & (Token.IsIdentifier | Token.Keyword)) { if (scope) { - addBlockName(parser, context, scope, parser.tokenValue, BindingKind.ArgumentList, BindingOrigin.Other); + addBlockName(parser, context, scope, parser.tokenValue, kind, BindingOrigin.Other); } - expr = parsePrimaryExpressionExtended( - parser, - context, - BindingKind.ArgumentList, - 0, - 1, - 0, - 1, - tokenPos, - linePos, - colPos - ); + expr = parsePrimaryExpressionExtended(parser, context, kind, 0, 1, 0, 1, tokenPos, linePos, colPos); if ((parser.token & Token.IsCommaOrRightParen) === Token.IsCommaOrRightParen) { if (parser.assignable & AssignmentKind.NotAssignable) { @@ -7296,30 +7298,8 @@ export function parseAsyncArrowOrCallExpression( } else if (token & Token.IsPatternStart) { expr = token === Token.LeftBrace - ? parseObjectLiteralOrPattern( - parser, - context, - scope, - 0, - 1, - BindingKind.ArgumentList, - BindingOrigin.None, - tokenPos, - linePos, - colPos - ) - : parseArrayExpressionOrPattern( - parser, - context, - scope, - 0, - 1, - BindingKind.ArgumentList, - BindingOrigin.None, - tokenPos, - linePos, - colPos - ); + ? parseObjectLiteralOrPattern(parser, context, scope, 0, 1, kind, origin, tokenPos, linePos, colPos) + : parseArrayExpressionOrPattern(parser, context, scope, 0, 1, kind, origin, tokenPos, linePos, colPos); destructible |= parser.destructible; @@ -7344,8 +7324,8 @@ export function parseAsyncArrowOrCallExpression( context, scope, Token.RightParen, - BindingKind.ArgumentList, - BindingOrigin.None, + kind, + origin, 1, 1, tokenPos, diff --git a/test/parser/declarations/let.ts b/test/parser/declarations/let.ts index 914d58b0..07b852c1 100644 --- a/test/parser/declarations/let.ts +++ b/test/parser/declarations/let.ts @@ -486,6 +486,10 @@ describe('Declarations - Let', () => { ['let [...[ x ] = []] = [];', Context.None], ['let [...[ x ] = []] = [];', Context.None], ['let [...{ x } = []] = [];', Context.None], + ['let\\nlet', Context.None], + ['do let [x] = 0; while (false);', Context.None], + ['if (x) let [x] = y; else x;', Context.None], + ['do let [] while (a);', Context.None], ['let [...x, y] = [1, 2, 3];', Context.None], ['let [...{ x }, y] = [1, 2, 3];', Context.None], ['let [...x = []] = [];', Context.None], diff --git a/test/parser/expressions/arrow.ts b/test/parser/expressions/arrow.ts index ebab0e52..4dfc5b7b 100644 --- a/test/parser/expressions/arrow.ts +++ b/test/parser/expressions/arrow.ts @@ -71,6 +71,12 @@ describe('Expressions - Arrow', () => { }); }); + it(`async ${arg};`, () => { + t.doesNotThrow(() => { + parseSource(`async ${arg};`, undefined, Context.OptionsWebCompat | Context.OptionsNext); + }); + }); + it(`bar ? (${arg}) : baz;`, () => { t.doesNotThrow(() => { parseSource(`bar ? (${arg}) : baz;`, undefined, Context.None); @@ -373,6 +379,12 @@ describe('Expressions - Arrow', () => { }); }); + it(`v = ${arg};`, () => { + t.doesNotThrow(() => { + parseSource(`v = ${arg};`, undefined, Context.OptionsNext); + }); + }); + it(`bar, ${arg};`, () => { t.doesNotThrow(() => { parseSource(`bar, ${arg};`, undefined, Context.None); @@ -451,6 +463,12 @@ describe('Expressions - Arrow', () => { }); }); + it(`var x = ()${arg} =>{}`, () => { + t.throws(() => { + parseSource(`var x = ()${arg} =>{}`, undefined, Context.OptionsNext); + }); + }); + it(`"use strict"; var x = ()${arg} =>{}`, () => { t.throws(() => { parseSource(`"use strict"; var x = ()${arg} =>{}`, undefined, Context.None); diff --git a/test/parser/expressions/class.ts b/test/parser/expressions/class.ts index 20e78238..ce061b48 100644 --- a/test/parser/expressions/class.ts +++ b/test/parser/expressions/class.ts @@ -686,6 +686,9 @@ describe('Expressions - Class', () => { ['(class A {async *prototype(){}})', Context.None], ['(class A {set constructor(x){}})', Context.None], ['(class A {async constructor(){}})', Context.None], + ['class a {**=f(){}', Context.None], + ['class a {*=f(){}}', Context.None], + ['class A {async *=f(){}}', Context.None], ['(class A {async *constructor(){}})', Context.None], ['(class A {get "constructor"(){}})', Context.None], ['(class A {async "constructor"(){}})', Context.None], diff --git a/test/parser/expressions/group.ts b/test/parser/expressions/group.ts index 5311eaf1..1abed17a 100644 --- a/test/parser/expressions/group.ts +++ b/test/parser/expressions/group.ts @@ -360,6 +360,11 @@ describe('Expressions - Group', () => { ['({function} = 0)', Context.None], ['({a:function} = 0)', Context.None], ['({a:for} = 0)', Context.None], + ['({*=f(){}})', Context.None], + ['({**f(){}})', Context.None], + ['({**=f(){}})', Context.None], + ['({async *=f(){}})', Context.None], + ['({async **=f(){}})', Context.None], ['({a: b += 0} = {})', Context.None], ['[a += b] = []', Context.None], ['({"a"} = 0)', Context.None], diff --git a/test/parser/expressions/yield.ts b/test/parser/expressions/yield.ts index 7ea96490..b20882c0 100644 --- a/test/parser/expressions/yield.ts +++ b/test/parser/expressions/yield.ts @@ -20,6 +20,8 @@ describe('Expressions - Yield', () => { 'function *foo() { do try {} catch (q) {} while ((yield* 810048018773152)); async (x = y) => {} }', 'function* foo() { class x extends (yield* (e = "x") => {}) {} }', 'function* foo() { return ( yield* ( ( j ) => {}) ) }', + `({get a(){}}); + yield;`, 'function* foo() { return ( yield* ( async ( j ) => {}) ) }', `function* foo() { switch ( y (yield) - ((a) => {})) { } }`, `function* foo() { switch ( y (yield) - (async (a) => {})) { } }`, @@ -105,6 +107,18 @@ describe('Expressions - Yield', () => { parseSource(`${arg}`, undefined, Context.None); }); }); + + it(`${arg}`, () => { + t.doesNotThrow(() => { + parseSource(`${arg}`, undefined, Context.OptionsWebCompat); + }); + }); + + it(`${arg}`, () => { + t.doesNotThrow(() => { + parseSource(`${arg}`, undefined, Context.OptionsNext); + }); + }); } for (const arg of [ @@ -156,6 +170,12 @@ describe('Expressions - Yield', () => { }); }); + it(`"use strict"; ${arg}`, () => { + t.throws(() => { + parseSource(`"use strict"; ${arg}`, undefined, Context.OptionsWebCompat); + }); + }); + it(`"use strict"; function foo() { ${arg}}`, () => { t.throws(() => { parseSource(`"use strict"; function foo() { ${arg}}`, undefined, Context.None); @@ -252,6 +272,17 @@ describe('Expressions - Yield', () => { }); }); + // Function context. + it(`"use strict"; function f() { ${test} }`, () => { + t.throws(() => { + parseSource( + `"use strict"; function f() { ${test} }`, + undefined, + Context.OptionsWebCompat | Context.OptionsNext + ); + }); + }); + // Generator it(`"use strict"; function* g() { ${test} }`, () => { t.throws(() => { @@ -1293,6 +1324,41 @@ yield d; } ], + [ + 'yield *= x;', + Context.OptionsRanges, + { + type: 'Program', + sourceType: 'script', + body: [ + { + type: 'ExpressionStatement', + expression: { + type: 'AssignmentExpression', + left: { + type: 'Identifier', + name: 'yield', + start: 0, + end: 5 + }, + operator: '*=', + right: { + type: 'Identifier', + name: 'x', + start: 9, + end: 10 + }, + start: 0, + end: 10 + }, + start: 0, + end: 11 + } + ], + start: 0, + end: 11 + } + ], [ 'function* g() { yield 1; try { yield 2; } catch (e) { yield e; } yield 3; }', Context.OptionsRanges, diff --git a/test/parser/lexical/arrow.ts b/test/parser/lexical/arrow.ts index 0e0ac3f2..5d47c99e 100644 --- a/test/parser/lexical/arrow.ts +++ b/test/parser/lexical/arrow.ts @@ -164,8 +164,10 @@ describe('Lexical - Arrows', () => { 'x => { function x() {} }', 'async a => let [a]', 'x => { var x; }', + 'a => { for (let a of b) c }', 'a => { let {b} = a }', '() => { let foo; }; foo => {}', + 'a => { for (let a of b) c }', '() => { let foo; }; () => { let foo; }' ]) { it(`${arg}`, () => { diff --git a/test/parser/module/import.ts b/test/parser/module/import.ts index 468542d0..cef9ab21 100644 --- a/test/parser/module/import.ts +++ b/test/parser/module/import.ts @@ -243,6 +243,10 @@ describe('Module - Import', () => { ["import { {} } from 'foo';", Context.Strict | Context.Module], ["import { !d } from 'foo';", Context.Strict | Context.Module], ["import { 123 } from 'foo';", Context.Strict | Context.Module], + ["import a, *= from 'foo';", Context.Strict | Context.Module], + ["import a, ** from 'foo';", Context.Strict | Context.Module], + ["import a, **= from 'foo';", Context.Strict | Context.Module], + ["import *= from 'foo';", Context.Strict | Context.Module], ["import { [123] } from 'foo';", Context.Strict | Context.Module], ['import { a } from;', Context.Strict | Context.Module], ["import / as a from 'a'", Context.Strict | Context.Module], diff --git a/test/parser/statements/do-while.ts b/test/parser/statements/do-while.ts index 1a4fae57..5d74da31 100644 --- a/test/parser/statements/do-while.ts +++ b/test/parser/statements/do-while.ts @@ -13,6 +13,17 @@ describe('Statements - Do while', () => { ['do try {} catch {} while(x) x', Context.None], ['do if (x) {} while(x) x', Context.None], ['do debugger; while(x) x', Context.None], + ['do x, y while (z)', Context.None], + ['do foo while (bar);', Context.None], + ['do ()=>x while(c)', Context.None], + [ + `do + a + b + while(c);`, + Context.None + ], + ['do let {} = y', Context.None], ['do debugger while(x) x', Context.None], ['do;while(0) 0;', Context.None], ['do x: function s(){}while(y)', Context.None], @@ -553,6 +564,51 @@ while(y) type: 'Program' } ], + + [ + `do + ()=>x + while(c)`, + Context.OptionsWebCompat | Context.OptionsRanges, + { + type: 'Program', + sourceType: 'script', + body: [ + { + type: 'DoWhileStatement', + body: { + type: 'ExpressionStatement', + expression: { + type: 'ArrowFunctionExpression', + body: { + type: 'Identifier', + name: 'x', + start: 13, + end: 14 + }, + params: [], + async: false, + expression: true, + start: 9, + end: 14 + }, + start: 9, + end: 14 + }, + test: { + type: 'Identifier', + name: 'c', + start: 25, + end: 26 + }, + start: 0, + end: 27 + } + ], + start: 0, + end: 27 + } + ], [ 'do foo; while (bar);', Context.OptionsWebCompat | Context.OptionsRanges,