/* eslint max-len: 0 */ import { eat, lookaheadType, lookaheadTypeAndKeyword, match, next, popTypeContext, pushTypeContext, } from "../tokenizer/index"; import {ContextualKeyword} from "../tokenizer/keywords"; import {TokenType, TokenType as tt} from "../tokenizer/types"; import {input, state} from "../traverser/base"; import { baseParseMaybeAssign, baseParseSubscript, baseParseSubscripts, parseArrow, parseArrowExpression, parseCallExpressionArguments, parseExprAtom, parseExpression, parseFunctionBody, parseIdentifier, parseLiteral, } from "../traverser/expression"; import { baseParseExportStar, parseExport, parseExportFrom, parseExportSpecifiers, parseFunctionParams, parseImport, parseStatement, } from "../traverser/statement"; import { canInsertSemicolon, eatContextual, expect, expectContextual, isContextual, isLookaheadContextual, semicolon, unexpected, } from "../traverser/util"; function isMaybeDefaultImport(lookahead) { return ( (lookahead.type === tt.name || !!(lookahead.type & TokenType.IS_KEYWORD)) && lookahead.contextualKeyword !== ContextualKeyword._from ); } function flowParseTypeInitialiser(tok) { const oldIsType = pushTypeContext(0); expect(tok || tt.colon); flowParseType(); popTypeContext(oldIsType); } function flowParsePredicate() { expect(tt.modulo); expectContextual(ContextualKeyword._checks); if (eat(tt.parenL)) { parseExpression(); expect(tt.parenR); } } function flowParseTypeAndPredicateInitialiser() { const oldIsType = pushTypeContext(0); expect(tt.colon); if (match(tt.modulo)) { flowParsePredicate(); } else { flowParseType(); if (match(tt.modulo)) { flowParsePredicate(); } } popTypeContext(oldIsType); } function flowParseDeclareClass() { next(); flowParseInterfaceish(/* isClass */ true); } function flowParseDeclareFunction() { next(); parseIdentifier(); if (match(tt.lessThan)) { flowParseTypeParameterDeclaration(); } expect(tt.parenL); flowParseFunctionTypeParams(); expect(tt.parenR); flowParseTypeAndPredicateInitialiser(); semicolon(); } function flowParseDeclare() { if (match(tt._class)) { flowParseDeclareClass(); } else if (match(tt._function)) { flowParseDeclareFunction(); } else if (match(tt._var)) { flowParseDeclareVariable(); } else if (eatContextual(ContextualKeyword._module)) { if (eat(tt.dot)) { flowParseDeclareModuleExports(); } else { flowParseDeclareModule(); } } else if (isContextual(ContextualKeyword._type)) { flowParseDeclareTypeAlias(); } else if (isContextual(ContextualKeyword._opaque)) { flowParseDeclareOpaqueType(); } else if (isContextual(ContextualKeyword._interface)) { flowParseDeclareInterface(); } else if (match(tt._export)) { flowParseDeclareExportDeclaration(); } else { unexpected(); } } function flowParseDeclareVariable() { next(); flowParseTypeAnnotatableIdentifier(); semicolon(); } function flowParseDeclareModule() { if (match(tt.string)) { parseExprAtom(); } else { parseIdentifier(); } expect(tt.braceL); while (!match(tt.braceR) && !state.error) { if (match(tt._import)) { next(); parseImport(); } else { unexpected(); } } expect(tt.braceR); } function flowParseDeclareExportDeclaration() { expect(tt._export); if (eat(tt._default)) { if (match(tt._function) || match(tt._class)) { // declare export default class ... // declare export default function ... flowParseDeclare(); } else { // declare export default [type]; flowParseType(); semicolon(); } } else if ( match(tt._var) || // declare export var ... match(tt._function) || // declare export function ... match(tt._class) || // declare export class ... isContextual(ContextualKeyword._opaque) // declare export opaque .. ) { flowParseDeclare(); } else if ( match(tt.star) || // declare export * from '' match(tt.braceL) || // declare export {} ... isContextual(ContextualKeyword._interface) || // declare export interface ... isContextual(ContextualKeyword._type) || // declare export type ... isContextual(ContextualKeyword._opaque) // declare export opaque type ... ) { parseExport(); } else { unexpected(); } } function flowParseDeclareModuleExports() { expectContextual(ContextualKeyword._exports); flowParseTypeAnnotation(); semicolon(); } function flowParseDeclareTypeAlias() { next(); flowParseTypeAlias(); } function flowParseDeclareOpaqueType() { next(); flowParseOpaqueType(true); } function flowParseDeclareInterface() { next(); flowParseInterfaceish(); } // Interfaces function flowParseInterfaceish(isClass = false) { flowParseRestrictedIdentifier(); if (match(tt.lessThan)) { flowParseTypeParameterDeclaration(); } if (eat(tt._extends)) { do { flowParseInterfaceExtends(); } while (!isClass && eat(tt.comma)); } if (isContextual(ContextualKeyword._mixins)) { next(); do { flowParseInterfaceExtends(); } while (eat(tt.comma)); } if (isContextual(ContextualKeyword._implements)) { next(); do { flowParseInterfaceExtends(); } while (eat(tt.comma)); } flowParseObjectType(isClass, false, isClass); } function flowParseInterfaceExtends() { flowParseQualifiedTypeIdentifier(false); if (match(tt.lessThan)) { flowParseTypeParameterInstantiation(); } } function flowParseInterface() { flowParseInterfaceish(); } function flowParseRestrictedIdentifier() { parseIdentifier(); } function flowParseTypeAlias() { flowParseRestrictedIdentifier(); if (match(tt.lessThan)) { flowParseTypeParameterDeclaration(); } flowParseTypeInitialiser(tt.eq); semicolon(); } function flowParseOpaqueType(declare) { expectContextual(ContextualKeyword._type); flowParseRestrictedIdentifier(); if (match(tt.lessThan)) { flowParseTypeParameterDeclaration(); } // Parse the supertype if (match(tt.colon)) { flowParseTypeInitialiser(tt.colon); } if (!declare) { flowParseTypeInitialiser(tt.eq); } semicolon(); } function flowParseTypeParameter() { flowParseVariance(); flowParseTypeAnnotatableIdentifier(); if (eat(tt.eq)) { flowParseType(); } } export function flowParseTypeParameterDeclaration() { const oldIsType = pushTypeContext(0); // istanbul ignore else: this condition is already checked at all call sites if (match(tt.lessThan) || match(tt.typeParameterStart)) { next(); } else { unexpected(); } do { flowParseTypeParameter(); if (!match(tt.greaterThan)) { expect(tt.comma); } } while (!match(tt.greaterThan) && !state.error); expect(tt.greaterThan); popTypeContext(oldIsType); } function flowParseTypeParameterInstantiation() { const oldIsType = pushTypeContext(0); expect(tt.lessThan); while (!match(tt.greaterThan) && !state.error) { flowParseType(); if (!match(tt.greaterThan)) { expect(tt.comma); } } expect(tt.greaterThan); popTypeContext(oldIsType); } function flowParseInterfaceType() { expectContextual(ContextualKeyword._interface); if (eat(tt._extends)) { do { flowParseInterfaceExtends(); } while (eat(tt.comma)); } flowParseObjectType(false, false, false); } function flowParseObjectPropertyKey() { if (match(tt.num) || match(tt.string)) { parseExprAtom(); } else { parseIdentifier(); } } function flowParseObjectTypeIndexer() { // Note: bracketL has already been consumed if (lookaheadType() === tt.colon) { flowParseObjectPropertyKey(); flowParseTypeInitialiser(); } else { flowParseType(); } expect(tt.bracketR); flowParseTypeInitialiser(); } function flowParseObjectTypeInternalSlot() { // Note: both bracketL have already been consumed flowParseObjectPropertyKey(); expect(tt.bracketR); expect(tt.bracketR); if (match(tt.lessThan) || match(tt.parenL)) { flowParseObjectTypeMethodish(); } else { eat(tt.question); flowParseTypeInitialiser(); } } function flowParseObjectTypeMethodish() { if (match(tt.lessThan)) { flowParseTypeParameterDeclaration(); } expect(tt.parenL); while (!match(tt.parenR) && !match(tt.ellipsis) && !state.error) { flowParseFunctionTypeParam(); if (!match(tt.parenR)) { expect(tt.comma); } } if (eat(tt.ellipsis)) { flowParseFunctionTypeParam(); } expect(tt.parenR); flowParseTypeInitialiser(); } function flowParseObjectTypeCallProperty() { flowParseObjectTypeMethodish(); } function flowParseObjectType(allowStatic, allowExact, allowProto) { let endDelim; if (allowExact && match(tt.braceBarL)) { expect(tt.braceBarL); endDelim = tt.braceBarR; } else { expect(tt.braceL); endDelim = tt.braceR; } while (!match(endDelim) && !state.error) { if (allowProto && isContextual(ContextualKeyword._proto)) { const lookahead = lookaheadType(); if (lookahead !== tt.colon && lookahead !== tt.question) { next(); allowStatic = false; } } if (allowStatic && isContextual(ContextualKeyword._static)) { const lookahead = lookaheadType(); if (lookahead !== tt.colon && lookahead !== tt.question) { next(); } } flowParseVariance(); if (eat(tt.bracketL)) { if (eat(tt.bracketL)) { flowParseObjectTypeInternalSlot(); } else { flowParseObjectTypeIndexer(); } } else if (match(tt.parenL) || match(tt.lessThan)) { flowParseObjectTypeCallProperty(); } else { if (isContextual(ContextualKeyword._get) || isContextual(ContextualKeyword._set)) { const lookahead = lookaheadType(); if (lookahead === tt.name || lookahead === tt.string || lookahead === tt.num) { next(); } } flowParseObjectTypeProperty(); } flowObjectTypeSemicolon(); } expect(endDelim); } function flowParseObjectTypeProperty() { if (match(tt.ellipsis)) { expect(tt.ellipsis); if (!eat(tt.comma)) { eat(tt.semi); } // Explicit inexact object syntax. if (match(tt.braceR)) { return; } flowParseType(); } else { flowParseObjectPropertyKey(); if (match(tt.lessThan) || match(tt.parenL)) { // This is a method property flowParseObjectTypeMethodish(); } else { eat(tt.question); flowParseTypeInitialiser(); } } } function flowObjectTypeSemicolon() { if (!eat(tt.semi) && !eat(tt.comma) && !match(tt.braceR) && !match(tt.braceBarR)) { unexpected(); } } function flowParseQualifiedTypeIdentifier(initialIdAlreadyParsed) { if (!initialIdAlreadyParsed) { parseIdentifier(); } while (eat(tt.dot)) { parseIdentifier(); } } function flowParseGenericType() { flowParseQualifiedTypeIdentifier(true); if (match(tt.lessThan)) { flowParseTypeParameterInstantiation(); } } function flowParseTypeofType() { expect(tt._typeof); flowParsePrimaryType(); } function flowParseTupleType() { expect(tt.bracketL); // We allow trailing commas while (state.pos < input.length && !match(tt.bracketR)) { flowParseType(); if (match(tt.bracketR)) { break; } expect(tt.comma); } expect(tt.bracketR); } function flowParseFunctionTypeParam() { const lookahead = lookaheadType(); if (lookahead === tt.colon || lookahead === tt.question) { parseIdentifier(); eat(tt.question); flowParseTypeInitialiser(); } else { flowParseType(); } } function flowParseFunctionTypeParams() { while (!match(tt.parenR) && !match(tt.ellipsis) && !state.error) { flowParseFunctionTypeParam(); if (!match(tt.parenR)) { expect(tt.comma); } } if (eat(tt.ellipsis)) { flowParseFunctionTypeParam(); } } // The parsing of types roughly parallels the parsing of expressions, and // primary types are kind of like primary expressions...they're the // primitives with which other types are constructed. function flowParsePrimaryType() { let isGroupedType = false; const oldNoAnonFunctionType = state.noAnonFunctionType; switch (state.type) { case tt.name: { if (isContextual(ContextualKeyword._interface)) { flowParseInterfaceType(); return; } parseIdentifier(); flowParseGenericType(); return; } case tt.braceL: flowParseObjectType(false, false, false); return; case tt.braceBarL: flowParseObjectType(false, true, false); return; case tt.bracketL: flowParseTupleType(); return; case tt.lessThan: flowParseTypeParameterDeclaration(); expect(tt.parenL); flowParseFunctionTypeParams(); expect(tt.parenR); expect(tt.arrow); flowParseType(); return; case tt.parenL: next(); // Check to see if this is actually a grouped type if (!match(tt.parenR) && !match(tt.ellipsis)) { if (match(tt.name)) { const token = lookaheadType(); isGroupedType = token !== tt.question && token !== tt.colon; } else { isGroupedType = true; } } if (isGroupedType) { state.noAnonFunctionType = false; flowParseType(); state.noAnonFunctionType = oldNoAnonFunctionType; // A `,` or a `) =>` means this is an anonymous function type if ( state.noAnonFunctionType || !(match(tt.comma) || (match(tt.parenR) && lookaheadType() === tt.arrow)) ) { expect(tt.parenR); return; } else { // Eat a comma if there is one eat(tt.comma); } } flowParseFunctionTypeParams(); expect(tt.parenR); expect(tt.arrow); flowParseType(); return; case tt.minus: next(); parseLiteral(); return; case tt.string: case tt.num: case tt._true: case tt._false: case tt._null: case tt._this: case tt._void: case tt.star: next(); return; default: if (state.type === tt._typeof) { flowParseTypeofType(); return; } else if (state.type & TokenType.IS_KEYWORD) { next(); state.tokens[state.tokens.length - 1].type = tt.name; return; } } unexpected(); } function flowParsePostfixType() { flowParsePrimaryType(); while (!canInsertSemicolon() && (match(tt.bracketL) || match(tt.questionDot))) { eat(tt.questionDot); expect(tt.bracketL); if (eat(tt.bracketR)) { // Array type } else { // Indexed access type flowParseType(); expect(tt.bracketR); } } } function flowParsePrefixType() { if (eat(tt.question)) { flowParsePrefixType(); } else { flowParsePostfixType(); } } function flowParseAnonFunctionWithoutParens() { flowParsePrefixType(); if (!state.noAnonFunctionType && eat(tt.arrow)) { flowParseType(); } } function flowParseIntersectionType() { eat(tt.bitwiseAND); flowParseAnonFunctionWithoutParens(); while (eat(tt.bitwiseAND)) { flowParseAnonFunctionWithoutParens(); } } function flowParseUnionType() { eat(tt.bitwiseOR); flowParseIntersectionType(); while (eat(tt.bitwiseOR)) { flowParseIntersectionType(); } } function flowParseType() { flowParseUnionType(); } export function flowParseTypeAnnotation() { flowParseTypeInitialiser(); } function flowParseTypeAnnotatableIdentifier() { parseIdentifier(); if (match(tt.colon)) { flowParseTypeAnnotation(); } } export function flowParseVariance() { if (match(tt.plus) || match(tt.minus)) { next(); state.tokens[state.tokens.length - 1].isType = true; } } // ================================== // Overrides // ================================== export function flowParseFunctionBodyAndFinish(funcContextId) { // For arrow functions, `parseArrow` handles the return type itself. if (match(tt.colon)) { flowParseTypeAndPredicateInitialiser(); } parseFunctionBody(false, funcContextId); } export function flowParseSubscript( startTokenIndex, noCalls, stopState, ) { if (match(tt.questionDot) && lookaheadType() === tt.lessThan) { if (noCalls) { stopState.stop = true; return; } next(); flowParseTypeParameterInstantiation(); expect(tt.parenL); parseCallExpressionArguments(); return; } else if (!noCalls && match(tt.lessThan)) { const snapshot = state.snapshot(); flowParseTypeParameterInstantiation(); expect(tt.parenL); parseCallExpressionArguments(); if (state.error) { state.restoreFromSnapshot(snapshot); } else { return; } } baseParseSubscript(startTokenIndex, noCalls, stopState); } export function flowStartParseNewArguments() { if (match(tt.lessThan)) { const snapshot = state.snapshot(); flowParseTypeParameterInstantiation(); if (state.error) { state.restoreFromSnapshot(snapshot); } } } // interfaces export function flowTryParseStatement() { if (match(tt.name) && state.contextualKeyword === ContextualKeyword._interface) { const oldIsType = pushTypeContext(0); next(); flowParseInterface(); popTypeContext(oldIsType); return true; } else { return false; } } // declares, interfaces and type aliases export function flowParseIdentifierStatement(contextualKeyword) { if (contextualKeyword === ContextualKeyword._declare) { if ( match(tt._class) || match(tt.name) || match(tt._function) || match(tt._var) || match(tt._export) ) { const oldIsType = pushTypeContext(1); flowParseDeclare(); popTypeContext(oldIsType); } } else if (match(tt.name)) { if (contextualKeyword === ContextualKeyword._interface) { const oldIsType = pushTypeContext(1); flowParseInterface(); popTypeContext(oldIsType); } else if (contextualKeyword === ContextualKeyword._type) { const oldIsType = pushTypeContext(1); flowParseTypeAlias(); popTypeContext(oldIsType); } else if (contextualKeyword === ContextualKeyword._opaque) { const oldIsType = pushTypeContext(1); flowParseOpaqueType(false); popTypeContext(oldIsType); } } semicolon(); } // export type export function flowShouldParseExportDeclaration() { return ( isContextual(ContextualKeyword._type) || isContextual(ContextualKeyword._interface) || isContextual(ContextualKeyword._opaque) ); } export function flowShouldDisallowExportDefaultSpecifier() { return ( match(tt.name) && (state.contextualKeyword === ContextualKeyword._type || state.contextualKeyword === ContextualKeyword._interface || state.contextualKeyword === ContextualKeyword._opaque) ); } export function flowParseExportDeclaration() { if (isContextual(ContextualKeyword._type)) { const oldIsType = pushTypeContext(1); next(); if (match(tt.braceL)) { // export type { foo, bar }; parseExportSpecifiers(); parseExportFrom(); } else { // export type Foo = Bar; flowParseTypeAlias(); } popTypeContext(oldIsType); } else if (isContextual(ContextualKeyword._opaque)) { const oldIsType = pushTypeContext(1); next(); // export opaque type Foo = Bar; flowParseOpaqueType(false); popTypeContext(oldIsType); } else if (isContextual(ContextualKeyword._interface)) { const oldIsType = pushTypeContext(1); next(); flowParseInterface(); popTypeContext(oldIsType); } else { parseStatement(true); } } export function flowShouldParseExportStar() { return match(tt.star) || (isContextual(ContextualKeyword._type) && lookaheadType() === tt.star); } export function flowParseExportStar() { if (eatContextual(ContextualKeyword._type)) { const oldIsType = pushTypeContext(2); baseParseExportStar(); popTypeContext(oldIsType); } else { baseParseExportStar(); } } // parse a the super class type parameters and implements export function flowAfterParseClassSuper(hasSuper) { if (hasSuper && match(tt.lessThan)) { flowParseTypeParameterInstantiation(); } if (isContextual(ContextualKeyword._implements)) { const oldIsType = pushTypeContext(0); next(); state.tokens[state.tokens.length - 1].type = tt._implements; do { flowParseRestrictedIdentifier(); if (match(tt.lessThan)) { flowParseTypeParameterInstantiation(); } } while (eat(tt.comma)); popTypeContext(oldIsType); } } // parse type parameters for object method shorthand export function flowStartParseObjPropValue() { // method shorthand if (match(tt.lessThan)) { flowParseTypeParameterDeclaration(); if (!match(tt.parenL)) unexpected(); } } export function flowParseAssignableListItemTypes() { const oldIsType = pushTypeContext(0); eat(tt.question); if (match(tt.colon)) { flowParseTypeAnnotation(); } popTypeContext(oldIsType); } // parse typeof and type imports export function flowStartParseImportSpecifiers() { if (match(tt._typeof) || isContextual(ContextualKeyword._type)) { const lh = lookaheadTypeAndKeyword(); if (isMaybeDefaultImport(lh) || lh.type === tt.braceL || lh.type === tt.star) { next(); } } } // parse import-type/typeof shorthand export function flowParseImportSpecifier() { const isTypeKeyword = state.contextualKeyword === ContextualKeyword._type || state.type === tt._typeof; if (isTypeKeyword) { next(); } else { parseIdentifier(); } if (isContextual(ContextualKeyword._as) && !isLookaheadContextual(ContextualKeyword._as)) { parseIdentifier(); if (isTypeKeyword && !match(tt.name) && !(state.type & TokenType.IS_KEYWORD)) { // `import {type as ,` or `import {type as }` } else { // `import {type as foo` parseIdentifier(); } } else if (isTypeKeyword && (match(tt.name) || !!(state.type & TokenType.IS_KEYWORD))) { // `import {type foo` parseIdentifier(); if (eatContextual(ContextualKeyword._as)) { parseIdentifier(); } } } // parse function type parameters - function foo() {} export function flowStartParseFunctionParams() { // Originally this checked if the method is a getter/setter, but if it was, we'd crash soon // anyway, so don't try to propagate that information. if (match(tt.lessThan)) { const oldIsType = pushTypeContext(0); flowParseTypeParameterDeclaration(); popTypeContext(oldIsType); } } // parse flow type annotations on variable declarator heads - let foo: string = bar export function flowAfterParseVarHead() { if (match(tt.colon)) { flowParseTypeAnnotation(); } } // parse the return type of an async arrow function - let foo = (async (): number => {}); export function flowStartParseAsyncArrowFromCallExpression() { if (match(tt.colon)) { const oldNoAnonFunctionType = state.noAnonFunctionType; state.noAnonFunctionType = true; flowParseTypeAnnotation(); state.noAnonFunctionType = oldNoAnonFunctionType; } } // We need to support type parameter declarations for arrow functions. This // is tricky. There are three situations we need to handle // // 1. This is either JSX or an arrow function. We'll try JSX first. If that // fails, we'll try an arrow function. If that fails, we'll throw the JSX // error. // 2. This is an arrow function. We'll parse the type parameter declaration, // parse the rest, make sure the rest is an arrow function, and go from // there // 3. This is neither. Just call the super method export function flowParseMaybeAssign(noIn, isWithinParens) { if (match(tt.lessThan)) { const snapshot = state.snapshot(); let wasArrow = baseParseMaybeAssign(noIn, isWithinParens); if (state.error) { state.restoreFromSnapshot(snapshot); state.type = tt.typeParameterStart; } else { return wasArrow; } const oldIsType = pushTypeContext(0); flowParseTypeParameterDeclaration(); popTypeContext(oldIsType); wasArrow = baseParseMaybeAssign(noIn, isWithinParens); if (wasArrow) { return true; } unexpected(); } return baseParseMaybeAssign(noIn, isWithinParens); } // handle return types for arrow functions export function flowParseArrow() { if (match(tt.colon)) { const oldIsType = pushTypeContext(0); const snapshot = state.snapshot(); const oldNoAnonFunctionType = state.noAnonFunctionType; state.noAnonFunctionType = true; flowParseTypeAndPredicateInitialiser(); state.noAnonFunctionType = oldNoAnonFunctionType; if (canInsertSemicolon()) unexpected(); if (!match(tt.arrow)) unexpected(); if (state.error) { state.restoreFromSnapshot(snapshot); } popTypeContext(oldIsType); } return eat(tt.arrow); } export function flowParseSubscripts(startTokenIndex, noCalls = false) { if ( state.tokens[state.tokens.length - 1].contextualKeyword === ContextualKeyword._async && match(tt.lessThan) ) { const snapshot = state.snapshot(); const wasArrow = parseAsyncArrowWithTypeParameters(); if (wasArrow && !state.error) { return; } state.restoreFromSnapshot(snapshot); } baseParseSubscripts(startTokenIndex, noCalls); } // Returns true if there was an arrow function here. function parseAsyncArrowWithTypeParameters() { state.scopeDepth++; const startTokenIndex = state.tokens.length; parseFunctionParams(); if (!parseArrow()) { return false; } parseArrowExpression(startTokenIndex); return true; }