import { SucraseContext , } from "../index" ; import { ContextualKeyword } from "../parser/tokenizer/keywords" ; import { TokenType as tt } from "../parser/tokenizer/types" ; import TokenProcessor from "../TokenProcessor" ; import TypeScriptTransformer from "./TypeScriptTransformer" ; export default class RootTransformer { private transformer : TypeScriptTransformer | null ; private tokens : TokenProcessor ; constructor ( sucraseContext ) { const tokenProcessor = sucraseContext . tokenProcessor ; this . tokens = tokenProcessor ; this . transformer = new TypeScriptTransformer ( this , tokenProcessor ) ; } transform ( ) { this . tokens . reset ( ) ; this . processBalancedCode ( ) ; return this . tokens . finish ( ) ; } processBalancedCode ( ) { let braceDepth = 0 ; let parenDepth = 0 ; while ( ! this . tokens . isAtEnd ( ) ) { if ( this . tokens . matches1 ( tt . braceL ) || this . tokens . matches1 ( tt . dollarBraceL ) ) { braceDepth ++ ; } else if ( this . tokens . matches1 ( tt . braceR ) ) { if ( braceDepth === 0 ) { return ; } braceDepth -- ; } if ( this . tokens . matches1 ( tt . parenL ) ) { parenDepth ++ ; } else if ( this . tokens . matches1 ( tt . parenR ) ) { if ( parenDepth === 0 ) { return ; } parenDepth -- ; } this . processToken ( ) ; } } processToken ( ) { if ( this . tokens . matches1 ( tt . _class ) ) { throw new Error ( 'classes not implemented' ) ; } const transformer = this . transformer ; if ( transformer === null ) { throw new Error ( `Transformer was not initialised` ) ; } const wasProcessed = transformer . process ( ) ; if ( wasProcessed ) { return ; } this . tokens . copyToken ( ) ; } /**
     * Skip past a class with a name and return that name.
     */ /**
     * We want to just handle class fields in all contexts, since TypeScript supports them. Later,
     * when some JS implementations support class fields, this should be made optional.
     */ makeConstructorInitCode ( constructorInitializerStatements , instanceInitializerNames , className ) { return [ ... constructorInitializerStatements , ... instanceInitializerNames . map ( ( name ) => `${ className }.prototype.${ name }.call(this)` ) , ] . join ( ";" ) ; } /**
     * Normally it's ok to simply remove type tokens, but we need to be more careful when dealing with
     * arrow function return types since they can confuse the parser. In that case, we want to move
     * the close-paren to the same line as the arrow.
     *
     * See https://github.com/alangpierce/sucrase/issues/391 for more details.
     */ processPossibleArrowParamEnd ( ) { if ( this . tokens . matches2 ( tt . parenR , tt . colon ) && this . tokens . tokenAtRelativeIndex ( 1 ) . isType ) { let nextNonTypeIndex = this . tokens . currentIndex ( ) + 1 ; while ( this . tokens . tokens [ nextNonTypeIndex ] . isType ) { nextNonTypeIndex ++ ; } if ( this . tokens . matches1AtIndex ( nextNonTypeIndex , tt . arrow ) ) { this . tokens . removeInitialToken ( ) ; while ( this . tokens . currentIndex ( ) < nextNonTypeIndex ) { this . tokens . removeToken ( ) ; } this . tokens . replaceTokenTrimmingLeftWhitespace ( ") =>" ) ; return true ; } } return false ; } /**
     * An async arrow function might be of the form:
     *
     * async <
     *   T
     * >() => {}
     *
     * in which case, removing the type parameters will cause a syntax error. Detect this case and
     * move the open-paren earlier.
     */ processPossibleAsyncArrowWithTypeParams ( ) { if ( ! this . tokens . matchesContextual ( ContextualKeyword . _async ) && ! this . tokens . matches1 ( tt . _async ) ) { return false ; } const nextToken = this . tokens . tokenAtRelativeIndex ( 1 ) ; if ( nextToken . type !== tt . lessThan || ! nextToken . isType ) { return false ; } let nextNonTypeIndex = this . tokens . currentIndex ( ) + 1 ; while ( this . tokens . tokens [ nextNonTypeIndex ] . isType ) { nextNonTypeIndex ++ ; } if ( this . tokens . matches1AtIndex ( nextNonTypeIndex , tt . parenL ) ) { this . tokens . replaceToken ( "async (" ) ; this . tokens . removeInitialToken ( ) ; while ( this . tokens . currentIndex ( ) < nextNonTypeIndex ) { this . tokens . removeToken ( ) ; } this . tokens . removeToken ( ) ; this . processBalancedCode ( ) ; this . processToken ( ) ; return true ; } return false ; } processPossibleTypeRange ( ) { if ( this . tokens . currentToken ( ) . isType ) { this . tokens . removeInitialToken ( ) ; while ( this . tokens . currentToken ( ) . isType ) { this . tokens . removeToken ( ) ; } return true ; } return false ; } }