namespace ts . refactor { const refactorName = "Extract type" ; const extractToTypeAliasAction = { name : "Extract to type alias" , description : getLocaleSpecificMessage ( Diagnostics . Extract_to_type_alias ) , kind : "refactor.extract.type" , } ; const extractToInterfaceAction = { name : "Extract to interface" , description : getLocaleSpecificMessage ( Diagnostics . Extract_to_interface ) , kind : "refactor.extract.interface" , } ; const extractToTypeDefAction = { name : "Extract to typedef" , description : getLocaleSpecificMessage ( Diagnostics . Extract_to_typedef ) , kind : "refactor.extract.typedef" } ; registerRefactor ( refactorName , { kinds : [ extractToTypeAliasAction . kind , extractToInterfaceAction . kind , extractToTypeDefAction . kind ] , getAvailableActions : function getRefactorActionsToExtractType ( context ) : readonly ApplicableRefactorInfo [ ] { const info = getRangeToExtract ( context , context . triggerReason === "invoked" ) ; if ( ! info ) return emptyArray ; if ( ! isRefactorErrorInfo ( info ) ) { return [ { name : refactorName , description : getLocaleSpecificMessage ( Diagnostics . Extract_type ) , actions : info . isJS ? [ extractToTypeDefAction ] : append ( [ extractToTypeAliasAction ] , info . typeElements && extractToInterfaceAction ) } ] ; } if ( context . preferences . provideRefactorNotApplicableReason ) { return [ { name : refactorName , description : getLocaleSpecificMessage ( Diagnostics . Extract_type ) , actions : [ { ... extractToTypeDefAction , notApplicableReason : info . error } , { ... extractToTypeAliasAction , notApplicableReason : info . error } , { ... extractToInterfaceAction , notApplicableReason : info . error } , ] } ] ; } return emptyArray ; } , getEditsForAction : function getRefactorEditsToExtractType ( context , actionName ) : RefactorEditInfo { const { file , } = context ; const info = getRangeToExtract ( context ) ; Debug . assert ( info && ! isRefactorErrorInfo ( info ) , "Expected to find a range to extract" ) ; const name = getUniqueName ( "NewType" , file ) ; const edits = textChanges . ChangeTracker . with ( context , changes => { switch ( actionName ) { case extractToTypeAliasAction . name : Debug . assert ( ! info . isJS , "Invalid actionName/JS combo" ) ; return doTypeAliasChange ( changes , file , name , info ) ; case extractToTypeDefAction . name : Debug . assert ( info . isJS , "Invalid actionName/JS combo" ) ; return doTypedefChange ( changes , file , name , info ) ; case extractToInterfaceAction . name : Debug . assert ( ! info . isJS && ! ! info . typeElements , "Invalid actionName/JS combo" ) ; return doInterfaceChange ( changes , file , name , info as InterfaceInfo ) ; default : Debug . fail ( "Unexpected action name" ) ; } } ) ; const renameFilename = file . fileName ; const renameLocation = getRenameLocation ( edits , renameFilename , name , false ) ; return { edits , renameFilename , renameLocation } ; } } ) ; interface TypeAliasInfo { isJS : boolean ; selection : TypeNode ; firstStatement : Statement ; typeParameters : readonly TypeParameterDeclaration [ ] ; typeElements ? : readonly TypeElement [ ] ; } interface InterfaceInfo { isJS : boolean ; selection : TypeNode ; firstStatement : Statement ; typeParameters : readonly TypeParameterDeclaration [ ] ; typeElements : readonly TypeElement [ ] ; } type ExtractInfo = TypeAliasInfo | InterfaceInfo ; function getRangeToExtract ( context , considerEmptySpans = true ) { const { file , startPosition } = context ; const isJS = isSourceFileJS ( file ) ; const current = getTokenAtPosition ( file , startPosition ) ; const range = createTextRangeFromSpan ( getRefactorContextSpan ( context ) ) ; const cursorRequest = range . pos === range . end && considerEmptySpans ; const selection = findAncestor ( current , ( node => node . parent && isTypeNode ( node ) && ! rangeContainsSkipTrivia ( range , node . parent , file ) && ( cursorRequest || nodeOverlapsWithStartEnd ( current , file , range . pos , range . end ) ) ) ) ; if ( ! selection || ! isTypeNode ( selection ) ) return { error : getLocaleSpecificMessage ( Diagnostics . Selection_is_not_a_valid_type_node ) } ; const checker = context . program . getTypeChecker ( ) ; const firstStatement = Debug . checkDefined ( findAncestor ( selection , isStatement ) , "Should find a statement" ) ; const typeParameters = collectTypeParameters ( checker , selection , firstStatement , file ) ; if ( ! typeParameters ) return { error : getLocaleSpecificMessage ( Diagnostics . No_type_could_be_extracted_from_this_type_node ) } ; const typeElements = flattenTypeLiteralNodeReference ( checker , selection ) ; return { isJS , selection , firstStatement , typeParameters , typeElements } ; } function flattenTypeLiteralNodeReference ( checker , node ) { if ( ! node ) return undefined ; if ( isIntersectionTypeNode ( node ) ) { const result = [ ] ; const seen = new Map < string , true > ( ) ; for ( const type of node . types ) { const flattenedTypeMembers = flattenTypeLiteralNodeReference ( checker , type ) ; if ( ! flattenedTypeMembers || ! flattenedTypeMembers . every ( type => type . name && addToSeen ( seen , getNameFromPropertyName ( type . name ) as string ) ) ) { return undefined ; } addRange ( result , flattenedTypeMembers ) ; } return result ; } else if ( isParenthesizedTypeNode ( node ) ) { return flattenTypeLiteralNodeReference ( checker , node . type ) ; } else if ( isTypeLiteralNode ( node ) ) { return node . members ; } return undefined ; } function rangeContainsSkipTrivia ( r1 , node , file ) { return rangeContainsStartEnd ( r1 , skipTrivia ( file . text , node . pos ) , node . end ) ; } function collectTypeParameters ( checker , selection , statement , file ) { const result = [ ] ; return visitor ( selection ) ? undefined : result ; function visitor ( node ) { if ( isTypeReferenceNode ( node ) ) { if ( isIdentifier ( node . typeName ) ) { const symbol = checker . resolveName ( node . typeName . text , node . typeName , SymbolFlags . TypeParameter , true ) ; const declaration = tryCast ( symbol ?. declarations ?. [ 0 ] , isTypeParameterDeclaration ) ; if ( declaration ) { if ( rangeContainsSkipTrivia ( statement , declaration , file ) && ! rangeContainsSkipTrivia ( selection , declaration , file ) ) { pushIfUnique ( result , declaration ) ; } } } } else if ( isInferTypeNode ( node ) ) { const conditionalTypeNode = findAncestor ( node , n => isConditionalTypeNode ( n ) && rangeContainsSkipTrivia ( n . extendsType , node , file ) ) ; if ( ! conditionalTypeNode || ! rangeContainsSkipTrivia ( selection , conditionalTypeNode , file ) ) { return true ; } } else if ( ( isTypePredicateNode ( node ) || isThisTypeNode ( node ) ) ) { const functionLikeNode = findAncestor ( node . parent , isFunctionLike ) ; if ( functionLikeNode && functionLikeNode . type && rangeContainsSkipTrivia ( functionLikeNode . type , node , file ) && ! rangeContainsSkipTrivia ( selection , functionLikeNode , file ) ) { return true ; } } else if ( isTypeQueryNode ( node ) ) { if ( isIdentifier ( node . exprName ) ) { const symbol = checker . resolveName ( node . exprName . text , node . exprName , SymbolFlags . Value , false ) ; if ( symbol ?. valueDeclaration && rangeContainsSkipTrivia ( statement , symbol . valueDeclaration , file ) && ! rangeContainsSkipTrivia ( selection , symbol . valueDeclaration , file ) ) { return true ; } } else { if ( isThisIdentifier ( node . exprName . left ) && ! rangeContainsSkipTrivia ( selection , node . parent , file ) ) { return true ; } } } if ( file && isTupleTypeNode ( node ) && ( getLineAndCharacterOfPosition ( file , node . pos ) . line === getLineAndCharacterOfPosition ( file , node . end ) . line ) ) { setEmitFlags ( node , EmitFlags . SingleLine ) ; } return forEachChild ( node , visitor ) ; } } function doTypeAliasChange ( changes , file , name , info ) { const { firstStatement , selection , typeParameters } = info ; const newTypeNode = factory . createTypeAliasDeclaration ( undefined , undefined , name , typeParameters . map ( id => factory . updateTypeParameterDeclaration ( id , id . name , id . constraint , undefined ) ) , selection ) ; changes . insertNodeBefore ( file , firstStatement , ignoreSourceNewlines ( newTypeNode ) , true ) ; changes . replaceNode ( file , selection , factory . createTypeReferenceNode ( name , typeParameters . map ( id => factory . createTypeReferenceNode ( id . name , undefined ) ) ) , { leadingTriviaOption : textChanges . LeadingTriviaOption . Exclude , trailingTriviaOption : textChanges . TrailingTriviaOption . ExcludeWhitespace } ) ; } function doInterfaceChange ( changes , file , name , info ) { const { firstStatement , selection , typeParameters , typeElements } = info ; const newTypeNode = factory . createInterfaceDeclaration ( undefined , undefined , name , typeParameters , undefined , typeElements ) ; setTextRange ( newTypeNode , typeElements [ 0 ] ?. parent ) ; changes . insertNodeBefore ( file , firstStatement , ignoreSourceNewlines ( newTypeNode ) , true ) ; changes . replaceNode ( file , selection , factory . createTypeReferenceNode ( name , typeParameters . map ( id => factory . createTypeReferenceNode ( id . name , undefined ) ) ) , { leadingTriviaOption : textChanges . LeadingTriviaOption . Exclude , trailingTriviaOption : textChanges . TrailingTriviaOption . ExcludeWhitespace } ) ; } function doTypedefChange ( changes , file , name , info ) { const { firstStatement , selection , typeParameters } = info ; const node = factory . createJSDocTypedefTag ( factory . createIdentifier ( "typedef" ) , factory . createJSDocTypeExpression ( selection ) , factory . createIdentifier ( name ) ) ; const templates = [ ] ; forEach ( typeParameters , typeParameter => { const constraint = getEffectiveConstraintOfTypeParameter ( typeParameter ) ; const parameter = factory . createTypeParameterDeclaration ( typeParameter . name ) ; const template = factory . createJSDocTemplateTag ( factory . createIdentifier ( "template" ) , constraint && cast ( constraint , isJSDocTypeExpression ) , [ parameter ] ) ; templates . push ( template ) ; } ) ; changes . insertNodeBefore ( file , firstStatement , factory . createJSDocComment ( undefined , factory . createNodeArray ( concatenate < JSDocTag > ( templates , [ node ] ) ) ) , true ) ; changes . replaceNode ( file , selection , factory . createTypeReferenceNode ( name , typeParameters . map ( id => factory . createTypeReferenceNode ( id . name , undefined ) ) ) ) ; } }