• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import {
2    __String, ANONYMOUS, ApplicableRefactorInfo, arrayFrom, assertType, BindingElement, Block, BlockLike,
3    BreakStatement, CancellationToken, canHaveModifiers, CharacterCodes, ClassElement, ClassLikeDeclaration, codefix,
4    compareProperties, compareStringsCaseSensitive, compareValues, contains, ContinueStatement, createDiagnosticForNode,
5    createFileDiagnostic, Debug, Declaration, Diagnostic, DiagnosticCategory, DiagnosticMessage, Diagnostics, EmitFlags,
6    emptyArray, EntityName, ESMap, Expression, ExpressionStatement, factory, find, findAncestor,
7    findFirstNonJsxWhitespaceToken, findTokenOnLeftOfPosition, first, firstOrUndefined, forEachChild,
8    formatStringFromArgs, FunctionDeclaration, FunctionLikeDeclaration, getContainingClass, getContainingFunction,
9    getEffectiveTypeParameterDeclarations, getEmitScriptTarget, getEnclosingBlockScopeContainer,
10    getLocaleSpecificMessage, getModifiers, getNodeId, getParentNodeInSpan, getRefactorContextSpan, getRenameLocation,
11    getSymbolId, getSynthesizedDeepClone, getThisContainer, getUniqueName, hasEffectiveModifier, hasSyntacticModifier,
12    Identifier, isArray, isArrowFunction, isAssignmentExpression, isBinaryExpression, isBlock, isBlockScope,
13    isCaseClause, isClassLike, isConstructorDeclaration, isDeclaration, isDeclarationWithTypeParameters,
14    isElementAccessExpression, isExpression, isExpressionNode, isExpressionStatement, isFunctionBody,
15    isFunctionExpression, isFunctionLike, isFunctionLikeDeclaration, isIdentifier, isInJSFile, isIterationStatement,
16    isJsxAttribute, isJsxElement, isJsxFragment, isJsxSelfClosingElement, isKeyword, isModuleBlock, isParenthesizedTypeNode,
17    isPartOfTypeNode, isPrivateIdentifier, isPropertyAccessExpression, isPropertyDeclaration, isQualifiedName,
18    isReturnStatement, isShorthandPropertyAssignment, isSourceFile, isStatement, isStatic, isStringLiteral, isSwitchStatement,
19    isThis, isUnaryExpressionWithWrite, isUnionTypeNode, isVariableDeclaration, isVariableDeclarationList, isVariableStatement,
20    LabeledStatement, last, map, Map, MethodDeclaration, Modifier, ModifierFlags, ModuleBlock, NamedDeclaration, Node,
21    NodeBuilderFlags, NodeFlags, nullTransformationContext, ObjectLiteralElementLike, ParameterDeclaration, positionIsSynthesized,
22    PropertyAccessExpression, rangeContainsStartEnd, ReadonlyESMap, RefactorActionInfo, RefactorContext, RefactorEditInfo, setEmitFlags,
23    ShorthandPropertyAssignment, SignatureKind, singleOrUndefined, skipParentheses, SourceFile, Statement,
24    StringLiteral, suppressLeadingAndTrailingTrivia, Symbol, SymbolFlags, SyntaxKind, textChanges, TextRange, TextSpan,
25    textSpanEnd, TryStatement, Type, TypeChecker, TypeElement, TypeFlags, TypeLiteralNode, TypeNode, TypeParameter,
26    TypeParameterDeclaration, VariableDeclaration, visitEachChild, visitNode, visitNodes, VisitResult,
27} from "../_namespaces/ts";
28import { refactorKindBeginsWith, registerRefactor } from "../_namespaces/ts.refactor";
29
30const refactorName = "Extract Symbol";
31
32const extractConstantAction = {
33    name: "Extract Constant",
34    description: getLocaleSpecificMessage(Diagnostics.Extract_constant),
35    kind: "refactor.extract.constant",
36};
37const extractFunctionAction = {
38    name: "Extract Function",
39    description: getLocaleSpecificMessage(Diagnostics.Extract_function),
40    kind: "refactor.extract.function",
41};
42registerRefactor(refactorName, {
43    kinds: [
44        extractConstantAction.kind,
45        extractFunctionAction.kind
46    ],
47    getEditsForAction: getRefactorEditsToExtractSymbol,
48    getAvailableActions: getRefactorActionsToExtractSymbol,
49});
50
51/**
52 * Compute the associated code actions
53 * Exported for tests.
54 *
55 * @internal
56 */
57export function getRefactorActionsToExtractSymbol(context: RefactorContext): readonly ApplicableRefactorInfo[] {
58    const requestedRefactor = context.kind;
59    const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context), context.triggerReason === "invoked");
60    const targetRange = rangeToExtract.targetRange;
61
62    if (targetRange === undefined) {
63        if (!rangeToExtract.errors || rangeToExtract.errors.length === 0 || !context.preferences.provideRefactorNotApplicableReason) {
64            return emptyArray;
65        }
66
67        const errors = [];
68        if (refactorKindBeginsWith(extractFunctionAction.kind, requestedRefactor)) {
69            errors.push({
70                name: refactorName,
71                description: extractFunctionAction.description,
72                actions: [{ ...extractFunctionAction, notApplicableReason: getStringError(rangeToExtract.errors) }]
73            });
74        }
75        if (refactorKindBeginsWith(extractConstantAction.kind, requestedRefactor)) {
76            errors.push({
77                name: refactorName,
78                description: extractConstantAction.description,
79                actions: [{ ...extractConstantAction, notApplicableReason: getStringError(rangeToExtract.errors) }]
80            });
81        }
82        return errors;
83    }
84
85    const extractions = getPossibleExtractions(targetRange, context);
86    if (extractions === undefined) {
87        // No extractions possible
88        return emptyArray;
89    }
90
91    const functionActions: RefactorActionInfo[] = [];
92    const usedFunctionNames = new Map<string, boolean>();
93    let innermostErrorFunctionAction: RefactorActionInfo | undefined;
94
95    const constantActions: RefactorActionInfo[] = [];
96    const usedConstantNames = new Map<string, boolean>();
97    let innermostErrorConstantAction: RefactorActionInfo | undefined;
98
99    let i = 0;
100    for (const { functionExtraction, constantExtraction } of extractions) {
101        if (refactorKindBeginsWith(extractFunctionAction.kind, requestedRefactor)) {
102            const description = functionExtraction.description;
103            if (functionExtraction.errors.length === 0) {
104                // Don't issue refactorings with duplicated names.
105                // Scopes come back in "innermost first" order, so extractions will
106                // preferentially go into nearer scopes
107                if (!usedFunctionNames.has(description)) {
108                    usedFunctionNames.set(description, true);
109                    functionActions.push({
110                        description,
111                        name: `function_scope_${i}`,
112                        kind: extractFunctionAction.kind
113                    });
114                }
115            }
116            else if (!innermostErrorFunctionAction) {
117                innermostErrorFunctionAction = {
118                    description,
119                    name: `function_scope_${i}`,
120                    notApplicableReason: getStringError(functionExtraction.errors),
121                    kind: extractFunctionAction.kind
122                };
123            }
124        }
125
126        if (refactorKindBeginsWith(extractConstantAction.kind, requestedRefactor)) {
127            const description = constantExtraction.description;
128            if (constantExtraction.errors.length === 0) {
129                // Don't issue refactorings with duplicated names.
130                // Scopes come back in "innermost first" order, so extractions will
131                // preferentially go into nearer scopes
132                if (!usedConstantNames.has(description)) {
133                    usedConstantNames.set(description, true);
134                    constantActions.push({
135                        description,
136                        name: `constant_scope_${i}`,
137                        kind: extractConstantAction.kind
138                    });
139                }
140            }
141            else if (!innermostErrorConstantAction) {
142                innermostErrorConstantAction = {
143                    description,
144                    name: `constant_scope_${i}`,
145                    notApplicableReason: getStringError(constantExtraction.errors),
146                    kind: extractConstantAction.kind
147                };
148            }
149        }
150
151        // *do* increment i anyway because we'll look for the i-th scope
152        // later when actually doing the refactoring if the user requests it
153        i++;
154    }
155
156    const infos: ApplicableRefactorInfo[] = [];
157
158    if (functionActions.length) {
159        infos.push({
160            name: refactorName,
161            description: getLocaleSpecificMessage(Diagnostics.Extract_function),
162            actions: functionActions,
163        });
164    }
165    else if (context.preferences.provideRefactorNotApplicableReason && innermostErrorFunctionAction) {
166        infos.push({
167            name: refactorName,
168            description: getLocaleSpecificMessage(Diagnostics.Extract_function),
169            actions: [ innermostErrorFunctionAction ]
170        });
171    }
172
173    if (constantActions.length) {
174        infos.push({
175            name: refactorName,
176            description: getLocaleSpecificMessage(Diagnostics.Extract_constant),
177            actions: constantActions
178        });
179    }
180    else if (context.preferences.provideRefactorNotApplicableReason && innermostErrorConstantAction) {
181        infos.push({
182            name: refactorName,
183            description: getLocaleSpecificMessage(Diagnostics.Extract_constant),
184            actions: [ innermostErrorConstantAction ]
185        });
186    }
187
188    return infos.length ? infos : emptyArray;
189
190    function getStringError(errors: readonly Diagnostic[]) {
191        let error = errors[0].messageText;
192        if (typeof error !== "string") {
193            error = error.messageText;
194        }
195        return error;
196    }
197}
198
199/**
200 * Exported for tests
201 *
202 * @internal
203 */
204export function getRefactorEditsToExtractSymbol(context: RefactorContext, actionName: string): RefactorEditInfo | undefined {
205    const rangeToExtract = getRangeToExtract(context.file, getRefactorContextSpan(context));
206    const targetRange = rangeToExtract.targetRange!; // TODO:GH#18217
207
208    const parsedFunctionIndexMatch = /^function_scope_(\d+)$/.exec(actionName);
209    if (parsedFunctionIndexMatch) {
210        const index = +parsedFunctionIndexMatch[1];
211        Debug.assert(isFinite(index), "Expected to parse a finite number from the function scope index");
212        return getFunctionExtractionAtIndex(targetRange, context, index);
213    }
214
215    const parsedConstantIndexMatch = /^constant_scope_(\d+)$/.exec(actionName);
216    if (parsedConstantIndexMatch) {
217        const index = +parsedConstantIndexMatch[1];
218        Debug.assert(isFinite(index), "Expected to parse a finite number from the constant scope index");
219        return getConstantExtractionAtIndex(targetRange, context, index);
220    }
221
222    Debug.fail("Unrecognized action name");
223}
224
225// Move these into diagnostic messages if they become user-facing
226/** @internal */
227export namespace Messages {
228    function createMessage(message: string): DiagnosticMessage {
229        return { message, code: 0, category: DiagnosticCategory.Message, key: message };
230    }
231
232    export const cannotExtractRange: DiagnosticMessage = createMessage("Cannot extract range.");
233    export const cannotExtractImport: DiagnosticMessage = createMessage("Cannot extract import statement.");
234    export const cannotExtractSuper: DiagnosticMessage = createMessage("Cannot extract super call.");
235    export const cannotExtractJSDoc: DiagnosticMessage = createMessage("Cannot extract JSDoc.");
236    export const cannotExtractEmpty: DiagnosticMessage = createMessage("Cannot extract empty range.");
237    export const expressionExpected: DiagnosticMessage = createMessage("expression expected.");
238    export const uselessConstantType: DiagnosticMessage = createMessage("No reason to extract constant of type.");
239    export const statementOrExpressionExpected: DiagnosticMessage = createMessage("Statement or expression expected.");
240    export const cannotExtractRangeContainingConditionalBreakOrContinueStatements: DiagnosticMessage = createMessage("Cannot extract range containing conditional break or continue statements.");
241    export const cannotExtractRangeContainingConditionalReturnStatement: DiagnosticMessage = createMessage("Cannot extract range containing conditional return statement.");
242    export const cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange: DiagnosticMessage = createMessage("Cannot extract range containing labeled break or continue with target outside of the range.");
243    export const cannotExtractRangeThatContainsWritesToReferencesLocatedOutsideOfTheTargetRangeInGenerators: DiagnosticMessage = createMessage("Cannot extract range containing writes to references located outside of the target range in generators.");
244    export const typeWillNotBeVisibleInTheNewScope = createMessage("Type will not visible in the new scope.");
245    export const functionWillNotBeVisibleInTheNewScope = createMessage("Function will not visible in the new scope.");
246    export const cannotExtractIdentifier = createMessage("Select more than a single identifier.");
247    export const cannotExtractExportedEntity = createMessage("Cannot extract exported declaration");
248    export const cannotWriteInExpression = createMessage("Cannot write back side-effects when extracting an expression");
249    export const cannotExtractReadonlyPropertyInitializerOutsideConstructor = createMessage("Cannot move initialization of read-only class property outside of the constructor");
250    export const cannotExtractAmbientBlock = createMessage("Cannot extract code from ambient contexts");
251    export const cannotAccessVariablesFromNestedScopes = createMessage("Cannot access variables from nested scopes");
252    export const cannotExtractToJSClass = createMessage("Cannot extract constant to a class scope in JS");
253    export const cannotExtractToExpressionArrowFunction = createMessage("Cannot extract constant to an arrow function without a block");
254    export const cannotExtractFunctionsContainingThisToMethod = createMessage("Cannot extract functions containing this to method");
255}
256
257/** @internal */
258export enum RangeFacts {
259    None = 0,
260    HasReturn = 1 << 0,
261    IsGenerator = 1 << 1,
262    IsAsyncFunction = 1 << 2,
263    UsesThis = 1 << 3,
264    UsesThisInFunction = 1 << 4,
265    /**
266     * The range is in a function which needs the 'static' modifier in a class
267     */
268    InStaticRegion = 1 << 5,
269}
270
271/**
272 * Represents an expression or a list of statements that should be extracted with some extra information
273 *
274 * @internal
275 */
276export interface TargetRange {
277    readonly range: Expression | Statement[];
278    readonly facts: RangeFacts;
279    /**
280     * If `this` is referring to a function instead of class, we need to retrieve its type.
281     */
282    readonly thisNode: Node | undefined;
283}
284
285/**
286 * Result of 'getRangeToExtract' operation: contains either a range or a list of errors
287 *
288 * @internal
289 */
290export type RangeToExtract = {
291    readonly targetRange?: never;
292    readonly errors: readonly Diagnostic[];
293} | {
294    readonly targetRange: TargetRange;
295    readonly errors?: never;
296};
297
298/*
299 * Scopes that can store newly extracted method
300 */
301type Scope = FunctionLikeDeclaration | SourceFile | ModuleBlock | ClassLikeDeclaration;
302
303// exported only for tests
304/**
305 * getRangeToExtract takes a span inside a text file and returns either an expression or an array
306 * of statements representing the minimum set of nodes needed to extract the entire span. This
307 * process may fail, in which case a set of errors is returned instead. These errors are shown to
308 * users if they have the provideRefactorNotApplicableReason option set.
309 *
310 * @internal
311 */
312export function getRangeToExtract(sourceFile: SourceFile, span: TextSpan, invoked = true): RangeToExtract {
313    const { length } = span;
314    if (length === 0 && !invoked) {
315        return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractEmpty)] };
316    }
317    const cursorRequest = length === 0 && invoked;
318
319    const startToken = findFirstNonJsxWhitespaceToken(sourceFile, span.start);
320    const endToken = findTokenOnLeftOfPosition(sourceFile, textSpanEnd(span));
321    /* If the refactoring command is invoked through a keyboard action it's safe to assume that the user is actively looking for
322    refactoring actions at the span location. As they may not know the exact range that will trigger a refactoring, we expand the
323    searched span to cover a real node range making it more likely that something useful will show up. */
324    const adjustedSpan = startToken && endToken && invoked ? getAdjustedSpanFromNodes(startToken, endToken, sourceFile) : span;
325
326    // Walk up starting from the the start position until we find a non-SourceFile node that subsumes the selected span.
327    // This may fail (e.g. you select two statements in the root of a source file)
328    const start = cursorRequest ? getExtractableParent(startToken) : getParentNodeInSpan(startToken, sourceFile, adjustedSpan);
329
330    // Do the same for the ending position
331    const end = cursorRequest ? start : getParentNodeInSpan(endToken, sourceFile, adjustedSpan);
332
333    // We'll modify these flags as we walk the tree to collect data
334    // about what things need to be done as part of the extraction.
335    let rangeFacts = RangeFacts.None;
336
337    let thisNode: Node | undefined;
338
339    if (!start || !end) {
340        // cannot find either start or end node
341        return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] };
342    }
343
344    if (start.flags & NodeFlags.JSDoc) {
345        return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractJSDoc)] };
346    }
347
348    if (start.parent !== end.parent) {
349        // start and end nodes belong to different subtrees
350        return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] };
351    }
352
353    if (start !== end) {
354        // start and end should be statements and parent should be either block or a source file
355        if (!isBlockLike(start.parent)) {
356            return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] };
357        }
358        const statements: Statement[] = [];
359        for (const statement of start.parent.statements) {
360            if (statement === start || statements.length) {
361                const errors = checkNode(statement);
362                if (errors) {
363                    return { errors };
364                }
365                statements.push(statement);
366            }
367            if (statement === end) {
368                break;
369            }
370        }
371
372        if (!statements.length) {
373            // https://github.com/Microsoft/TypeScript/issues/20559
374            // Ranges like [|case 1: break;|] will fail to populate `statements` because
375            // they will never find `start` in `start.parent.statements`.
376            // Consider: We could support ranges like [|case 1:|] by refining them to just
377            // the expression.
378            return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] };
379        }
380
381        return { targetRange: { range: statements, facts: rangeFacts, thisNode } };
382    }
383
384    if (isReturnStatement(start) && !start.expression) {
385        // Makes no sense to extract an expression-less return statement.
386        return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.cannotExtractRange)] };
387    }
388
389    // We have a single node (start)
390    const node = refineNode(start);
391
392    const errors = checkRootNode(node) || checkNode(node);
393    if (errors) {
394        return { errors };
395    }
396    return { targetRange: { range: getStatementOrExpressionRange(node)!, facts: rangeFacts, thisNode } }; // TODO: GH#18217
397
398    /**
399     * Attempt to refine the extraction node (generally, by shrinking it) to produce better results.
400     * @param node The unrefined extraction node.
401     */
402    function refineNode(node: Node): Node {
403        if (isReturnStatement(node)) {
404            if (node.expression) {
405                return node.expression;
406            }
407        }
408        else if (isVariableStatement(node) || isVariableDeclarationList(node)) {
409            const declarations = isVariableStatement(node) ? node.declarationList.declarations : node.declarations;
410            let numInitializers = 0;
411            let lastInitializer: Expression | undefined;
412            for (const declaration of declarations) {
413                if (declaration.initializer) {
414                    numInitializers++;
415                    lastInitializer = declaration.initializer;
416                }
417            }
418            if (numInitializers === 1) {
419                return lastInitializer!;
420            }
421            // No special handling if there are multiple initializers.
422        }
423        else if (isVariableDeclaration(node)) {
424            if (node.initializer) {
425                return node.initializer;
426            }
427        }
428        return node;
429    }
430
431    function checkRootNode(node: Node): Diagnostic[] | undefined {
432        if (isIdentifier(isExpressionStatement(node) ? node.expression : node)) {
433            return [createDiagnosticForNode(node, Messages.cannotExtractIdentifier)];
434        }
435        return undefined;
436    }
437
438    function checkForStaticContext(nodeToCheck: Node, containingClass: Node) {
439        let current: Node = nodeToCheck;
440        while (current !== containingClass) {
441            if (current.kind === SyntaxKind.PropertyDeclaration) {
442                if (isStatic(current)) {
443                    rangeFacts |= RangeFacts.InStaticRegion;
444                }
445                break;
446            }
447            else if (current.kind === SyntaxKind.Parameter) {
448                const ctorOrMethod = getContainingFunction(current)!;
449                if (ctorOrMethod.kind === SyntaxKind.Constructor) {
450                    rangeFacts |= RangeFacts.InStaticRegion;
451                }
452                break;
453            }
454            else if (current.kind === SyntaxKind.MethodDeclaration) {
455                if (isStatic(current)) {
456                    rangeFacts |= RangeFacts.InStaticRegion;
457                }
458            }
459            current = current.parent;
460        }
461    }
462
463    // Verifies whether we can actually extract this node or not.
464    function checkNode(nodeToCheck: Node): Diagnostic[] | undefined {
465        const enum PermittedJumps {
466            None = 0,
467            Break = 1 << 0,
468            Continue = 1 << 1,
469            Return = 1 << 2
470        }
471
472        // We believe it's true because the node is from the (unmodified) tree.
473        Debug.assert(nodeToCheck.pos <= nodeToCheck.end, "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809 (1)");
474
475        // For understanding how skipTrivia functioned:
476        Debug.assert(!positionIsSynthesized(nodeToCheck.pos), "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809 (2)");
477
478        if (!isStatement(nodeToCheck) && !(isExpressionNode(nodeToCheck) && isExtractableExpression(nodeToCheck)) && !isStringLiteralJsxAttribute(nodeToCheck)) {
479            return [createDiagnosticForNode(nodeToCheck, Messages.statementOrExpressionExpected)];
480        }
481
482        if (nodeToCheck.flags & NodeFlags.Ambient) {
483            return [createDiagnosticForNode(nodeToCheck, Messages.cannotExtractAmbientBlock)];
484        }
485
486        // If we're in a class, see whether we're in a static region (static property initializer, static method, class constructor parameter default)
487        const containingClass = getContainingClass(nodeToCheck);
488        if (containingClass) {
489            checkForStaticContext(nodeToCheck, containingClass);
490        }
491
492        let errors: Diagnostic[] | undefined;
493        let permittedJumps = PermittedJumps.Return;
494        let seenLabels: __String[];
495
496        visit(nodeToCheck);
497
498        if (rangeFacts & RangeFacts.UsesThis) {
499            const container = getThisContainer(nodeToCheck, /** includeArrowFunctions */ false);
500            if (
501                container.kind === SyntaxKind.FunctionDeclaration ||
502                (container.kind === SyntaxKind.MethodDeclaration && container.parent.kind === SyntaxKind.ObjectLiteralExpression) ||
503                container.kind === SyntaxKind.FunctionExpression
504            ) {
505                rangeFacts |= RangeFacts.UsesThisInFunction;
506            }
507        }
508
509        return errors;
510
511        function visit(node: Node) {
512            if (errors) {
513                // already found an error - can stop now
514                return true;
515            }
516
517            if (isDeclaration(node)) {
518                const declaringNode = (node.kind === SyntaxKind.VariableDeclaration) ? node.parent.parent : node;
519                if (hasSyntacticModifier(declaringNode, ModifierFlags.Export)) {
520                    // TODO: GH#18217 Silly to use `errors ||` since it's definitely not defined (see top of `visit`)
521                    // Also, if we're only pushing one error, just use `let error: Diagnostic | undefined`!
522                    // Also TODO: GH#19956
523                    (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractExportedEntity));
524                    return true;
525                }
526            }
527
528            // Some things can't be extracted in certain situations
529            switch (node.kind) {
530                case SyntaxKind.ImportDeclaration:
531                    (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractImport));
532                    return true;
533                case SyntaxKind.ExportAssignment:
534                    (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractExportedEntity));
535                    return true;
536                case SyntaxKind.SuperKeyword:
537                    // For a super *constructor call*, we have to be extracting the entire class,
538                    // but a super *method call* simply implies a 'this' reference
539                    if (node.parent.kind === SyntaxKind.CallExpression) {
540                        // Super constructor call
541                        const containingClass = getContainingClass(node);
542                        if (containingClass === undefined || containingClass.pos < span.start || containingClass.end >= (span.start + span.length)) {
543                            (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractSuper));
544                            return true;
545                        }
546                    }
547                    else {
548                        rangeFacts |= RangeFacts.UsesThis;
549                        thisNode = node;
550                    }
551                    break;
552                case SyntaxKind.ArrowFunction:
553                    // check if arrow function uses this
554                    forEachChild(node, function check(n) {
555                        if (isThis(n)) {
556                            rangeFacts |= RangeFacts.UsesThis;
557                            thisNode = node;
558                        }
559                        else if (isClassLike(n) || (isFunctionLike(n) && !isArrowFunction(n))) {
560                            return false;
561                        }
562                        else {
563                            forEachChild(n, check);
564                        }
565                    });
566                    // falls through
567                case SyntaxKind.ClassDeclaration:
568                case SyntaxKind.FunctionDeclaration:
569                    if (isSourceFile(node.parent) && node.parent.externalModuleIndicator === undefined) {
570                        // You cannot extract global declarations
571                        (errors ||= []).push(createDiagnosticForNode(node, Messages.functionWillNotBeVisibleInTheNewScope));
572                    }
573                    // falls through
574                case SyntaxKind.ClassExpression:
575                case SyntaxKind.FunctionExpression:
576                case SyntaxKind.MethodDeclaration:
577                case SyntaxKind.Constructor:
578                case SyntaxKind.GetAccessor:
579                case SyntaxKind.SetAccessor:
580                    // do not dive into functions or classes
581                    return false;
582            }
583
584            const savedPermittedJumps = permittedJumps;
585            switch (node.kind) {
586                case SyntaxKind.IfStatement:
587                    permittedJumps &= ~PermittedJumps.Return;
588                    break;
589                case SyntaxKind.TryStatement:
590                    // forbid all jumps inside try blocks
591                    permittedJumps = PermittedJumps.None;
592                    break;
593                case SyntaxKind.Block:
594                    if (node.parent && node.parent.kind === SyntaxKind.TryStatement && (node.parent as TryStatement).finallyBlock === node) {
595                        // allow unconditional returns from finally blocks
596                        permittedJumps = PermittedJumps.Return;
597                    }
598                    break;
599                case SyntaxKind.DefaultClause:
600                case SyntaxKind.CaseClause:
601                    // allow unlabeled break inside case clauses
602                    permittedJumps |= PermittedJumps.Break;
603                    break;
604                default:
605                    if (isIterationStatement(node, /*lookInLabeledStatements*/ false)) {
606                        // allow unlabeled break/continue inside loops
607                        permittedJumps |= PermittedJumps.Break | PermittedJumps.Continue;
608                    }
609                    break;
610            }
611
612            switch (node.kind) {
613                case SyntaxKind.ThisType:
614                case SyntaxKind.ThisKeyword:
615                    rangeFacts |= RangeFacts.UsesThis;
616                    thisNode = node;
617                    break;
618                case SyntaxKind.LabeledStatement: {
619                    const label = (node as LabeledStatement).label;
620                    (seenLabels || (seenLabels = [])).push(label.escapedText);
621                    forEachChild(node, visit);
622                    seenLabels.pop();
623                    break;
624                }
625                case SyntaxKind.BreakStatement:
626                case SyntaxKind.ContinueStatement: {
627                    const label = (node as BreakStatement | ContinueStatement).label;
628                    if (label) {
629                        if (!contains(seenLabels, label.escapedText)) {
630                            // attempts to jump to label that is not in range to be extracted
631                            (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange));
632                        }
633                    }
634                    else {
635                        if (!(permittedJumps & (node.kind === SyntaxKind.BreakStatement ? PermittedJumps.Break : PermittedJumps.Continue))) {
636                            // attempt to break or continue in a forbidden context
637                            (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingConditionalBreakOrContinueStatements));
638                        }
639                    }
640                    break;
641                }
642                case SyntaxKind.AwaitExpression:
643                    rangeFacts |= RangeFacts.IsAsyncFunction;
644                    break;
645                case SyntaxKind.YieldExpression:
646                    rangeFacts |= RangeFacts.IsGenerator;
647                    break;
648                case SyntaxKind.ReturnStatement:
649                    if (permittedJumps & PermittedJumps.Return) {
650                        rangeFacts |= RangeFacts.HasReturn;
651                    }
652                    else {
653                        (errors ||= []).push(createDiagnosticForNode(node, Messages.cannotExtractRangeContainingConditionalReturnStatement));
654                    }
655                    break;
656                default:
657                    forEachChild(node, visit);
658                    break;
659            }
660
661            permittedJumps = savedPermittedJumps;
662        }
663    }
664}
665
666/**
667 * Includes the final semicolon so that the span covers statements in cases where it would otherwise
668 * only cover the declaration list.
669 */
670function getAdjustedSpanFromNodes(startNode: Node, endNode: Node, sourceFile: SourceFile): TextSpan {
671    const start = startNode.getStart(sourceFile);
672    let end = endNode.getEnd();
673    if (sourceFile.text.charCodeAt(end) === CharacterCodes.semicolon) {
674        end++;
675    }
676    return { start, length: end - start };
677}
678
679function getStatementOrExpressionRange(node: Node): Statement[] | Expression | undefined {
680    if (isStatement(node)) {
681        return [node];
682    }
683    if (isExpressionNode(node)) {
684        // If our selection is the expression in an ExpressionStatement, expand
685        // the selection to include the enclosing Statement (this stops us
686        // from trying to care about the return value of the extracted function
687        // and eliminates double semicolon insertion in certain scenarios)
688        return isExpressionStatement(node.parent) ? [node.parent] : node as Expression;
689    }
690    if (isStringLiteralJsxAttribute(node)) {
691        return node;
692    }
693    return undefined;
694}
695
696function isScope(node: Node): node is Scope {
697    return isArrowFunction(node) ? isFunctionBody(node.body) :
698        isFunctionLikeDeclaration(node) || isSourceFile(node) || isModuleBlock(node) || isClassLike(node);
699}
700
701/**
702 * Computes possible places we could extract the function into. For example,
703 * you may be able to extract into a class method *or* local closure *or* namespace function,
704 * depending on what's in the extracted body.
705 */
706function collectEnclosingScopes(range: TargetRange): Scope[] {
707    let current: Node = isReadonlyArray(range.range) ? first(range.range) : range.range;
708    if (range.facts & RangeFacts.UsesThis && !(range.facts & RangeFacts.UsesThisInFunction)) {
709        // if range uses this as keyword or as type inside the class then it can only be extracted to a method of the containing class
710        const containingClass = getContainingClass(current);
711        if (containingClass) {
712            const containingFunction = findAncestor(current, isFunctionLikeDeclaration);
713            return containingFunction
714                ? [containingFunction, containingClass]
715                : [containingClass];
716        }
717    }
718
719    const scopes: Scope[] = [];
720    while (true) {
721        current = current.parent;
722        // A function parameter's initializer is actually in the outer scope, not the function declaration
723        if (current.kind === SyntaxKind.Parameter) {
724            // Skip all the way to the outer scope of the function that declared this parameter
725            current = findAncestor(current, parent => isFunctionLikeDeclaration(parent))!.parent;
726        }
727
728        // We want to find the nearest parent where we can place an "equivalent" sibling to the node we're extracting out of.
729        // Walk up to the closest parent of a place where we can logically put a sibling:
730        //  * Function declaration
731        //  * Class declaration or expression
732        //  * Module/namespace or source file
733        if (isScope(current)) {
734            scopes.push(current);
735            if (current.kind === SyntaxKind.SourceFile) {
736                return scopes;
737            }
738        }
739    }
740}
741
742function getFunctionExtractionAtIndex(targetRange: TargetRange, context: RefactorContext, requestedChangesIndex: number): RefactorEditInfo {
743    const { scopes, readsAndWrites: { target, usagesPerScope, functionErrorsPerScope, exposedVariableDeclarations } } = getPossibleExtractionsWorker(targetRange, context);
744    Debug.assert(!functionErrorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?");
745    context.cancellationToken!.throwIfCancellationRequested(); // TODO: GH#18217
746    return extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], exposedVariableDeclarations, targetRange, context);
747}
748
749function getConstantExtractionAtIndex(targetRange: TargetRange, context: RefactorContext, requestedChangesIndex: number): RefactorEditInfo {
750    const { scopes, readsAndWrites: { target, usagesPerScope, constantErrorsPerScope, exposedVariableDeclarations } } = getPossibleExtractionsWorker(targetRange, context);
751    Debug.assert(!constantErrorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?");
752    Debug.assert(exposedVariableDeclarations.length === 0, "Extract constant accepted a range containing a variable declaration?");
753    context.cancellationToken!.throwIfCancellationRequested();
754    const expression = isExpression(target)
755        ? target
756        : (target.statements[0] as ExpressionStatement).expression;
757    return extractConstantInScope(expression, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange.facts, context);
758}
759
760interface Extraction {
761    readonly description: string;
762    readonly errors: readonly Diagnostic[];
763}
764
765interface ScopeExtractions {
766    readonly functionExtraction: Extraction;
767    readonly constantExtraction: Extraction;
768}
769
770/**
771 * Given a piece of text to extract ('targetRange'), computes a list of possible extractions.
772 * Each returned ExtractResultForScope corresponds to a possible target scope and is either a set of changes
773 * or an error explaining why we can't extract into that scope.
774 */
775function getPossibleExtractions(targetRange: TargetRange, context: RefactorContext): readonly ScopeExtractions[] | undefined {
776    const { scopes, readsAndWrites: { functionErrorsPerScope, constantErrorsPerScope } } = getPossibleExtractionsWorker(targetRange, context);
777    // Need the inner type annotation to avoid https://github.com/Microsoft/TypeScript/issues/7547
778    const extractions = scopes.map((scope, i): ScopeExtractions => {
779        const functionDescriptionPart = getDescriptionForFunctionInScope(scope);
780        const constantDescriptionPart = getDescriptionForConstantInScope(scope);
781
782        const scopeDescription = isFunctionLikeDeclaration(scope)
783            ? getDescriptionForFunctionLikeDeclaration(scope)
784            : isClassLike(scope)
785                ? getDescriptionForClassLikeDeclaration(scope)
786                : getDescriptionForModuleLikeDeclaration(scope);
787
788        let functionDescription: string;
789        let constantDescription: string;
790        if (scopeDescription === SpecialScope.Global) {
791            functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [functionDescriptionPart, "global"]);
792            constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [constantDescriptionPart, "global"]);
793        }
794        else if (scopeDescription === SpecialScope.Module) {
795            functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [functionDescriptionPart, "module"]);
796            constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1_scope), [constantDescriptionPart, "module"]);
797        }
798        else {
799            functionDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1), [functionDescriptionPart, scopeDescription]);
800            constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_1), [constantDescriptionPart, scopeDescription]);
801        }
802
803        // Customize the phrasing for the innermost scope to increase clarity.
804        if (i === 0 && !isClassLike(scope)) {
805            constantDescription = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Extract_to_0_in_enclosing_scope), [constantDescriptionPart]);
806        }
807
808        return {
809            functionExtraction: {
810                description: functionDescription,
811                errors: functionErrorsPerScope[i],
812            },
813            constantExtraction: {
814                description: constantDescription,
815                errors: constantErrorsPerScope[i],
816            },
817        };
818    });
819    return extractions;
820}
821
822function getPossibleExtractionsWorker(targetRange: TargetRange, context: RefactorContext): { readonly scopes: Scope[], readonly readsAndWrites: ReadsAndWrites } {
823    const { file: sourceFile } = context;
824
825    const scopes = collectEnclosingScopes(targetRange);
826    const enclosingTextRange = getEnclosingTextRange(targetRange, sourceFile);
827    const readsAndWrites = collectReadsAndWrites(
828        targetRange,
829        scopes,
830        enclosingTextRange,
831        sourceFile,
832        context.program.getTypeChecker(),
833        context.cancellationToken!);
834    return { scopes, readsAndWrites };
835}
836
837function getDescriptionForFunctionInScope(scope: Scope): string {
838    return isFunctionLikeDeclaration(scope)
839        ? "inner function"
840        : isClassLike(scope)
841            ? "method"
842            : "function";
843}
844function getDescriptionForConstantInScope(scope: Scope): string {
845    return isClassLike(scope)
846        ? "readonly field"
847        : "constant";
848}
849function getDescriptionForFunctionLikeDeclaration(scope: FunctionLikeDeclaration): string {
850    switch (scope.kind) {
851        case SyntaxKind.Constructor:
852            return "constructor";
853        case SyntaxKind.FunctionExpression:
854        case SyntaxKind.FunctionDeclaration:
855            return scope.name
856                ? `function '${scope.name.text}'`
857                : ANONYMOUS;
858        case SyntaxKind.ArrowFunction:
859            return "arrow function";
860        case SyntaxKind.MethodDeclaration:
861            return `method '${scope.name.getText()}'`;
862        case SyntaxKind.GetAccessor:
863            return `'get ${scope.name.getText()}'`;
864        case SyntaxKind.SetAccessor:
865            return `'set ${scope.name.getText()}'`;
866        default:
867            throw Debug.assertNever(scope, `Unexpected scope kind ${(scope as FunctionLikeDeclaration).kind}`);
868    }
869}
870function getDescriptionForClassLikeDeclaration(scope: ClassLikeDeclaration): string {
871    return scope.kind === SyntaxKind.ClassDeclaration
872        ? scope.name ? `class '${scope.name.text}'` : "anonymous class declaration"
873        : scope.name ? `class expression '${scope.name.text}'` : "anonymous class expression";
874}
875function getDescriptionForModuleLikeDeclaration(scope: SourceFile | ModuleBlock): string | SpecialScope {
876    return scope.kind === SyntaxKind.ModuleBlock
877        ? `namespace '${scope.parent.name.getText()}'`
878        : scope.externalModuleIndicator ? SpecialScope.Module : SpecialScope.Global;
879}
880
881const enum SpecialScope {
882    Module,
883    Global,
884}
885
886/**
887 * Result of 'extractRange' operation for a specific scope.
888 * Stores either a list of changes that should be applied to extract a range or a list of errors
889 */
890function extractFunctionInScope(
891    node: Statement | Expression | Block,
892    scope: Scope,
893    { usages: usagesInScope, typeParameterUsages, substitutions }: ScopeUsages,
894    exposedVariableDeclarations: readonly VariableDeclaration[],
895    range: TargetRange,
896    context: RefactorContext): RefactorEditInfo {
897
898    const checker = context.program.getTypeChecker();
899    const scriptTarget = getEmitScriptTarget(context.program.getCompilerOptions());
900    const importAdder = codefix.createImportAdder(context.file, context.program, context.preferences, context.host);
901
902    // Make a unique name for the extracted function
903    const file = scope.getSourceFile();
904    const functionNameText = getUniqueName(isClassLike(scope) ? "newMethod" : "newFunction", file);
905    const isJS = isInJSFile(scope);
906
907    const functionName = factory.createIdentifier(functionNameText);
908
909    let returnType: TypeNode | undefined;
910    const parameters: ParameterDeclaration[] = [];
911    const callArguments: Identifier[] = [];
912    let writes: UsageEntry[] | undefined;
913    usagesInScope.forEach((usage, name) => {
914        let typeNode: TypeNode | undefined;
915        if (!isJS) {
916            let type = checker.getTypeOfSymbolAtLocation(usage.symbol, usage.node);
917            // Widen the type so we don't emit nonsense annotations like "function fn(x: 3) {"
918            type = checker.getBaseTypeOfLiteralType(type);
919            typeNode = codefix.typeToAutoImportableTypeNode(checker, importAdder, type, scope, scriptTarget, NodeBuilderFlags.NoTruncation);
920        }
921
922        const paramDecl = factory.createParameterDeclaration(
923            /*modifiers*/ undefined,
924            /*dotDotDotToken*/ undefined,
925            /*name*/ name,
926            /*questionToken*/ undefined,
927            typeNode
928        );
929        parameters.push(paramDecl);
930        if (usage.usage === Usage.Write) {
931            (writes || (writes = [])).push(usage);
932        }
933        callArguments.push(factory.createIdentifier(name));
934    });
935
936    const typeParametersAndDeclarations = arrayFrom(typeParameterUsages.values()).map(type => ({ type, declaration: getFirstDeclaration(type) }));
937    const sortedTypeParametersAndDeclarations = typeParametersAndDeclarations.sort(compareTypesByDeclarationOrder);
938
939    const typeParameters: readonly TypeParameterDeclaration[] | undefined = sortedTypeParametersAndDeclarations.length === 0
940        ? undefined
941        : sortedTypeParametersAndDeclarations.map(t => t.declaration as TypeParameterDeclaration);
942
943    // Strictly speaking, we should check whether each name actually binds to the appropriate type
944    // parameter.  In cases of shadowing, they may not.
945    const callTypeArguments: readonly TypeNode[] | undefined = typeParameters !== undefined
946        ? typeParameters.map(decl => factory.createTypeReferenceNode(decl.name, /*typeArguments*/ undefined))
947        : undefined;
948
949    // Provide explicit return types for contextually-typed functions
950    // to avoid problems when there are literal types present
951    if (isExpression(node) && !isJS) {
952        const contextualType = checker.getContextualType(node);
953        returnType = checker.typeToTypeNode(contextualType!, scope, NodeBuilderFlags.NoTruncation); // TODO: GH#18217
954    }
955
956    const { body, returnValueProperty } = transformFunctionBody(node, exposedVariableDeclarations, writes, substitutions, !!(range.facts & RangeFacts.HasReturn));
957    suppressLeadingAndTrailingTrivia(body);
958
959    let newFunction: MethodDeclaration | FunctionDeclaration;
960
961    const callThis = !!(range.facts & RangeFacts.UsesThisInFunction);
962
963    if (isClassLike(scope)) {
964        // always create private method in TypeScript files
965        const modifiers: Modifier[] = isJS ? [] : [factory.createModifier(SyntaxKind.PrivateKeyword)];
966        if (range.facts & RangeFacts.InStaticRegion) {
967            modifiers.push(factory.createModifier(SyntaxKind.StaticKeyword));
968        }
969        if (range.facts & RangeFacts.IsAsyncFunction) {
970            modifiers.push(factory.createModifier(SyntaxKind.AsyncKeyword));
971        }
972        newFunction = factory.createMethodDeclaration(
973            modifiers.length ? modifiers : undefined,
974            range.facts & RangeFacts.IsGenerator ? factory.createToken(SyntaxKind.AsteriskToken) : undefined,
975            functionName,
976            /*questionToken*/ undefined,
977            typeParameters,
978            parameters,
979            returnType,
980            body
981        );
982    }
983    else {
984        if (callThis) {
985            parameters.unshift(
986                factory.createParameterDeclaration(
987                    /*modifiers*/ undefined,
988                    /*dotDotDotToken*/ undefined,
989                    /*name*/ "this",
990                    /*questionToken*/ undefined,
991                    checker.typeToTypeNode(
992                        checker.getTypeAtLocation(range.thisNode!),
993                        scope,
994                        NodeBuilderFlags.NoTruncation
995                    ),
996                    /*initializer*/ undefined,
997                )
998            );
999        }
1000        newFunction = factory.createFunctionDeclaration(
1001            range.facts & RangeFacts.IsAsyncFunction ? [factory.createToken(SyntaxKind.AsyncKeyword)] : undefined,
1002            range.facts & RangeFacts.IsGenerator ? factory.createToken(SyntaxKind.AsteriskToken) : undefined,
1003            functionName,
1004            typeParameters,
1005            parameters,
1006            returnType,
1007            body
1008        );
1009    }
1010
1011    const changeTracker = textChanges.ChangeTracker.fromContext(context);
1012    const minInsertionPos = (isReadonlyArray(range.range) ? last(range.range) : range.range).end;
1013    const nodeToInsertBefore = getNodeToInsertFunctionBefore(minInsertionPos, scope);
1014    if (nodeToInsertBefore) {
1015        changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newFunction, /*blankLineBetween*/ true);
1016    }
1017    else {
1018        changeTracker.insertNodeAtEndOfScope(context.file, scope, newFunction);
1019    }
1020    importAdder.writeFixes(changeTracker);
1021
1022    const newNodes: Node[] = [];
1023    // replace range with function call
1024    const called = getCalledExpression(scope, range, functionNameText);
1025
1026    if (callThis) {
1027        callArguments.unshift(factory.createIdentifier("this"));
1028    }
1029
1030    let call: Expression = factory.createCallExpression(
1031        callThis ? factory.createPropertyAccessExpression(
1032            called,
1033            "call"
1034        ) : called,
1035        callTypeArguments, // Note that no attempt is made to take advantage of type argument inference
1036        callArguments);
1037    if (range.facts & RangeFacts.IsGenerator) {
1038        call = factory.createYieldExpression(factory.createToken(SyntaxKind.AsteriskToken), call);
1039    }
1040    if (range.facts & RangeFacts.IsAsyncFunction) {
1041        call = factory.createAwaitExpression(call);
1042    }
1043    if (isInJSXContent(node)) {
1044        call = factory.createJsxExpression(/*dotDotDotToken*/ undefined, call);
1045    }
1046
1047    if (exposedVariableDeclarations.length && !writes) {
1048        // No need to mix declarations and writes.
1049
1050        // How could any variables be exposed if there's a return statement?
1051        Debug.assert(!returnValueProperty, "Expected no returnValueProperty");
1052        Debug.assert(!(range.facts & RangeFacts.HasReturn), "Expected RangeFacts.HasReturn flag to be unset");
1053
1054        if (exposedVariableDeclarations.length === 1) {
1055            // Declaring exactly one variable: let x = newFunction();
1056            const variableDeclaration = exposedVariableDeclarations[0];
1057            newNodes.push(factory.createVariableStatement(
1058                /*modifiers*/ undefined,
1059                factory.createVariableDeclarationList(
1060                    [factory.createVariableDeclaration(getSynthesizedDeepClone(variableDeclaration.name), /*exclamationToken*/ undefined, /*type*/ getSynthesizedDeepClone(variableDeclaration.type), /*initializer*/ call)], // TODO (acasey): test binding patterns
1061                    variableDeclaration.parent.flags)));
1062        }
1063        else {
1064            // Declaring multiple variables / return properties:
1065            //   let {x, y} = newFunction();
1066            const bindingElements: BindingElement[] = [];
1067            const typeElements: TypeElement[] = [];
1068            let commonNodeFlags = exposedVariableDeclarations[0].parent.flags;
1069            let sawExplicitType = false;
1070            for (const variableDeclaration of exposedVariableDeclarations) {
1071                bindingElements.push(factory.createBindingElement(
1072                    /*dotDotDotToken*/ undefined,
1073                    /*propertyName*/ undefined,
1074                    /*name*/ getSynthesizedDeepClone(variableDeclaration.name)));
1075
1076                // Being returned through an object literal will have widened the type.
1077                const variableType: TypeNode | undefined = checker.typeToTypeNode(
1078                    checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(variableDeclaration)),
1079                    scope,
1080                    NodeBuilderFlags.NoTruncation);
1081
1082                typeElements.push(factory.createPropertySignature(
1083                    /*modifiers*/ undefined,
1084                    /*name*/ variableDeclaration.symbol.name,
1085                    /*questionToken*/ undefined,
1086                    /*type*/ variableType));
1087                sawExplicitType = sawExplicitType || variableDeclaration.type !== undefined;
1088                commonNodeFlags = commonNodeFlags & variableDeclaration.parent.flags;
1089            }
1090
1091            const typeLiteral: TypeLiteralNode | undefined = sawExplicitType ? factory.createTypeLiteralNode(typeElements) : undefined;
1092            if (typeLiteral) {
1093                setEmitFlags(typeLiteral, EmitFlags.SingleLine);
1094            }
1095
1096            newNodes.push(factory.createVariableStatement(
1097                /*modifiers*/ undefined,
1098                factory.createVariableDeclarationList(
1099                    [factory.createVariableDeclaration(
1100                        factory.createObjectBindingPattern(bindingElements),
1101                        /*exclamationToken*/ undefined,
1102                        /*type*/ typeLiteral,
1103                        /*initializer*/call)],
1104                    commonNodeFlags)));
1105        }
1106    }
1107    else if (exposedVariableDeclarations.length || writes) {
1108        if (exposedVariableDeclarations.length) {
1109            // CONSIDER: we're going to create one statement per variable, but we could actually preserve their original grouping.
1110            for (const variableDeclaration of exposedVariableDeclarations) {
1111                let flags: NodeFlags = variableDeclaration.parent.flags;
1112                if (flags & NodeFlags.Const) {
1113                    flags = (flags & ~NodeFlags.Const) | NodeFlags.Let;
1114                }
1115
1116                newNodes.push(factory.createVariableStatement(
1117                    /*modifiers*/ undefined,
1118                    factory.createVariableDeclarationList(
1119                        [factory.createVariableDeclaration(variableDeclaration.symbol.name, /*exclamationToken*/ undefined, getTypeDeepCloneUnionUndefined(variableDeclaration.type))],
1120                        flags)));
1121            }
1122        }
1123
1124        if (returnValueProperty) {
1125            // has both writes and return, need to create variable declaration to hold return value;
1126            newNodes.push(factory.createVariableStatement(
1127                /*modifiers*/ undefined,
1128                factory.createVariableDeclarationList(
1129                    [factory.createVariableDeclaration(returnValueProperty, /*exclamationToken*/ undefined, getTypeDeepCloneUnionUndefined(returnType))],
1130                    NodeFlags.Let)));
1131        }
1132
1133        const assignments = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes);
1134        if (returnValueProperty) {
1135            assignments.unshift(factory.createShorthandPropertyAssignment(returnValueProperty));
1136        }
1137
1138        // propagate writes back
1139        if (assignments.length === 1) {
1140            // We would only have introduced a return value property if there had been
1141            // other assignments to make.
1142            Debug.assert(!returnValueProperty, "Shouldn't have returnValueProperty here");
1143
1144            newNodes.push(factory.createExpressionStatement(factory.createAssignment(assignments[0].name, call)));
1145
1146            if (range.facts & RangeFacts.HasReturn) {
1147                newNodes.push(factory.createReturnStatement());
1148            }
1149        }
1150        else {
1151            // emit e.g.
1152            //   { a, b, __return } = newFunction(a, b);
1153            //   return __return;
1154            newNodes.push(factory.createExpressionStatement(factory.createAssignment(factory.createObjectLiteralExpression(assignments), call)));
1155            if (returnValueProperty) {
1156                newNodes.push(factory.createReturnStatement(factory.createIdentifier(returnValueProperty)));
1157            }
1158        }
1159    }
1160    else {
1161        if (range.facts & RangeFacts.HasReturn) {
1162            newNodes.push(factory.createReturnStatement(call));
1163        }
1164        else if (isReadonlyArray(range.range)) {
1165            newNodes.push(factory.createExpressionStatement(call));
1166        }
1167        else {
1168            newNodes.push(call);
1169        }
1170    }
1171
1172    if (isReadonlyArray(range.range)) {
1173        changeTracker.replaceNodeRangeWithNodes(context.file, first(range.range), last(range.range), newNodes);
1174    }
1175    else {
1176        changeTracker.replaceNodeWithNodes(context.file, range.range, newNodes);
1177    }
1178
1179    const edits = changeTracker.getChanges();
1180    const renameRange = isReadonlyArray(range.range) ? first(range.range) : range.range;
1181
1182    const renameFilename = renameRange.getSourceFile().fileName;
1183    const renameLocation = getRenameLocation(edits, renameFilename, functionNameText, /*isDeclaredBeforeUse*/ false);
1184    return { renameFilename, renameLocation, edits };
1185
1186    function getTypeDeepCloneUnionUndefined(typeNode: TypeNode | undefined): TypeNode | undefined {
1187        if (typeNode === undefined) {
1188            return undefined;
1189        }
1190
1191        const clone = getSynthesizedDeepClone(typeNode);
1192        let withoutParens = clone;
1193        while (isParenthesizedTypeNode(withoutParens)) {
1194            withoutParens = withoutParens.type;
1195        }
1196        return isUnionTypeNode(withoutParens) && find(withoutParens.types, t => t.kind === SyntaxKind.UndefinedKeyword)
1197            ? clone
1198            : factory.createUnionTypeNode([clone, factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword)]);
1199    }
1200}
1201
1202/**
1203 * Result of 'extractRange' operation for a specific scope.
1204 * Stores either a list of changes that should be applied to extract a range or a list of errors
1205 */
1206function extractConstantInScope(
1207    node: Expression,
1208    scope: Scope,
1209    { substitutions }: ScopeUsages,
1210    rangeFacts: RangeFacts,
1211    context: RefactorContext): RefactorEditInfo {
1212
1213    const checker = context.program.getTypeChecker();
1214
1215    // Make a unique name for the extracted variable
1216    const file = scope.getSourceFile();
1217    const localNameText = isPropertyAccessExpression(node) && !isClassLike(scope) && !checker.resolveName(node.name.text, node, SymbolFlags.Value, /*excludeGlobals*/ false) && !isPrivateIdentifier(node.name) && !isKeyword(node.name.originalKeywordKind!)
1218        ? node.name.text
1219        : getUniqueName(isClassLike(scope) ? "newProperty" : "newLocal", file);
1220    const isJS = isInJSFile(scope);
1221
1222    let variableType = isJS || !checker.isContextSensitive(node)
1223        ? undefined
1224        : checker.typeToTypeNode(checker.getContextualType(node)!, scope, NodeBuilderFlags.NoTruncation); // TODO: GH#18217
1225
1226    let initializer = transformConstantInitializer(skipParentheses(node), substitutions);
1227
1228    ({ variableType, initializer } = transformFunctionInitializerAndType(variableType, initializer));
1229
1230    suppressLeadingAndTrailingTrivia(initializer);
1231
1232    const changeTracker = textChanges.ChangeTracker.fromContext(context);
1233
1234    if (isClassLike(scope)) {
1235        Debug.assert(!isJS, "Cannot extract to a JS class"); // See CannotExtractToJSClass
1236        const modifiers: Modifier[] = [];
1237        modifiers.push(factory.createModifier(SyntaxKind.PrivateKeyword));
1238        if (rangeFacts & RangeFacts.InStaticRegion) {
1239            modifiers.push(factory.createModifier(SyntaxKind.StaticKeyword));
1240        }
1241        modifiers.push(factory.createModifier(SyntaxKind.ReadonlyKeyword));
1242
1243        const newVariable = factory.createPropertyDeclaration(
1244            modifiers,
1245            localNameText,
1246            /*questionToken*/ undefined,
1247            variableType,
1248            initializer);
1249
1250        let localReference: Expression = factory.createPropertyAccessExpression(
1251            rangeFacts & RangeFacts.InStaticRegion
1252                ? factory.createIdentifier(scope.name!.getText()) // TODO: GH#18217
1253                : factory.createThis(),
1254                factory.createIdentifier(localNameText));
1255
1256        if (isInJSXContent(node)) {
1257            localReference = factory.createJsxExpression(/*dotDotDotToken*/ undefined, localReference);
1258        }
1259
1260        // Declare
1261        const maxInsertionPos = node.pos;
1262        const nodeToInsertBefore = getNodeToInsertPropertyBefore(maxInsertionPos, scope);
1263        changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariable, /*blankLineBetween*/ true);
1264
1265        // Consume
1266        changeTracker.replaceNode(context.file, node, localReference);
1267    }
1268    else {
1269        const newVariableDeclaration = factory.createVariableDeclaration(localNameText, /*exclamationToken*/ undefined, variableType, initializer);
1270
1271        // If the node is part of an initializer in a list of variable declarations, insert a new
1272        // variable declaration into the list (in case it depends on earlier ones).
1273        // CONSIDER: If the declaration list isn't const, we might want to split it into multiple
1274        // lists so that the newly extracted one can be const.
1275        const oldVariableDeclaration = getContainingVariableDeclarationIfInList(node, scope);
1276        if (oldVariableDeclaration) {
1277            // Declare
1278            // CONSIDER: could detect that each is on a separate line (See `extractConstant_VariableList_MultipleLines` in `extractConstants.ts`)
1279            changeTracker.insertNodeBefore(context.file, oldVariableDeclaration, newVariableDeclaration);
1280
1281            // Consume
1282            const localReference = factory.createIdentifier(localNameText);
1283            changeTracker.replaceNode(context.file, node, localReference);
1284        }
1285        else if (node.parent.kind === SyntaxKind.ExpressionStatement && scope === findAncestor(node, isScope)) {
1286            // If the parent is an expression statement and the target scope is the immediately enclosing one,
1287            // replace the statement with the declaration.
1288            const newVariableStatement = factory.createVariableStatement(
1289                /*modifiers*/ undefined,
1290                factory.createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const));
1291            changeTracker.replaceNode(context.file, node.parent, newVariableStatement);
1292        }
1293        else {
1294            const newVariableStatement = factory.createVariableStatement(
1295                /*modifiers*/ undefined,
1296                factory.createVariableDeclarationList([newVariableDeclaration], NodeFlags.Const));
1297
1298            // Declare
1299            const nodeToInsertBefore = getNodeToInsertConstantBefore(node, scope);
1300            if (nodeToInsertBefore.pos === 0) {
1301                changeTracker.insertNodeAtTopOfFile(context.file, newVariableStatement, /*blankLineBetween*/ false);
1302            }
1303            else {
1304                changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariableStatement, /*blankLineBetween*/ false);
1305            }
1306
1307            // Consume
1308            if (node.parent.kind === SyntaxKind.ExpressionStatement) {
1309                // If the parent is an expression statement, delete it.
1310                changeTracker.delete(context.file, node.parent);
1311            }
1312            else {
1313                let localReference: Expression = factory.createIdentifier(localNameText);
1314                // When extract to a new variable in JSX content, need to wrap a {} out of the new variable
1315                // or it will become a plain text
1316                if (isInJSXContent(node)) {
1317                    localReference = factory.createJsxExpression(/*dotDotDotToken*/ undefined, localReference);
1318                }
1319                changeTracker.replaceNode(context.file, node, localReference);
1320            }
1321        }
1322    }
1323
1324    const edits = changeTracker.getChanges();
1325
1326    const renameFilename = node.getSourceFile().fileName;
1327    const renameLocation = getRenameLocation(edits, renameFilename, localNameText, /*isDeclaredBeforeUse*/ true);
1328    return { renameFilename, renameLocation, edits };
1329
1330    function transformFunctionInitializerAndType(variableType: TypeNode | undefined, initializer: Expression): { variableType: TypeNode | undefined, initializer: Expression } {
1331        // If no contextual type exists there is nothing to transfer to the function signature
1332        if (variableType === undefined) return { variableType, initializer };
1333        // Only do this for function expressions and arrow functions that are not generic
1334        if (!isFunctionExpression(initializer) && !isArrowFunction(initializer) || !!initializer.typeParameters) return { variableType, initializer };
1335        const functionType = checker.getTypeAtLocation(node);
1336        const functionSignature = singleOrUndefined(checker.getSignaturesOfType(functionType, SignatureKind.Call));
1337
1338        // If no function signature, maybe there was an error, do nothing
1339        if (!functionSignature) return { variableType, initializer };
1340        // If the function signature has generic type parameters we don't attempt to move the parameters
1341        if (!!functionSignature.getTypeParameters()) return { variableType, initializer };
1342
1343        // We add parameter types if needed
1344        const parameters: ParameterDeclaration[] = [];
1345        let hasAny = false;
1346        for (const p of initializer.parameters) {
1347            if (p.type) {
1348                parameters.push(p);
1349            }
1350            else {
1351                const paramType = checker.getTypeAtLocation(p);
1352                if (paramType === checker.getAnyType()) hasAny = true;
1353
1354                parameters.push(factory.updateParameterDeclaration(p,
1355                    p.modifiers, p.dotDotDotToken,
1356                    p.name, p.questionToken, p.type || checker.typeToTypeNode(paramType, scope, NodeBuilderFlags.NoTruncation), p.initializer));
1357            }
1358        }
1359        // If a parameter was inferred as any we skip adding function parameters at all.
1360        // Turning an implicit any (which under common settings is a error) to an explicit
1361        // is probably actually a worse refactor outcome.
1362        if (hasAny) return { variableType, initializer };
1363        variableType = undefined;
1364        if (isArrowFunction(initializer)) {
1365            initializer = factory.updateArrowFunction(initializer, canHaveModifiers(node) ? getModifiers(node) : undefined, initializer.typeParameters,
1366                parameters,
1367                initializer.type || checker.typeToTypeNode(functionSignature.getReturnType(), scope, NodeBuilderFlags.NoTruncation),
1368                initializer.equalsGreaterThanToken,
1369                initializer.body);
1370        }
1371        else {
1372            if (functionSignature && !!functionSignature.thisParameter) {
1373                const firstParameter = firstOrUndefined(parameters);
1374                // If the function signature has a this parameter and if the first defined parameter is not the this parameter, we must add it
1375                // Note: If this parameter was already there, it would have been previously updated with the type if not type was present
1376                if ((!firstParameter || (isIdentifier(firstParameter.name) && firstParameter.name.escapedText !== "this"))) {
1377                    const thisType = checker.getTypeOfSymbolAtLocation(functionSignature.thisParameter, node);
1378                    parameters.splice(0, 0, factory.createParameterDeclaration(
1379                        /* modifiers */ undefined,
1380                        /* dotDotDotToken */ undefined,
1381                        "this",
1382                        /* questionToken */ undefined,
1383                        checker.typeToTypeNode(thisType, scope, NodeBuilderFlags.NoTruncation)
1384                    ));
1385                }
1386            }
1387            initializer = factory.updateFunctionExpression(initializer, canHaveModifiers(node) ? getModifiers(node) : undefined, initializer.asteriskToken,
1388                initializer.name, initializer.typeParameters,
1389                parameters,
1390                initializer.type || checker.typeToTypeNode(functionSignature.getReturnType(), scope, NodeBuilderFlags.NoTruncation),
1391                initializer.body);
1392        }
1393        return { variableType, initializer };
1394    }
1395}
1396
1397function getContainingVariableDeclarationIfInList(node: Node, scope: Scope) {
1398    let prevNode;
1399    while (node !== undefined && node !== scope) {
1400        if (isVariableDeclaration(node) &&
1401            node.initializer === prevNode &&
1402            isVariableDeclarationList(node.parent) &&
1403            node.parent.declarations.length > 1) {
1404
1405            return node;
1406        }
1407
1408        prevNode = node;
1409        node = node.parent;
1410    }
1411}
1412
1413function getFirstDeclaration(type: Type): Declaration | undefined {
1414    let firstDeclaration;
1415
1416    const symbol = type.symbol;
1417    if (symbol && symbol.declarations) {
1418        for (const declaration of symbol.declarations) {
1419            if (firstDeclaration === undefined || declaration.pos < firstDeclaration.pos) {
1420                firstDeclaration = declaration;
1421            }
1422        }
1423    }
1424
1425    return firstDeclaration;
1426}
1427
1428function compareTypesByDeclarationOrder(
1429    { type: type1, declaration: declaration1 }: { type: Type, declaration?: Declaration },
1430    { type: type2, declaration: declaration2 }: { type: Type, declaration?: Declaration }) {
1431
1432    return compareProperties(declaration1, declaration2, "pos", compareValues)
1433        || compareStringsCaseSensitive(
1434            type1.symbol ? type1.symbol.getName() : "",
1435            type2.symbol ? type2.symbol.getName() : "")
1436        || compareValues(type1.id, type2.id);
1437}
1438
1439function getCalledExpression(scope: Node, range: TargetRange, functionNameText: string): Expression {
1440    const functionReference = factory.createIdentifier(functionNameText);
1441    if (isClassLike(scope)) {
1442        const lhs = range.facts & RangeFacts.InStaticRegion ? factory.createIdentifier(scope.name!.text) : factory.createThis(); // TODO: GH#18217
1443        return factory.createPropertyAccessExpression(lhs, functionReference);
1444    }
1445    else {
1446        return functionReference;
1447    }
1448}
1449
1450function transformFunctionBody(body: Node, exposedVariableDeclarations: readonly VariableDeclaration[], writes: readonly UsageEntry[] | undefined, substitutions: ReadonlyESMap<string, Node>, hasReturn: boolean): { body: Block, returnValueProperty: string | undefined } {
1451    const hasWritesOrVariableDeclarations = writes !== undefined || exposedVariableDeclarations.length > 0;
1452    if (isBlock(body) && !hasWritesOrVariableDeclarations && substitutions.size === 0) {
1453        // already block, no declarations or writes to propagate back, no substitutions - can use node as is
1454        return { body: factory.createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined };
1455    }
1456    let returnValueProperty: string | undefined;
1457    let ignoreReturns = false;
1458    const statements = factory.createNodeArray(isBlock(body) ? body.statements.slice(0) : [isStatement(body) ? body : factory.createReturnStatement(skipParentheses(body as Expression))]);
1459    // rewrite body if either there are writes that should be propagated back via return statements or there are substitutions
1460    if (hasWritesOrVariableDeclarations || substitutions.size) {
1461        const rewrittenStatements = visitNodes(statements, visitor).slice();
1462        if (hasWritesOrVariableDeclarations && !hasReturn && isStatement(body)) {
1463            // add return at the end to propagate writes back in case if control flow falls out of the function body
1464            // it is ok to know that range has at least one return since it we only allow unconditional returns
1465            const assignments = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes);
1466            if (assignments.length === 1) {
1467                rewrittenStatements.push(factory.createReturnStatement(assignments[0].name));
1468            }
1469            else {
1470                rewrittenStatements.push(factory.createReturnStatement(factory.createObjectLiteralExpression(assignments)));
1471            }
1472        }
1473        return { body: factory.createBlock(rewrittenStatements, /*multiLine*/ true), returnValueProperty };
1474    }
1475    else {
1476        return { body: factory.createBlock(statements, /*multiLine*/ true), returnValueProperty: undefined };
1477    }
1478
1479    function visitor(node: Node): VisitResult<Node> {
1480        if (!ignoreReturns && isReturnStatement(node) && hasWritesOrVariableDeclarations) {
1481            const assignments: ObjectLiteralElementLike[] = getPropertyAssignmentsForWritesAndVariableDeclarations(exposedVariableDeclarations, writes);
1482            if (node.expression) {
1483                if (!returnValueProperty) {
1484                    returnValueProperty = "__return";
1485                }
1486                assignments.unshift(factory.createPropertyAssignment(returnValueProperty, visitNode(node.expression, visitor)));
1487            }
1488            if (assignments.length === 1) {
1489                return factory.createReturnStatement(assignments[0].name as Expression);
1490            }
1491            else {
1492                return factory.createReturnStatement(factory.createObjectLiteralExpression(assignments));
1493            }
1494        }
1495        else {
1496            const oldIgnoreReturns = ignoreReturns;
1497            ignoreReturns = ignoreReturns || isFunctionLikeDeclaration(node) || isClassLike(node);
1498            const substitution = substitutions.get(getNodeId(node).toString());
1499            const result = substitution ? getSynthesizedDeepClone(substitution) : visitEachChild(node, visitor, nullTransformationContext);
1500            ignoreReturns = oldIgnoreReturns;
1501            return result;
1502        }
1503    }
1504}
1505
1506function transformConstantInitializer(initializer: Expression, substitutions: ReadonlyESMap<string, Node>): Expression {
1507    return substitutions.size
1508        ? visitor(initializer) as Expression
1509        : initializer;
1510
1511    function visitor(node: Node): VisitResult<Node> {
1512        const substitution = substitutions.get(getNodeId(node).toString());
1513        return substitution ? getSynthesizedDeepClone(substitution) : visitEachChild(node, visitor, nullTransformationContext);
1514    }
1515}
1516
1517function getStatementsOrClassElements(scope: Scope): readonly Statement[] | readonly ClassElement[] {
1518    if (isFunctionLikeDeclaration(scope)) {
1519        const body = scope.body!; // TODO: GH#18217
1520        if (isBlock(body)) {
1521            return body.statements;
1522        }
1523    }
1524    else if (isModuleBlock(scope) || isSourceFile(scope)) {
1525        return scope.statements;
1526    }
1527    else if (isClassLike(scope)) {
1528        return scope.members;
1529    }
1530    else {
1531        assertType<never>(scope);
1532    }
1533
1534    return emptyArray;
1535}
1536
1537/**
1538 * If `scope` contains a function after `minPos`, then return the first such function.
1539 * Otherwise, return `undefined`.
1540 */
1541function getNodeToInsertFunctionBefore(minPos: number, scope: Scope): Statement | ClassElement | undefined {
1542    return find<Statement | ClassElement>(getStatementsOrClassElements(scope), child =>
1543        child.pos >= minPos && isFunctionLikeDeclaration(child) && !isConstructorDeclaration(child));
1544}
1545
1546function getNodeToInsertPropertyBefore(maxPos: number, scope: ClassLikeDeclaration): ClassElement {
1547    const members = scope.members;
1548    Debug.assert(members.length > 0, "Found no members"); // There must be at least one child, since we extracted from one.
1549
1550    let prevMember: ClassElement | undefined;
1551    let allProperties = true;
1552    for (const member of members) {
1553        if (member.pos > maxPos) {
1554            return prevMember || members[0];
1555        }
1556        if (allProperties && !isPropertyDeclaration(member)) {
1557            // If it is non-vacuously true that all preceding members are properties,
1558            // insert before the current member (i.e. at the end of the list of properties).
1559            if (prevMember !== undefined) {
1560                return member;
1561            }
1562
1563            allProperties = false;
1564        }
1565        prevMember = member;
1566    }
1567
1568    if (prevMember === undefined) return Debug.fail(); // If the loop didn't return, then it did set prevMember.
1569    return prevMember;
1570}
1571
1572function getNodeToInsertConstantBefore(node: Node, scope: Scope): Statement {
1573    Debug.assert(!isClassLike(scope));
1574
1575    let prevScope: Scope | undefined;
1576    for (let curr = node; curr !== scope; curr = curr.parent) {
1577        if (isScope(curr)) {
1578            prevScope = curr;
1579        }
1580    }
1581
1582    for (let curr = (prevScope || node).parent; ; curr = curr.parent) {
1583        if (isBlockLike(curr)) {
1584            let prevStatement: Statement | undefined;
1585            for (const statement of curr.statements) {
1586                if (statement.pos > node.pos) {
1587                    break;
1588                }
1589                prevStatement = statement;
1590            }
1591
1592            if (!prevStatement && isCaseClause(curr)) {
1593                // We must have been in the expression of the case clause.
1594                Debug.assert(isSwitchStatement(curr.parent.parent), "Grandparent isn't a switch statement");
1595                return curr.parent.parent;
1596            }
1597
1598            // There must be at least one statement since we started in one.
1599            return Debug.checkDefined(prevStatement, "prevStatement failed to get set");
1600        }
1601
1602        Debug.assert(curr !== scope, "Didn't encounter a block-like before encountering scope");
1603    }
1604}
1605
1606function getPropertyAssignmentsForWritesAndVariableDeclarations(
1607    exposedVariableDeclarations: readonly VariableDeclaration[],
1608    writes: readonly UsageEntry[] | undefined
1609): ShorthandPropertyAssignment[] {
1610    const variableAssignments = map(exposedVariableDeclarations, v => factory.createShorthandPropertyAssignment(v.symbol.name));
1611    const writeAssignments = map(writes, w => factory.createShorthandPropertyAssignment(w.symbol.name));
1612
1613    // TODO: GH#18217 `variableAssignments` not possibly undefined!
1614    return variableAssignments === undefined
1615        ? writeAssignments!
1616        : writeAssignments === undefined
1617            ? variableAssignments
1618            : variableAssignments.concat(writeAssignments);
1619}
1620
1621function isReadonlyArray(v: any): v is readonly any[] {
1622    return isArray(v);
1623}
1624
1625/**
1626 * Produces a range that spans the entirety of nodes, given a selection
1627 * that might start/end in the middle of nodes.
1628 *
1629 * For example, when the user makes a selection like this
1630 *                     v---v
1631 *   var someThing = foo + bar;
1632 *  this returns     ^-------^
1633 */
1634function getEnclosingTextRange(targetRange: TargetRange, sourceFile: SourceFile): TextRange {
1635    return isReadonlyArray(targetRange.range)
1636        ? { pos: first(targetRange.range).getStart(sourceFile), end: last(targetRange.range).getEnd() }
1637        : targetRange.range;
1638}
1639
1640const enum Usage {
1641    // value should be passed to extracted method
1642    Read = 1,
1643    // value should be passed to extracted method and propagated back
1644    Write = 2
1645}
1646
1647interface UsageEntry {
1648    readonly usage: Usage;
1649    readonly symbol: Symbol;
1650    readonly node: Node;
1651}
1652
1653interface ScopeUsages {
1654    readonly usages: ESMap<string, UsageEntry>;
1655    readonly typeParameterUsages: ESMap<string, TypeParameter>; // Key is type ID
1656    readonly substitutions: ESMap<string, Node>;
1657}
1658
1659interface ReadsAndWrites {
1660    readonly target: Expression | Block;
1661    readonly usagesPerScope: readonly ScopeUsages[];
1662    readonly functionErrorsPerScope: readonly (readonly Diagnostic[])[];
1663    readonly constantErrorsPerScope: readonly (readonly Diagnostic[])[];
1664    readonly exposedVariableDeclarations: readonly VariableDeclaration[];
1665}
1666function collectReadsAndWrites(
1667    targetRange: TargetRange,
1668    scopes: Scope[],
1669    enclosingTextRange: TextRange,
1670    sourceFile: SourceFile,
1671    checker: TypeChecker,
1672    cancellationToken: CancellationToken): ReadsAndWrites {
1673
1674    const allTypeParameterUsages = new Map<string, TypeParameter>(); // Key is type ID
1675    const usagesPerScope: ScopeUsages[] = [];
1676    const substitutionsPerScope: ESMap<string, Node>[] = [];
1677    const functionErrorsPerScope: Diagnostic[][] = [];
1678    const constantErrorsPerScope: Diagnostic[][] = [];
1679    const visibleDeclarationsInExtractedRange: NamedDeclaration[] = [];
1680    const exposedVariableSymbolSet = new Map<string, true>(); // Key is symbol ID
1681    const exposedVariableDeclarations: VariableDeclaration[] = [];
1682    let firstExposedNonVariableDeclaration: NamedDeclaration | undefined;
1683
1684    const expression = !isReadonlyArray(targetRange.range)
1685        ? targetRange.range
1686        : targetRange.range.length === 1 && isExpressionStatement(targetRange.range[0])
1687            ? targetRange.range[0].expression
1688            : undefined;
1689
1690    let expressionDiagnostic: Diagnostic | undefined;
1691    if (expression === undefined) {
1692        const statements = targetRange.range as readonly Statement[];
1693        const start = first(statements).getStart();
1694        const end = last(statements).end;
1695        expressionDiagnostic = createFileDiagnostic(sourceFile, start, end - start, Messages.expressionExpected);
1696    }
1697    else if (checker.getTypeAtLocation(expression).flags & (TypeFlags.Void | TypeFlags.Never)) {
1698        expressionDiagnostic = createDiagnosticForNode(expression, Messages.uselessConstantType);
1699    }
1700
1701    // initialize results
1702    for (const scope of scopes) {
1703        usagesPerScope.push({ usages: new Map<string, UsageEntry>(), typeParameterUsages: new Map<string, TypeParameter>(), substitutions: new Map<string, Expression>() });
1704        substitutionsPerScope.push(new Map<string, Expression>());
1705
1706        functionErrorsPerScope.push([]);
1707
1708        const constantErrors = [];
1709        if (expressionDiagnostic) {
1710            constantErrors.push(expressionDiagnostic);
1711        }
1712        if (isClassLike(scope) && isInJSFile(scope)) {
1713            constantErrors.push(createDiagnosticForNode(scope, Messages.cannotExtractToJSClass));
1714        }
1715        if (isArrowFunction(scope) && !isBlock(scope.body)) {
1716            // TODO (https://github.com/Microsoft/TypeScript/issues/18924): allow this
1717            constantErrors.push(createDiagnosticForNode(scope, Messages.cannotExtractToExpressionArrowFunction));
1718        }
1719        constantErrorsPerScope.push(constantErrors);
1720    }
1721
1722    const seenUsages = new Map<string, Usage>();
1723    const target = isReadonlyArray(targetRange.range) ? factory.createBlock(targetRange.range) : targetRange.range;
1724
1725    const unmodifiedNode = isReadonlyArray(targetRange.range) ? first(targetRange.range) : targetRange.range;
1726    const inGenericContext = isInGenericContext(unmodifiedNode);
1727
1728    collectUsages(target);
1729
1730    // Unfortunately, this code takes advantage of the knowledge that the generated method
1731    // will use the contextual type of an expression as the return type of the extracted
1732    // method (and will therefore "use" all the types involved).
1733    if (inGenericContext && !isReadonlyArray(targetRange.range) && !isJsxAttribute(targetRange.range)) {
1734        const contextualType = checker.getContextualType(targetRange.range)!; // TODO: GH#18217
1735        recordTypeParameterUsages(contextualType);
1736    }
1737
1738    if (allTypeParameterUsages.size > 0) {
1739        const seenTypeParameterUsages = new Map<string, TypeParameter>(); // Key is type ID
1740
1741        let i = 0;
1742        for (let curr: Node = unmodifiedNode; curr !== undefined && i < scopes.length; curr = curr.parent) {
1743            if (curr === scopes[i]) {
1744                // Copy current contents of seenTypeParameterUsages into scope.
1745                seenTypeParameterUsages.forEach((typeParameter, id) => {
1746                    usagesPerScope[i].typeParameterUsages.set(id, typeParameter);
1747                });
1748
1749                i++;
1750            }
1751
1752            // Note that we add the current node's type parameters *after* updating the corresponding scope.
1753            if (isDeclarationWithTypeParameters(curr)) {
1754                for (const typeParameterDecl of getEffectiveTypeParameterDeclarations(curr)) {
1755                    const typeParameter = checker.getTypeAtLocation(typeParameterDecl) as TypeParameter;
1756                    if (allTypeParameterUsages.has(typeParameter.id.toString())) {
1757                        seenTypeParameterUsages.set(typeParameter.id.toString(), typeParameter);
1758                    }
1759                }
1760            }
1761        }
1762
1763        // If we didn't get through all the scopes, then there were some that weren't in our
1764        // parent chain (impossible at time of writing).  A conservative solution would be to
1765        // copy allTypeParameterUsages into all remaining scopes.
1766        Debug.assert(i === scopes.length, "Should have iterated all scopes");
1767    }
1768
1769    // If there are any declarations in the extracted block that are used in the same enclosing
1770    // lexical scope, we can't move the extraction "up" as those declarations will become unreachable
1771    if (visibleDeclarationsInExtractedRange.length) {
1772        const containingLexicalScopeOfExtraction = isBlockScope(scopes[0], scopes[0].parent)
1773            ? scopes[0]
1774            : getEnclosingBlockScopeContainer(scopes[0]);
1775        forEachChild(containingLexicalScopeOfExtraction, checkForUsedDeclarations);
1776    }
1777
1778    for (let i = 0; i < scopes.length; i++) {
1779        const scopeUsages = usagesPerScope[i];
1780        // Special case: in the innermost scope, all usages are available.
1781        // (The computed value reflects the value at the top-level of the scope, but the
1782        // local will actually be declared at the same level as the extracted expression).
1783        if (i > 0 && (scopeUsages.usages.size > 0 || scopeUsages.typeParameterUsages.size > 0)) {
1784            const errorNode = isReadonlyArray(targetRange.range) ? targetRange.range[0] : targetRange.range;
1785            constantErrorsPerScope[i].push(createDiagnosticForNode(errorNode, Messages.cannotAccessVariablesFromNestedScopes));
1786        }
1787
1788        if (targetRange.facts & RangeFacts.UsesThisInFunction && isClassLike(scopes[i])) {
1789            functionErrorsPerScope[i].push(createDiagnosticForNode(targetRange.thisNode!, Messages.cannotExtractFunctionsContainingThisToMethod));
1790        }
1791
1792        let hasWrite = false;
1793        let readonlyClassPropertyWrite: Declaration | undefined;
1794        usagesPerScope[i].usages.forEach(value => {
1795            if (value.usage === Usage.Write) {
1796                hasWrite = true;
1797                if (value.symbol.flags & SymbolFlags.ClassMember &&
1798                    value.symbol.valueDeclaration &&
1799                    hasEffectiveModifier(value.symbol.valueDeclaration, ModifierFlags.Readonly)) {
1800                    readonlyClassPropertyWrite = value.symbol.valueDeclaration;
1801                }
1802            }
1803        });
1804
1805        // If an expression was extracted, then there shouldn't have been any variable declarations.
1806        Debug.assert(isReadonlyArray(targetRange.range) || exposedVariableDeclarations.length === 0, "No variable declarations expected if something was extracted");
1807
1808        if (hasWrite && !isReadonlyArray(targetRange.range)) {
1809            const diag = createDiagnosticForNode(targetRange.range, Messages.cannotWriteInExpression);
1810            functionErrorsPerScope[i].push(diag);
1811            constantErrorsPerScope[i].push(diag);
1812        }
1813        else if (readonlyClassPropertyWrite && i > 0) {
1814            const diag = createDiagnosticForNode(readonlyClassPropertyWrite, Messages.cannotExtractReadonlyPropertyInitializerOutsideConstructor);
1815            functionErrorsPerScope[i].push(diag);
1816            constantErrorsPerScope[i].push(diag);
1817        }
1818        else if (firstExposedNonVariableDeclaration) {
1819            const diag = createDiagnosticForNode(firstExposedNonVariableDeclaration, Messages.cannotExtractExportedEntity);
1820            functionErrorsPerScope[i].push(diag);
1821            constantErrorsPerScope[i].push(diag);
1822        }
1823    }
1824
1825    return { target, usagesPerScope, functionErrorsPerScope, constantErrorsPerScope, exposedVariableDeclarations };
1826
1827    function isInGenericContext(node: Node) {
1828        return !!findAncestor(node, n => isDeclarationWithTypeParameters(n) && getEffectiveTypeParameterDeclarations(n).length !== 0);
1829    }
1830
1831    function recordTypeParameterUsages(type: Type) {
1832        // PERF: This is potentially very expensive.  `type` could be a library type with
1833        // a lot of properties, each of which the walker will visit.  Unfortunately, the
1834        // solution isn't as trivial as filtering to user types because of (e.g.) Array.
1835        const symbolWalker = checker.getSymbolWalker(() => (cancellationToken.throwIfCancellationRequested(), true));
1836        const { visitedTypes } = symbolWalker.walkType(type);
1837
1838        for (const visitedType of visitedTypes) {
1839            if (visitedType.isTypeParameter()) {
1840                allTypeParameterUsages.set(visitedType.id.toString(), visitedType);
1841            }
1842        }
1843    }
1844
1845    function collectUsages(node: Node, valueUsage = Usage.Read) {
1846        if (inGenericContext) {
1847            const type = checker.getTypeAtLocation(node);
1848            recordTypeParameterUsages(type);
1849        }
1850
1851        if (isDeclaration(node) && node.symbol) {
1852            visibleDeclarationsInExtractedRange.push(node);
1853        }
1854
1855        if (isAssignmentExpression(node)) {
1856            // use 'write' as default usage for values
1857            collectUsages(node.left, Usage.Write);
1858            collectUsages(node.right);
1859        }
1860        else if (isUnaryExpressionWithWrite(node)) {
1861            collectUsages(node.operand, Usage.Write);
1862        }
1863        else if (isPropertyAccessExpression(node) || isElementAccessExpression(node)) {
1864            // use 'write' as default usage for values
1865            forEachChild(node, collectUsages);
1866        }
1867        else if (isIdentifier(node)) {
1868            if (!node.parent) {
1869                return;
1870            }
1871            if (isQualifiedName(node.parent) && node !== node.parent.left) {
1872                return;
1873            }
1874            if (isPropertyAccessExpression(node.parent) && node !== node.parent.expression) {
1875                return;
1876            }
1877            recordUsage(node, valueUsage, /*isTypeNode*/ isPartOfTypeNode(node));
1878        }
1879        else {
1880            forEachChild(node, collectUsages);
1881        }
1882    }
1883
1884    function recordUsage(n: Identifier, usage: Usage, isTypeNode: boolean) {
1885        const symbolId = recordUsagebySymbol(n, usage, isTypeNode);
1886        if (symbolId) {
1887            for (let i = 0; i < scopes.length; i++) {
1888                // push substitution from map<symbolId, subst> to map<nodeId, subst> to simplify rewriting
1889                const substitution = substitutionsPerScope[i].get(symbolId);
1890                if (substitution) {
1891                    usagesPerScope[i].substitutions.set(getNodeId(n).toString(), substitution);
1892                }
1893            }
1894        }
1895    }
1896
1897    function recordUsagebySymbol(identifier: Identifier, usage: Usage, isTypeName: boolean) {
1898        const symbol = getSymbolReferencedByIdentifier(identifier);
1899        if (!symbol) {
1900            // cannot find symbol - do nothing
1901            return undefined;
1902        }
1903        const symbolId = getSymbolId(symbol).toString();
1904        const lastUsage = seenUsages.get(symbolId);
1905        // there are two kinds of value usages
1906        // - reads - if range contains a read from the value located outside of the range then value should be passed as a parameter
1907        // - writes - if range contains a write to a value located outside the range the value should be passed as a parameter and
1908        //   returned as a return value
1909        // 'write' case is a superset of 'read' so if we already have processed 'write' of some symbol there is not need to handle 'read'
1910        // since all information is already recorded
1911        if (lastUsage && lastUsage >= usage) {
1912            return symbolId;
1913        }
1914
1915        seenUsages.set(symbolId, usage);
1916        if (lastUsage) {
1917            // if we get here this means that we are trying to handle 'write' and 'read' was already processed
1918            // walk scopes and update existing records.
1919            for (const perScope of usagesPerScope) {
1920                const prevEntry = perScope.usages.get(identifier.text);
1921                if (prevEntry) {
1922                    perScope.usages.set(identifier.text, { usage, symbol, node: identifier });
1923                }
1924            }
1925            return symbolId;
1926        }
1927        // find first declaration in this file
1928        const decls = symbol.getDeclarations();
1929        const declInFile = decls && find(decls, d => d.getSourceFile() === sourceFile);
1930        if (!declInFile) {
1931            return undefined;
1932        }
1933        if (rangeContainsStartEnd(enclosingTextRange, declInFile.getStart(), declInFile.end)) {
1934            // declaration is located in range to be extracted - do nothing
1935            return undefined;
1936        }
1937        if (targetRange.facts & RangeFacts.IsGenerator && usage === Usage.Write) {
1938            // this is write to a reference located outside of the target scope and range is extracted into generator
1939            // currently this is unsupported scenario
1940            const diag = createDiagnosticForNode(identifier, Messages.cannotExtractRangeThatContainsWritesToReferencesLocatedOutsideOfTheTargetRangeInGenerators);
1941            for (const errors of functionErrorsPerScope) {
1942                errors.push(diag);
1943            }
1944            for (const errors of constantErrorsPerScope) {
1945                errors.push(diag);
1946            }
1947        }
1948        for (let i = 0; i < scopes.length; i++) {
1949            const scope = scopes[i];
1950            const resolvedSymbol = checker.resolveName(symbol.name, scope, symbol.flags, /*excludeGlobals*/ false);
1951            if (resolvedSymbol === symbol) {
1952                continue;
1953            }
1954            if (!substitutionsPerScope[i].has(symbolId)) {
1955                const substitution = tryReplaceWithQualifiedNameOrPropertyAccess(symbol.exportSymbol || symbol, scope, isTypeName);
1956                if (substitution) {
1957                    substitutionsPerScope[i].set(symbolId, substitution);
1958                }
1959                else if (isTypeName) {
1960                    // If the symbol is a type parameter that won't be in scope, we'll pass it as a type argument
1961                    // so there's no problem.
1962                    if (!(symbol.flags & SymbolFlags.TypeParameter)) {
1963                        const diag = createDiagnosticForNode(identifier, Messages.typeWillNotBeVisibleInTheNewScope);
1964                        functionErrorsPerScope[i].push(diag);
1965                        constantErrorsPerScope[i].push(diag);
1966                    }
1967                }
1968                else {
1969                    usagesPerScope[i].usages.set(identifier.text, { usage, symbol, node: identifier });
1970                }
1971            }
1972        }
1973        return symbolId;
1974    }
1975
1976    function checkForUsedDeclarations(node: Node) {
1977        // If this node is entirely within the original extraction range, we don't need to do anything.
1978        if (node === targetRange.range || (isReadonlyArray(targetRange.range) && targetRange.range.indexOf(node as Statement) >= 0)) {
1979            return;
1980        }
1981
1982        // Otherwise check and recurse.
1983        const sym = isIdentifier(node)
1984            ? getSymbolReferencedByIdentifier(node)
1985            : checker.getSymbolAtLocation(node);
1986        if (sym) {
1987            const decl = find(visibleDeclarationsInExtractedRange, d => d.symbol === sym);
1988            if (decl) {
1989                if (isVariableDeclaration(decl)) {
1990                    const idString = decl.symbol.id!.toString();
1991                    if (!exposedVariableSymbolSet.has(idString)) {
1992                        exposedVariableDeclarations.push(decl);
1993                        exposedVariableSymbolSet.set(idString, true);
1994                    }
1995                }
1996                else {
1997                    // CONSIDER: this includes binding elements, which we could
1998                    // expose in the same way as variables.
1999                    firstExposedNonVariableDeclaration = firstExposedNonVariableDeclaration || decl;
2000                }
2001            }
2002        }
2003
2004        forEachChild(node, checkForUsedDeclarations);
2005    }
2006
2007    /**
2008     * Return the symbol referenced by an identifier (even if it declares a different symbol).
2009     */
2010    function getSymbolReferencedByIdentifier(identifier: Identifier) {
2011        // If the identifier is both a property name and its value, we're only interested in its value
2012        // (since the name is a declaration and will be included in the extracted range).
2013        return identifier.parent && isShorthandPropertyAssignment(identifier.parent) && identifier.parent.name === identifier
2014            ? checker.getShorthandAssignmentValueSymbol(identifier.parent)
2015            : checker.getSymbolAtLocation(identifier);
2016    }
2017
2018    function tryReplaceWithQualifiedNameOrPropertyAccess(symbol: Symbol | undefined, scopeDecl: Node, isTypeNode: boolean): PropertyAccessExpression | EntityName | undefined {
2019        if (!symbol) {
2020            return undefined;
2021        }
2022        const decls = symbol.getDeclarations();
2023        if (decls && decls.some(d => d.parent === scopeDecl)) {
2024            return factory.createIdentifier(symbol.name);
2025        }
2026        const prefix = tryReplaceWithQualifiedNameOrPropertyAccess(symbol.parent, scopeDecl, isTypeNode);
2027        if (prefix === undefined) {
2028            return undefined;
2029        }
2030        return isTypeNode
2031            ? factory.createQualifiedName(prefix as EntityName, factory.createIdentifier(symbol.name))
2032            : factory.createPropertyAccessExpression(prefix as Expression, symbol.name);
2033    }
2034}
2035
2036function getExtractableParent(node: Node | undefined): Node | undefined {
2037    return findAncestor(node, node => node.parent && isExtractableExpression(node) && !isBinaryExpression(node.parent));
2038}
2039
2040/**
2041 * Computes whether or not a node represents an expression in a position where it could
2042 * be extracted.
2043 * The isExpression() in utilities.ts returns some false positives we need to handle,
2044 * such as `import x from 'y'` -- the 'y' is a StringLiteral but is *not* an expression
2045 * in the sense of something that you could extract on
2046 */
2047function isExtractableExpression(node: Node): boolean {
2048    const { parent } = node;
2049    switch (parent.kind) {
2050        case SyntaxKind.EnumMember:
2051            return false;
2052    }
2053
2054    switch (node.kind) {
2055        case SyntaxKind.StringLiteral:
2056            return parent.kind !== SyntaxKind.ImportDeclaration &&
2057                parent.kind !== SyntaxKind.ImportSpecifier;
2058
2059        case SyntaxKind.SpreadElement:
2060        case SyntaxKind.ObjectBindingPattern:
2061        case SyntaxKind.BindingElement:
2062            return false;
2063
2064        case SyntaxKind.Identifier:
2065            return parent.kind !== SyntaxKind.BindingElement &&
2066                parent.kind !== SyntaxKind.ImportSpecifier &&
2067                parent.kind !== SyntaxKind.ExportSpecifier;
2068    }
2069    return true;
2070}
2071
2072function isBlockLike(node: Node): node is BlockLike {
2073    switch (node.kind) {
2074        case SyntaxKind.Block:
2075        case SyntaxKind.SourceFile:
2076        case SyntaxKind.ModuleBlock:
2077        case SyntaxKind.CaseClause:
2078            return true;
2079        default:
2080            return false;
2081    }
2082}
2083
2084function isInJSXContent(node: Node) {
2085    return isStringLiteralJsxAttribute(node) ||
2086        (isJsxElement(node) || isJsxSelfClosingElement(node) || isJsxFragment(node)) && (isJsxElement(node.parent) || isJsxFragment(node.parent));
2087}
2088
2089function isStringLiteralJsxAttribute(node: Node): node is StringLiteral {
2090    return isStringLiteral(node) && node.parent && isJsxAttribute(node.parent);
2091}
2092