Skip to content

Commit

Permalink
fix(parser): fixed lexical edge case
Browse files Browse the repository at this point in the history
  • Loading branch information
KFlash committed Jul 24, 2019
1 parent 1a100ba commit 98c6ee7
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 50 deletions.
95 changes: 59 additions & 36 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,26 +531,59 @@ export function addChildScope(parent: any, type: ScopeKind): ScopeState {
};
}


/**
* Adds either a var binding or a block scoped binding.
*
* @param parser Parser state
* @param context Context masks
* @param scope Scope state
* @param name Binding name
* @param type Binding kind
* @param origin Binding Origin
*/
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);
}
}

/**
* Adds a variable binding
*
* @param parser Parser state
* @param context Context masks
* @param scope Scope state
* @param name Binding name
* @param type Binding kind
*/
export function addVarName(
parser: ParserState,
context: Context,
scope: ScopeState,
name: any,
name: string,
type: BindingKind
): void {

const hashed = '#' + name;
const isLexicalBinding = (type & BindingKind.LexicalBinding) !== 0;
const isWebCompat = context & Context.OptionsWebCompat && (context & Context.Strict) === 0;

let currentScope: any = scope;

while (currentScope && (currentScope.type & ScopeKind.FuncRoot) === 0) {

const value: ScopeKind = currentScope[hashed];
const value: ScopeKind = currentScope['#' + name];

if (value & BindingKind.LexicalBinding) {
if (isWebCompat &&
if (context & Context.OptionsWebCompat && (context & Context.Strict) === 0 &&
((type & BindingKind.FunctionStatement && value & BindingKind.LexicalOrFunction) ||
(value & BindingKind.FunctionStatement && type & BindingKind.LexicalOrFunction))
) {
Expand All @@ -559,9 +592,7 @@ export function addVarName(
}
}
if (currentScope === scope) {
if (value && (value & BindingKind.EmptyBinding) === 0 && isLexicalBinding) {
report(parser, Errors.DuplicateBinding, name);
} else if (value & BindingKind.ArgumentList && type & BindingKind.ArgumentList) {
if (value & BindingKind.ArgumentList && type & BindingKind.ArgumentList) {
currentScope.scopeError = recordScopeError(parser, Errors.Unexpected);
}
}
Expand All @@ -571,40 +602,32 @@ export function addVarName(
}
}

currentScope[hashed] = type;
currentScope['#' + name] = type;

currentScope = currentScope.parent;
}
}

export function addVarOrBlock(parser: ParserState,
context: Context,
scope: any,
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);
}
}

