UNPKG

buntis

Version:

A 100% compliant, self-hosted typescript parser that emits an ESTree-compatible abstract syntax tree

463 lines (423 loc) 13.4 kB
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; }