namespace ts . codefix { const fixId = "inferFromUsage" ; const errorCodes = [ Diagnostics . Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined . code , Diagnostics . Variable_0_implicitly_has_an_1_type . code , Diagnostics . Parameter_0_implicitly_has_an_1_type . code , Diagnostics . Rest_parameter_0_implicitly_has_an_any_type . code , Diagnostics . Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation . code , Diagnostics . _0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type . code , Diagnostics . Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation . code , Diagnostics . Member_0_implicitly_has_an_1_type . code , Diagnostics . Variable_0_implicitly_has_type_1_in_some_locations_but_a_better_type_may_be_inferred_from_usage . code , Diagnostics . Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage . code , Diagnostics . Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage . code , Diagnostics . Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage . code , Diagnostics . Property_0_implicitly_has_type_any_but_a_better_type_for_its_get_accessor_may_be_inferred_from_usage . code , Diagnostics . _0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage . code , Diagnostics . Property_0_implicitly_has_type_any_but_a_better_type_for_its_set_accessor_may_be_inferred_from_usage . code , Diagnostics . Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage . code , Diagnostics . this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation . code , ] ; registerCodeFix ( { errorCodes , getCodeActions ( context ) { const { sourceFile , program , span : { start } , errorCode , cancellationToken , host , preferences } = context ; const token = getTokenAtPosition ( sourceFile , start ) ; let declaration ; const changes = textChanges . ChangeTracker . with ( context , changes => { declaration = doChange ( changes , sourceFile , token , errorCode , program , cancellationToken , returnTrue , host , preferences ) ; } ) ; const name = declaration && getNameOfDeclaration ( declaration ) ; return ! name || changes . length === 0 ? undefined : [ createCodeFixAction ( fixId , changes , [ getDiagnostic ( errorCode , token ) , name . getText ( sourceFile ) ] , fixId , Diagnostics . Infer_all_types_from_usage ) ] ; } , fixIds : [ fixId ] , getAllCodeActions ( context ) { const { sourceFile , program , cancellationToken , host , preferences } = context ; const markSeen = nodeSeenTracker ( ) ; return codeFixAll ( context , errorCodes , ( changes , err ) => { doChange ( changes , sourceFile , getTokenAtPosition ( err . file , err . start ) , err . code , program , cancellationToken , markSeen , host , preferences ) ; } ) ; } , } ) ; function getDiagnostic ( errorCode , token ) { switch ( errorCode ) { case Diagnostics . Parameter_0_implicitly_has_an_1_type . code : case Diagnostics . Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage . code : return isSetAccessorDeclaration ( getContainingFunction ( token ) ! ) ? Diagnostics . Infer_type_of_0_from_usage : Diagnostics . Infer_parameter_types_from_usage ; case Diagnostics . Rest_parameter_0_implicitly_has_an_any_type . code : case Diagnostics . Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage . code : return Diagnostics . Infer_parameter_types_from_usage ; case Diagnostics . this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation . code : return Diagnostics . Infer_this_type_of_0_from_usage ; default : return Diagnostics . Infer_type_of_0_from_usage ; } } /** Map suggestion code to error code */ function mapSuggestionDiagnostic ( errorCode ) { switch ( errorCode ) { case Diagnostics . Variable_0_implicitly_has_type_1_in_some_locations_but_a_better_type_may_be_inferred_from_usage . code : return Diagnostics . Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined . code ; case Diagnostics . Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage . code : return Diagnostics . Variable_0_implicitly_has_an_1_type . code ; case Diagnostics . Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage . code : return Diagnostics . Parameter_0_implicitly_has_an_1_type . code ; case Diagnostics . Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage . code : return Diagnostics . Rest_parameter_0_implicitly_has_an_any_type . code ; case Diagnostics . Property_0_implicitly_has_type_any_but_a_better_type_for_its_get_accessor_may_be_inferred_from_usage . code : return Diagnostics . Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation . code ; case Diagnostics . _0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage . code : return Diagnostics . _0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type . code ; case Diagnostics . Property_0_implicitly_has_type_any_but_a_better_type_for_its_set_accessor_may_be_inferred_from_usage . code : return Diagnostics . Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation . code ; case Diagnostics . Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage . code : return Diagnostics . Member_0_implicitly_has_an_1_type . code ; } return errorCode ; } function doChange ( changes , sourceFile , token , errorCode , program , cancellationToken , markSeen , host , preferences ) { if ( ! isParameterPropertyModifier ( token . kind ) && token . kind !== SyntaxKind . Identifier && token . kind !== SyntaxKind . DotDotDotToken && token . kind !== SyntaxKind . ThisKeyword ) { return undefined ; } const { parent } = token ; const importAdder = createImportAdder ( sourceFile , program , preferences , host ) ; errorCode = mapSuggestionDiagnostic ( errorCode ) ; switch ( errorCode ) { case Diagnostics . Member_0_implicitly_has_an_1_type . code : case Diagnostics . Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined . code : if ( ( isVariableDeclaration ( parent ) && markSeen ( parent ) ) || isPropertyDeclaration ( parent ) || isPropertySignature ( parent ) ) { annotateVariableDeclaration ( changes , importAdder , sourceFile , parent , program , host , cancellationToken ) ; importAdder . writeFixes ( changes ) ; return parent ; } if ( isPropertyAccessExpression ( parent ) ) { const type = inferTypeForVariableFromUsage ( parent . name , program , cancellationToken ) ; const typeNode = getTypeNodeIfAccessible ( type , parent , program , host ) ; if ( typeNode ) { const typeTag = factory . createJSDocTypeTag ( undefined , factory . createJSDocTypeExpression ( typeNode ) , undefined ) ; changes . addJSDocTags ( sourceFile , cast ( parent . parent . parent , isExpressionStatement ) , [ typeTag ] ) ; } importAdder . writeFixes ( changes ) ; return parent ; } return undefined ; case Diagnostics . Variable_0_implicitly_has_an_1_type . code : { const symbol = program . getTypeChecker ( ) . getSymbolAtLocation ( token ) ; if ( symbol && symbol . valueDeclaration && isVariableDeclaration ( symbol . valueDeclaration ) && markSeen ( symbol . valueDeclaration ) ) { annotateVariableDeclaration ( changes , importAdder , sourceFile , symbol . valueDeclaration , program , host , cancellationToken ) ; importAdder . writeFixes ( changes ) ; return symbol . valueDeclaration ; } return undefined ; } } const containingFunction = getContainingFunction ( token ) ; if ( containingFunction === undefined ) { return undefined ; } let declaration ; switch ( errorCode ) { case Diagnostics . Parameter_0_implicitly_has_an_1_type . code : if ( isSetAccessorDeclaration ( containingFunction ) ) { annotateSetAccessor ( changes , importAdder , sourceFile , containingFunction , program , host , cancellationToken ) ; declaration = containingFunction ; break ; } case Diagnostics . Rest_parameter_0_implicitly_has_an_any_type . code : if ( markSeen ( containingFunction ) ) { const param = cast ( parent , isParameter ) ; annotateParameters ( changes , importAdder , sourceFile , param , containingFunction , program , host , cancellationToken ) ; declaration = param ; } break ; case Diagnostics . Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation . code : case Diagnostics . _0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type . code : if ( isGetAccessorDeclaration ( containingFunction ) && isIdentifier ( containingFunction . name ) ) { annotate ( changes , importAdder , sourceFile , containingFunction , inferTypeForVariableFromUsage ( containingFunction . name , program , cancellationToken ) , program , host ) ; declaration = containingFunction ; } break ; case Diagnostics . Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation . code : if ( isSetAccessorDeclaration ( containingFunction ) ) { annotateSetAccessor ( changes , importAdder , sourceFile , containingFunction , program , host , cancellationToken ) ; declaration = containingFunction ; } break ; case Diagnostics . this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation . code : if ( textChanges . isThisTypeAnnotatable ( containingFunction ) && markSeen ( containingFunction ) ) { annotateThis ( changes , sourceFile , containingFunction , program , host , cancellationToken ) ; declaration = containingFunction ; } break ; default : return Debug . fail ( String ( errorCode ) ) ; } importAdder . writeFixes ( changes ) ; return declaration ; } function annotateVariableDeclaration ( changes , importAdder , sourceFile , declaration , program , host , cancellationToken ) { if ( isIdentifier ( declaration . name ) ) { annotate ( changes , importAdder , sourceFile , declaration , inferTypeForVariableFromUsage ( declaration . name , program , cancellationToken ) , program , host ) ; } } function annotateParameters ( changes , importAdder , sourceFile , parameterDeclaration , containingFunction , program , host , cancellationToken ) { if ( ! isIdentifier ( parameterDeclaration . name ) ) { return ; } const parameterInferences = inferTypeForParametersFromUsage ( containingFunction , sourceFile , program , cancellationToken ) ; Debug . assert ( containingFunction . parameters . length === parameterInferences . length , "Parameter count and inference count should match" ) ; if ( isInJSFile ( containingFunction ) ) { annotateJSDocParameters ( changes , sourceFile , parameterInferences , program , host ) ; } else { const needParens = isArrowFunction ( containingFunction ) && ! findChildOfKind ( containingFunction , SyntaxKind . OpenParenToken , sourceFile ) ; if ( needParens ) changes . insertNodeBefore ( sourceFile , first ( containingFunction . parameters ) , factory . createToken ( SyntaxKind . OpenParenToken ) ) ; for ( const { declaration , type } of parameterInferences ) { if ( declaration && ! declaration . type && ! declaration . initializer ) { annotate ( changes , importAdder , sourceFile , declaration , type , program , host ) ; } } if ( needParens ) changes . insertNodeAfter ( sourceFile , last ( containingFunction . parameters ) , factory . createToken ( SyntaxKind . CloseParenToken ) ) ; } } function annotateThis ( changes , sourceFile , containingFunction , program , host , cancellationToken ) { const references = getFunctionReferences ( containingFunction , sourceFile , program , cancellationToken ) ; if ( ! references || ! references . length ) { return ; } const thisInference = inferTypeFromReferences ( program , references , cancellationToken ) . thisParameter ( ) ; const typeNode = getTypeNodeIfAccessible ( thisInference , containingFunction , program , host ) ; if ( ! typeNode ) { return ; } if ( isInJSFile ( containingFunction ) ) { annotateJSDocThis ( changes , sourceFile , containingFunction , typeNode ) ; } else { changes . tryInsertThisTypeAnnotation ( sourceFile , containingFunction , typeNode ) ; } } function annotateJSDocThis ( changes , sourceFile , containingFunction , typeNode ) { changes . addJSDocTags ( sourceFile , containingFunction , [ factory . createJSDocThisTag ( undefined , factory . createJSDocTypeExpression ( typeNode ) ) , ] ) ; } function annotateSetAccessor ( changes , importAdder , sourceFile , setAccessorDeclaration , program , host , cancellationToken ) { const param = firstOrUndefined ( setAccessorDeclaration . parameters ) ; if ( param && isIdentifier ( setAccessorDeclaration . name ) && isIdentifier ( param . name ) ) { let type = inferTypeForVariableFromUsage ( setAccessorDeclaration . name , program , cancellationToken ) ; if ( type === program . getTypeChecker ( ) . getAnyType ( ) ) { type = inferTypeForVariableFromUsage ( param . name , program , cancellationToken ) ; } if ( isInJSFile ( setAccessorDeclaration ) ) { annotateJSDocParameters ( changes , sourceFile , [ { declaration : param , type } ] , program , host ) ; } else { annotate ( changes , importAdder , sourceFile , param , type , program , host ) ; } } } function annotate ( changes , importAdder , sourceFile , declaration , type , program , host ) { const typeNode = getTypeNodeIfAccessible ( type , declaration , program , host ) ; if ( typeNode ) { if ( isInJSFile ( sourceFile ) && declaration . kind !== SyntaxKind . PropertySignature ) { const parent = isVariableDeclaration ( declaration ) ? tryCast ( declaration . parent . parent , isVariableStatement ) : declaration ; if ( ! parent ) { return ; } const typeExpression = factory . createJSDocTypeExpression ( typeNode ) ; const typeTag = isGetAccessorDeclaration ( declaration ) ? factory . createJSDocReturnTag ( undefined , typeExpression , undefined ) : factory . createJSDocTypeTag ( undefined , typeExpression , undefined ) ; changes . addJSDocTags ( sourceFile , parent , [ typeTag ] ) ; } else if ( ! tryReplaceImportTypeNodeWithAutoImport ( typeNode , declaration , sourceFile , changes , importAdder , getEmitScriptTarget ( program . getCompilerOptions ( ) ) ) ) { changes . tryInsertTypeAnnotation ( sourceFile , declaration , typeNode ) ; } } } function tryReplaceImportTypeNodeWithAutoImport ( typeNode , declaration , sourceFile , changes , importAdder , scriptTarget ) { const importableReference = tryGetAutoImportableReferenceFromTypeNode ( typeNode , scriptTarget ) ; if ( importableReference && changes . tryInsertTypeAnnotation ( sourceFile , declaration , importableReference . typeNode ) ) { forEach ( importableReference . symbols , s => importAdder . addImportFromExportedSymbol ( s , true ) ) ; return true ; } return false ; } function annotateJSDocParameters ( changes , sourceFile , parameterInferences , program , host ) { const signature = parameterInferences . length && parameterInferences [ 0 ] . declaration . parent ; if ( ! signature ) { return ; } const inferences = mapDefined ( parameterInferences , inference => { const param = inference . declaration ; if ( param . initializer || getJSDocType ( param ) || ! isIdentifier ( param . name ) ) { return ; } const typeNode = inference . type && getTypeNodeIfAccessible ( inference . type , param , program , host ) ; if ( typeNode ) { const name = factory . cloneNode ( param . name ) ; setEmitFlags ( name , EmitFlags . NoComments | EmitFlags . NoNestedComments ) ; return { name : factory . cloneNode ( param . name ) , param , isOptional : ! ! inference . isOptional , typeNode } ; } } ) ; if ( ! inferences . length ) { return ; } if ( isArrowFunction ( signature ) || isFunctionExpression ( signature ) ) { const needParens = isArrowFunction ( signature ) && ! findChildOfKind ( signature , SyntaxKind . OpenParenToken , sourceFile ) ; if ( needParens ) { changes . insertNodeBefore ( sourceFile , first ( signature . parameters ) , factory . createToken ( SyntaxKind . OpenParenToken ) ) ; } forEach ( inferences , ( { typeNode , param } ) => { const typeTag = factory . createJSDocTypeTag ( undefined , factory . createJSDocTypeExpression ( typeNode ) ) ; const jsDoc = factory . createJSDocComment ( undefined , [ typeTag ] ) ; changes . insertNodeAt ( sourceFile , param . getStart ( sourceFile ) , jsDoc , { suffix : " " } ) ; } ) ; if ( needParens ) { changes . insertNodeAfter ( sourceFile , last ( signature . parameters ) , factory . createToken ( SyntaxKind . CloseParenToken ) ) ; } } else { const paramTags = map ( inferences , ( { name , typeNode , isOptional } ) => factory . createJSDocParameterTag ( undefined , name , ! ! isOptional , factory . createJSDocTypeExpression ( typeNode ) , false , undefined ) ) ; changes . addJSDocTags ( sourceFile , signature , paramTags ) ; } } function getReferences ( token , program , cancellationToken ) { return mapDefined ( FindAllReferences . getReferenceEntriesForNode ( - 1 , token , program , program . getSourceFiles ( ) , cancellationToken ) , entry => entry . kind !== FindAllReferences . EntryKind . Span ? tryCast ( entry . node , isIdentifier ) : undefined ) ; } function inferTypeForVariableFromUsage ( token , program , cancellationToken ) { const references = getReferences ( token , program , cancellationToken ) ; return inferTypeFromReferences ( program , references , cancellationToken ) . single ( ) ; } function inferTypeForParametersFromUsage ( func , sourceFile , program , cancellationToken ) { const references = getFunctionReferences ( func , sourceFile , program , cancellationToken ) ; return references && inferTypeFromReferences ( program , references , cancellationToken ) . parameters ( func ) || func . parameters . map < ParameterInference > ( p => ( { declaration : p , type : isIdentifier ( p . name ) ? inferTypeForVariableFromUsage ( p . name , program , cancellationToken ) : program . getTypeChecker ( ) . getAnyType ( ) } ) ) ; } function getFunctionReferences ( containingFunction , sourceFile , program , cancellationToken ) { let searchToken ; switch ( containingFunction . kind ) { case SyntaxKind . Constructor : searchToken = findChildOfKind < Token < SyntaxKind . ConstructorKeyword > > ( containingFunction , SyntaxKind . ConstructorKeyword , sourceFile ) ; break ; case SyntaxKind . ArrowFunction : case SyntaxKind . FunctionExpression : const parent = containingFunction . parent ; searchToken = ( isVariableDeclaration ( parent ) || isPropertyDeclaration ( parent ) ) && isIdentifier ( parent . name ) ? parent . name : containingFunction . name ; break ; case SyntaxKind . FunctionDeclaration : case SyntaxKind . MethodDeclaration : case SyntaxKind . MethodSignature : searchToken = containingFunction . name ; break ; } if ( ! searchToken ) { return undefined ; } return getReferences ( searchToken , program , cancellationToken ) ; } interface ParameterInference { readonly declaration : ParameterDeclaration ; readonly type : Type ; readonly isOptional ? : boolean ; } function inferTypeFromReferences ( program , references , cancellationToken ) { const checker = program . getTypeChecker ( ) ; const builtinConstructors = { string : ( ) => checker . getStringType ( ) , number : ( ) => checker . getNumberType ( ) , Array : t => checker . createArrayType ( t ) , Promise : t => checker . createPromiseType ( t ) , } ; const builtins = [ checker . getStringType ( ) , checker . getNumberType ( ) , checker . createArrayType ( checker . getAnyType ( ) ) , checker . createPromiseType ( checker . getAnyType ( ) ) , ] ; return { single , parameters , thisParameter , } ; interface CallUsage { argumentTypes : Type [ ] ; return_ : Usage ; } interface Usage { isNumber : boolean | undefined ; isString : boolean | undefined ; /** Used ambiguously, eg x + ___ or object[___]; results in string | number if no other evidence exists */ isNumberOrString : boolean | undefined ; candidateTypes : Type [ ] | undefined ; properties : UnderscoreEscapedMap < Usage > | undefined ; calls : CallUsage [ ] | undefined ; constructs : CallUsage [ ] | undefined ; numberIndex : Usage | undefined ; stringIndex : Usage | undefined ; candidateThisTypes : Type [ ] | undefined ; inferredTypes : Type [ ] | undefined ; } function createEmptyUsage ( ) { return { isNumber : undefined , isString : undefined , isNumberOrString : undefined , candidateTypes : undefined , properties : undefined , calls : undefined , constructs : undefined , numberIndex : undefined , stringIndex : undefined , candidateThisTypes : undefined , inferredTypes : undefined , } ; } function combineUsages ( usages ) { const combinedProperties = new Map < __String , Usage [ ] > ( ) ; for ( const u of usages ) { if ( u . properties ) { u . properties . forEach ( ( p , name ) => { if ( ! combinedProperties . has ( name ) ) { combinedProperties . set ( name , [ ] ) ; } combinedProperties . get ( name ) ! . push ( p ) ; } ) ; } } const properties = new Map < __String , Usage > ( ) ; combinedProperties . forEach ( ( ps , name ) => { properties . set ( name , combineUsages ( ps ) ) ; } ) ; return { isNumber : usages . some ( u => u . isNumber ) , isString : usages . some ( u => u . isString ) , isNumberOrString : usages . some ( u => u . isNumberOrString ) , candidateTypes : flatMap ( usages , u => u . candidateTypes ) as Type [ ] , properties , calls : flatMap ( usages , u => u . calls ) as CallUsage [ ] , constructs : flatMap ( usages , u => u . constructs ) as CallUsage [ ] , numberIndex : forEach ( usages , u => u . numberIndex ) , stringIndex : forEach ( usages , u => u . stringIndex ) , candidateThisTypes : flatMap ( usages , u => u . candidateThisTypes ) as Type [ ] , inferredTypes : undefined , } ; } function single ( ) { return combineTypes ( inferTypesFromReferencesSingle ( references ) ) ; } function parameters ( declaration ) { if ( references . length === 0 || ! declaration . parameters ) { return undefined ; } const usage = createEmptyUsage ( ) ; for ( const reference of references ) { cancellationToken . throwIfCancellationRequested ( ) ; calculateUsageOfNode ( reference , usage ) ; } const calls = [ ... usage . constructs || [ ] , ... usage . calls || [ ] ] ; return declaration . parameters . map ( ( parameter , parameterIndex ) : ParameterInference => { const types = [ ] ; const isRest = isRestParameter ( parameter ) ; let isOptional = false ; for ( const call of calls ) { if ( call . argumentTypes . length <= parameterIndex ) { isOptional = isInJSFile ( declaration ) ; types . push ( checker . getUndefinedType ( ) ) ; } else if ( isRest ) { for ( let i = parameterIndex ; i < call . argumentTypes . length ; i ++ ) { types . push ( checker . getBaseTypeOfLiteralType ( call . argumentTypes [ i ] ) ) ; } } else { types . push ( checker . getBaseTypeOfLiteralType ( call . argumentTypes [ parameterIndex ] ) ) ; } } if ( isIdentifier ( parameter . name ) ) { const inferred = inferTypesFromReferencesSingle ( getReferences ( parameter . name , program , cancellationToken ) ) ; types . push ( ... ( isRest ? mapDefined ( inferred , checker . getElementTypeOfArrayType ) : inferred ) ) ; } const type = combineTypes ( types ) ; return { type : isRest ? checker . createArrayType ( type ) : type , isOptional : isOptional && ! isRest , declaration : parameter } ; } ) ; } function thisParameter ( ) { const usage = createEmptyUsage ( ) ; for ( const reference of references ) { cancellationToken . throwIfCancellationRequested ( ) ; calculateUsageOfNode ( reference , usage ) ; } return combineTypes ( usage . candidateThisTypes || emptyArray ) ; } function inferTypesFromReferencesSingle ( references ) { const usage = createEmptyUsage ( ) ; for ( const reference of references ) { cancellationToken . throwIfCancellationRequested ( ) ; calculateUsageOfNode ( reference , usage ) ; } return inferTypes ( usage ) ; } function calculateUsageOfNode ( node , usage ) { while ( isRightSideOfQualifiedNameOrPropertyAccess ( node ) ) { node = node . parent as Expression ; } switch ( node . parent . kind ) { case SyntaxKind . ExpressionStatement : inferTypeFromExpressionStatement ( node , usage ) ; break ; case SyntaxKind . PostfixUnaryExpression : usage . isNumber = true ; break ; case SyntaxKind . PrefixUnaryExpression : inferTypeFromPrefixUnaryExpression ( node . parent as PrefixUnaryExpression , usage ) ; break ; case SyntaxKind . BinaryExpression : inferTypeFromBinaryExpression ( node , node . parent as BinaryExpression , usage ) ; break ; case SyntaxKind . CaseClause : case SyntaxKind . DefaultClause : inferTypeFromSwitchStatementLabel ( node . parent as CaseOrDefaultClause , usage ) ; break ; case SyntaxKind . CallExpression : case SyntaxKind . NewExpression : if ( ( node . parent as CallExpression | NewExpression ) . expression === node ) { inferTypeFromCallExpression ( node . parent as CallExpression | NewExpression , usage ) ; } else { inferTypeFromContextualType ( node , usage ) ; } break ; case SyntaxKind . PropertyAccessExpression : inferTypeFromPropertyAccessExpression ( node . parent as PropertyAccessExpression , usage ) ; break ; case SyntaxKind . ElementAccessExpression : inferTypeFromPropertyElementExpression ( node . parent as ElementAccessExpression , node , usage ) ; break ; case SyntaxKind . PropertyAssignment : case SyntaxKind . ShorthandPropertyAssignment : inferTypeFromPropertyAssignment ( node . parent as PropertyAssignment | ShorthandPropertyAssignment , usage ) ; break ; case SyntaxKind . PropertyDeclaration : inferTypeFromPropertyDeclaration ( node . parent as PropertyDeclaration , usage ) ; break ; case SyntaxKind . VariableDeclaration : { const { name , initializer } = node . parent as VariableDeclaration ; if ( node === name ) { if ( initializer ) { addCandidateType ( usage , checker . getTypeAtLocation ( initializer ) ) ; } break ; } } default : return inferTypeFromContextualType ( node , usage ) ; } } function inferTypeFromContextualType ( node , usage ) { if ( isExpressionNode ( node ) ) { addCandidateType ( usage , checker . getContextualType ( node ) ) ; } } function inferTypeFromExpressionStatement ( node , usage ) { addCandidateType ( usage , isCallExpression ( node ) ? checker . getVoidType ( ) : checker . getAnyType ( ) ) ; } function inferTypeFromPrefixUnaryExpression ( node , usage ) { switch ( node . operator ) { case SyntaxKind . PlusPlusToken : case SyntaxKind . MinusMinusToken : case SyntaxKind . MinusToken : case SyntaxKind . TildeToken : usage . isNumber = true ; break ; case SyntaxKind . PlusToken : usage . isNumberOrString = true ; break ; } } function inferTypeFromBinaryExpression ( node , parent , usage ) { switch ( parent . operatorToken . kind ) { case SyntaxKind . AsteriskAsteriskToken : case SyntaxKind . AsteriskToken : case SyntaxKind . SlashToken : case SyntaxKind . PercentToken : case SyntaxKind . LessThanLessThanToken : case SyntaxKind . GreaterThanGreaterThanToken : case SyntaxKind . GreaterThanGreaterThanGreaterThanToken : case SyntaxKind . AmpersandToken : case SyntaxKind . BarToken : case SyntaxKind . CaretToken : case SyntaxKind . MinusEqualsToken : case SyntaxKind . AsteriskAsteriskEqualsToken : case SyntaxKind . AsteriskEqualsToken : case SyntaxKind . SlashEqualsToken : case SyntaxKind . PercentEqualsToken : case SyntaxKind . AmpersandEqualsToken : case SyntaxKind . BarEqualsToken : case SyntaxKind . CaretEqualsToken : case SyntaxKind . LessThanLessThanEqualsToken : case SyntaxKind . GreaterThanGreaterThanGreaterThanEqualsToken : case SyntaxKind . GreaterThanGreaterThanEqualsToken : case SyntaxKind . MinusToken : case SyntaxKind . LessThanToken : case SyntaxKind . LessThanEqualsToken : case SyntaxKind . GreaterThanToken : case SyntaxKind . GreaterThanEqualsToken : const operandType = checker . getTypeAtLocation ( parent . left === node ? parent . right : parent . left ) ; if ( operandType . flags & TypeFlags . EnumLike ) { addCandidateType ( usage , operandType ) ; } else { usage . isNumber = true ; } break ; case SyntaxKind . PlusEqualsToken : case SyntaxKind . PlusToken : const otherOperandType = checker . getTypeAtLocation ( parent . left === node ? parent . right : parent . left ) ; if ( otherOperandType . flags & TypeFlags . EnumLike ) { addCandidateType ( usage , otherOperandType ) ; } else if ( otherOperandType . flags & TypeFlags . NumberLike ) { usage . isNumber = true ; } else if ( otherOperandType . flags & TypeFlags . StringLike ) { usage . isString = true ; } else if ( otherOperandType . flags & TypeFlags . Any ) { } else { usage . isNumberOrString = true ; } break ; case SyntaxKind . EqualsToken : case SyntaxKind . EqualsEqualsToken : case SyntaxKind . EqualsEqualsEqualsToken : case SyntaxKind . ExclamationEqualsEqualsToken : case SyntaxKind . ExclamationEqualsToken : addCandidateType ( usage , checker . getTypeAtLocation ( parent . left === node ? parent . right : parent . left ) ) ; break ; case SyntaxKind . InKeyword : if ( node === parent . left ) { usage . isString = true ; } break ; case SyntaxKind . BarBarToken : case SyntaxKind . QuestionQuestionToken : if ( node === parent . left && ( node . parent . parent . kind === SyntaxKind . VariableDeclaration || isAssignmentExpression ( node . parent . parent , true ) ) ) { addCandidateType ( usage , checker . getTypeAtLocation ( parent . right ) ) ; } break ; case SyntaxKind . AmpersandAmpersandToken : case SyntaxKind . CommaToken : case SyntaxKind . InstanceOfKeyword : break ; } } function inferTypeFromSwitchStatementLabel ( parent , usage ) { addCandidateType ( usage , checker . getTypeAtLocation ( parent . parent . parent . expression ) ) ; } function inferTypeFromCallExpression ( parent , usage ) { const call = { argumentTypes : [ ] , return_ : createEmptyUsage ( ) } ; if ( parent . arguments ) { for ( const argument of parent . arguments ) { call . argumentTypes . push ( checker . getTypeAtLocation ( argument ) ) ; } } calculateUsageOfNode ( parent , call . return_ ) ; if ( parent . kind === SyntaxKind . CallExpression ) { ( usage . calls || ( usage . calls = [ ] ) ) . push ( call ) ; } else { ( usage . constructs || ( usage . constructs = [ ] ) ) . push ( call ) ; } } function inferTypeFromPropertyAccessExpression ( parent , usage ) { const name = escapeLeadingUnderscores ( parent . name . text ) ; if ( ! usage . properties ) { usage . properties = new Map ( ) ; } const propertyUsage = usage . properties . get ( name ) || createEmptyUsage ( ) ; calculateUsageOfNode ( parent , propertyUsage ) ; usage . properties . set ( name , propertyUsage ) ; } function inferTypeFromPropertyElementExpression ( parent , node , usage ) { if ( node === parent . argumentExpression ) { usage . isNumberOrString = true ; return ; } else { const indexType = checker . getTypeAtLocation ( parent . argumentExpression ) ; const indexUsage = createEmptyUsage ( ) ; calculateUsageOfNode ( parent , indexUsage ) ; if ( indexType . flags & TypeFlags . NumberLike ) { usage . numberIndex = indexUsage ; } else { usage . stringIndex = indexUsage ; } } } function inferTypeFromPropertyAssignment ( assignment , usage ) { const nodeWithRealType = isVariableDeclaration ( assignment . parent . parent ) ? assignment . parent . parent : assignment . parent ; addCandidateThisType ( usage , checker . getTypeAtLocation ( nodeWithRealType ) ) ; } function inferTypeFromPropertyDeclaration ( declaration , usage ) { addCandidateThisType ( usage , checker . getTypeAtLocation ( declaration . parent ) ) ; } interface Priority { high : ( t : Type ) => boolean ; low : ( t : Type ) => boolean ; } function removeLowPriorityInferences ( inferences , priorities ) { const toRemove = [ ] ; for ( const i of inferences ) { for ( const { high , low } of priorities ) { if ( high ( i ) ) { Debug . assert ( ! low ( i ) , "Priority can't have both low and high" ) ; toRemove . push ( low ) ; } } } return inferences . filter ( i => toRemove . every ( f => ! f ( i ) ) ) ; } function combineFromUsage ( usage ) { return combineTypes ( inferTypes ( usage ) ) ; } function combineTypes ( inferences ) { if ( ! inferences . length ) return checker . getAnyType ( ) ; const stringNumber = checker . getUnionType ( [ checker . getStringType ( ) , checker . getNumberType ( ) ] ) ; const priorities = [ { high : t => t === checker . getStringType ( ) || t === checker . getNumberType ( ) , low : t => t === stringNumber } , { high : t => ! ( t . flags & ( TypeFlags . Any | TypeFlags . Void ) ) , low : t => ! ! ( t . flags & ( TypeFlags . Any | TypeFlags . Void ) ) } , { high : t => ! ( t . flags & ( TypeFlags . Nullable | TypeFlags . Any | TypeFlags . Void ) ) && ! ( getObjectFlags ( t ) & ObjectFlags . Anonymous ) , low : t => ! ! ( getObjectFlags ( t ) & ObjectFlags . Anonymous ) } ] ; let good = removeLowPriorityInferences ( inferences , priorities ) ; const anons = good . filter ( i => getObjectFlags ( i ) & ObjectFlags . Anonymous ) as AnonymousType [ ] ; if ( anons . length ) { good = good . filter ( i => ! ( getObjectFlags ( i ) & ObjectFlags . Anonymous ) ) ; good . push ( combineAnonymousTypes ( anons ) ) ; } return checker . getWidenedType ( checker . getUnionType ( good . map ( checker . getBaseTypeOfLiteralType ) , UnionReduction . Subtype ) ) ; } function combineAnonymousTypes ( anons ) { if ( anons . length === 1 ) { return anons [ 0 ] ; } const calls = [ ] ; const constructs = [ ] ; const stringIndices = [ ] ; const numberIndices = [ ] ; let stringIndexReadonly = false ; let numberIndexReadonly = false ; const props = createMultiMap < Type > ( ) ; for ( const anon of anons ) { for ( const p of checker . getPropertiesOfType ( anon ) ) { props . add ( p . name , p . valueDeclaration ? checker . getTypeOfSymbolAtLocation ( p , p . valueDeclaration ) : checker . getAnyType ( ) ) ; } calls . push ( ... checker . getSignaturesOfType ( anon , SignatureKind . Call ) ) ; constructs . push ( ... checker . getSignaturesOfType ( anon , SignatureKind . Construct ) ) ; const stringIndexInfo = checker . getIndexInfoOfType ( anon , IndexKind . String ) ; if ( stringIndexInfo ) { stringIndices . push ( stringIndexInfo . type ) ; stringIndexReadonly = stringIndexReadonly || stringIndexInfo . isReadonly ; } const numberIndexInfo = checker . getIndexInfoOfType ( anon , IndexKind . Number ) ; if ( numberIndexInfo ) { numberIndices . push ( numberIndexInfo . type ) ; numberIndexReadonly = numberIndexReadonly || numberIndexInfo . isReadonly ; } } const members = mapEntries ( props , ( name , types ) => { const isOptional = types . length < anons . length ? SymbolFlags . Optional : 0 ; const s = checker . createSymbol ( SymbolFlags . Property | isOptional , name as __String ) ; s . type = checker . getUnionType ( types ) ; return [ name , s ] ; } ) ; const indexInfos = [ ] ; if ( stringIndices . length ) indexInfos . push ( checker . createIndexInfo ( checker . getStringType ( ) , checker . getUnionType ( stringIndices ) , stringIndexReadonly ) ) ; if ( numberIndices . length ) indexInfos . push ( checker . createIndexInfo ( checker . getNumberType ( ) , checker . getUnionType ( numberIndices ) , numberIndexReadonly ) ) ; return checker . createAnonymousType ( anons [ 0 ] . symbol , members as UnderscoreEscapedMap < TransientSymbol > , calls , constructs , indexInfos ) ; } function inferTypes ( usage ) { const types = [ ] ; if ( usage . isNumber ) { types . push ( checker . getNumberType ( ) ) ; } if ( usage . isString ) { types . push ( checker . getStringType ( ) ) ; } if ( usage . isNumberOrString ) { types . push ( checker . getUnionType ( [ checker . getStringType ( ) , checker . getNumberType ( ) ] ) ) ; } if ( usage . numberIndex ) { types . push ( checker . createArrayType ( combineFromUsage ( usage . numberIndex ) ) ) ; } if ( usage . properties ?. size || usage . constructs ?. length || usage . stringIndex ) { types . push ( inferStructuralType ( usage ) ) ; } const candidateTypes = ( usage . candidateTypes || [ ] ) . map ( t => checker . getBaseTypeOfLiteralType ( t ) ) ; const callsType = usage . calls ?. length ? inferStructuralType ( usage ) : undefined ; if ( callsType && candidateTypes ) { types . push ( checker . getUnionType ( [ callsType , ... candidateTypes ] , UnionReduction . Subtype ) ) ; } else { if ( callsType ) { types . push ( callsType ) ; } if ( length ( candidateTypes ) ) { types . push ( ... candidateTypes ) ; } } types . push ( ... inferNamedTypesFromProperties ( usage ) ) ; return types ; } function inferStructuralType ( usage ) { const members = new Map < __String , Symbol > ( ) ; if ( usage . properties ) { usage . properties . forEach ( ( u , name ) => { const symbol = checker . createSymbol ( SymbolFlags . Property , name ) ; symbol . type = combineFromUsage ( u ) ; members . set ( name , symbol ) ; } ) ; } const callSignatures = usage . calls ? [ getSignatureFromCalls ( usage . calls ) ] : [ ] ; const constructSignatures = usage . constructs ? [ getSignatureFromCalls ( usage . constructs ) ] : [ ] ; const indexInfos = usage . stringIndex ? [ checker . createIndexInfo ( checker . getStringType ( ) , combineFromUsage ( usage . stringIndex ) , false ) ] : [ ] ; return checker . createAnonymousType ( undefined , members , callSignatures , constructSignatures , indexInfos ) ; } function inferNamedTypesFromProperties ( usage ) { if ( ! usage . properties || ! usage . properties . size ) return [ ] ; const types = builtins . filter ( t => allPropertiesAreAssignableToUsage ( t , usage ) ) ; if ( 0 < types . length && types . length < 3 ) { return types . map ( t => inferInstantiationFromUsage ( t , usage ) ) ; } return [ ] ; } function allPropertiesAreAssignableToUsage ( type , usage ) { if ( ! usage . properties ) return false ; return ! forEachEntry ( usage . properties , ( propUsage , name ) => { const source = checker . getTypeOfPropertyOfType ( type , name as string ) ; if ( ! source ) { return true ; } if ( propUsage . calls ) { const sigs = checker . getSignaturesOfType ( source , SignatureKind . Call ) ; return ! sigs . length || ! checker . isTypeAssignableTo ( source , getFunctionFromCalls ( propUsage . calls ) ) ; } else { return ! checker . isTypeAssignableTo ( source , combineFromUsage ( propUsage ) ) ; } } ) ; } /**
         * inference is limited to
         * 1. generic types with a single parameter
         * 2. inference to/from calls with a single signature
         */ function inferInstantiationFromUsage ( type , usage ) { if ( ! ( getObjectFlags ( type ) & ObjectFlags . Reference ) || ! usage . properties ) { return type ; } const generic = ( type as TypeReference ) . target ; const singleTypeParameter = singleOrUndefined ( generic . typeParameters ) ; if ( ! singleTypeParameter ) return type ; const types = [ ] ; usage . properties . forEach ( ( propUsage , name ) => { const genericPropertyType = checker . getTypeOfPropertyOfType ( generic , name as string ) ; Debug . assert ( ! ! genericPropertyType , "generic should have all the properties of its reference." ) ; types . push ( ... inferTypeParameters ( genericPropertyType , combineFromUsage ( propUsage ) , singleTypeParameter ) ) ; } ) ; return builtinConstructors [ type . symbol . escapedName as string ] ( combineTypes ( types ) ) ; } function inferTypeParameters ( genericType , usageType , typeParameter ) { if ( genericType === typeParameter ) { return [ usageType ] ; } else if ( genericType . flags & TypeFlags . UnionOrIntersection ) { return flatMap ( ( genericType as UnionOrIntersectionType ) . types , t => inferTypeParameters ( t , usageType , typeParameter ) ) ; } else if ( getObjectFlags ( genericType ) & ObjectFlags . Reference && getObjectFlags ( usageType ) & ObjectFlags . Reference ) { const genericArgs = checker . getTypeArguments ( genericType as TypeReference ) ; const usageArgs = checker . getTypeArguments ( usageType as TypeReference ) ; const types = [ ] ; if ( genericArgs && usageArgs ) { for ( let i = 0 ; i < genericArgs . length ; i ++ ) { if ( usageArgs [ i ] ) { types . push ( ... inferTypeParameters ( genericArgs [ i ] , usageArgs [ i ] , typeParameter ) ) ; } } } return types ; } const genericSigs = checker . getSignaturesOfType ( genericType , SignatureKind . Call ) ; const usageSigs = checker . getSignaturesOfType ( usageType , SignatureKind . Call ) ; if ( genericSigs . length === 1 && usageSigs . length === 1 ) { return inferFromSignatures ( genericSigs [ 0 ] , usageSigs [ 0 ] , typeParameter ) ; } return [ ] ; } function inferFromSignatures ( genericSig , usageSig , typeParameter ) { const types = [ ] ; for ( let i = 0 ; i < genericSig . parameters . length ; i ++ ) { const genericParam = genericSig . parameters [ i ] ; const usageParam = usageSig . parameters [ i ] ; const isRest = genericSig . declaration && isRestParameter ( genericSig . declaration . parameters [ i ] ) ; if ( ! usageParam ) { break ; } let genericParamType = genericParam . valueDeclaration ? checker . getTypeOfSymbolAtLocation ( genericParam , genericParam . valueDeclaration ) : checker . getAnyType ( ) ; const elementType = isRest && checker . getElementTypeOfArrayType ( genericParamType ) ; if ( elementType ) { genericParamType = elementType ; } const targetType = ( usageParam as SymbolLinks ) . type || ( usageParam . valueDeclaration ? checker . getTypeOfSymbolAtLocation ( usageParam , usageParam . valueDeclaration ) : checker . getAnyType ( ) ) ; types . push ( ... inferTypeParameters ( genericParamType , targetType , typeParameter ) ) ; } const genericReturn = checker . getReturnTypeOfSignature ( genericSig ) ; const usageReturn = checker . getReturnTypeOfSignature ( usageSig ) ; types . push ( ... inferTypeParameters ( genericReturn , usageReturn , typeParameter ) ) ; return types ; } function getFunctionFromCalls ( calls ) { return checker . createAnonymousType ( undefined , createSymbolTable ( ) , [ getSignatureFromCalls ( calls ) ] , emptyArray , emptyArray ) ; } function getSignatureFromCalls ( calls ) { const parameters = [ ] ; const length = Math . max ( ... calls . map ( c => c . argumentTypes . length ) ) ; for ( let i = 0 ; i < length ; i ++ ) { const symbol = checker . createSymbol ( SymbolFlags . FunctionScopedVariable , escapeLeadingUnderscores ( `arg${ i }` ) ) ; symbol . type = combineTypes ( calls . map ( call => call . argumentTypes [ i ] || checker . getUndefinedType ( ) ) ) ; if ( calls . some ( call => call . argumentTypes [ i ] === undefined ) ) { symbol . flags |= SymbolFlags . Optional ; } parameters . push ( symbol ) ; } const returnType = combineFromUsage ( combineUsages ( calls . map ( call => call . return_ ) ) ) ; return checker . createSignature ( undefined , undefined , undefined , parameters , returnType , undefined , length , SignatureFlags . None ) ; } function addCandidateType ( usage , type ) { if ( type && ! ( type . flags & TypeFlags . Any ) && ! ( type . flags & TypeFlags . Never ) ) { ( usage . candidateTypes || ( usage . candidateTypes = [ ] ) ) . push ( type ) ; } } function addCandidateThisType ( usage , type ) { if ( type && ! ( type . flags & TypeFlags . Any ) && ! ( type . flags & TypeFlags . Never ) ) { ( usage . candidateThisTypes || ( usage . candidateThisTypes = [ ] ) ) . push ( type ) ; } } } }