Skip to content

Commit

Permalink
fix(parser): Context based escape keyword validation
Browse files Browse the repository at this point in the history
  • Loading branch information
KFlash committed Aug 8, 2019
1 parent 7576780 commit 17d4649
Show file tree
Hide file tree
Showing 15 changed files with 200 additions and 735 deletions.
7 changes: 0 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

* Conforms to the standard ECMAScript® 2020 (ECMA-262 10th Edition) language specification
* Support TC39 proposals via option
* Support V8 experimental features via option
* Support for additional ECMAScript features for Web Browsers
* JSX support via option
* Optionally track syntactic node locations
Expand All @@ -41,12 +40,6 @@

**Note:** These features need to be enabled with the `next` option.

## V8 features

* Intrinsic

**Note:** These features need to be enabled with the `next` option.

## Installation

```sh
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const enum Context {
DisallowIn = 1 << 27,
OptionsIdentifierPattern = 1 << 28,
OptionsSpecDeviation = 1 << 29,
OptionsV8 = 1 << 30,
AllowEscapedKeyword = 1 << 30,
}

/**
Expand Down Expand Up @@ -345,9 +345,9 @@ export function validateBindingIdentifier(
t: Token,
skipEvalArgCheck: 0 | 1
): void {
if ((t & Token.Keyword) !== Token.Keyword) return;

if (context & Context.Strict) {

if (t === Token.StaticKeyword) {
report(parser, Errors.InvalidStrictStatic);
}
Expand All @@ -360,9 +360,9 @@ export function validateBindingIdentifier(
report(parser, Errors.StrictEvalArguments);
}

if (t === Token.EscapedFutureReserved) {
if (t === Token.EscapedFutureReserved) {
report(parser, Errors.InvalidEscapedKeyword);
}
}
}

if ((t & Token.Reserved) === Token.Reserved) {
Expand Down
6 changes: 3 additions & 3 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export const errorMessages: {
[key: string]: string;
} = {
[Errors.Unexpected]: 'Unexpected token',
[Errors.UnexpectedToken]: "Unexpected token '%0'",
[Errors.UnexpectedToken]: "Unexpected token: '%0'",
[Errors.StrictOctalEscape]: 'Octal escape sequences are not allowed in strict mode',
[Errors.TemplateOctalLiteral]: 'Octal escape sequences are not allowed in template strings',
[Errors.InvalidPrivateName]: 'Unexpected token `#`',
Expand Down Expand Up @@ -287,8 +287,8 @@ export const errorMessages: {
[Errors.IllegalReturn]: 'Illegal return statement',
[Errors.InvalidForLHSBinding]: 'The for-header left hand side binding declaration is not destructible',
[Errors.InvalidNewTarget]: 'new.target only allowed within functions',
[Errors.InvalidEscapedKeyword]: 'Keywords must be written literally, without embedded escapes',
[Errors.MissingPrivateName]: "''#' not followed by identifier",
[Errors.InvalidEscapedKeyword]: 'Kasdfafdsadfsy, without embedded escapes',
[Errors.MissingPrivateName]: "'#' not followed by identifier",
[Errors.InvalidStrictStatic]: '`Static` is a reserved word in strict mode',
[Errors.FutureReservedWordInStrictModeNotId]:
'The use of a future reserved word for an identifier is invalid. The identifier name is reserved in strict mode',
Expand Down
37 changes: 27 additions & 10 deletions src/lexer/identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,35 @@ export function scanIdentifierSlowCase(

if (isValidAsKeyword && (length >= 2 && length <= 11)) {
const token: Token | undefined = descKeywordTable[parser.tokenValue];
if (token === void 0) return Token.Identifier;
if (!hasEscape) return token;
if (context & Context.Strict) {
if (token === Token.AwaitKeyword)
return context & (Context.Module | Context.InAwaitContext) ? Token.EscapedReserved : token;

return token === void 0
? Token.Identifier
: token === Token.YieldKeyword || !hasEscape
? token
: context & Context.Strict && (token === Token.LetKeyword || token === Token.StaticKeyword)
? Token.EscapedFutureReserved
: (token & Token.FutureReserved) === Token.FutureReserved
? context & Context.Strict
return token === Token.StaticKeyword
? Token.EscapedFutureReserved
: token
: Token.EscapedReserved;
: (token & Token.FutureReserved) === Token.FutureReserved
? Token.EscapedFutureReserved
: Token.EscapedReserved;
}

if (token === Token.YieldKeyword) {
if (context & Context.AllowEscapedKeyword) return Token.ReservedIfStrict;

return context & Context.InYieldContext ? Token.EscapedReserved : token;
}
if (token === Token.AwaitKeyword) return context & Context.InAwaitContext ? Token.EscapedReserved : token;
if (token === Token.AsyncKeyword) {
if (context & Context.AllowEscapedKeyword) return Token.Identifier;
}

if ((token & Token.FutureReserved) === Token.FutureReserved) {
if (context & Context.Strict) return Token.EscapedFutureReserved;
return token;
}

return Token.EscapedReserved;
}
return Token.Identifier;
}
Expand Down
92 changes: 21 additions & 71 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,6 @@ export interface Options {
jsx?: boolean;
// Allow edge cases that deviate from the spec
specDeviation?: boolean;
// Enabled V8 features
v8?: boolean;
// Allowes comment extraction. Accepts either a a callback function or an array
onComment?: OnComment;
}
Expand All @@ -225,7 +223,6 @@ export function parseSource(source: string, options: Options | void, context: Co
if (options.preserveParens) context |= Context.OptionsPreserveParens;
if (options.impliedStrict) context |= Context.Strict;
if (options.jsx) context |= Context.OptionsJSX;
if (options.v8) context |= Context.OptionsV8;
if (options.identifierPattern) context |= Context.OptionsIdentifierPattern;
if (options.specDeviation) context |= Context.OptionsSpecDeviation;
if (options.source) sourceFile = options.source;
Expand Down Expand Up @@ -300,7 +297,7 @@ export function parseStatementList(
// StatementList ::
// (StatementListItem)* <end_token>

nextToken(parser, context | Context.AllowRegExp);
nextToken(parser, context | Context.AllowRegExp | Context.AllowEscapedKeyword);

const statements: ESTree.Statement[] = [];

Expand Down Expand Up @@ -1845,6 +1842,7 @@ export function parseLetIdentOrVarDeclarationStatement(
/* VariableDeclarations ::
* ('let') (Identifier ('=' AssignmentExpression)?)+[',']
*/

const declarations = parseVariableDeclarationList(parser, context, scope, BindingKind.Let, Origin.None);

matchOrInsertSemicolon(parser, context | Context.AllowRegExp);
Expand Down Expand Up @@ -3422,7 +3420,7 @@ export function parseAsyncExpression(
column: number
): any {
const { token } = parser;
const expr = parseIdentifier(parser, context | Context.TaggedTemplate, identifierPattern);
const expr = parseIdentifier(parser, context, identifierPattern);
const { flags } = parser;

if ((flags & Flags.NewLine) < 1) {
Expand Down Expand Up @@ -4120,8 +4118,6 @@ export function parsePrimaryExpressionExtended(

// Only a "simple validation" is done here to forbid escaped keywords and 'let' edge cases

if ((token & Token.EscapedReserved) === Token.EscapedReserved) report(parser, Errors.InvalidEscapedKeyword);

if (token === Token.LetKeyword) {
if (context & Context.Strict) report(parser, Errors.StrictInvalidLetInExprPos);
if (kind & (BindingKind.Let | BindingKind.Const)) report(parser, Errors.InvalidLetConstBinding);
Expand Down Expand Up @@ -4197,58 +4193,12 @@ export function parsePrimaryExpressionExtended(
case Token.LessThan:
if (context & Context.OptionsJSX)
return parseJSXRootElementOrFragment(parser, context, /*inJSXChild*/ 1, start, line, column);
case Token.Modulo:
if (context & Context.OptionsV8) return parseV8Intrinsic(parser, context, start, line, column);
default:
if (isValidIdentifier(context, parser.token)) return parseIdentifierOrArrow(parser, context, start, line, column);
report(parser, Errors.UnexpectedToken, KeywordDescTable[parser.token & Token.Type]);
}
}

/**
* Parses V8 intrinsic
*
* @param parser Parser object
* @param context Context masks
* @param inGroup
* @param start
* @param line
* @param column
*/
export function parseV8Intrinsic(
parser: ParserState,
context: Context,
start: number,
line: number,
column: number
): any {
// CallRuntime ::
// '%' Identifier Arguments

nextToken(parser, context); // skips: '%'

const expr = v8IntrinsicIdentifier(parser, context);

if (parser.token !== Token.LeftParen) report(parser, Errors.Unexpected);

return parseMemberOrUpdateExpression(parser, context, expr, 0, 0, 0, start, line, column);
}

/**
* Parses V8 intrinsic identifier
*
* @param parser Parser object
* @param context Context masks
*/
export function v8IntrinsicIdentifier(parser: ParserState, context: Context): ESTree.Identifier {
const { tokenValue, tokenPos, linePos, colPos } = parser;
nextToken(parser, context);
return finishNode(parser, context, tokenPos, linePos, colPos, {
type: 'V8IntrinsicIdentifier',
name: tokenValue
} as any);
}

/**
* Parses Import call expression
*
Expand Down Expand Up @@ -4804,7 +4754,8 @@ export function parseFunctionDeclaration(
context =
((context | 0b0000001111011000000_0000_00000000) ^ 0b0000001111011000000_0000_00000000) |
Context.AllowNewTarget |
((isAsync * 2 + isGenerator) << 21);
((isAsync * 2 + isGenerator) << 21) |
(isGenerator ? 0 : Context.AllowEscapedKeyword);

if (scope) functionScope = addChildScope(functionScope, ScopeKind.FunctionParams);

Expand Down Expand Up @@ -4887,7 +4838,8 @@ export function parseFunctionExpression(
context =
((context | 0b0000001111011000000_0000_00000000) ^ 0b0000001111011000000_0000_00000000) |
Context.AllowNewTarget |
generatorAndAsyncFlags;
generatorAndAsyncFlags |
(isGenerator ? 0 : Context.AllowEscapedKeyword);

if (scope) scope = addChildScope(scope, ScopeKind.FunctionParams);

Expand Down Expand Up @@ -5676,7 +5628,7 @@ export function parseObjectLiteralOrPattern(
* MethodDefinition
*/

nextToken(parser, context);
nextToken(parser, context | Context.AllowEscapedKeyword);

const properties: (ESTree.Property | ESTree.SpreadElement)[] = [];
let destructible: DestructuringKind | AssignmentKind = 0;
Expand Down Expand Up @@ -5708,7 +5660,7 @@ export function parseObjectLiteralOrPattern(
let key: ESTree.Expression | null = null;
let value;

if (parser.token & (Token.IsIdentifier | (parser.token & Token.Keyword))) {
if (parser.token & (Token.IsIdentifier | Token.Keyword)) {
key = parseIdentifier(parser, context, 0);

if (parser.token === Token.Comma || parser.token === Token.RightBrace || parser.token === Token.Assign) {
Expand Down Expand Up @@ -5904,8 +5856,6 @@ export function parseObjectLiteralOrPattern(
}
key = parseIdentifier(parser, context, 0);

if (token === Token.EscapedReserved) report(parser, Errors.UnexpectedStrictReserved);

state |=
token === Token.GetKeyword
? PropertyKind.Getter
Expand Down Expand Up @@ -5936,7 +5886,7 @@ export function parseObjectLiteralOrPattern(
);
} else if (parser.token === Token.Multiply) {
destructible |= DestructuringKind.CannotDestruct;
if (token === Token.EscapedReserved) report(parser, Errors.InvalidEscapeIdentifier);

if (token === Token.GetKeyword || token === Token.SetKeyword) {
report(parser, Errors.InvalidGeneratorGetter);
}
Expand Down Expand Up @@ -6539,7 +6489,7 @@ export function parseParenthesizedExpression(
): any {
parser.flags = (parser.flags | Flags.SimpleParameterList) ^ Flags.SimpleParameterList;

nextToken(parser, context | Context.AllowRegExp);
nextToken(parser, context | Context.AllowRegExp | Context.AllowEscapedKeyword);

const scope = context & Context.OptionsLexical ? addChildScope(createScope(), ScopeKind.ArrowParams) : void 0;

Expand Down Expand Up @@ -7647,15 +7597,13 @@ export function parseClassDeclaration(
// DecoratorList[?Yield, ?Await]optclassBindingIdentifier[?Yield, ?Await]ClassTail[?Yield, ?Await]
// DecoratorList[?Yield, ?Await]optclassClassTail[?Yield, ?Await]
//

context = (context | Context.InConstructor | Context.Strict) ^ Context.InConstructor;

let id: ESTree.Expression | null = null;
let superClass: ESTree.Expression | null = null;

const decorators: ESTree.Decorator[] = context & Context.OptionsNext ? parseDecorators(parser, context) : [];

nextToken(parser, context);
let id: ESTree.Expression | null = null;
let superClass: ESTree.Expression | null = null;

const { tokenValue } = parser;

Expand Down Expand Up @@ -8080,8 +8028,12 @@ function parseClassElementList(
} else if (context & Context.OptionsNext && (parser.token & Token.IsClassField) === Token.IsClassField) {
kind |= PropertyKind.ClassField;
context = context | Context.InClass;
} else if (token === Token.EscapedFutureReserved) {
key = parseIdentifier(parser, context, 0);
if (parser.token !== Token.LeftParen)
report(parser, Errors.UnexpectedToken, KeywordDescTable[parser.token & Token.Type]);
} else {
report(parser, Errors.Unexpected);
report(parser, Errors.UnexpectedToken, KeywordDescTable[parser.token & Token.Type]);
}

if (kind & (PropertyKind.Generator | PropertyKind.Async | PropertyKind.GetSet)) {
Expand All @@ -8092,6 +8044,8 @@ function parseClassElementList(
} else if (parser.token === Token.LeftBracket) {
kind |= PropertyKind.Computed;
key = parseComputedPropertyName(parser, context, /* inGroup */ 0);
} else if (parser.token === Token.EscapedFutureReserved) {
key = parseIdentifier(parser, context, 0);
} else if (context & Context.OptionsNext && parser.token === Token.PrivateField) {
kind |= PropertyKind.PrivateField;
key = parsePrivateName(parser, context, tokenPos, linePos, colPos);
Expand Down Expand Up @@ -8346,8 +8300,6 @@ function parseAndClassifyIdentifier(
report(parser, Errors.StrictEvalArguments);
} else if ((token & Token.FutureReserved) === Token.FutureReserved) {
report(parser, Errors.FutureReservedWordInStrictModeNotId);
} else if (token === Token.EscapedFutureReserved) {
report(parser, Errors.InvalidEscapedKeyword);
}
}

Expand All @@ -8364,11 +8316,9 @@ function parseAndClassifyIdentifier(
if (context & (Context.InAwaitContext | Context.Module) && token === Token.AwaitKeyword) {
report(parser, Errors.AwaitOutsideAsync);
}
if (token === Token.EscapedReserved) {
report(parser, Errors.InvalidEscapedKeyword);
}

nextToken(parser, context);

return finishNode(parser, context, start, line, column, {
type: 'Identifier',
name: tokenValue
Expand Down
6 changes: 3 additions & 3 deletions src/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ export const enum Token {
Eval = 116 | IsEvalOrArguments,
Arguments = 117 | IsEvalOrArguments,

EscapedReserved = 118 | IsIdentifier,
EscapedFutureReserved = 119 | IsIdentifier,
EscapedReserved = 118,
EscapedFutureReserved = 119,
ReservedIfStrict = 120 | IsIdentifier,

// Stage #3 proposals
Expand Down Expand Up @@ -234,7 +234,7 @@ export const KeywordDescTable = [
'as', 'async', 'await', 'constructor', 'get', 'set', 'from', 'of',

/* Others */
'enum', 'eval', 'arguments', 'escaped reserved', 'escaped future reserved', 'reserved if strict', '#',
'enum', 'eval', 'arguments', 'escaped keyword', 'escaped future reserved keyword', 'reserved if strict', '#',

'BigIntLiteral', '??', '?.', 'WhiteSpace', 'Illegal', 'LineTerminator', 'PrivateField',

Expand Down
2 changes: 2 additions & 0 deletions test/parser/expressions/arrow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,8 @@ describe('Expressions - Arrow', () => {
}

fail('Expressions - Array (fail)', [
['"use strict"; let => {}', Context.OptionsLexical],
['let => {}', Context.Strict | Context.Module],
['function *a() { yield => foo }', Context.None],
['yield x => zoo', Context.None],
['foo bar => zoo', Context.None],
Expand Down
Loading

0 comments on commit 17d4649

Please sign in to comment.