namespace ts . refactor . extractSymbol { const refactorName = "Extract Symbol" ; const extractConstantAction = { name : "Extract Constant" , description : getLocaleSpecificMessage ( Diagnostics . Extract_constant ) , kind : "refactor.extract.constant" , } ; const extractFunctionAction = { name : "Extract Function" , description : getLocaleSpecificMessage ( Diagnostics . Extract_function ) , kind : "refactor.extract.function" , } ; registerRefactor ( refactorName , { kinds : [ extractConstantAction . kind , extractFunctionAction . kind ] , getEditsForAction : getRefactorEditsToExtractSymbol , getAvailableActions : getRefactorActionsToExtractSymbol , } ) ; /**
     * Compute the associated code actions
     * Exported for tests.
     */ export function getRefactorActionsToExtractSymbol ( context ) { const requestedRefactor = context . kind ; const rangeToExtract = getRangeToExtract ( context . file , getRefactorContextSpan ( context ) , context . triggerReason === "invoked" ) ; const targetRange = rangeToExtract . targetRange ; if ( targetRange === undefined ) { if ( ! rangeToExtract . errors || rangeToExtract . errors . length === 0 || ! context . preferences . provideRefactorNotApplicableReason ) { return emptyArray ; } const errors = [ ] ; if ( refactorKindBeginsWith ( extractFunctionAction . kind , requestedRefactor ) ) { errors . push ( { name : refactorName , description : extractFunctionAction . description , actions : [ { ... extractFunctionAction , notApplicableReason : getStringError ( rangeToExtract . errors ) } ] } ) ; } if ( refactorKindBeginsWith ( extractConstantAction . kind , requestedRefactor ) ) { errors . push ( { name : refactorName , description : extractConstantAction . description , actions : [ { ... extractConstantAction , notApplicableReason : getStringError ( rangeToExtract . errors ) } ] } ) ; } return errors ; } const extractions = getPossibleExtractions ( targetRange , context ) ; if ( extractions === undefined ) { return emptyArray ; } const functionActions = [ ] ; const usedFunctionNames = new Map < string , boolean > ( ) ; let innermostErrorFunctionAction ; const constantActions = [ ] ; const usedConstantNames = new Map < string , boolean > ( ) ; let innermostErrorConstantAction ; let i = 0 ; for ( const { functionExtraction , constantExtraction } of extractions ) { const description = functionExtraction . description ; if ( refactorKindBeginsWith ( extractFunctionAction . kind , requestedRefactor ) ) { if ( functionExtraction . errors . length === 0 ) { if ( ! usedFunctionNames . has ( description ) ) { usedFunctionNames . set ( description , true ) ; functionActions . push ( { description , name : `function_scope_${ i }` , kind : extractFunctionAction . kind } ) ; } } else if ( ! innermostErrorFunctionAction ) { innermostErrorFunctionAction = { description , name : `function_scope_${ i }` , notApplicableReason : getStringError ( functionExtraction . errors ) , kind : extractFunctionAction . kind } ; } } if ( refactorKindBeginsWith ( extractConstantAction . kind , requestedRefactor ) ) { if ( constantExtraction . errors . length === 0 ) { const description = constantExtraction . description ; if ( ! usedConstantNames . has ( description ) ) { usedConstantNames . set ( description , true ) ; constantActions . push ( { description , name : `constant_scope_${ i }` , kind : extractConstantAction . kind } ) ; } } else if ( ! innermostErrorConstantAction ) { innermostErrorConstantAction = { description , name : `constant_scope_${ i }` , notApplicableReason : getStringError ( constantExtraction . errors ) , kind : extractConstantAction . kind } ; } } i ++ ; } const infos = [ ] ; if ( functionActions . length ) { infos . push ( { name : refactorName , description : getLocaleSpecificMessage ( Diagnostics . Extract_function ) , actions : functionActions , } ) ; } else if ( context . preferences . provideRefactorNotApplicableReason && innermostErrorFunctionAction ) { infos . push ( { name : refactorName , description : getLocaleSpecificMessage ( Diagnostics . Extract_function ) , actions : [ innermostErrorFunctionAction ] } ) ; } if ( constantActions . length ) { infos . push ( { name : refactorName , description : getLocaleSpecificMessage ( Diagnostics . Extract_constant ) , actions : constantActions } ) ; } else if ( context . preferences . provideRefactorNotApplicableReason && innermostErrorConstantAction ) { infos . push ( { name : refactorName , description : getLocaleSpecificMessage ( Diagnostics . Extract_constant ) , actions : [ innermostErrorConstantAction ] } ) ; } return infos . length ? infos : emptyArray ; function getStringError ( errors ) { let error = errors [ 0 ] . messageText ; if ( typeof error !== "string" ) { error = error . messageText ; } return error ; } } export function getRefactorEditsToExtractSymbol ( context , actionName ) { const rangeToExtract = getRangeToExtract ( context . file , getRefactorContextSpan ( context ) ) ; const targetRange = rangeToExtract . targetRange ! ; const parsedFunctionIndexMatch = /^function_scope_(\d+)$/ . exec ( actionName ) ; if ( parsedFunctionIndexMatch ) { const index = + parsedFunctionIndexMatch [ 1 ] ; Debug . assert ( isFinite ( index ) , "Expected to parse a finite number from the function scope index" ) ; return getFunctionExtractionAtIndex ( targetRange , context , index ) ; } const parsedConstantIndexMatch = /^constant_scope_(\d+)$/ . exec ( actionName ) ; if ( parsedConstantIndexMatch ) { const index = + parsedConstantIndexMatch [ 1 ] ; Debug . assert ( isFinite ( index ) , "Expected to parse a finite number from the constant scope index" ) ; return getConstantExtractionAtIndex ( targetRange , context , index ) ; } Debug . fail ( "Unrecognized action name" ) ; } export namespace Messages { function createMessage ( message ) { return { message , code : 0 , category : DiagnosticCategory . Message , key : message } ; } export const cannotExtractRange = createMessage ( "Cannot extract range." ) ; export const cannotExtractImport = createMessage ( "Cannot extract import statement." ) ; export const cannotExtractSuper = createMessage ( "Cannot extract super call." ) ; export const cannotExtractJSDoc = createMessage ( "Cannot extract JSDoc." ) ; export const cannotExtractEmpty = createMessage ( "Cannot extract empty range." ) ; export const expressionExpected = createMessage ( "expression expected." ) ; export const uselessConstantType = createMessage ( "No reason to extract constant of type." ) ; export const statementOrExpressionExpected = createMessage ( "Statement or expression expected." ) ; export const cannotExtractRangeContainingConditionalBreakOrContinueStatements = createMessage ( "Cannot extract range containing conditional break or continue statements." ) ; export const cannotExtractRangeContainingConditionalReturnStatement = createMessage ( "Cannot extract range containing conditional return statement." ) ; export const cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange = createMessage ( "Cannot extract range containing labeled break or continue with target outside of the range." ) ; export const cannotExtractRangeThatContainsWritesToReferencesLocatedOutsideOfTheTargetRangeInGenerators = createMessage ( "Cannot extract range containing writes to references located outside of the target range in generators." ) ; export const typeWillNotBeVisibleInTheNewScope = createMessage ( "Type will not visible in the new scope." ) ; export const functionWillNotBeVisibleInTheNewScope = createMessage ( "Function will not visible in the new scope." ) ; export const cannotExtractIdentifier = createMessage ( "Select more than a single identifier." ) ; export const cannotExtractExportedEntity = createMessage ( "Cannot extract exported declaration" ) ; export const cannotWriteInExpression = createMessage ( "Cannot write back side-effects when extracting an expression" ) ; export const cannotExtractReadonlyPropertyInitializerOutsideConstructor = createMessage ( "Cannot move initialization of read-only class property outside of the constructor" ) ; export const cannotExtractAmbientBlock = createMessage ( "Cannot extract code from ambient contexts" ) ; export const cannotAccessVariablesFromNestedScopes = createMessage ( "Cannot access variables from nested scopes" ) ; export const cannotExtractToJSClass = createMessage ( "Cannot extract constant to a class scope in JS" ) ; export const cannotExtractToExpressionArrowFunction = createMessage ( "Cannot extract constant to an arrow function without a block" ) ; } enum RangeFacts { None = 0 , HasReturn = 1 << 0 , IsGenerator = 1 << 1 , IsAsyncFunction = 1 << 2 , UsesThis = 1 << 3 , /**
         * The range is in a function which needs the 'static' modifier in a class
         */ InStaticRegion = 1 << 4 } /**
     * Represents an expression or a list of statements that should be extracted with some extra information
     */ interface TargetRange { readonly range : Expression | Statement [ ] ; readonly facts : RangeFacts ; /**
         * A list of symbols that are declared in the selected range which are visible in the containing lexical scope
         * Used to ensure we don't turn something used outside the range free (or worse, resolve to a different entity).
         */ readonly declarations : Symbol [ ] ; } /**
     * Result of 'getRangeToExtract' operation: contains either a range or a list of errors
     */ type RangeToExtract = { readonly targetRange ? : never ; readonly errors : readonly Diagnostic [ ] ; } | { readonly targetRange : TargetRange ; readonly errors ? : never ; } ; type Scope = FunctionLikeDeclaration | SourceFile | ModuleBlock | ClassLikeDeclaration ; /**
     * getRangeToExtract takes a span inside a text file and returns either an expression or an array
     * of statements representing the minimum set of nodes needed to extract the entire span. This
     * process may fail, in which case a set of errors is returned instead. These errors are shown to
     * users if they have the provideRefactorNotApplicableReason option set.
     */ export function getRangeToExtract ( sourceFile , span , invoked = true ) { const { length } = span ; if ( length === 0 && ! invoked ) { return { errors : [ createFileDiagnostic ( sourceFile , span . start , length , Messages . cannotExtractEmpty ) ] } ; } const cursorRequest = length === 0 && invoked ; const startToken = findFirstNonJsxWhitespaceToken ( sourceFile , span . start ) ; const endToken = findTokenOnLeftOfPosition ( sourceFile , textSpanEnd ( span ) ) ; const adjustedSpan = startToken && endToken && invoked ? getAdjustedSpanFromNodes ( startToken , endToken , sourceFile ) : span ; const start = cursorRequest ? getExtractableParent ( startToken ) : getParentNodeInSpan ( startToken , sourceFile , adjustedSpan ) ; const end = cursorRequest ? start : getParentNodeInSpan ( endToken , sourceFile , adjustedSpan ) ; const declarations = [ ] ; let rangeFacts = RangeFacts . None ; if ( ! start || ! end ) { return { errors : [ createFileDiagnostic ( sourceFile , span . start , length , Messages . cannotExtractRange ) ] } ; } if ( isJSDoc ( start ) ) { return { errors : [ createFileDiagnostic ( sourceFile , span . start , length , Messages . cannotExtractJSDoc ) ] } ; } if ( start . parent !== end . parent ) { return { errors : [ createFileDiagnostic ( sourceFile , span . start , length , Messages . cannotExtractRange ) ] } ; } if ( start !== end ) { if ( ! isBlockLike ( start . parent ) ) { return { errors : [ createFileDiagnostic ( sourceFile , span . start , length , Messages . cannotExtractRange ) ] } ; } const statements = [ ] ; for ( const statement of start . parent . statements ) { if ( statement === start || statements . length ) { const errors = checkNode ( statement ) ; if ( errors ) { return { errors } ; } statements . push ( statement ) ; } if ( statement === end ) { break ; } } if ( ! statements . length ) { return { errors : [ createFileDiagnostic ( sourceFile , span . start , length , Messages . cannotExtractRange ) ] } ; } return { targetRange : { range : statements , facts : rangeFacts , declarations } } ; } if ( isReturnStatement ( start ) && ! start . expression ) { return { errors : [ createFileDiagnostic ( sourceFile , span . start , length , Messages . cannotExtractRange ) ] } ; } const node = refineNode ( start ) ; const errors = checkRootNode ( node ) || checkNode ( node ) ; if ( errors ) { return { errors } ; } return { targetRange : { range : getStatementOrExpressionRange ( node ) ! , facts : rangeFacts , declarations } } ; param node function refineNode ( node ) { if ( isReturnStatement ( node ) ) { if ( node . expression ) { return node . expression ; } } else if ( isVariableStatement ( node ) || isVariableDeclarationList ( node ) ) { const declarations = isVariableStatement ( node ) ? node . declarationList . declarations : node . declarations ; let numInitializers = 0 ; let lastInitializer ; for ( const declaration of declarations ) { if ( declaration . initializer ) { numInitializers ++ ; lastInitializer = declaration . initializer ; } } if ( numInitializers === 1 ) { return lastInitializer ! ; } } else if ( isVariableDeclaration ( node ) ) { if ( node . initializer ) { return node . initializer ; } } return node ; } function checkRootNode ( node ) { if ( isIdentifier ( isExpressionStatement ( node ) ? node . expression : node ) ) { return [ createDiagnosticForNode ( node , Messages . cannotExtractIdentifier ) ] ; } return undefined ; } function checkForStaticContext ( nodeToCheck , containingClass ) { let current = nodeToCheck ; while ( current !== containingClass ) { if ( current . kind === SyntaxKind . PropertyDeclaration ) { if ( isStatic ( current ) ) { rangeFacts |= RangeFacts . InStaticRegion ; } break ; } else if ( current . kind === SyntaxKind . Parameter ) { const ctorOrMethod = getContainingFunction ( current ) ! ; if ( ctorOrMethod . kind === SyntaxKind . Constructor ) { rangeFacts |= RangeFacts . InStaticRegion ; } break ; } else if ( current . kind === SyntaxKind . MethodDeclaration ) { if ( isStatic ( current ) ) { rangeFacts |= RangeFacts . InStaticRegion ; } } current = current . parent ; } } function checkNode ( nodeToCheck ) { const enum PermittedJumps { None = 0 , Break = 1 << 0 , Continue = 1 << 1 , Return = 1 << 2 } Debug . assert ( nodeToCheck . pos <= nodeToCheck . end , "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809 (1)" ) ; Debug . assert ( ! positionIsSynthesized ( nodeToCheck . pos ) , "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809 (2)" ) ; if ( ! isStatement ( nodeToCheck ) && ! ( isExpressionNode ( nodeToCheck ) && isExtractableExpression ( nodeToCheck ) ) ) { return [ createDiagnosticForNode ( nodeToCheck , Messages . statementOrExpressionExpected ) ] ; } if ( nodeToCheck . flags & NodeFlags . Ambient ) { return [ createDiagnosticForNode ( nodeToCheck , Messages . cannotExtractAmbientBlock ) ] ; } const containingClass = getContainingClass ( nodeToCheck ) ; if ( containingClass ) { checkForStaticContext ( nodeToCheck , containingClass ) ; } let errors ; let permittedJumps = PermittedJumps . Return ; let seenLabels ; visit ( nodeToCheck ) ; return errors ; function visit ( node ) { if ( errors ) { return true ; } if ( isDeclaration ( node ) ) { const declaringNode = ( node . kind === SyntaxKind . VariableDeclaration ) ? node . parent . parent : node ; if ( hasSyntacticModifier ( declaringNode , ModifierFlags . Export ) ) { ( errors ||= [ ] ) . push ( createDiagnosticForNode ( node , Messages . cannotExtractExportedEntity ) ) ; return true ; } declarations . push ( node . symbol ) ; } switch ( node . kind ) { case SyntaxKind . ImportDeclaration : ( errors ||= [ ] ) . push ( createDiagnosticForNode ( node , Messages . cannotExtractImport ) ) ; return true ; case SyntaxKind . ExportAssignment : ( errors ||= [ ] ) . push ( createDiagnosticForNode ( node , Messages . cannotExtractExportedEntity ) ) ; return true ; case SyntaxKind . SuperKeyword : if ( node . parent . kind === SyntaxKind . CallExpression ) { const containingClass = getContainingClass ( node ) ! ; if ( containingClass . pos < span . start || containingClass . end >= ( span . start + span . length ) ) { ( errors ||= [ ] ) . push ( createDiagnosticForNode ( node , Messages . cannotExtractSuper ) ) ; return true ; } } else { rangeFacts |= RangeFacts . UsesThis ; } break ; case SyntaxKind . ArrowFunction : forEachChild ( node , function check ( n ) { if ( isThis ( n ) ) { rangeFacts |= RangeFacts . UsesThis ; } else if ( isClassLike ( n ) || ( isFunctionLike ( n ) && ! isArrowFunction ( n ) ) ) { return false ; } else { forEachChild ( n , check ) ; } } ) ; case SyntaxKind . ClassDeclaration : case SyntaxKind . FunctionDeclaration : if ( isSourceFile ( node . parent ) && node . parent . externalModuleIndicator === undefined ) { ( errors ||= [ ] ) . push ( createDiagnosticForNode ( node , Messages . functionWillNotBeVisibleInTheNewScope ) ) ; } case SyntaxKind . ClassExpression : case SyntaxKind . FunctionExpression : case SyntaxKind . MethodDeclaration : case SyntaxKind . Constructor : case SyntaxKind . GetAccessor : case SyntaxKind . SetAccessor : return false ; } const savedPermittedJumps = permittedJumps ; switch ( node . kind ) { case SyntaxKind . IfStatement : permittedJumps = PermittedJumps . None ; break ; case SyntaxKind . TryStatement : permittedJumps = PermittedJumps . None ; break ; case SyntaxKind . Block : if ( node . parent && node . parent . kind === SyntaxKind . TryStatement && ( node . parent as TryStatement ) . finallyBlock === node ) { permittedJumps = PermittedJumps . Return ; } break ; case SyntaxKind . DefaultClause : case SyntaxKind . CaseClause : permittedJumps |= PermittedJumps . Break ; break ; default : if ( isIterationStatement ( node , false ) ) { permittedJumps |= PermittedJumps . Break | PermittedJumps . Continue ; } break ; } switch ( node . kind ) { case SyntaxKind . ThisType : case SyntaxKind . ThisKeyword : rangeFacts |= RangeFacts . UsesThis ; break ; case SyntaxKind . LabeledStatement : { const label = ( node as LabeledStatement ) . label ; ( seenLabels || ( seenLabels = [ ] ) ) . push ( label . escapedText ) ; forEachChild ( node , visit ) ; seenLabels . pop ( ) ; break ; } case SyntaxKind . BreakStatement : case SyntaxKind . ContinueStatement : { const label = ( node as BreakStatement | ContinueStatement ) . label ; if ( label ) { if ( ! contains ( seenLabels , label . escapedText ) ) { ( errors ||= [ ] ) . push ( createDiagnosticForNode ( node , Messages . cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange ) ) ; } } else { if ( ! ( permittedJumps & ( node . kind === SyntaxKind . BreakStatement ? PermittedJumps . Break : PermittedJumps . Continue ) ) ) { ( errors ||= [ ] ) . push ( createDiagnosticForNode ( node , Messages . cannotExtractRangeContainingConditionalBreakOrContinueStatements ) ) ; } } break ; } case SyntaxKind . AwaitExpression : rangeFacts |= RangeFacts . IsAsyncFunction ; break ; case SyntaxKind . YieldExpression : rangeFacts |= RangeFacts . IsGenerator ; break ; case SyntaxKind . ReturnStatement : if ( permittedJumps & PermittedJumps . Return ) { rangeFacts |= RangeFacts . HasReturn ; } else { ( errors ||= [ ] ) . push ( createDiagnosticForNode ( node , Messages . cannotExtractRangeContainingConditionalReturnStatement ) ) ; } break ; default : forEachChild ( node , visit ) ; break ; } permittedJumps = savedPermittedJumps ; } } } /**
     * Includes the final semicolon so that the span covers statements in cases where it would otherwise
     * only cover the declaration list.
     */ function getAdjustedSpanFromNodes ( startNode , endNode , sourceFile ) { const start = startNode . getStart ( sourceFile ) ; let end = endNode . getEnd ( ) ; if ( sourceFile . text . charCodeAt ( end ) === CharacterCodes . semicolon ) { end ++ ; } return { start , length : end - start } ; } function getStatementOrExpressionRange ( node ) { if ( isStatement ( node ) ) { return [ node ] ; } else if ( isExpressionNode ( node ) ) { return isExpressionStatement ( node . parent ) ? [ node . parent ] : node as Expression ; } return undefined ; } function isScope ( node ) { return isFunctionLikeDeclaration ( node ) || isSourceFile ( node ) || isModuleBlock ( node ) || isClassLike ( node ) ; } /**
     * Computes possible places we could extract the function into. For example,
     * you may be able to extract into a class method *or* local closure *or* namespace function,
     * depending on what's in the extracted body.
     */ function collectEnclosingScopes ( range ) { let current = isReadonlyArray ( range . range ) ? first ( range . range ) : range . range ; if ( range . facts & RangeFacts . UsesThis ) { const containingClass = getContainingClass ( current ) ; if ( containingClass ) { const containingFunction = findAncestor ( current , isFunctionLikeDeclaration ) ; return containingFunction ? [ containingFunction , containingClass ] : [ containingClass ] ; } } const scopes = [ ] ; while ( true ) { current = current . parent ; if ( current . kind === SyntaxKind . Parameter ) { current = findAncestor ( current , parent => isFunctionLikeDeclaration ( parent ) ) ! . parent ; } if ( isScope ( current ) ) { scopes . push ( current ) ; if ( current . kind === SyntaxKind . SourceFile ) { return scopes ; } } } } function getFunctionExtractionAtIndex ( targetRange , context , requestedChangesIndex ) { const { scopes , readsAndWrites : { target , usagesPerScope , functionErrorsPerScope , exposedVariableDeclarations } } = getPossibleExtractionsWorker ( targetRange , context ) ; Debug . assert ( ! functionErrorsPerScope [ requestedChangesIndex ] . length , "The extraction went missing? How?" ) ; context . cancellationToken ! . throwIfCancellationRequested ( ) ; return extractFunctionInScope ( target , scopes [ requestedChangesIndex ] , usagesPerScope [ requestedChangesIndex ] , exposedVariableDeclarations , targetRange , context ) ; } function getConstantExtractionAtIndex ( targetRange , context , requestedChangesIndex ) { const { scopes , readsAndWrites : { target , usagesPerScope , constantErrorsPerScope , exposedVariableDeclarations } } = getPossibleExtractionsWorker ( targetRange , context ) ; Debug . assert ( ! constantErrorsPerScope [ requestedChangesIndex ] . length , "The extraction went missing? How?" ) ; Debug . assert ( exposedVariableDeclarations . length === 0 , "Extract constant accepted a range containing a variable declaration?" ) ; context . cancellationToken ! . throwIfCancellationRequested ( ) ; const expression = isExpression ( target ) ? target : ( target . statements [ 0 ] as ExpressionStatement ) . expression ; return extractConstantInScope ( expression , scopes [ requestedChangesIndex ] , usagesPerScope [ requestedChangesIndex ] , targetRange . facts , context ) ; } interface Extraction { readonly description : string ; readonly errors : readonly Diagnostic [ ] ; } interface ScopeExtractions { readonly functionExtraction : Extraction ; readonly constantExtraction : Extraction ; } /**
     * Given a piece of text to extract ('targetRange'), computes a list of possible extractions.
     * Each returned ExtractResultForScope corresponds to a possible target scope and is either a set of changes
     * or an error explaining why we can't extract into that scope.
     */ function getPossibleExtractions ( targetRange , context ) { const { scopes , readsAndWrites : { functionErrorsPerScope , constantErrorsPerScope } } = getPossibleExtractionsWorker ( targetRange , context ) ; const extractions = scopes . map ( ( scope , i ) : ScopeExtractions => { const functionDescriptionPart = getDescriptionForFunctionInScope ( scope ) ; const constantDescriptionPart = getDescriptionForConstantInScope ( scope ) ; const scopeDescription = isFunctionLikeDeclaration ( scope ) ? getDescriptionForFunctionLikeDeclaration ( scope ) : isClassLike ( scope ) ? getDescriptionForClassLikeDeclaration ( scope ) : getDescriptionForModuleLikeDeclaration ( scope ) ; let functionDescription ; let constantDescription ; if ( scopeDescription === SpecialScope . Global ) { functionDescription = formatStringFromArgs ( getLocaleSpecificMessage ( Diagnostics . Extract_to_0_in_1_scope ) , [ functionDescriptionPart , "global" ] ) ; constantDescription = formatStringFromArgs ( getLocaleSpecificMessage ( Diagnostics . Extract_to_0_in_1_scope ) , [ constantDescriptionPart , "global" ] ) ; } else if ( scopeDescription === SpecialScope . Module ) { functionDescription = formatStringFromArgs ( getLocaleSpecificMessage ( Diagnostics . Extract_to_0_in_1_scope ) , [ functionDescriptionPart , "module" ] ) ; constantDescription = formatStringFromArgs ( getLocaleSpecificMessage ( Diagnostics . Extract_to_0_in_1_scope ) , [ constantDescriptionPart , "module" ] ) ; } else { functionDescription = formatStringFromArgs ( getLocaleSpecificMessage ( Diagnostics . Extract_to_0_in_1 ) , [ functionDescriptionPart , scopeDescription ] ) ; constantDescription = formatStringFromArgs ( getLocaleSpecificMessage ( Diagnostics . Extract_to_0_in_1 ) , [ constantDescriptionPart , scopeDescription ] ) ; } if ( i === 0 && ! isClassLike ( scope ) ) { constantDescription = formatStringFromArgs ( getLocaleSpecificMessage ( Diagnostics . Extract_to_0_in_enclosing_scope ) , [ constantDescriptionPart ] ) ; } return { functionExtraction : { description : functionDescription , errors : functionErrorsPerScope [ i ] , } , constantExtraction : { description : constantDescription , errors : constantErrorsPerScope [ i ] , } , } ; } ) ; return extractions ; } function getPossibleExtractionsWorker ( targetRange , context ) { const { file : sourceFile } = context ; const scopes = collectEnclosingScopes ( targetRange ) ; const enclosingTextRange = getEnclosingTextRange ( targetRange , sourceFile ) ; const readsAndWrites = collectReadsAndWrites ( targetRange , scopes , enclosingTextRange , sourceFile , context . program . getTypeChecker ( ) , context . cancellationToken ! ) ; return { scopes , readsAndWrites } ; } function getDescriptionForFunctionInScope ( scope ) { return isFunctionLikeDeclaration ( scope ) ? "inner function" : isClassLike ( scope ) ? "method" : "function" ; } function getDescriptionForConstantInScope ( scope ) { return isClassLike ( scope ) ? "readonly field" : "constant" ; } function getDescriptionForFunctionLikeDeclaration ( scope ) { switch ( scope . kind ) { case SyntaxKind . Constructor : return "constructor" ; case SyntaxKind . FunctionExpression : case SyntaxKind . FunctionDeclaration : return scope . name ? `function '${ scope . name . text }'` : ANONYMOUS ; case SyntaxKind . ArrowFunction : return "arrow function" ; case SyntaxKind . MethodDeclaration : return `method '${ scope . name . getText ( ) }'` ; case SyntaxKind . GetAccessor : return `'get ${ scope . name . getText ( ) }'` ; case SyntaxKind . SetAccessor : return `'set ${ scope . name . getText ( ) }'` ; default : throw Debug . assertNever ( scope , `Unexpected scope kind ${ ( scope as FunctionLikeDeclaration ) . kind }` ) ; } } function getDescriptionForClassLikeDeclaration ( scope ) { return scope . kind === SyntaxKind . ClassDeclaration ? scope . name ? `class '${ scope . name . text }'` : "anonymous class declaration" : scope . name ? `class expression '${ scope . name . text }'` : "anonymous class expression" ; } function getDescriptionForModuleLikeDeclaration ( scope ) { return scope . kind === SyntaxKind . ModuleBlock ? `namespace '${ scope . parent . name . getText ( ) }'` : scope . externalModuleIndicator ? SpecialScope . Module : SpecialScope . Global ; } const enum SpecialScope { Module , Global } /**
     * Result of 'extractRange' operation for a specific scope.
     * Stores either a list of changes that should be applied to extract a range or a list of errors
     */ function extractFunctionInScope ( node , scope , { usages : usagesInScope , typeParameterUsages , substitutions } , exposedVariableDeclarations , range , context ) { const checker = context . program . getTypeChecker ( ) ; const scriptTarget = getEmitScriptTarget ( context . program . getCompilerOptions ( ) ) ; const importAdder = codefix . createImportAdder ( context . file , context . program , context . preferences , context . host ) ; const file = scope . getSourceFile ( ) ; const functionNameText = getUniqueName ( isClassLike ( scope ) ? "newMethod" : "newFunction" , file ) ; const isJS = isInJSFile ( scope ) ; const functionName = factory . createIdentifier ( functionNameText ) ; let returnType ; const parameters = [ ] ; const callArguments = [ ] ; let writes ; usagesInScope . forEach ( ( usage , name ) => { let typeNode ; if ( ! isJS ) { let type = checker . getTypeOfSymbolAtLocation ( usage . symbol , usage . node ) ; type = checker . getBaseTypeOfLiteralType ( type ) ; typeNode = codefix . typeToAutoImportableTypeNode ( checker , importAdder , type , scope , scriptTarget , NodeBuilderFlags . NoTruncation ) ; } const paramDecl = factory . createParameterDeclaration ( undefined , undefined , undefined , name , undefined , typeNode ) ; parameters . push ( paramDecl ) ; if ( usage . usage === Usage . Write ) { ( writes || ( writes = [ ] ) ) . push ( usage ) ; } callArguments . push ( factory . createIdentifier ( name ) ) ; } ) ; const typeParametersAndDeclarations = arrayFrom ( typeParameterUsages . values ( ) ) . map ( type => ( { type , declaration : getFirstDeclaration ( type ) } ) ) ; const sortedTypeParametersAndDeclarations = typeParametersAndDeclarations . sort ( compareTypesByDeclarationOrder ) ; const typeParameters = sortedTypeParametersAndDeclarations . length === 0 ? undefined : sortedTypeParametersAndDeclarations . map ( t => t . declaration as TypeParameterDeclaration ) ; const callTypeArguments = typeParameters !== undefined ? typeParameters . map ( decl => factory . createTypeReferenceNode ( decl . name , undefined ) ) : undefined ; if ( isExpression ( node ) && ! isJS ) { const contextualType = checker . getContextualType ( node ) ; returnType = checker . typeToTypeNode ( contextualType ! , scope , NodeBuilderFlags . NoTruncation ) ; } const { body , returnValueProperty } = transformFunctionBody ( node , exposedVariableDeclarations , writes , substitutions , ! ! ( range . facts & RangeFacts . HasReturn ) ) ; suppressLeadingAndTrailingTrivia ( body ) ; let newFunction ; if ( isClassLike ( scope ) ) { const modifiers = isJS ? [ ] : [ factory . createModifier ( SyntaxKind . PrivateKeyword ) ] ; if ( range . facts & RangeFacts . InStaticRegion ) { modifiers . push ( factory . createModifier ( SyntaxKind . StaticKeyword ) ) ; } if ( range . facts & RangeFacts . IsAsyncFunction ) { modifiers . push ( factory . createModifier ( SyntaxKind . AsyncKeyword ) ) ; } newFunction = factory . createMethodDeclaration ( undefined , modifiers . length ? modifiers : undefined , range . facts & RangeFacts . IsGenerator ? factory . createToken ( SyntaxKind . AsteriskToken ) : undefined , functionName , undefined , typeParameters , parameters , returnType , body ) ; } else { newFunction = factory . createFunctionDeclaration ( undefined , range . facts & RangeFacts . IsAsyncFunction ? [ factory . createToken ( SyntaxKind . AsyncKeyword ) ] : undefined , range . facts & RangeFacts . IsGenerator ? factory . createToken ( SyntaxKind . AsteriskToken ) : undefined , functionName , typeParameters , parameters , returnType , body ) ; } const changeTracker = textChanges . ChangeTracker . fromContext ( context ) ; const minInsertionPos = ( isReadonlyArray ( range . range ) ? last ( range . range ) : range . range ) . end ; const nodeToInsertBefore = getNodeToInsertFunctionBefore ( minInsertionPos , scope ) ; if ( nodeToInsertBefore ) { changeTracker . insertNodeBefore ( context . file , nodeToInsertBefore , newFunction , true ) ; } else { changeTracker . insertNodeAtEndOfScope ( context . file , scope , newFunction ) ; } importAdder . writeFixes ( changeTracker ) ; const newNodes = [ ] ; const called = getCalledExpression ( scope , range , functionNameText ) ; let call = factory . createCallExpression ( called , callTypeArguments , callArguments ) ; if ( range . facts & RangeFacts . IsGenerator ) { call = factory . createYieldExpression ( factory . createToken ( SyntaxKind . AsteriskToken ) , call ) ; } if ( range . facts & RangeFacts . IsAsyncFunction ) { call = factory . createAwaitExpression ( call ) ; } if ( isInJSXContent ( node ) ) { call = factory . createJsxExpression ( undefined , call ) ; } if ( exposedVariableDeclarations . length && ! writes ) { Debug . assert ( ! returnValueProperty , "Expected no returnValueProperty" ) ; Debug . assert ( ! ( range . facts & RangeFacts . HasReturn ) , "Expected RangeFacts.HasReturn flag to be unset" ) ; if ( exposedVariableDeclarations . length === 1 ) { const variableDeclaration = exposedVariableDeclarations [ 0 ] ; newNodes . push ( factory . createVariableStatement ( undefined , factory . createVariableDeclarationList ( [ factory . createVariableDeclaration ( getSynthesizedDeepClone ( variableDeclaration . name ) , undefined , getSynthesizedDeepClone ( variableDeclaration . type ) , call ) ] , variableDeclaration . parent . flags ) ) ) ; } else { const bindingElements = [ ] ; const typeElements = [ ] ; let commonNodeFlags = exposedVariableDeclarations [ 0 ] . parent . flags ; let sawExplicitType = false ; for ( const variableDeclaration of exposedVariableDeclarations ) { bindingElements . push ( factory . createBindingElement ( undefined , undefined , getSynthesizedDeepClone ( variableDeclaration . name ) ) ) ; const variableType = checker . typeToTypeNode ( checker . getBaseTypeOfLiteralType ( checker . getTypeAtLocation ( variableDeclaration ) ) , scope , NodeBuilderFlags . NoTruncation ) ; typeElements . push ( factory . createPropertySignature ( undefined , variableDeclaration . symbol . name , undefined , variableType ) ) ; sawExplicitType = sawExplicitType || variableDeclaration . type !== undefined ; commonNodeFlags = commonNodeFlags & variableDeclaration . parent . flags ; } const typeLiteral = sawExplicitType ? factory . createTypeLiteralNode ( typeElements ) : undefined ; if ( typeLiteral ) { setEmitFlags ( typeLiteral , EmitFlags . SingleLine ) ; } newNodes . push ( factory . createVariableStatement ( undefined , factory . createVariableDeclarationList ( [ factory . createVariableDeclaration ( factory . createObjectBindingPattern ( bindingElements ) , undefined , typeLiteral , call ) ] , commonNodeFlags ) ) ) ; } } else if ( exposedVariableDeclarations . length || writes ) { if ( exposedVariableDeclarations . length ) { for ( const variableDeclaration of exposedVariableDeclarations ) { let flags = variableDeclaration . parent . flags ; if ( flags & NodeFlags . Const ) { flags = ( flags & ~ NodeFlags . Const ) | NodeFlags . Let ; } newNodes . push ( factory . createVariableStatement ( undefined , factory . createVariableDeclarationList ( [ factory . createVariableDeclaration ( variableDeclaration . symbol . name , undefined , getTypeDeepCloneUnionUndefined ( variableDeclaration . type ) ) ] , flags ) ) ) ; } } if ( returnValueProperty ) { newNodes . push ( factory . createVariableStatement ( undefined , factory . createVariableDeclarationList ( [ factory . createVariableDeclaration ( returnValueProperty , undefined , getTypeDeepCloneUnionUndefined ( returnType ) ) ] , NodeFlags . Let ) ) ) ; } const assignments = getPropertyAssignmentsForWritesAndVariableDeclarations ( exposedVariableDeclarations , writes ) ; if ( returnValueProperty ) { assignments . unshift ( factory . createShorthandPropertyAssignment ( returnValueProperty ) ) ; } if ( assignments . length === 1 ) { Debug . assert ( ! returnValueProperty , "Shouldn't have returnValueProperty here" ) ; newNodes . push ( factory . createExpressionStatement ( factory . createAssignment ( assignments [ 0 ] . name , call ) ) ) ; if ( range . facts & RangeFacts . HasReturn ) { newNodes . push ( factory . createReturnStatement ( ) ) ; } } else { newNodes . push ( factory . createExpressionStatement ( factory . createAssignment ( factory . createObjectLiteralExpression ( assignments ) , call ) ) ) ; if ( returnValueProperty ) { newNodes . push ( factory . createReturnStatement ( factory . createIdentifier ( returnValueProperty ) ) ) ; } } } else { if ( range . facts & RangeFacts . HasReturn ) { newNodes . push ( factory . createReturnStatement ( call ) ) ; } else if ( isReadonlyArray ( range . range ) ) { newNodes . push ( factory . createExpressionStatement ( call ) ) ; } else { newNodes . push ( call ) ; } } if ( isReadonlyArray ( range . range ) ) { changeTracker . replaceNodeRangeWithNodes ( context . file , first ( range . range ) , last ( range . range ) , newNodes ) ; } else { changeTracker . replaceNodeWithNodes ( context . file , range . range , newNodes ) ; } const edits = changeTracker . getChanges ( ) ; const renameRange = isReadonlyArray ( range . range ) ? first ( range . range ) : range . range ; const renameFilename = renameRange . getSourceFile ( ) . fileName ; const renameLocation = getRenameLocation ( edits , renameFilename , functionNameText , false ) ; return { renameFilename , renameLocation , edits } ; function getTypeDeepCloneUnionUndefined ( typeNode ) { if ( typeNode === undefined ) { return undefined ; } const clone = getSynthesizedDeepClone ( typeNode ) ; let withoutParens = clone ; while ( isParenthesizedTypeNode ( withoutParens ) ) { withoutParens = withoutParens . type ; } return isUnionTypeNode ( withoutParens ) && find ( withoutParens . types , t => t . kind === SyntaxKind . UndefinedKeyword ) ? clone : factory . createUnionTypeNode ( [ clone , factory . createKeywordTypeNode ( SyntaxKind . UndefinedKeyword ) ] ) ; } } /**
     * Result of 'extractRange' operation for a specific scope.
     * Stores either a list of changes that should be applied to extract a range or a list of errors
     */ function extractConstantInScope ( node , scope , { substitutions } , rangeFacts , context ) { const checker = context . program . getTypeChecker ( ) ; const file = scope . getSourceFile ( ) ; const localNameText = getUniqueName ( isClassLike ( scope ) ? "newProperty" : "newLocal" , file ) ; const isJS = isInJSFile ( scope ) ; let variableType = isJS || ! checker . isContextSensitive ( node ) ? undefined : checker . typeToTypeNode ( checker . getContextualType ( node ) ! , scope , NodeBuilderFlags . NoTruncation ) ; let initializer = transformConstantInitializer ( skipParentheses ( node ) , substitutions ) ; ( { variableType , initializer } = transformFunctionInitializerAndType ( variableType , initializer ) ) ; suppressLeadingAndTrailingTrivia ( initializer ) ; const changeTracker = textChanges . ChangeTracker . fromContext ( context ) ; if ( isClassLike ( scope ) ) { Debug . assert ( ! isJS , "Cannot extract to a JS class" ) ; const modifiers = [ ] ; modifiers . push ( factory . createModifier ( SyntaxKind . PrivateKeyword ) ) ; if ( rangeFacts & RangeFacts . InStaticRegion ) { modifiers . push ( factory . createModifier ( SyntaxKind . StaticKeyword ) ) ; } modifiers . push ( factory . createModifier ( SyntaxKind . ReadonlyKeyword ) ) ; const newVariable = factory . createPropertyDeclaration ( undefined , modifiers , localNameText , undefined , variableType , initializer ) ; let localReference = factory . createPropertyAccessExpression ( rangeFacts & RangeFacts . InStaticRegion ? factory . createIdentifier ( scope . name ! . getText ( ) ) : factory . createThis ( ) , factory . createIdentifier ( localNameText ) ) ; if ( isInJSXContent ( node ) ) { localReference = factory . createJsxExpression ( undefined , localReference ) ; } const maxInsertionPos = node . pos ; const nodeToInsertBefore = getNodeToInsertPropertyBefore ( maxInsertionPos , scope ) ; changeTracker . insertNodeBefore ( context . file , nodeToInsertBefore , newVariable , true ) ; changeTracker . replaceNode ( context . file , node , localReference ) ; } else { const newVariableDeclaration = factory . createVariableDeclaration ( localNameText , undefined , variableType , initializer ) ; const oldVariableDeclaration = getContainingVariableDeclarationIfInList ( node , scope ) ; if ( oldVariableDeclaration ) { changeTracker . insertNodeBefore ( context . file , oldVariableDeclaration , newVariableDeclaration ) ; const localReference = factory . createIdentifier ( localNameText ) ; changeTracker . replaceNode ( context . file , node , localReference ) ; } else if ( node . parent . kind === SyntaxKind . ExpressionStatement && scope === findAncestor ( node , isScope ) ) { const newVariableStatement = factory . createVariableStatement ( undefined , factory . createVariableDeclarationList ( [ newVariableDeclaration ] , NodeFlags . Const ) ) ; changeTracker . replaceNode ( context . file , node . parent , newVariableStatement ) ; } else { const newVariableStatement = factory . createVariableStatement ( undefined , factory . createVariableDeclarationList ( [ newVariableDeclaration ] , NodeFlags . Const ) ) ; const nodeToInsertBefore = getNodeToInsertConstantBefore ( node , scope ) ; if ( nodeToInsertBefore . pos === 0 ) { changeTracker . insertNodeAtTopOfFile ( context . file , newVariableStatement , false ) ; } else { changeTracker . insertNodeBefore ( context . file , nodeToInsertBefore , newVariableStatement , false ) ; } if ( node . parent . kind === SyntaxKind . ExpressionStatement ) { changeTracker . delete ( context . file , node . parent ) ; } else { let localReference = factory . createIdentifier ( localNameText ) ; if ( isInJSXContent ( node ) ) { localReference = factory . createJsxExpression ( undefined , localReference ) ; } changeTracker . replaceNode ( context . file , node , localReference ) ; } } } const edits = changeTracker . getChanges ( ) ; const renameFilename = node . getSourceFile ( ) . fileName ; const renameLocation = getRenameLocation ( edits , renameFilename , localNameText , true ) ; return { renameFilename , renameLocation , edits } ; function transformFunctionInitializerAndType ( variableType , initializer ) { if ( variableType === undefined ) return { variableType , initializer } ; if ( ! isFunctionExpression ( initializer ) && ! isArrowFunction ( initializer ) || ! ! initializer . typeParameters ) return { variableType , initializer } ; const functionType = checker . getTypeAtLocation ( node ) ; const functionSignature = singleOrUndefined ( checker . getSignaturesOfType ( functionType , SignatureKind . Call ) ) ; if ( ! functionSignature ) return { variableType , initializer } ; if ( ! ! functionSignature . getTypeParameters ( ) ) return { variableType , initializer } ; const parameters = [ ] ; let hasAny = false ; for ( const p of initializer . parameters ) { if ( p . type ) { parameters . push ( p ) ; } else { const paramType = checker . getTypeAtLocation ( p ) ; if ( paramType === checker . getAnyType ( ) ) hasAny = true ; parameters . push ( factory . updateParameterDeclaration ( p , p . decorators , p . modifiers , p . dotDotDotToken , p . name , p . questionToken , p . type || checker . typeToTypeNode ( paramType , scope , NodeBuilderFlags . NoTruncation ) , p . initializer ) ) ; } } if ( hasAny ) return { variableType , initializer } ; variableType = undefined ; if ( isArrowFunction ( initializer ) ) { initializer = factory . updateArrowFunction ( initializer , node . modifiers , initializer . typeParameters , parameters , initializer . type || checker . typeToTypeNode ( functionSignature . getReturnType ( ) , scope , NodeBuilderFlags . NoTruncation ) , initializer . equalsGreaterThanToken , initializer . body ) ; } else { if ( functionSignature && ! ! functionSignature . thisParameter ) { const firstParameter = firstOrUndefined ( parameters ) ; if ( ( ! firstParameter || ( isIdentifier ( firstParameter . name ) && firstParameter . name . escapedText !== "this" ) ) ) { const thisType = checker . getTypeOfSymbolAtLocation ( functionSignature . thisParameter , node ) ; parameters . splice ( 0 , 0 , factory . createParameterDeclaration ( undefined , undefined , undefined , "this" , undefined , checker . typeToTypeNode ( thisType , scope , NodeBuilderFlags . NoTruncation ) ) ) ; } } initializer = factory . updateFunctionExpression ( initializer , node . modifiers , initializer . asteriskToken , initializer . name , initializer . typeParameters , parameters , initializer . type || checker . typeToTypeNode ( functionSignature . getReturnType ( ) , scope , NodeBuilderFlags . NoTruncation ) , initializer . body ) ; } return { variableType , initializer } ; } } function getContainingVariableDeclarationIfInList ( node , scope ) { let prevNode ; while ( node !== undefined && node !== scope ) { if ( isVariableDeclaration ( node ) && node . initializer === prevNode && isVariableDeclarationList ( node . parent ) && node . parent . declarations . length > 1 ) { return node ; } prevNode = node ; node = node . parent ; } } function getFirstDeclaration ( type ) { let firstDeclaration ; const symbol = type . symbol ; if ( symbol && symbol . declarations ) { for ( const declaration of symbol . declarations ) { if ( firstDeclaration === undefined || declaration . pos < firstDeclaration . pos ) { firstDeclaration = declaration ; } } } return firstDeclaration ; } function compareTypesByDeclarationOrder ( { type : type1 , declaration : declaration1 } , { type : type2 , declaration : declaration2 } ) { return compareProperties ( declaration1 , declaration2 , "pos" , compareValues ) || compareStringsCaseSensitive ( type1 . symbol ? type1 . symbol . getName ( ) : "" , type2 . symbol ? type2 . symbol . getName ( ) : "" ) || compareValues ( type1 . id , type2 . id ) ; } function getCalledExpression ( scope , range , functionNameText ) { const functionReference = factory . createIdentifier ( functionNameText ) ; if ( isClassLike ( scope ) ) { const lhs = range . facts & RangeFacts . InStaticRegion ? factory . createIdentifier ( scope . name ! . text ) : factory . createThis ( ) ; return factory . createPropertyAccessExpression ( lhs , functionReference ) ; } else { return functionReference ; } } function transformFunctionBody ( body , exposedVariableDeclarations , writes , substitutions , hasReturn ) { const hasWritesOrVariableDeclarations = writes !== undefined || exposedVariableDeclarations . length > 0 ; if ( isBlock ( body ) && ! hasWritesOrVariableDeclarations && substitutions . size === 0 ) { return { body : factory . createBlock ( body . statements , true ) , returnValueProperty : undefined } ; } let returnValueProperty ; let ignoreReturns = false ; const statements = factory . createNodeArray ( isBlock ( body ) ? body . statements . slice ( 0 ) : [ isStatement ( body ) ? body : factory . createReturnStatement ( skipParentheses ( body as Expression ) ) ] ) ; if ( hasWritesOrVariableDeclarations || substitutions . size ) { const rewrittenStatements = visitNodes ( statements , visitor ) . slice ( ) ; if ( hasWritesOrVariableDeclarations && ! hasReturn && isStatement ( body ) ) { const assignments = getPropertyAssignmentsForWritesAndVariableDeclarations ( exposedVariableDeclarations , writes ) ; if ( assignments . length === 1 ) { rewrittenStatements . push ( factory . createReturnStatement ( assignments [ 0 ] . name ) ) ; } else { rewrittenStatements . push ( factory . createReturnStatement ( factory . createObjectLiteralExpression ( assignments ) ) ) ; } } return { body : factory . createBlock ( rewrittenStatements , true ) , returnValueProperty } ; } else { return { body : factory . createBlock ( statements , true ) , returnValueProperty : undefined } ; } function visitor ( node ) { if ( ! ignoreReturns && isReturnStatement ( node ) && hasWritesOrVariableDeclarations ) { const assignments = getPropertyAssignmentsForWritesAndVariableDeclarations ( exposedVariableDeclarations , writes ) ; if ( node . expression ) { if ( ! returnValueProperty ) { returnValueProperty = "__return" ; } assignments . unshift ( factory . createPropertyAssignment ( returnValueProperty , visitNode ( node . expression , visitor ) ) ) ; } if ( assignments . length === 1 ) { return factory . createReturnStatement ( assignments [ 0 ] . name as Expression ) ; } else { return factory . createReturnStatement ( factory . createObjectLiteralExpression ( assignments ) ) ; } } else { const oldIgnoreReturns = ignoreReturns ; ignoreReturns = ignoreReturns || isFunctionLikeDeclaration ( node ) || isClassLike ( node ) ; const substitution = substitutions . get ( getNodeId ( node ) . toString ( ) ) ; const result = substitution ? getSynthesizedDeepClone ( substitution ) : visitEachChild ( node , visitor , nullTransformationContext ) ; ignoreReturns = oldIgnoreReturns ; return result ; } } } function transformConstantInitializer ( initializer , substitutions ) { return substitutions . size ? visitor ( initializer ) as Expression : initializer ; function visitor ( node ) { const substitution = substitutions . get ( getNodeId ( node ) . toString ( ) ) ; return substitution ? getSynthesizedDeepClone ( substitution ) : visitEachChild ( node , visitor , nullTransformationContext ) ; } } function getStatementsOrClassElements ( scope ) { if ( isFunctionLikeDeclaration ( scope ) ) { const body = scope . body ! ; if ( isBlock ( body ) ) { return body . statements ; } } else if ( isModuleBlock ( scope ) || isSourceFile ( scope ) ) { return scope . statements ; } else if ( isClassLike ( scope ) ) { return scope . members ; } else { assertType < never > ( scope ) ; } return emptyArray ; } /**
     * If `scope` contains a function after `minPos`, then return the first such function.
     * Otherwise, return `undefined`.
     */ function getNodeToInsertFunctionBefore ( minPos , scope ) { return find < Statement | ClassElement > ( getStatementsOrClassElements ( scope ) , child => child . pos >= minPos && isFunctionLikeDeclaration ( child ) && ! isConstructorDeclaration ( child ) ) ; } function getNodeToInsertPropertyBefore ( maxPos , scope ) { const members = scope . members ; Debug . assert ( members . length > 0 , "Found no members" ) ; let prevMember ; let allProperties = true ; for ( const member of members ) { if ( member . pos > maxPos ) { return prevMember || members [ 0 ] ; } if ( allProperties && ! isPropertyDeclaration ( member ) ) { if ( prevMember !== undefined ) { return member ; } allProperties = false ; } prevMember = member ; } if ( prevMember === undefined ) return Debug . fail ( ) ; return prevMember ; } function getNodeToInsertConstantBefore ( node , scope ) { Debug . assert ( ! isClassLike ( scope ) ) ; let prevScope ; for ( let curr = node ; curr !== scope ; curr = curr . parent ) { if ( isScope ( curr ) ) { prevScope = curr ; } } for ( let curr = ( prevScope || node ) . parent ; ; curr = curr . parent ) { if ( isBlockLike ( curr ) ) { let prevStatement ; for ( const statement of curr . statements ) { if ( statement . pos > node . pos ) { break ; } prevStatement = statement ; } if ( ! prevStatement && isCaseClause ( curr ) ) { Debug . assert ( isSwitchStatement ( curr . parent . parent ) , "Grandparent isn't a switch statement" ) ; return curr . parent . parent ; } return Debug . checkDefined ( prevStatement , "prevStatement failed to get set" ) ; } Debug . assert ( curr !== scope , "Didn't encounter a block-like before encountering scope" ) ; } } function getPropertyAssignmentsForWritesAndVariableDeclarations ( exposedVariableDeclarations , writes ) { const variableAssignments = map ( exposedVariableDeclarations , v => factory . createShorthandPropertyAssignment ( v . symbol . name ) ) ; const writeAssignments = map ( writes , w => factory . createShorthandPropertyAssignment ( w . symbol . name ) ) ; return variableAssignments === undefined ? writeAssignments ! : writeAssignments === undefined ? variableAssignments : variableAssignments . concat ( writeAssignments ) ; } function isReadonlyArray ( v ) { return isArray ( v ) ; } /**
     * Produces a range that spans the entirety of nodes, given a selection
     * that might start/end in the middle of nodes.
     *
     * For example, when the user makes a selection like this
     *                     v---v
     *   var someThing = foo + bar;
     *  this returns     ^-------^
     */ function getEnclosingTextRange ( targetRange , sourceFile ) { return isReadonlyArray ( targetRange . range ) ? { pos : first ( targetRange . range ) . getStart ( sourceFile ) , end : last ( targetRange . range ) . getEnd ( ) } : targetRange . range ; } const enum Usage { Read = 1 , Write = 2 } interface UsageEntry { readonly usage : Usage ; readonly symbol : Symbol ; readonly node : Node ; } interface ScopeUsages { readonly usages : ESMap < string , UsageEntry > ; readonly typeParameterUsages : ESMap < string , TypeParameter > ; readonly substitutions : ESMap < string , Node > ; } interface ReadsAndWrites { readonly target : Expression | Block ; readonly usagesPerScope : readonly ScopeUsages [ ] ; readonly functionErrorsPerScope : readonly ( readonly Diagnostic [ ] ) [ ] ; readonly constantErrorsPerScope : readonly ( readonly Diagnostic [ ] ) [ ] ; readonly exposedVariableDeclarations : readonly VariableDeclaration [ ] ; } function collectReadsAndWrites ( targetRange , scopes , enclosingTextRange , sourceFile , checker , cancellationToken ) { const allTypeParameterUsages = new Map < string , TypeParameter > ( ) ; const usagesPerScope = [ ] ; const substitutionsPerScope = [ ] ; const functionErrorsPerScope = [ ] ; const constantErrorsPerScope = [ ] ; const visibleDeclarationsInExtractedRange = [ ] ; const exposedVariableSymbolSet = new Map < string , true > ( ) ; const exposedVariableDeclarations = [ ] ; let firstExposedNonVariableDeclaration ; const expression = ! isReadonlyArray ( targetRange . range ) ? targetRange . range : targetRange . range . length === 1 && isExpressionStatement ( targetRange . range [ 0 ] ) ? targetRange . range [ 0 ] . expression : undefined ; let expressionDiagnostic ; if ( expression === undefined ) { const statements = targetRange . range as readonly Statement [ ] ; const start = first ( statements ) . getStart ( ) ; const end = last ( statements ) . end ; expressionDiagnostic = createFileDiagnostic ( sourceFile , start , end - start , Messages . expressionExpected ) ; } else if ( checker . getTypeAtLocation ( expression ) . flags & ( TypeFlags . Void | TypeFlags . Never ) ) { expressionDiagnostic = createDiagnosticForNode ( expression , Messages . uselessConstantType ) ; } for ( const scope of scopes ) { usagesPerScope . push ( { usages : new Map < string , UsageEntry > ( ) , typeParameterUsages : new Map < string , TypeParameter > ( ) , substitutions : new Map < string , Expression > ( ) } ) ; substitutionsPerScope . push ( new Map < string , Expression > ( ) ) ; functionErrorsPerScope . push ( [ ] ) ; const constantErrors = [ ] ; if ( expressionDiagnostic ) { constantErrors . push ( expressionDiagnostic ) ; } if ( isClassLike ( scope ) && isInJSFile ( scope ) ) { constantErrors . push ( createDiagnosticForNode ( scope , Messages . cannotExtractToJSClass ) ) ; } if ( isArrowFunction ( scope ) && ! isBlock ( scope . body ) ) { constantErrors . push ( createDiagnosticForNode ( scope , Messages . cannotExtractToExpressionArrowFunction ) ) ; } constantErrorsPerScope . push ( constantErrors ) ; } const seenUsages = new Map < string , Usage > ( ) ; const target = isReadonlyArray ( targetRange . range ) ? factory . createBlock ( targetRange . range ) : targetRange . range ; const unmodifiedNode = isReadonlyArray ( targetRange . range ) ? first ( targetRange . range ) : targetRange . range ; const inGenericContext = isInGenericContext ( unmodifiedNode ) ; collectUsages ( target ) ; if ( inGenericContext && ! isReadonlyArray ( targetRange . range ) ) { const contextualType = checker . getContextualType ( targetRange . range ) ! ; recordTypeParameterUsages ( contextualType ) ; } if ( allTypeParameterUsages . size > 0 ) { const seenTypeParameterUsages = new Map < string , TypeParameter > ( ) ; let i = 0 ; for ( let curr = unmodifiedNode ; curr !== undefined && i < scopes . length ; curr = curr . parent ) { if ( curr === scopes [ i ] ) { seenTypeParameterUsages . forEach ( ( typeParameter , id ) => { usagesPerScope [ i ] . typeParameterUsages . set ( id , typeParameter ) ; } ) ; i ++ ; } if ( isDeclarationWithTypeParameters ( curr ) ) { for ( const typeParameterDecl of getEffectiveTypeParameterDeclarations ( curr ) ) { const typeParameter = checker . getTypeAtLocation ( typeParameterDecl ) as TypeParameter ; if ( allTypeParameterUsages . has ( typeParameter . id . toString ( ) ) ) { seenTypeParameterUsages . set ( typeParameter . id . toString ( ) , typeParameter ) ; } } } } Debug . assert ( i === scopes . length , "Should have iterated all scopes" ) ; } if ( visibleDeclarationsInExtractedRange . length ) { const containingLexicalScopeOfExtraction = isBlockScope ( scopes [ 0 ] , scopes [ 0 ] . parent ) ? scopes [ 0 ] : getEnclosingBlockScopeContainer ( scopes [ 0 ] ) ; forEachChild ( containingLexicalScopeOfExtraction , checkForUsedDeclarations ) ; } for ( let i = 0 ; i < scopes . length ; i ++ ) { const scopeUsages = usagesPerScope [ i ] ; if ( i > 0 && ( scopeUsages . usages . size > 0 || scopeUsages . typeParameterUsages . size > 0 ) ) { const errorNode = isReadonlyArray ( targetRange . range ) ? targetRange . range [ 0 ] : targetRange . range ; constantErrorsPerScope [ i ] . push ( createDiagnosticForNode ( errorNode , Messages . cannotAccessVariablesFromNestedScopes ) ) ; } let hasWrite = false ; let readonlyClassPropertyWrite ; usagesPerScope [ i ] . usages . forEach ( value => { if ( value . usage === Usage . Write ) { hasWrite = true ; if ( value . symbol . flags & SymbolFlags . ClassMember && value . symbol . valueDeclaration && hasEffectiveModifier ( value . symbol . valueDeclaration , ModifierFlags . Readonly ) ) { readonlyClassPropertyWrite = value . symbol . valueDeclaration ; } } } ) ; Debug . assert ( isReadonlyArray ( targetRange . range ) || exposedVariableDeclarations . length === 0 , "No variable declarations expected if something was extracted" ) ; if ( hasWrite && ! isReadonlyArray ( targetRange . range ) ) { const diag = createDiagnosticForNode ( targetRange . range , Messages . cannotWriteInExpression ) ; functionErrorsPerScope [ i ] . push ( diag ) ; constantErrorsPerScope [ i ] . push ( diag ) ; } else if ( readonlyClassPropertyWrite && i > 0 ) { const diag = createDiagnosticForNode ( readonlyClassPropertyWrite , Messages . cannotExtractReadonlyPropertyInitializerOutsideConstructor ) ; functionErrorsPerScope [ i ] . push ( diag ) ; constantErrorsPerScope [ i ] . push ( diag ) ; } else if ( firstExposedNonVariableDeclaration ) { const diag = createDiagnosticForNode ( firstExposedNonVariableDeclaration , Messages . cannotExtractExportedEntity ) ; functionErrorsPerScope [ i ] . push ( diag ) ; constantErrorsPerScope [ i ] . push ( diag ) ; } } return { target , usagesPerScope , functionErrorsPerScope , constantErrorsPerScope , exposedVariableDeclarations } ; function isInGenericContext ( node ) { return ! ! findAncestor ( node , n => isDeclarationWithTypeParameters ( n ) && getEffectiveTypeParameterDeclarations ( n ) . length !== 0 ) ; } function recordTypeParameterUsages ( type ) { const symbolWalker = checker . getSymbolWalker ( ( ) => ( cancellationToken . throwIfCancellationRequested ( ) , true ) ) ; const { visitedTypes } = symbolWalker . walkType ( type ) ; for ( const visitedType of visitedTypes ) { if ( visitedType . isTypeParameter ( ) ) { allTypeParameterUsages . set ( visitedType . id . toString ( ) , visitedType ) ; } } } function collectUsages ( node , valueUsage = Usage . Read ) { if ( inGenericContext ) { const type = checker . getTypeAtLocation ( node ) ; recordTypeParameterUsages ( type ) ; } if ( isDeclaration ( node ) && node . symbol ) { visibleDeclarationsInExtractedRange . push ( node ) ; } if ( isAssignmentExpression ( node ) ) { collectUsages ( node . left , Usage . Write ) ; collectUsages ( node . right ) ; } else if ( isUnaryExpressionWithWrite ( node ) ) { collectUsages ( node . operand , Usage . Write ) ; } else if ( isPropertyAccessExpression ( node ) || isElementAccessExpression ( node ) ) { forEachChild ( node , collectUsages ) ; } else if ( isIdentifier ( node ) ) { if ( ! node . parent ) { return ; } if ( isQualifiedName ( node . parent ) && node !== node . parent . left ) { return ; } if ( isPropertyAccessExpression ( node . parent ) && node !== node . parent . expression ) { return ; } recordUsage ( node , valueUsage , isPartOfTypeNode ( node ) ) ; } else { forEachChild ( node , collectUsages ) ; } } function recordUsage ( n , usage , isTypeNode ) { const symbolId = recordUsagebySymbol ( n , usage , isTypeNode ) ; if ( symbolId ) { for ( let i = 0 ; i < scopes . length ; i ++ ) { const substitution = substitutionsPerScope [ i ] . get ( symbolId ) ; if ( substitution ) { usagesPerScope [ i ] . substitutions . set ( getNodeId ( n ) . toString ( ) , substitution ) ; } } } } function recordUsagebySymbol ( identifier , usage , isTypeName ) { const symbol = getSymbolReferencedByIdentifier ( identifier ) ; if ( ! symbol ) { return undefined ; } const symbolId = getSymbolId ( symbol ) . toString ( ) ; const lastUsage = seenUsages . get ( symbolId ) ; if ( lastUsage && lastUsage >= usage ) { return symbolId ; } seenUsages . set ( symbolId , usage ) ; if ( lastUsage ) { for ( const perScope of usagesPerScope ) { const prevEntry = perScope . usages . get ( identifier . text ) ; if ( prevEntry ) { perScope . usages . set ( identifier . text , { usage , symbol , node : identifier } ) ; } } return symbolId ; } const decls = symbol . getDeclarations ( ) ; const declInFile = decls && find ( decls , d => d . getSourceFile ( ) === sourceFile ) ; if ( ! declInFile ) { return undefined ; } if ( rangeContainsStartEnd ( enclosingTextRange , declInFile . getStart ( ) , declInFile . end ) ) { return undefined ; } if ( targetRange . facts & RangeFacts . IsGenerator && usage === Usage . Write ) { const diag = createDiagnosticForNode ( identifier , Messages . cannotExtractRangeThatContainsWritesToReferencesLocatedOutsideOfTheTargetRangeInGenerators ) ; for ( const errors of functionErrorsPerScope ) { errors . push ( diag ) ; } for ( const errors of constantErrorsPerScope ) { errors . push ( diag ) ; } } for ( let i = 0 ; i < scopes . length ; i ++ ) { const scope = scopes [ i ] ; const resolvedSymbol = checker . resolveName ( symbol . name , scope , symbol . flags , false ) ; if ( resolvedSymbol === symbol ) { continue ; } if ( ! substitutionsPerScope [ i ] . has ( symbolId ) ) { const substitution = tryReplaceWithQualifiedNameOrPropertyAccess ( symbol . exportSymbol || symbol , scope , isTypeName ) ; if ( substitution ) { substitutionsPerScope [ i ] . set ( symbolId , substitution ) ; } else if ( isTypeName ) { if ( ! ( symbol . flags & SymbolFlags . TypeParameter ) ) { const diag = createDiagnosticForNode ( identifier , Messages . typeWillNotBeVisibleInTheNewScope ) ; functionErrorsPerScope [ i ] . push ( diag ) ; constantErrorsPerScope [ i ] . push ( diag ) ; } } else { usagesPerScope [ i ] . usages . set ( identifier . text , { usage , symbol , node : identifier } ) ; } } } return symbolId ; } function checkForUsedDeclarations ( node ) { if ( node === targetRange . range || ( isReadonlyArray ( targetRange . range ) && targetRange . range . indexOf ( node as Statement ) >= 0 ) ) { return ; } const sym = isIdentifier ( node ) ? getSymbolReferencedByIdentifier ( node ) : checker . getSymbolAtLocation ( node ) ; if ( sym ) { const decl = find ( visibleDeclarationsInExtractedRange , d => d . symbol === sym ) ; if ( decl ) { if ( isVariableDeclaration ( decl ) ) { const idString = decl . symbol . id ! . toString ( ) ; if ( ! exposedVariableSymbolSet . has ( idString ) ) { exposedVariableDeclarations . push ( decl ) ; exposedVariableSymbolSet . set ( idString , true ) ; } } else { firstExposedNonVariableDeclaration = firstExposedNonVariableDeclaration || decl ; } } } forEachChild ( node , checkForUsedDeclarations ) ; } /**
         * Return the symbol referenced by an identifier (even if it declares a different symbol).
         */ function getSymbolReferencedByIdentifier ( identifier ) { return identifier . parent && isShorthandPropertyAssignment ( identifier . parent ) && identifier . parent . name === identifier ? checker . getShorthandAssignmentValueSymbol ( identifier . parent ) : checker . getSymbolAtLocation ( identifier ) ; } function tryReplaceWithQualifiedNameOrPropertyAccess ( symbol , scopeDecl , isTypeNode ) { if ( ! symbol ) { return undefined ; } const decls = symbol . getDeclarations ( ) ; if ( decls && decls . some ( d => d . parent === scopeDecl ) ) { return factory . createIdentifier ( symbol . name ) ; } const prefix = tryReplaceWithQualifiedNameOrPropertyAccess ( symbol . parent , scopeDecl , isTypeNode ) ; if ( prefix === undefined ) { return undefined ; } return isTypeNode ? factory . createQualifiedName ( prefix as EntityName , factory . createIdentifier ( symbol . name ) ) : factory . createPropertyAccessExpression ( prefix as Expression , symbol . name ) ; } } function getExtractableParent ( node ) { return findAncestor ( node , node => node . parent && isExtractableExpression ( node ) && ! isBinaryExpression ( node . parent ) ) ; } /**
     * Computes whether or not a node represents an expression in a position where it could
     * be extracted.
     * The isExpression() in utilities.ts returns some false positives we need to handle,
     * such as `import x from 'y'` -- the 'y' is a StringLiteral but is *not* an expression
     * in the sense of something that you could extract on
     */ function isExtractableExpression ( node ) { const { parent } = node ; switch ( parent . kind ) { case SyntaxKind . EnumMember : return false ; } switch ( node . kind ) { case SyntaxKind . StringLiteral : return parent . kind !== SyntaxKind . ImportDeclaration && parent . kind !== SyntaxKind . ImportSpecifier ; case SyntaxKind . SpreadElement : case SyntaxKind . ObjectBindingPattern : case SyntaxKind . BindingElement : return false ; case SyntaxKind . Identifier : return parent . kind !== SyntaxKind . BindingElement && parent . kind !== SyntaxKind . ImportSpecifier && parent . kind !== SyntaxKind . ExportSpecifier ; } return true ; } function isBlockLike ( node ) { switch ( node . kind ) { case SyntaxKind . Block : case SyntaxKind . SourceFile : case SyntaxKind . ModuleBlock : case SyntaxKind . CaseClause : return true ; default : return false ; } } function isInJSXContent ( node ) { return ( isJsxElement ( node ) || isJsxSelfClosingElement ( node ) || isJsxFragment ( node ) ) && ( isJsxElement ( node . parent ) || isJsxFragment ( node . parent ) ) ; } }