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