license import * as doctrine from 'doctrine' ; import * as ts from './ts-ast' ; const { parseType , parseParamType } = require ( 'doctrine/lib/typed.js' ) ; /**
 * Convert a Closure type expression string to its equivalent TypeScript AST
 * node.
 *
 * Note that function and method parameters should instead use
 * `closureParamToTypeScript`.
 */ export function closureTypeToTypeScript ( closureType , templateTypes = [ ] ) { if ( ! closureType ) { return ts . anyType ; } let ast ; try { ast = parseType ( closureType ) ; } catch { return ts . anyType ; } return convert ( ast , templateTypes ) ; } /**
 * Convert a Closure function or method parameter type expression string to its
 * equivalent TypeScript AST node.
 *
 * This differs from `closureTypeToTypeScript` in that it always returns a
 * `ParamType`, and can parse the optional (`foo=`) and rest (`...foo`)
 * syntaxes, which only apply when parsing an expression in the context of a
 * parameter.
 */ export function closureParamToTypeScript ( name , closureType , templateTypes = [ ] ) { if ( ! closureType ) { return new ts . ParamType ( { name , type : ts . anyType , optional : false , rest : false , } ) ; } let ast ; try { ast = parseParamType ( closureType ) ; } catch { return new ts . ParamType ( { name , type : ts . anyType , optional : closureType . endsWith ( '=' ) , rest : false , } ) ; } return convertParam ( name , ast , templateTypes ) ; } /**
 * Convert a doctrine function or method parameter AST node to its equivalent
 * TypeScript parameter AST node.
 */ function convertParam ( name , node , templateTypes ) { switch ( node . type ) { case 'OptionalType' : return new ts . ParamType ( { name , type : convert ( node . expression , templateTypes ) , optional : true , rest : false , } ) ; case 'RestType' : return new ts . ParamType ( { name , type : new ts . ArrayType ( node . expression !== undefined ? convert ( node . expression , templateTypes ) : ts . anyType ) , optional : false , rest : true , } ) ; default : return new ts . ParamType ( { name , type : convert ( node , templateTypes ) , optional : false , rest : false , } ) ; } } /**
 * Convert a doctrine AST node to its equivalent TypeScript AST node.
 */ function convert ( node , templateTypes ) { let nullable ; if ( isNullable ( node ) ) { nullable = true ; node = node . expression ; } else if ( isNonNullable ( node ) ) { nullable = false ; node = node . expression ; } else if ( isName ( node ) && templateTypes . includes ( node . name ) ) { nullable = false ; } else { nullable = nullableByDefault ( node ) ; } let t ; if ( isParameterizedArray ( node ) ) { t = convertArray ( node , templateTypes ) ; } else if ( isParameterizedObject ( node ) ) { t = convertIndexableObject ( node , templateTypes ) ; } else if ( isBarePromise ( node ) ) { t = new ts . ParameterizedType ( 'Promise' , [ ts . anyType ] ) ; } else if ( isParameterizedType ( node ) ) { t = convertParameterizedType ( node , templateTypes ) ; } else if ( isUnion ( node ) ) { t = convertUnion ( node , templateTypes ) ; } else if ( isFunction ( node ) ) { t = convertFunction ( node , templateTypes ) ; } else if ( isBareArray ( node ) ) { t = new ts . ArrayType ( ts . anyType ) ; } else if ( isRecordType ( node ) ) { t = convertRecord ( node , templateTypes ) ; } else if ( isAllLiteral ( node ) ) { t = ts . anyType ; } else if ( isNullableLiteral ( node ) ) { t = ts . anyType ; } else if ( isNullLiteral ( node ) ) { t = ts . nullType ; } else if ( isUndefinedLiteral ( node ) ) { t = ts . undefinedType ; } else if ( isVoidLiteral ( node ) ) { t = new ts . NameType ( 'void' ) ; } else if ( isName ( node ) ) { t = new ts . NameType ( renameMap . get ( node . name ) || node . name ) ; } else { console . error ( 'Unknown syntax.' ) ; return ts . anyType ; } if ( nullable ) { t = new ts . UnionType ( [ t , ts . nullType ] ) ; } return t ; } /**
 * Special cases where a named type in Closure maps to something different in
 * TypeScript.
 */ const renameMap = new Map < string , string > ( [ [ 'Object' , 'object' ] , [ 'ITemplateArray' , 'TemplateStringsArray' ] , ] ) ; const parameterizedRenameMap = new Map < string , string > ( [ [ 'HTMLCollection' , 'HTMLCollectionOf' ] , [ 'NodeList' , 'NodeListOf' ] , ] ) ; /**
 * Return whether the given AST node is an expression that is nullable by
 * default in the Closure type system.
 */ function nullableByDefault ( node ) { if ( isName ( node ) ) { switch ( node . name ) { case 'string' : case 'number' : case 'boolean' : case 'void' : return false ; } return true ; } return isParameterizedArray ( node ) ; } function convertArray ( node , templateTypes ) { const applications = node . applications ; return new ts . ArrayType ( applications . length === 1 ? convert ( applications [ 0 ] , templateTypes ) : ts . anyType ) ; } function convertIndexableObject ( node , templateTypes ) { if ( node . applications . length !== 2 ) { console . error ( 'Parameterized Object must have two parameters.' ) ; return ts . anyType ; } return new ts . IndexableObjectType ( convert ( node . applications [ 0 ] , templateTypes ) , convert ( node . applications [ 1 ] , templateTypes ) ) ; } function convertParameterizedType ( node , templateTypes ) { if ( ! isName ( node . expression ) ) { console . error ( 'Could not find name of parameterized type' ) ; return ts . anyType ; } const types = node . applications . map ( ( application ) => convert ( application , templateTypes ) ) ; const name = renameMap . get ( node . expression . name ) || parameterizedRenameMap . get ( node . expression . name ) || node . expression . name ; return new ts . ParameterizedType ( name , types ) ; } function convertUnion ( node , templateTypes ) { return new ts . UnionType ( node . elements . map ( ( element ) => convert ( element , templateTypes ) ) ) ; } function convertFunction ( node , templateTypes ) { const params = node . params . map ( ( param , idx ) => convertParam ( 'p' + idx , param , templateTypes ) ) ; if ( node . new ) { return new ts . ConstructorType ( params , isName ( node . this ) ? new ts . NameType ( node . this . name ) : ts . anyType ) ; } else { return new ts . FunctionType ( params , node . result ? convert ( node . result as { } as doctrine . Type , templateTypes ) : ts . anyType ) ; } } function convertRecord ( node , templateTypes ) { const fields = [ ] ; for ( const field of node . fields ) { if ( field . type !== 'FieldType' ) { return ts . anyType ; } const fieldType = field . value ? convert ( field . value , templateTypes ) : ts . anyType ; let optional = false ; if ( fieldType . kind === 'union' ) { fieldType . members = fieldType . members . filter ( ( member ) => { if ( member . kind === 'name' && member . name === 'undefined' ) { optional = true ; return false ; } return true ; } ) ; fieldType . simplify ( ) ; } fields . push ( new ts . ParamType ( { name : field . key , type : fieldType , optional } ) ) ; } return new ts . RecordType ( fields ) ; } function isParameterizedArray ( node ) { return node . type === 'TypeApplication' && node . expression . type === 'NameExpression' && node . expression . name === 'Array' ; } function isParameterizedType ( node ) { return node . type === 'TypeApplication' ; } function isBareArray ( node ) { return node . type === 'NameExpression' && node . name === 'Array' ; } function isBarePromise ( node ) { return node . type === 'NameExpression' && node . name === 'Promise' ; } /**
 * Matches `Object<foo, bar>` but not `Object` (which is a NameExpression).
 */ function isParameterizedObject ( node ) { return node . type === 'TypeApplication' && node . expression . type === 'NameExpression' && node . expression . name === 'Object' ; } function isUnion ( node ) { return node . type === 'UnionType' ; } function isFunction ( node ) { return node . type === 'FunctionType' ; } function isRecordType ( node ) { return node . type === 'RecordType' ; } function isNullable ( node ) { return node . type === 'NullableType' ; } function isNonNullable ( node ) { return node . type === 'NonNullableType' ; } function isAllLiteral ( node ) { return node . type === 'AllLiteral' ; } function isNullLiteral ( node ) { return node . type === 'NullLiteral' ; } function isNullableLiteral ( node ) { return node . type === 'NullableLiteral' ; } function isUndefinedLiteral ( node ) { return node . type === 'UndefinedLiteral' ; } function isVoidLiteral ( node ) { return node . type === 'VoidLiteral' ; } function isName ( node ) { return node . type === 'NameExpression' ; }