/**
* Adds block scoped binding
*
* @param parser Parser state
* @param context Context masks
* @param scope Scope state
* @param name Binding name
* @param type Binding kind
* @param origin Binding Origin
*/
export function addBlockName(
parser: ParserState,
context: Context,
scope: any,
scope: ScopeState,
name: string,
type: BindingKind,
origin: BindingOrigin
) {

const hashed = '#' + name;
const value = scope[hashed];
const value = (scope as any)['#' + name];

if (value && (value & BindingKind.EmptyBinding) === 0) {
if (type & BindingKind.ArgumentList) {
Expand All @@ -621,7 +644,7 @@ export function addBlockName(

if (
scope.type & ScopeKind.FuncBody &&
(scope.parent[hashed] && (scope.parent[hashed] & BindingKind.EmptyBinding) === 0)
((scope as any).parent['#' + name] && ((scope as any).parent['#' + name] & BindingKind.EmptyBinding) === 0)
) {
report(parser, Errors.DuplicateBinding, name);
}
Expand All @@ -637,13 +660,13 @@ export function addBlockName(
if (type & (BindingKind.CatchIdentifier | BindingKind.CatchPattern)) {
if (value & (BindingKind.CatchIdentifier | BindingKind.CatchPattern)) report(parser, Errors.ShadowedCatchClause, name);
} else if (scope.type & ScopeKind.CatchBody) {
if (scope.parent[hashed] & BindingKind.CatchIdentifierOrPattern) report(parser, Errors.ShadowedCatchClause, name);
if ((scope as any).parent['#' + name] & BindingKind.CatchIdentifierOrPattern) report(parser, Errors.ShadowedCatchClause, name);
}

let currentScope = scope.parent;
let currentScope: any = scope.parent;

while (currentScope && (currentScope.type & ScopeKind.FuncRoot) !== ScopeKind.FuncRoot) {
const value = currentScope[hashed];
const value = currentScope['#' + name];
if (currentScope.type & ScopeKind.ArrowParams) {
if (value && (value & BindingKind.EmptyBinding) === 0 && (type & BindingKind.CatchIdentifierOrPattern) === 0 ) {
report(parser, Errors.DuplicateBinding, name);
Expand All @@ -654,7 +677,7 @@ export function addBlockName(
currentScope = currentScope.parent;
}

scope[hashed] = type;
(scope as any)['#' + name] = type;
}

/**
Expand Down
19 changes: 14 additions & 5 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,6 @@ export function parseExpressionOrLabelledStatement(
switch (token) {
case Token.LetKeyword:
expr = parseIdentifier(parser, context, 0, start, line, column);

if (context & Context.Strict) report(parser, Errors.UnexpectedLetStrictReserved);

if (parser.token === Token.Colon)
Expand Down Expand Up @@ -1809,7 +1808,10 @@ export function parseLetIdentOrVarDeclarationStatement(
let expr: ESTree.Identifier | ESTree.Expression = parseIdentifier(parser, context, 0, start, line, column);
// If the next token is an identifier, `[`, or `{`, this is not
// a `let` declaration, and we parse it as an identifier.
if ((parser.token & (Token.IsIdentifier | Token.IsPatternStart)) === 0) {
if (
(parser.token & (Token.IsIdentifier | Token.IsPatternStart)) === 0 ||
(parser.flags & Flags.NewLine && parser.token & Token.IsIdentifier)
) {
parser.assignable = AssignmentKind.Assignable;

if (context & Context.Strict) report(parser, Errors.UnexpectedLetStrictReserved);
Expand Down Expand Up @@ -2343,7 +2345,7 @@ function parseImportDeclaration(
if (parser.token & Token.IsIdentifier) {
validateBindingIdentifier(parser, context, BindingKind.Const, parser.token, 0);
if (context & Context.OptionsLexical)
addBlockName(parser, context, scope, parser.tokenValue, BindingKind.Let, BindingOrigin.Other);
addBlockName(parser, context, scope as ScopeState, parser.tokenValue, BindingKind.Let, BindingOrigin.Other);
const local = parseIdentifier(parser, context, 0, tokenPos, linePos, colPos);
specifiers.push(
finishNode(parser, context, tokenPos, linePos, colPos, {
Expand Down Expand Up @@ -4404,7 +4406,7 @@ export function parseFunctionDeclaration(
if (type & BindingKind.Variable) {
addVarName(parser, context, scope as ScopeState, parser.tokenValue, type);
} else {
addBlockName(parser, context, scope, parser.tokenValue, type, origin);
addBlockName(parser, context, scope as ScopeState, parser.tokenValue, type, origin);
}

innerscope = addChildScope(innerscope, ScopeKind.FuncRoot);
Expand Down Expand Up @@ -6356,7 +6358,14 @@ export function parseParenthesizedExpression(

if (token & (Token.IsIdentifier | Token.Keyword)) {
if (context & Context.OptionsLexical) {
addBlockName(parser, context, scope, parser.tokenValue, BindingKind.ArgumentList, BindingOrigin.Other);
addBlockName(
parser,
context,
scope as ScopeState,
parser.tokenValue,
BindingKind.ArgumentList,
BindingOrigin.Other
);
}
expr = parsePrimaryExpressionExtended(
parser,
Expand Down
34 changes: 34 additions & 0 deletions test/parser/declarations/let.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3243,6 +3243,40 @@ describe('Declarations - Let', () => {
sourceType: 'script'
}
],
[
'l\\u0065t\na',
Context.OptionsRanges,
{
body: [
{
end: 8,
expression: {
end: 8,
name: 'let',
start: 0,
type: 'Identifier'
},
start: 0,
type: 'ExpressionStatement'
},
{
end: 10,
expression: {
end: 10,
name: 'a',
start: 9,
type: 'Identifier'
},
start: 9,
type: 'ExpressionStatement'
}
],
end: 10,
sourceType: 'script',
start: 0,
type: 'Program'
}
],
[
'let {x} = a, {y} = obj;',
Context.OptionsRanges,
Expand Down
49 changes: 40 additions & 9 deletions test/parser/miscellaneous/failure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,40 @@ describe('Miscellaneous - Failure', () => {
'(foo, eval) => {"use strict";}',
'(package) => {"use strict";}',
'(break) => {"use strict";}',
'let\nfoo()',
'(break) => {}',
` for await (a of b) let
[] = y`,
'for (a of b) let [x] = y',
'with (a) let [x] = y',
'async let [] = y',
'for (a in b) let x',
'with (a) let [x] = y',
'with (a) let [] = y',
'async let []',
'async let [] = y',
'async let {}',
'{ let [] }',
'() => { let {} }',
'switch (a) { case b: let [x] }',
'switch (a) { case b: let {} }',
'switch (a) { case b: let {x} }',
`do let [x]
while (a);`,
`do let [x] = y
while (a);`,
`do let {}
while (a);`,
`do let {} = y
while (a);`,
`do let {x}
while (a);`,
'do let [] = y while (a);',
'do let [x] while (a);',
'do let {} while (a);',
'do let {x} = y while (a);',
'do let [] = y; while (a);',
'do let {}; while (a)',
'() => let x',
'var\\u1680x;',
'var\\u180ex;',
'var\\u2000x;',
Expand Down Expand Up @@ -145,13 +177,12 @@ describe('Miscellaneous - Failure', () => {
'let [(x().foo)] = x',
'let [(x) = y] = [];',
'let [(x)] = [];',
// '['++([])',
// '['(++[])',
'[new ++([])',
'this.foo[foo].bar(this)(bar)[foo]()--',
'((x,x)) = 5',
'(((x,x))) = 5',
'async ({a = b})',
// 'new Date++;',
'new Date++;',
'for(let.a of 0);',
'({...{b = 0}.x} = {});',
'[[(x, y)]] = x;',
Expand Down Expand Up @@ -1085,7 +1116,7 @@ describe('Miscellaneous - Failure', () => {
'for(var x=1 of [1,2,3]) 0',
'for(let x=1 of [1,2,3]) 0',
'for(; false;) let {}',
//'while(true) let[a] = 0',
'while(true) let[a] = 0',
'for ((i in {}));',
'while(true) let a',
'while(true) const a',
Expand Down Expand Up @@ -1335,7 +1366,7 @@ describe('Miscellaneous - Failure', () => {
'for (var [x, y] = {} in {});',
'function foo(...b, a) { return a }',
'(x, ...y)',
//'with ({}) let [a] = [42];',
'with ({}) let [a] = [42];',
'let {...{}} = {};',
'(function anonymous()',
'(10, 20) => 00',
Expand Down Expand Up @@ -1419,7 +1450,7 @@ describe('Miscellaneous - Failure', () => {
'super',
'x(void);',
'x(break);',
//'x(enum);',
'x(enum);',
'x=5+y>>>=8',
'x=5+y>>=8',
'x=5+y<<=8',
Expand All @@ -1445,7 +1476,7 @@ describe('Miscellaneous - Failure', () => {
'for (var key ik bar);',
'for (var key inhere bar);',
'for (var key instanceof bar);',
// "continue;",
'continue;',
'try {}',
'();',
'foo(());',
Expand Down Expand Up @@ -1644,7 +1675,7 @@ describe('Miscellaneous - Failure', () => {
'(x--)=b',
'(a=b.c)=d;',
'a=(a=b.c)=d;',
//"new Date++;",
'new Date++;',
'new (A).foo = bar',
'++++x',
'async function as(){ class A {async f(yield) {}} }',
Expand Down

0 comments on commit 98c6ee7

Please sign in to comment.