buntis
Version:
A 100% compliant, self-hosted typescript parser that emits an ESTree-compatible abstract syntax tree
463 lines (423 loc) • 13.4 kB
text/typescript
import { Comment, Node } from './estree';
import { Token, KeywordDescTable } from './token';
import { nextToken } from './scanner/scan';
import { Errors, report } from './errors';
/**
* The core context, passed around everywhere as a simple immutable bit set.
*/
export const enum Context {
Empty = 0,
OptionsNext = 1 << 0,
OptionsRanges = 1 << 1,
OptionsLoc = 1 << 2,
OptionsDirectives = 1 << 3,
OptionsJSX = 1 << 4,
OptionsGlobalReturn = 1 << 5,
OptionsRaw = 1 << 6,
OptionsTS = 1 << 7,
DisableWebCompat = 1 << 8,
OptionsIdentifierPattern = 1 << 9,
Strict = 1 << 10,
Module = 1 << 11,
InSwitch = 1 << 12,
InGlobal = 1 << 13,
InClass = 1 << 14,
AllowRegExp = 1 << 15,
TaggedTemplate = 1 << 16,
InIteration = 1 << 17,
SuperProperty = 1 << 18,
SuperCall = 1 << 19,
InYieldContext = 1 << 21,
InAwaitContext = 1 << 22,
InArgumentList = 1 << 23,
InConstructor = 1 << 24,
InMethod = 1 << 25,
AllowNewTarget = 1 << 26,
DisallowIn = 1 << 27,
InTSTypes = 1 << 28,
Conditional = 1 << 29,
Speculative = 1 << 30,
InDecoratorContext = 1 << 20
}
export const enum ModifierKind {
None = 0,
Abstract = 1 << 0,
Public = 1 << 1,
Private = 1 << 2,
Protected = 1 << 3,
Readonly = 1 << 4,
Static = 1 << 5,
Accessibility = Private | Protected | Public
}
export const enum TypeAssertionState {
None = 0,
TypeParameter = 1 << 0,
TypeAssertion = 1 << 1,
Unfinished = 1 << 2, // Unfinished type assertion
UnKnown = 1 << 3 // Either type param or type assertion
}
export const enum TypeAliasState {
None = 0,
Pattern = 1 << 0,
TypeMember = 1 << 1
}
/*@internal*/
export const primaryTypes: { [key: string]: string } = {
['any']: 'AnyKeyword',
['boolean']: 'BooleanKeyword',
['bigint']: 'BigIntKeyword',
['never']: 'NeverKeyword',
['number']: 'NumberKeyword',
['object']: 'ObjectKeyword',
['string']: 'StringKeyword',
['symbol']: 'SymbolKeyword',
['undefined']: 'UndefinedKeyword',
['unknown']: 'UnknownKeyword'
};
/**
* The mutable parser flags, in case any flags need passed by reference.
*/
export const enum Flags {
Empty = 0,
HasConstructor = 1 << 0
}
export const enum PropertyKind {
None = 0,
Method = 1 << 0,
Computed = 1 << 1,
Shorthand = 1 << 2,
Generator = 1 << 3,
Async = 1 << 4,
Static = 1 << 5,
Constructor = 1 << 6,
ClassField = 1 << 7,
Getter = 1 << 8,
Setter = 1 << 9,
Extends = 1 << 10,
Literal = 1 << 11,
// Modifiers
Abstract = 1 << 12,
Readonly = 1 << 13,
GetSet = Getter | Setter
}
/**
* Masks to track the binding kind
*/
export const enum BindingKind {
None = 0,
ArgumentList = 1 << 0,
Empty = 1 << 1,
Variable = 1 << 2,
Let = 1 << 3,
Const = 1 << 4,
Class = 1 << 5,
FunctionLexical = 1 << 6,
FunctionStatement = 1 << 7,
CatchPattern = 1 << 8,
CatchIdentifier = 1 << 9,
CatchIdentifierOrPattern = CatchIdentifier | CatchPattern,
LexicalOrFunction = Variable | FunctionLexical,
LexicalBinding = Let | Const | FunctionLexical | FunctionStatement | Class
}
/**
* The masks to track where something begins. E.g. statements, declarations or arrows.
*/
export const enum Origin {
None = 0,
Statement = 1 << 0,
BlockStatement = 1 << 1,
TopLevel = 1 << 2,
Declaration = 1 << 3,
Arrow = 1 << 4,
ForStatement = 1 << 5,
Export = 1 << 6,
Declare = 1 << 7
}
export const enum ClassAndFunctionFlags {
None,
Hoisted = 1 << 0,
Export = 1 << 1
}
export const enum TypeScriptMadness {
Definite = 1 << 1
}
/**
* The parser options.
*/
export interface Options {
onComment?: CommentCallback;
onError?: ErrorCallback;
next?: boolean;
ts?: boolean;
module?: boolean;
ranges?: boolean;
jsx?: boolean;
raw?: boolean;
loc?: boolean;
directives?: boolean;
globalReturn?: boolean;
impliedStrict?: boolean;
disableWebCompat?: boolean;
identifierPattern?: boolean;
}
/**
* The type of the `onComment` option.
*/
export type CommentCallback = void | Comment[] | ((type: string, value: string, start: number, end: number) => any);
/**
* The type of the `onError` option.
*/
export type ErrorCallback = void | {
(message: string, index: number, line: number, column: number, early: 0 | 1): void;
};
/**
* The parser interface.
*/
export interface ParserState {
source: string;
onComment: CommentCallback | undefined;
onError: ErrorCallback | undefined;
startPos: number;
tokenPos: number;
startColumn: number;
startLine: number;
endLine: number;
endColumn: number;
flags: Flags;
index: number;
line: number;
column: number;
length: number;
uid: number;
precedingLineBreak: 0 | 1;
token: Token;
tokenValue: any;
tokenRaw: string;
tokenRegExp: void | {
pattern: string;
flags: string;
};
// For the scanner to work around lack of multiple return.
nextCodePoint: number;
}
export function consume(parser: ParserState, context: Context, t: Token) {
if (parser.token === t) {
nextToken(parser, context);
return true;
}
report(parser, context, Errors.Expected, /* early */ 0, KeywordDescTable[t & Token.Type]);
return false;
}
export function consumeOpt(parser: ParserState, context: Context, t: Token): boolean {
if (parser.token === t) {
nextToken(parser, context);
return true;
}
return false;
}
export function optionalBit(parser: ParserState, context: Context, t: Token): 0 | 1 {
if (parser.token === t) {
nextToken(parser, context);
return 1;
}
return 0;
}
export function canParseSemicolon(parser: ParserState) {
// If there's a real semicolon, then we can always parse it out.
if (parser.token === Token.Semicolon) {
return true;
}
// We can parse out an optional semicolon in ASI cases in the following cases.
return parser.token === Token.LeftBrace || parser.token === Token.EndOfSource || parser.precedingLineBreak;
}
export function consumeSemicolon(parser: ParserState, context: Context): boolean {
if (parser.precedingLineBreak === 0 && (parser.token & Token.IsAutoSemicolon) !== Token.IsAutoSemicolon) {
// report(parser, context, Errors.UnexpectedToken, 0, KeywordDescTable[parser.token & Token.Type]);
}
consumeOpt(parser, context, Token.Semicolon);
return true;
}
export function reinterpretToPattern(state: ParserState, node: any): void {
switch (node.type) {
case 'ArrayExpression':
node.type = 'ArrayPattern';
const elements = node.elements;
for (let i = 0, n = elements.length; i < n; ++i) {
const element = elements[i];
if (element) reinterpretToPattern(state, element);
}
return;
case 'ObjectExpression':
node.type = 'ObjectPattern';
const properties = node.properties;
for (let i = 0, n = properties.length; i < n; ++i) {
reinterpretToPattern(state, properties[i]);
}
return;
case 'AssignmentExpression':
node.type = 'AssignmentPattern';
// if (node.operator !== '=') report(state, Errors.InvalidDestructuringTarget);
delete node.operator;
reinterpretToPattern(state, node.left);
return;
case 'Property':
reinterpretToPattern(state, node.value);
return;
case 'SpreadElement':
node.type = 'RestElement';
reinterpretToPattern(state, node.argument);
default: // ignore
}
}
export function isValidIdentifier(context: Context, t: Token): boolean {
if (context & (Context.Strict | Context.InYieldContext)) {
// Module code is also "strict mode code"
if (context & Context.Module && t === Token.AwaitKeyword) return false;
if (context & Context.InYieldContext && t === Token.YieldKeyword) return false;
return (t & Token.IsIdentifier) === Token.IsIdentifier || (t & Token.Contextual) === Token.Contextual;
}
return (
(t & Token.IsIdentifier) === Token.IsIdentifier ||
(t & Token.Contextual) === Token.Contextual ||
(t & Token.FutureReserved) === Token.FutureReserved
);
}
export function reinterpretToTypeLiteral(node: any): void {
switch (node.type) {
case 'ObjectPattern':
node.type = 'TypeLiteral';
const members = [];
const elements = node.properties;
for (let i = 0, n = elements.length; i < n; ++i) {
const element = elements[i];
reinterpretToTypeLiteral(element);
members.push(element);
}
node.members = members;
delete node.properties;
return;
case 'Property':
node.type = 'PropertySignature';
return;
}
}
export function lookAhead<T>(
parser: ParserState,
context: Context,
callback: (parser: ParserState, context: Context) => T
): T {
return speculationHelper(parser, context, callback, /*isLookahead*/ true);
}
export function tryScan<T>(
parser: ParserState,
context: Context,
callback: (parser: ParserState, context: Context) => T
): T {
return speculationHelper(parser, context, callback, /*isLookahead*/ false);
}
export function speculationHelper<T>(
parser: ParserState,
context: Context,
callback: (parser: ParserState, context: Context) => T,
isLookahead: boolean
): T {
const savePos = parser.index;
const saveStartPos = parser.startPos;
const saveTokenPos = parser.tokenPos;
const saveCodePoint = parser.nextCodePoint;
const savePreceding = parser.precedingLineBreak;
const saveLine = parser.line;
const saveColumn = parser.column;
const saveLength = parser.length;
const saveRaw = parser.tokenRaw;
const saveToken = parser.token;
const saveTokenValue = parser.tokenValue;
const saveTokenFlags = parser.flags;
const result = callback(parser, context);
if (!result || isLookahead) {
parser.index = savePos;
parser.startPos = saveStartPos;
parser.tokenPos = saveTokenPos;
parser.token = saveToken;
parser.tokenValue = saveTokenValue;
parser.flags = saveTokenFlags;
parser.nextCodePoint = saveCodePoint;
parser.precedingLineBreak = savePreceding;
parser.line = saveLine;
parser.column = saveColumn;
parser.length = saveLength;
parser.tokenRaw = saveRaw;
}
return result;
}
export function canFollowTypeArgumentsInExpression(parser: ParserState): boolean {
switch (parser.token) {
case Token.LeftParen: // foo<x>(
case Token.TemplateTail: // foo<T> `...`
case Token.Period: // foo<x>.
case Token.RightParen: // foo<x>)
case Token.RightBracket: // foo<x>]
case Token.Colon: // foo<x>:
case Token.Semicolon: // foo<x>;
case Token.QuestionMark: // foo<x>?
case Token.LooseEqual: // foo<x> ==
case Token.StrictEqual: // foo<x> ===
case Token.LooseNotEqual: // foo<x> !=
case Token.StrictNotEqual: // foo<x> !==
case Token.LogicalAnd: // foo<x> &&
case Token.LogicalOr: // foo<x> ||
case Token.BitwiseXor: // foo<x> ^
case Token.BitwiseAnd: // foo<x> &
case Token.BitwiseOr: // foo<x> |
case Token.RightBrace: // foo<x> }
case Token.EndOfSource: // foo<x>
return true;
case Token.NumericLiteral: // foo<x>,
case Token.Comma: // foo<x>,
case Token.LeftBrace: // foo<x> {
default:
return false;
}
}
/** @internal */
export function isEqualTagName(elementName: any): any {
switch (elementName.type) {
case 'JSXIdentifier':
return elementName.name;
case 'JSXNamespacedName':
return elementName.namespace + ':' + elementName.name;
case 'JSXMemberExpression':
return isEqualTagName(elementName.object) + '.' + isEqualTagName(elementName.property);
/* istanbul ignore next */
default:
// ignore
}
}
export function isStrictReservedWord(parser: ParserState, context: Context, t: Token): boolean {
if (t === Token.AwaitKeyword) {
if (context & (Context.InAwaitContext | Context.Module))
report(parser, context, Errors.AwaitOutsideAsync, /* early */ 1);
}
if (t === Token.YieldKeyword && context & Context.InYieldContext)
report(parser, context, Errors.DisallowedInContext, /* early */ 1, 'yield');
return (t & Token.Keywords) === Token.Keywords || (t & Token.FutureReserved) === Token.FutureReserved;
}
export function validateFunctionName(parser: ParserState, context: Context, t: Token): void {
if (context & Context.Strict) {
if ((t & Token.FutureReserved) === Token.FutureReserved) {
report(parser, context, Errors.UnexpectedStrictReserved, /* early */ 1);
}
}
if ((t & Token.Keywords) === Token.Keywords) {
report(parser, context, Errors.KeywordNotId, /* early */ 1);
}
if (context & (Context.InAwaitContext | Context.Module) && t === Token.AwaitKeyword) {
report(parser, context, Errors.AwaitOutsideAsync, /* early */ 1);
}
if (context & (Context.InYieldContext | Context.Strict) && t === Token.YieldKeyword) {
report(parser, context, Errors.DisallowedInContext, /* early */ 1, 'yield');
}
}
export function finishNode<T extends Node>(node: T): T {
return node;
}