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