mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
extractMethod: Support renameLocation (#18050)
* extractMethod: Support renameLocation * Add tslint disable * Properly analyze list of changes to always get a correct rename location * Update test * Ensure name is really unique * Improvements to test code * Respond to PR comments
This commit is contained in:
@@ -2758,11 +2758,11 @@ namespace FourSlash {
|
||||
}
|
||||
}
|
||||
|
||||
private getSelection() {
|
||||
return ({
|
||||
private getSelection(): ts.TextRange {
|
||||
return {
|
||||
pos: this.currentCaretPosition,
|
||||
end: this.selectionEnd === -1 ? this.currentCaretPosition : this.selectionEnd
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
public verifyRefactorAvailable(negative: boolean, name: string, actionName?: string) {
|
||||
@@ -2803,7 +2803,7 @@ namespace FourSlash {
|
||||
}
|
||||
}
|
||||
|
||||
public applyRefactor({ refactorName, actionName, actionDescription }: FourSlashInterface.ApplyRefactorOptions) {
|
||||
public applyRefactor({ refactorName, actionName, actionDescription, newContent: newContentWithRenameMarker }: FourSlashInterface.ApplyRefactorOptions) {
|
||||
const range = this.getSelection();
|
||||
const refactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, range);
|
||||
const refactor = refactors.find(r => r.name === refactorName);
|
||||
@@ -2823,6 +2823,35 @@ namespace FourSlash {
|
||||
for (const edit of editInfo.edits) {
|
||||
this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false);
|
||||
}
|
||||
|
||||
const { renamePosition, newContent } = parseNewContent();
|
||||
|
||||
this.verifyCurrentFileContent(newContent);
|
||||
|
||||
if (renamePosition === undefined) {
|
||||
if (editInfo.renameLocation !== undefined) {
|
||||
this.raiseError(`Did not expect a rename location, got ${editInfo.renameLocation}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO: test editInfo.renameFilename value
|
||||
assert.isDefined(editInfo.renameFilename);
|
||||
if (renamePosition !== editInfo.renameLocation) {
|
||||
this.raiseError(`Expected rename position of ${renamePosition}, but got ${editInfo.renameLocation}`);
|
||||
}
|
||||
}
|
||||
|
||||
function parseNewContent(): { renamePosition: number | undefined, newContent: string } {
|
||||
const renamePosition = newContentWithRenameMarker.indexOf("/*RENAME*/");
|
||||
if (renamePosition === -1) {
|
||||
return { renamePosition: undefined, newContent: newContentWithRenameMarker };
|
||||
}
|
||||
else {
|
||||
const newContent = newContentWithRenameMarker.slice(0, renamePosition) + newContentWithRenameMarker.slice(renamePosition + "/*RENAME*/".length);
|
||||
return { renamePosition, newContent };
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public verifyFileAfterApplyingRefactorAtMarker(
|
||||
@@ -4319,6 +4348,7 @@ namespace FourSlashInterface {
|
||||
refactorName: string;
|
||||
actionName: string;
|
||||
actionDescription: string;
|
||||
newContent: string;
|
||||
}
|
||||
|
||||
export interface CompletionsAtOptions {
|
||||
|
||||
@@ -745,9 +745,12 @@ function M3() { }`);
|
||||
data.push(`// ==ORIGINAL==`);
|
||||
data.push(sourceFile.text);
|
||||
for (const r of results) {
|
||||
const changes = refactor.extractMethod.getPossibleExtractions(result.targetRange, context, results.indexOf(r))[0].changes;
|
||||
const { renameLocation, edits } = refactor.extractMethod.getExtractionAtIndex(result.targetRange, context, results.indexOf(r));
|
||||
assert.lengthOf(edits, 1);
|
||||
data.push(`// ==SCOPE::${r.scopeDescription}==`);
|
||||
data.push(textChanges.applyChanges(sourceFile.text, changes[0].textChanges));
|
||||
const newText = textChanges.applyChanges(sourceFile.text, edits[0].textChanges);
|
||||
const newTextWithRename = newText.slice(0, renameLocation) + "/*RENAME*/" + newText.slice(renameLocation);
|
||||
data.push(newTextWithRename);
|
||||
}
|
||||
return data.join(newLineCharacter);
|
||||
});
|
||||
|
||||
@@ -586,9 +586,7 @@ namespace ts.server {
|
||||
const response = this.processResponse<protocol.GetEditsForRefactorResponse>(request);
|
||||
|
||||
if (!response.body) {
|
||||
return {
|
||||
edits: []
|
||||
};
|
||||
return { edits: [], renameFilename: undefined, renameLocation: undefined };
|
||||
}
|
||||
|
||||
const edits: FileTextChanges[] = this.convertCodeEditsToTextChanges(response.body.edits);
|
||||
|
||||
@@ -97,7 +97,9 @@ namespace ts.refactor.convertFunctionToES6Class {
|
||||
}
|
||||
|
||||
return {
|
||||
edits: changeTracker.getChanges()
|
||||
edits: changeTracker.getChanges(),
|
||||
renameFilename: undefined,
|
||||
renameLocation: undefined,
|
||||
};
|
||||
|
||||
function deleteNode(node: Node, inList = false) {
|
||||
|
||||
@@ -31,16 +31,16 @@ namespace ts.refactor.extractMethod {
|
||||
const usedNames: Map<boolean> = createMap();
|
||||
|
||||
let i = 0;
|
||||
for (const extr of extractions) {
|
||||
for (const { scopeDescription, errors } of extractions) {
|
||||
// Skip these since we don't have a way to report errors yet
|
||||
if (extr.errors && extr.errors.length) {
|
||||
if (errors.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't issue refactorings with duplicated names.
|
||||
// Scopes come back in "innermost first" order, so extractions will
|
||||
// preferentially go into nearer scopes
|
||||
const description = formatStringFromArgs(Diagnostics.Extract_to_0.message, [extr.scopeDescription]);
|
||||
const description = formatStringFromArgs(Diagnostics.Extract_to_0.message, [scopeDescription]);
|
||||
if (!usedNames.has(description)) {
|
||||
usedNames.set(description, true);
|
||||
actions.push({
|
||||
@@ -75,10 +75,7 @@ namespace ts.refactor.extractMethod {
|
||||
const index = +parsedIndexMatch[1];
|
||||
Debug.assert(isFinite(index), "Expected to parse a finite number from the scope index");
|
||||
|
||||
const extractions = getPossibleExtractions(targetRange, context, index);
|
||||
// Scope is no longer valid from when the user issued the refactor (??)
|
||||
Debug.assert(extractions !== undefined, "The extraction went missing? How?");
|
||||
return ({ edits: extractions[0].changes });
|
||||
return getExtractionAtIndex(targetRange, context, index);
|
||||
}
|
||||
|
||||
// Move these into diagnostic messages if they become user-facing
|
||||
@@ -102,7 +99,7 @@ namespace ts.refactor.extractMethod {
|
||||
export const CannotExtractAmbientBlock = createMessage("Cannot extract code from ambient contexts");
|
||||
}
|
||||
|
||||
export enum RangeFacts {
|
||||
enum RangeFacts {
|
||||
None = 0,
|
||||
HasReturn = 1 << 0,
|
||||
IsGenerator = 1 << 1,
|
||||
@@ -117,7 +114,7 @@ namespace ts.refactor.extractMethod {
|
||||
/**
|
||||
* Represents an expression or a list of statements that should be extracted with some extra information
|
||||
*/
|
||||
export interface TargetRange {
|
||||
interface TargetRange {
|
||||
readonly range: Expression | Statement[];
|
||||
readonly facts: RangeFacts;
|
||||
/**
|
||||
@@ -130,7 +127,7 @@ namespace ts.refactor.extractMethod {
|
||||
/**
|
||||
* Result of 'getRangeToExtract' operation: contains either a range or a list of errors
|
||||
*/
|
||||
export type RangeToExtract = {
|
||||
type RangeToExtract = {
|
||||
readonly targetRange?: never;
|
||||
readonly errors: ReadonlyArray<Diagnostic>;
|
||||
} | {
|
||||
@@ -141,18 +138,7 @@ namespace ts.refactor.extractMethod {
|
||||
/*
|
||||
* Scopes that can store newly extracted method
|
||||
*/
|
||||
export type Scope = FunctionLikeDeclaration | SourceFile | ModuleBlock | ClassLikeDeclaration;
|
||||
|
||||
/**
|
||||
* Result of 'extractRange' operation for a specific scope.
|
||||
* Stores either a list of changes that should be applied to extract a range or a list of errors
|
||||
*/
|
||||
export interface ExtractResultForScope {
|
||||
readonly scope: Scope;
|
||||
readonly scopeDescription: string;
|
||||
readonly changes?: FileTextChanges[];
|
||||
readonly errors?: Diagnostic[];
|
||||
}
|
||||
type Scope = FunctionLikeDeclaration | SourceFile | ModuleBlock | ClassLikeDeclaration;
|
||||
|
||||
/**
|
||||
* getRangeToExtract takes a span inside a text file and returns either an expression or an array
|
||||
@@ -160,6 +146,7 @@ namespace ts.refactor.extractMethod {
|
||||
* process may fail, in which case a set of errors is returned instead (these are currently
|
||||
* not shown to the user, but can be used by us diagnostically)
|
||||
*/
|
||||
// exported only for tests
|
||||
export function getRangeToExtract(sourceFile: SourceFile, span: TextSpan): RangeToExtract {
|
||||
const length = span.length || 0;
|
||||
// Walk up starting from the the start position until we find a non-SourceFile node that subsumes the selected span.
|
||||
@@ -458,7 +445,7 @@ namespace ts.refactor.extractMethod {
|
||||
* you may be able to extract into a class method *or* local closure *or* namespace function,
|
||||
* depending on what's in the extracted body.
|
||||
*/
|
||||
export function collectEnclosingScopes(range: TargetRange): Scope[] | undefined {
|
||||
function collectEnclosingScopes(range: TargetRange): Scope[] | undefined {
|
||||
let current: Node = isReadonlyArray(range.range) ? firstOrUndefined(range.range) : range.range;
|
||||
if (range.facts & RangeFacts.UsesThis) {
|
||||
// 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
|
||||
@@ -494,12 +481,32 @@ namespace ts.refactor.extractMethod {
|
||||
return scopes;
|
||||
}
|
||||
|
||||
// exported only for tests
|
||||
export function getExtractionAtIndex(targetRange: TargetRange, context: RefactorContext, requestedChangesIndex: number): RefactorEditInfo {
|
||||
const { scopes, readsAndWrites: { target, usagesPerScope, errorsPerScope } } = getPossibleExtractionsWorker(targetRange, context);
|
||||
Debug.assert(!errorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?");
|
||||
context.cancellationToken.throwIfCancellationRequested();
|
||||
return extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange, context);
|
||||
}
|
||||
|
||||
interface PossibleExtraction {
|
||||
readonly scopeDescription: string;
|
||||
readonly errors: ReadonlyArray<Diagnostic>;
|
||||
}
|
||||
/**
|
||||
* Given a piece of text to extract ('targetRange'), computes a list of possible extractions.
|
||||
* Each returned ExtractResultForScope corresponds to a possible target scope and is either a set of changes
|
||||
* or an error explaining why we can't extract into that scope.
|
||||
*/
|
||||
export function getPossibleExtractions(targetRange: TargetRange, context: RefactorContext, requestedChangesIndex: number = undefined): ReadonlyArray<ExtractResultForScope> | undefined {
|
||||
// exported only for tests
|
||||
export function getPossibleExtractions(targetRange: TargetRange, context: RefactorContext): ReadonlyArray<PossibleExtraction> | undefined {
|
||||
const { scopes, readsAndWrites: { errorsPerScope } } = getPossibleExtractionsWorker(targetRange, context);
|
||||
// Need the inner type annotation to avoid https://github.com/Microsoft/TypeScript/issues/7547
|
||||
return scopes.map((scope, i): PossibleExtraction =>
|
||||
({ scopeDescription: getDescriptionForScope(scope), errors: errorsPerScope[i] }));
|
||||
}
|
||||
|
||||
function getPossibleExtractionsWorker(targetRange: TargetRange, context: RefactorContext): { readonly scopes: Scope[], readonly readsAndWrites: ReadsAndWrites } {
|
||||
const { file: sourceFile } = context;
|
||||
|
||||
if (targetRange === undefined) {
|
||||
@@ -512,35 +519,14 @@ namespace ts.refactor.extractMethod {
|
||||
}
|
||||
|
||||
const enclosingTextRange = getEnclosingTextRange(targetRange, sourceFile);
|
||||
const { target, usagesPerScope, errorsPerScope } = collectReadsAndWrites(
|
||||
const readsAndWrites = collectReadsAndWrites(
|
||||
targetRange,
|
||||
scopes,
|
||||
enclosingTextRange,
|
||||
sourceFile,
|
||||
context.program.getTypeChecker(),
|
||||
context.cancellationToken);
|
||||
|
||||
context.cancellationToken.throwIfCancellationRequested();
|
||||
|
||||
if (requestedChangesIndex !== undefined) {
|
||||
if (errorsPerScope[requestedChangesIndex].length) {
|
||||
return undefined;
|
||||
}
|
||||
return [extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange, context)];
|
||||
}
|
||||
else {
|
||||
return scopes.map((scope, i) => {
|
||||
const errors = errorsPerScope[i];
|
||||
if (errors.length) {
|
||||
return {
|
||||
scope,
|
||||
scopeDescription: getDescriptionForScope(scope),
|
||||
errors
|
||||
};
|
||||
}
|
||||
return { scope, scopeDescription: getDescriptionForScope(scope) };
|
||||
});
|
||||
}
|
||||
return { scopes, readsAndWrites };
|
||||
}
|
||||
|
||||
function getDescriptionForScope(scope: Scope): string {
|
||||
@@ -583,34 +569,33 @@ namespace ts.refactor.extractMethod {
|
||||
: scope.externalModuleIndicator ? "module scope" : "global scope";
|
||||
}
|
||||
|
||||
function getUniqueName(isNameOkay: (name: string) => boolean) {
|
||||
function getUniqueName(fileText: string): string {
|
||||
let functionNameText = "newFunction";
|
||||
if (isNameOkay(functionNameText)) {
|
||||
return functionNameText;
|
||||
}
|
||||
let i = 1;
|
||||
while (!isNameOkay(functionNameText = `newFunction_${i}`)) {
|
||||
i++;
|
||||
for (let i = 1; fileText.indexOf(functionNameText) !== -1; i++) {
|
||||
functionNameText = `newFunction_${i}`;
|
||||
}
|
||||
return functionNameText;
|
||||
}
|
||||
|
||||
export function extractFunctionInScope(
|
||||
/**
|
||||
* Result of 'extractRange' operation for a specific scope.
|
||||
* Stores either a list of changes that should be applied to extract a range or a list of errors
|
||||
*/
|
||||
function extractFunctionInScope(
|
||||
node: Statement | Expression | Block,
|
||||
scope: Scope,
|
||||
{ usages: usagesInScope, typeParameterUsages, substitutions }: ScopeUsages,
|
||||
range: TargetRange,
|
||||
context: RefactorContext): ExtractResultForScope {
|
||||
context: RefactorContext): RefactorEditInfo {
|
||||
|
||||
const checker = context.program.getTypeChecker();
|
||||
|
||||
// Make a unique name for the extracted function
|
||||
const file = scope.getSourceFile();
|
||||
const functionNameText: string = getUniqueName(n => !file.identifiers.has(n));
|
||||
const functionNameText = getUniqueName(file.text);
|
||||
const isJS = isInJavaScriptFile(scope);
|
||||
|
||||
const functionName = createIdentifier(functionNameText as string);
|
||||
const functionReference = createIdentifier(functionNameText as string);
|
||||
const functionName = createIdentifier(functionNameText);
|
||||
|
||||
let returnType: TypeNode = undefined;
|
||||
const parameters: ParameterDeclaration[] = [];
|
||||
@@ -660,7 +645,7 @@ namespace ts.refactor.extractMethod {
|
||||
returnType = checker.typeToTypeNode(contextualType);
|
||||
}
|
||||
|
||||
const { body, returnValueProperty } = transformFunctionBody(node);
|
||||
const { body, returnValueProperty } = transformFunctionBody(node, writes, substitutions, !!(range.facts & RangeFacts.HasReturn));
|
||||
let newFunction: MethodDeclaration | FunctionDeclaration;
|
||||
|
||||
if (isClassLike(scope)) {
|
||||
@@ -709,8 +694,10 @@ namespace ts.refactor.extractMethod {
|
||||
|
||||
const newNodes: Node[] = [];
|
||||
// replace range with function call
|
||||
const called = getCalledExpression(scope, range, functionNameText);
|
||||
|
||||
let call: Expression = createCall(
|
||||
isClassLike(scope) ? createPropertyAccess(range.facts & RangeFacts.InStaticRegion ? createIdentifier(scope.name.getText()) : createThis(), functionReference) : functionReference,
|
||||
called,
|
||||
callTypeArguments, // Note that no attempt is made to take advantage of type argument inference
|
||||
callArguments);
|
||||
if (range.facts & RangeFacts.IsGenerator) {
|
||||
@@ -779,147 +766,174 @@ namespace ts.refactor.extractMethod {
|
||||
changeTracker.replaceNodeWithNodes(context.file, range.range, newNodes, { nodeSeparator: context.newLineCharacter });
|
||||
}
|
||||
|
||||
return {
|
||||
scope,
|
||||
scopeDescription: getDescriptionForScope(scope),
|
||||
changes: changeTracker.getChanges()
|
||||
};
|
||||
const edits = changeTracker.getChanges();
|
||||
const renameRange = isReadonlyArray(range.range) ? range.range[0] : range.range;
|
||||
|
||||
function getFirstDeclaration(type: Type): Declaration | undefined {
|
||||
let firstDeclaration = undefined;
|
||||
const renameFilename = renameRange.getSourceFile().fileName;
|
||||
const renameLocation = getRenameLocation(edits, renameFilename, functionNameText);
|
||||
return { renameFilename, renameLocation, edits };
|
||||
}
|
||||
|
||||
const symbol = type.symbol;
|
||||
if (symbol && symbol.declarations) {
|
||||
for (const declaration of symbol.declarations) {
|
||||
if (firstDeclaration === undefined || declaration.pos < firstDeclaration.pos) {
|
||||
firstDeclaration = declaration;
|
||||
}
|
||||
function getRenameLocation(edits: ReadonlyArray<FileTextChanges>, renameFilename: string, functionNameText: string): number {
|
||||
let delta = 0;
|
||||
for (const { fileName, textChanges } of edits) {
|
||||
Debug.assert(fileName === renameFilename);
|
||||
for (const change of textChanges) {
|
||||
const { span, newText } = change;
|
||||
// TODO(acasey): We are assuming that the call expression comes before the function declaration,
|
||||
// because we want the new cursor to be on the call expression,
|
||||
// which is closer to where the user was before extracting the function.
|
||||
const index = newText.indexOf(functionNameText);
|
||||
if (index !== -1) {
|
||||
return span.start + delta + index;
|
||||
}
|
||||
delta += newText.length - span.length;
|
||||
}
|
||||
}
|
||||
throw new Error(); // Didn't find the text we inserted?
|
||||
}
|
||||
|
||||
function getFirstDeclaration(type: Type): Declaration | undefined {
|
||||
let firstDeclaration = undefined;
|
||||
|
||||
const symbol = type.symbol;
|
||||
if (symbol && symbol.declarations) {
|
||||
for (const declaration of symbol.declarations) {
|
||||
if (firstDeclaration === undefined || declaration.pos < firstDeclaration.pos) {
|
||||
firstDeclaration = declaration;
|
||||
}
|
||||
}
|
||||
|
||||
return firstDeclaration;
|
||||
}
|
||||
|
||||
function compareTypesByDeclarationOrder(
|
||||
{type: type1, declaration: declaration1}: {type: Type, declaration?: Declaration},
|
||||
{type: type2, declaration: declaration2}: {type: Type, declaration?: Declaration}) {
|
||||
return firstDeclaration;
|
||||
}
|
||||
|
||||
if (declaration1) {
|
||||
if (declaration2) {
|
||||
const positionDiff = declaration1.pos - declaration2.pos;
|
||||
if (positionDiff !== 0) {
|
||||
return positionDiff;
|
||||
}
|
||||
function compareTypesByDeclarationOrder(
|
||||
{type: type1, declaration: declaration1}: {type: Type, declaration?: Declaration},
|
||||
{type: type2, declaration: declaration2}: {type: Type, declaration?: Declaration}) {
|
||||
|
||||
if (declaration1) {
|
||||
if (declaration2) {
|
||||
const positionDiff = declaration1.pos - declaration2.pos;
|
||||
if (positionDiff !== 0) {
|
||||
return positionDiff;
|
||||
}
|
||||
else {
|
||||
return 1; // Sort undeclared type parameters to the front.
|
||||
}
|
||||
}
|
||||
else if (declaration2) {
|
||||
return -1; // Sort undeclared type parameters to the front.
|
||||
}
|
||||
|
||||
const name1 = type1.symbol ? type1.symbol.getName() : "";
|
||||
const name2 = type2.symbol ? type2.symbol.getName() : "";
|
||||
const nameDiff = compareStrings(name1, name2);
|
||||
if (nameDiff !== 0) {
|
||||
return nameDiff;
|
||||
}
|
||||
|
||||
// IDs are guaranteed to be unique, so this ensures a total ordering.
|
||||
return type1.id - type2.id;
|
||||
}
|
||||
|
||||
function getPropertyAssignmentsForWrites(writes: UsageEntry[]) {
|
||||
return writes.map(w => createShorthandPropertyAssignment(w.symbol.name));
|
||||
}
|
||||
|
||||
function generateReturnValueProperty() {
|
||||
return "__return";
|
||||
}
|
||||
|
||||
function getStatementsOrClassElements(scope: Scope): ReadonlyArray<Statement> | ReadonlyArray<ClassElement> {
|
||||
if (isFunctionLike(scope)) {
|
||||
const body = scope.body;
|
||||
if (isBlock(body)) {
|
||||
return body.statements;
|
||||
}
|
||||
}
|
||||
else if (isModuleBlock(scope) || isSourceFile(scope)) {
|
||||
return scope.statements;
|
||||
}
|
||||
else if (isClassLike(scope)) {
|
||||
return scope.members;
|
||||
}
|
||||
else {
|
||||
assertTypeIsNever(scope);
|
||||
}
|
||||
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* If `scope` contains a function after `minPos`, then return the first such function.
|
||||
* Otherwise, return `undefined`.
|
||||
*/
|
||||
function getNodeToInsertBefore(minPos: number, scope: Scope): Node | undefined {
|
||||
const children = getStatementsOrClassElements(scope);
|
||||
for (const child of children) {
|
||||
if (child.pos >= minPos && isFunctionLike(child) && !isConstructorDeclaration(child)) {
|
||||
return child;
|
||||
}
|
||||
return 1; // Sort undeclared type parameters to the front.
|
||||
}
|
||||
}
|
||||
else if (declaration2) {
|
||||
return -1; // Sort undeclared type parameters to the front.
|
||||
}
|
||||
|
||||
function transformFunctionBody(body: Node) {
|
||||
if (isBlock(body) && !writes && substitutions.size === 0) {
|
||||
// already block, no writes to propagate back, no substitutions - can use node as is
|
||||
return { body: createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined };
|
||||
}
|
||||
let returnValueProperty: string;
|
||||
const statements = createNodeArray(isBlock(body) ? body.statements.slice(0) : [isStatement(body) ? body : createReturn(<Expression>body)]);
|
||||
// rewrite body if either there are writes that should be propagated back via return statements or there are substitutions
|
||||
if (writes || substitutions.size) {
|
||||
const rewrittenStatements = visitNodes(statements, visitor).slice();
|
||||
if (writes && !(range.facts & RangeFacts.HasReturn) && isStatement(body)) {
|
||||
// add return at the end to propagate writes back in case if control flow falls out of the function body
|
||||
// it is ok to know that range has at least one return since it we only allow unconditional returns
|
||||
const assignments = getPropertyAssignmentsForWrites(writes);
|
||||
if (assignments.length === 1) {
|
||||
rewrittenStatements.push(createReturn(assignments[0].name));
|
||||
}
|
||||
else {
|
||||
rewrittenStatements.push(createReturn(createObjectLiteral(assignments)));
|
||||
}
|
||||
}
|
||||
return { body: createBlock(rewrittenStatements, /*multiLine*/ true), returnValueProperty };
|
||||
}
|
||||
else {
|
||||
return { body: createBlock(statements, /*multiLine*/ true), returnValueProperty: undefined };
|
||||
}
|
||||
const name1 = type1.symbol ? type1.symbol.getName() : "";
|
||||
const name2 = type2.symbol ? type2.symbol.getName() : "";
|
||||
const nameDiff = compareStrings(name1, name2);
|
||||
if (nameDiff !== 0) {
|
||||
return nameDiff;
|
||||
}
|
||||
|
||||
function visitor(node: Node): VisitResult<Node> {
|
||||
if (node.kind === SyntaxKind.ReturnStatement && writes) {
|
||||
const assignments: ObjectLiteralElementLike[] = getPropertyAssignmentsForWrites(writes);
|
||||
if ((<ReturnStatement>node).expression) {
|
||||
if (!returnValueProperty) {
|
||||
returnValueProperty = generateReturnValueProperty();
|
||||
}
|
||||
assignments.unshift(createPropertyAssignment(returnValueProperty, visitNode((<ReturnStatement>node).expression, visitor)));
|
||||
}
|
||||
if (assignments.length === 1) {
|
||||
return createReturn(assignments[0].name as Expression);
|
||||
}
|
||||
else {
|
||||
return createReturn(createObjectLiteral(assignments));
|
||||
}
|
||||
// IDs are guaranteed to be unique, so this ensures a total ordering.
|
||||
return type1.id - type2.id;
|
||||
}
|
||||
|
||||
function getCalledExpression(scope: Node, range: TargetRange, functionNameText: string): Expression {
|
||||
const functionReference = createIdentifier(functionNameText);
|
||||
if (isClassLike(scope)) {
|
||||
const lhs = range.facts & RangeFacts.InStaticRegion ? createIdentifier(scope.name.text) : createThis();
|
||||
return createPropertyAccess(lhs, functionReference);
|
||||
}
|
||||
else {
|
||||
return functionReference;
|
||||
}
|
||||
}
|
||||
|
||||
function transformFunctionBody(body: Node, writes: ReadonlyArray<UsageEntry>, substitutions: ReadonlyMap<Node>, hasReturn: boolean): { body: Block, returnValueProperty: string } {
|
||||
if (isBlock(body) && !writes && substitutions.size === 0) {
|
||||
// already block, no writes to propagate back, no substitutions - can use node as is
|
||||
return { body: createBlock(body.statements, /*multLine*/ true), returnValueProperty: undefined };
|
||||
}
|
||||
let returnValueProperty: string;
|
||||
const statements = createNodeArray(isBlock(body) ? body.statements.slice(0) : [isStatement(body) ? body : createReturn(<Expression>body)]);
|
||||
// rewrite body if either there are writes that should be propagated back via return statements or there are substitutions
|
||||
if (writes || substitutions.size) {
|
||||
const rewrittenStatements = visitNodes(statements, visitor).slice();
|
||||
if (writes && !hasReturn && isStatement(body)) {
|
||||
// add return at the end to propagate writes back in case if control flow falls out of the function body
|
||||
// it is ok to know that range has at least one return since it we only allow unconditional returns
|
||||
const assignments = getPropertyAssignmentsForWrites(writes);
|
||||
if (assignments.length === 1) {
|
||||
rewrittenStatements.push(createReturn(assignments[0].name));
|
||||
}
|
||||
else {
|
||||
const substitution = substitutions.get(getNodeId(node).toString());
|
||||
return substitution || visitEachChild(node, visitor, nullTransformationContext);
|
||||
rewrittenStatements.push(createReturn(createObjectLiteral(assignments)));
|
||||
}
|
||||
}
|
||||
return { body: createBlock(rewrittenStatements, /*multiLine*/ true), returnValueProperty };
|
||||
}
|
||||
else {
|
||||
return { body: createBlock(statements, /*multiLine*/ true), returnValueProperty: undefined };
|
||||
}
|
||||
|
||||
function visitor(node: Node): VisitResult<Node> {
|
||||
if (node.kind === SyntaxKind.ReturnStatement && writes) {
|
||||
const assignments: ObjectLiteralElementLike[] = getPropertyAssignmentsForWrites(writes);
|
||||
if ((<ReturnStatement>node).expression) {
|
||||
if (!returnValueProperty) {
|
||||
returnValueProperty = "__return";
|
||||
}
|
||||
assignments.unshift(createPropertyAssignment(returnValueProperty, visitNode((<ReturnStatement>node).expression, visitor)));
|
||||
}
|
||||
if (assignments.length === 1) {
|
||||
return createReturn(assignments[0].name as Expression);
|
||||
}
|
||||
else {
|
||||
return createReturn(createObjectLiteral(assignments));
|
||||
}
|
||||
}
|
||||
else {
|
||||
const substitution = substitutions.get(getNodeId(node).toString());
|
||||
return substitution || visitEachChild(node, visitor, nullTransformationContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getStatementsOrClassElements(scope: Scope): ReadonlyArray<Statement> | ReadonlyArray<ClassElement> {
|
||||
if (isFunctionLike(scope)) {
|
||||
const body = scope.body;
|
||||
if (isBlock(body)) {
|
||||
return body.statements;
|
||||
}
|
||||
}
|
||||
else if (isModuleBlock(scope) || isSourceFile(scope)) {
|
||||
return scope.statements;
|
||||
}
|
||||
else if (isClassLike(scope)) {
|
||||
return scope.members;
|
||||
}
|
||||
else {
|
||||
assertTypeIsNever(scope);
|
||||
}
|
||||
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* If `scope` contains a function after `minPos`, then return the first such function.
|
||||
* Otherwise, return `undefined`.
|
||||
*/
|
||||
function getNodeToInsertBefore(minPos: number, scope: Scope): Node | undefined {
|
||||
const children = getStatementsOrClassElements(scope);
|
||||
for (const child of children) {
|
||||
if (child.pos >= minPos && isFunctionLike(child) && !isConstructorDeclaration(child)) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getPropertyAssignmentsForWrites(writes: ReadonlyArray<UsageEntry>): ShorthandPropertyAssignment[] {
|
||||
return writes.map(w => createShorthandPropertyAssignment(w.symbol.name));
|
||||
}
|
||||
|
||||
function isReadonlyArray(v: any): v is ReadonlyArray<any> {
|
||||
@@ -948,25 +962,30 @@ namespace ts.refactor.extractMethod {
|
||||
Write = 2
|
||||
}
|
||||
|
||||
export interface UsageEntry {
|
||||
interface UsageEntry {
|
||||
readonly usage: Usage;
|
||||
readonly symbol: Symbol;
|
||||
readonly node: Node;
|
||||
}
|
||||
|
||||
export interface ScopeUsages {
|
||||
usages: Map<UsageEntry>;
|
||||
typeParameterUsages: Map<TypeParameter>; // Key is type ID
|
||||
substitutions: Map<Node>;
|
||||
interface ScopeUsages {
|
||||
readonly usages: Map<UsageEntry>;
|
||||
readonly typeParameterUsages: Map<TypeParameter>; // Key is type ID
|
||||
readonly substitutions: Map<Node>;
|
||||
}
|
||||
|
||||
interface ReadsAndWrites {
|
||||
readonly target: Expression | Block;
|
||||
readonly usagesPerScope: ReadonlyArray<ScopeUsages>;
|
||||
readonly errorsPerScope: ReadonlyArray<ReadonlyArray<Diagnostic>>;
|
||||
}
|
||||
function collectReadsAndWrites(
|
||||
targetRange: TargetRange,
|
||||
scopes: Scope[],
|
||||
enclosingTextRange: TextRange,
|
||||
sourceFile: SourceFile,
|
||||
checker: TypeChecker,
|
||||
cancellationToken: CancellationToken) {
|
||||
cancellationToken: CancellationToken): ReadsAndWrites {
|
||||
|
||||
const allTypeParameterUsages = createMap<TypeParameter>(); // Key is type ID
|
||||
const usagesPerScope: ScopeUsages[] = [];
|
||||
|
||||
@@ -414,8 +414,8 @@ namespace ts {
|
||||
*/
|
||||
export interface RefactorEditInfo {
|
||||
edits: FileTextChanges[];
|
||||
renameFilename?: string;
|
||||
renameLocation?: number;
|
||||
renameFilename: string | undefined;
|
||||
renameLocation: number | undefined;
|
||||
}
|
||||
|
||||
export interface TextInsertion {
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace A {
|
||||
function a() {
|
||||
let a = 1;
|
||||
|
||||
newFunction();
|
||||
/*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
let y = 5;
|
||||
@@ -43,7 +43,7 @@ namespace A {
|
||||
function a() {
|
||||
let a = 1;
|
||||
|
||||
a = newFunction(a);
|
||||
a = /*RENAME*/newFunction(a);
|
||||
}
|
||||
|
||||
function newFunction(a: number) {
|
||||
@@ -64,7 +64,7 @@ namespace A {
|
||||
function a() {
|
||||
let a = 1;
|
||||
|
||||
a = newFunction(a);
|
||||
a = /*RENAME*/newFunction(a);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace A {
|
||||
function a() {
|
||||
let a = 1;
|
||||
|
||||
a = newFunction(x, a, foo);
|
||||
a = /*RENAME*/newFunction(x, a, foo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace A {
|
||||
class C {
|
||||
a() {
|
||||
let z = 1;
|
||||
return this.newFunction();
|
||||
return this./*RENAME*/newFunction();
|
||||
}
|
||||
|
||||
private newFunction() {
|
||||
@@ -30,7 +30,7 @@ namespace A {
|
||||
class C {
|
||||
a() {
|
||||
let z = 1;
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace A {
|
||||
class C {
|
||||
a() {
|
||||
let z = 1;
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace A {
|
||||
a() {
|
||||
let z = 1;
|
||||
var __return: any;
|
||||
({ __return, z } = this.newFunction(z));
|
||||
({ __return, z } = this./*RENAME*/newFunction(z));
|
||||
return __return;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace A {
|
||||
a() {
|
||||
let z = 1;
|
||||
var __return: any;
|
||||
({ __return, z } = newFunction(z));
|
||||
({ __return, z } = /*RENAME*/newFunction(z));
|
||||
return __return;
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ namespace A {
|
||||
a() {
|
||||
let z = 1;
|
||||
var __return: any;
|
||||
({ __return, y, z } = newFunction(y, z));
|
||||
({ __return, y, z } = /*RENAME*/newFunction(y, z));
|
||||
return __return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace A {
|
||||
a() {
|
||||
let z = 1;
|
||||
var __return: any;
|
||||
({ __return, z } = this.newFunction(z));
|
||||
({ __return, z } = this./*RENAME*/newFunction(z));
|
||||
return __return;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<U2a, U2b>(u2a: U2a, u2b: U2b) => {
|
||||
function F2<T2a, T2b>(t2a: T2a, t2b: T2b) {
|
||||
<U3a, U3b>(u3a: U3a, u3b: U3b) => {
|
||||
newFunction<U3a>(u3a);
|
||||
/*RENAME*/newFunction<U3a>(u3a);
|
||||
}
|
||||
|
||||
function newFunction<U3a>(u3a: U3a) {
|
||||
@@ -40,7 +40,7 @@
|
||||
<U2a, U2b>(u2a: U2a, u2b: U2b) => {
|
||||
function F2<T2a, T2b>(t2a: T2a, t2b: T2b) {
|
||||
<U3a, U3b>(u3a: U3a, u3b: U3b) => {
|
||||
newFunction<U2a, T2a, U3a>(t2a, u2a, u3a);
|
||||
/*RENAME*/newFunction<U2a, T2a, U3a>(t2a, u2a, u3a);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@
|
||||
<U2a, U2b>(u2a: U2a, u2b: U2b) => {
|
||||
function F2<T2a, T2b>(t2a: T2a, t2b: T2b) {
|
||||
<U3a, U3b>(u3a: U3a, u3b: U3b) => {
|
||||
newFunction<U1a, T1a, U2a, T2a, U3a>(t1a, t2a, u1a, u2a, u3a);
|
||||
/*RENAME*/newFunction<U1a, T1a, U2a, T2a, U3a>(t1a, t2a, u1a, u2a, u3a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ function F<T>(t1: T) {
|
||||
// ==SCOPE::inner function in function 'F'==
|
||||
function F<T>(t1: T) {
|
||||
function F<T>(t2: T) {
|
||||
newFunction();
|
||||
/*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
t1.toString();
|
||||
@@ -19,7 +19,7 @@ function F<T>(t1: T) {
|
||||
// ==SCOPE::inner function in function 'F'==
|
||||
function F<T>(t1: T) {
|
||||
function F<T>(t2: T) {
|
||||
newFunction<T>(t2);
|
||||
/*RENAME*/newFunction<T>(t2);
|
||||
}
|
||||
|
||||
function newFunction<T>(t2: T) {
|
||||
@@ -30,7 +30,7 @@ function F<T>(t1: T) {
|
||||
// ==SCOPE::function in global scope==
|
||||
function F<T>(t1: T) {
|
||||
function F<T>(t2: T) {
|
||||
newFunction<T, T>(t1, t2);
|
||||
/*RENAME*/newFunction<T, T>(t1, t2);
|
||||
}
|
||||
}
|
||||
function newFunction<T, T>(t1: T, t2: T) {
|
||||
|
||||
@@ -7,7 +7,7 @@ function F<T>(t1: T) {
|
||||
// ==SCOPE::inner function in function 'F'==
|
||||
function F<T>(t1: T) {
|
||||
function F<U extends T[]>(t2: U) {
|
||||
newFunction();
|
||||
/*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
t2.toString();
|
||||
@@ -17,7 +17,7 @@ function F<T>(t1: T) {
|
||||
// ==SCOPE::inner function in function 'F'==
|
||||
function F<T>(t1: T) {
|
||||
function F<U extends T[]>(t2: U) {
|
||||
newFunction<U>(t2);
|
||||
/*RENAME*/newFunction<U>(t2);
|
||||
}
|
||||
|
||||
function newFunction<U extends T[]>(t2: U) {
|
||||
@@ -27,7 +27,7 @@ function F<T>(t1: T) {
|
||||
// ==SCOPE::function in global scope==
|
||||
function F<T>(t1: T) {
|
||||
function F<U extends T[]>(t2: U) {
|
||||
newFunction<T, U>(t2);
|
||||
/*RENAME*/newFunction<T, U>(t2);
|
||||
}
|
||||
}
|
||||
function newFunction<T, U extends T[]>(t2: U) {
|
||||
|
||||
@@ -4,7 +4,7 @@ function F<T>() {
|
||||
}
|
||||
// ==SCOPE::inner function in function 'F'==
|
||||
function F<T>() {
|
||||
const array: T[] = newFunction();
|
||||
const array: T[] = /*RENAME*/newFunction();
|
||||
|
||||
function newFunction(): T[] {
|
||||
return [];
|
||||
@@ -12,7 +12,7 @@ function F<T>() {
|
||||
}
|
||||
// ==SCOPE::function in global scope==
|
||||
function F<T>() {
|
||||
const array: T[] = newFunction<T>();
|
||||
const array: T[] = /*RENAME*/newFunction<T>();
|
||||
}
|
||||
function newFunction<T>(): T[] {
|
||||
return [];
|
||||
|
||||
@@ -7,7 +7,7 @@ class C<T1, T2> {
|
||||
// ==SCOPE::method in class 'C'==
|
||||
class C<T1, T2> {
|
||||
M(t1: T1, t2: T2) {
|
||||
this.newFunction(t1);
|
||||
this./*RENAME*/newFunction(t1);
|
||||
}
|
||||
|
||||
private newFunction(t1: T1) {
|
||||
@@ -17,7 +17,7 @@ class C<T1, T2> {
|
||||
// ==SCOPE::function in global scope==
|
||||
class C<T1, T2> {
|
||||
M(t1: T1, t2: T2) {
|
||||
newFunction<T1>(t1);
|
||||
/*RENAME*/newFunction<T1>(t1);
|
||||
}
|
||||
}
|
||||
function newFunction<T1>(t1: T1) {
|
||||
|
||||
@@ -7,7 +7,7 @@ class C {
|
||||
// ==SCOPE::method in class 'C'==
|
||||
class C {
|
||||
M<T1, T2>(t1: T1, t2: T2) {
|
||||
this.newFunction<T1>(t1);
|
||||
this./*RENAME*/newFunction<T1>(t1);
|
||||
}
|
||||
|
||||
private newFunction<T1>(t1: T1) {
|
||||
@@ -17,7 +17,7 @@ class C {
|
||||
// ==SCOPE::function in global scope==
|
||||
class C {
|
||||
M<T1, T2>(t1: T1, t2: T2) {
|
||||
newFunction<T1>(t1);
|
||||
/*RENAME*/newFunction<T1>(t1);
|
||||
}
|
||||
}
|
||||
function newFunction<T1>(t1: T1) {
|
||||
|
||||
@@ -4,7 +4,7 @@ function F<T, U extends T[], V extends U[]>(v: V) {
|
||||
}
|
||||
// ==SCOPE::inner function in function 'F'==
|
||||
function F<T, U extends T[], V extends U[]>(v: V) {
|
||||
newFunction();
|
||||
/*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
v.toString();
|
||||
@@ -12,7 +12,7 @@ function F<T, U extends T[], V extends U[]>(v: V) {
|
||||
}
|
||||
// ==SCOPE::function in global scope==
|
||||
function F<T, U extends T[], V extends U[]>(v: V) {
|
||||
newFunction<T, U, V>(v);
|
||||
/*RENAME*/newFunction<T, U, V>(v);
|
||||
}
|
||||
function newFunction<T, U extends T[], V extends U[]>(v: V) {
|
||||
v.toString();
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace A {
|
||||
namespace B {
|
||||
function a() {
|
||||
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
let y = 5;
|
||||
@@ -38,7 +38,7 @@ namespace A {
|
||||
namespace B {
|
||||
function a() {
|
||||
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
|
||||
function newFunction() {
|
||||
@@ -56,7 +56,7 @@ namespace A {
|
||||
namespace B {
|
||||
function a() {
|
||||
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace A {
|
||||
namespace B {
|
||||
function a() {
|
||||
|
||||
return newFunction(x, foo);
|
||||
return /*RENAME*/newFunction(x, foo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ const _ = class {
|
||||
// ==SCOPE::method in anonymous class expression==
|
||||
const _ = class {
|
||||
a() {
|
||||
return this.newFunction();
|
||||
return this./*RENAME*/newFunction();
|
||||
}
|
||||
|
||||
private newFunction() {
|
||||
@@ -19,7 +19,7 @@ const _ = class {
|
||||
// ==SCOPE::function in global scope==
|
||||
const _ = class {
|
||||
a() {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
}
|
||||
function newFunction() {
|
||||
|
||||
@@ -7,7 +7,7 @@ function foo() {
|
||||
// ==SCOPE::inner function in function 'foo'==
|
||||
function foo() {
|
||||
let x = 10;
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
x++;
|
||||
@@ -17,7 +17,7 @@ function foo() {
|
||||
// ==SCOPE::function in global scope==
|
||||
function foo() {
|
||||
let x = 10;
|
||||
x = newFunction(x);
|
||||
x = /*RENAME*/newFunction(x);
|
||||
return;
|
||||
}
|
||||
function newFunction(x: number) {
|
||||
|
||||
@@ -11,7 +11,7 @@ function test() {
|
||||
try {
|
||||
}
|
||||
finally {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
|
||||
function newFunction() {
|
||||
@@ -23,7 +23,7 @@ function test() {
|
||||
try {
|
||||
}
|
||||
finally {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
}
|
||||
function newFunction() {
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace NS {
|
||||
namespace NS {
|
||||
function M1() { }
|
||||
function M2() {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
return 1;
|
||||
@@ -22,7 +22,7 @@ namespace NS {
|
||||
namespace NS {
|
||||
function M1() { }
|
||||
function M2() {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
function newFunction() {
|
||||
return 1;
|
||||
@@ -34,7 +34,7 @@ namespace NS {
|
||||
namespace NS {
|
||||
function M1() { }
|
||||
function M2() {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
function M3() { }
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ function Outer() {
|
||||
function Outer() {
|
||||
function M1() { }
|
||||
function M2() {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
return 1;
|
||||
@@ -22,7 +22,7 @@ function Outer() {
|
||||
function Outer() {
|
||||
function M1() { }
|
||||
function M2() {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
function newFunction() {
|
||||
return 1;
|
||||
@@ -34,7 +34,7 @@ function Outer() {
|
||||
function Outer() {
|
||||
function M1() { }
|
||||
function M2() {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
function M3() { }
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ function M3() { }
|
||||
// ==SCOPE::inner function in function 'M2'==
|
||||
function M1() { }
|
||||
function M2() {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
return 1;
|
||||
@@ -17,7 +17,7 @@ function M3() { }
|
||||
// ==SCOPE::function in global scope==
|
||||
function M1() { }
|
||||
function M2() {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
function newFunction() {
|
||||
return 1;
|
||||
|
||||
@@ -10,7 +10,7 @@ class C {
|
||||
class C {
|
||||
M1() { }
|
||||
M2() {
|
||||
return this.newFunction();
|
||||
return this./*RENAME*/newFunction();
|
||||
}
|
||||
private newFunction() {
|
||||
return 1;
|
||||
@@ -22,7 +22,7 @@ class C {
|
||||
class C {
|
||||
M1() { }
|
||||
M2() {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
M3() { }
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class C {
|
||||
class C {
|
||||
M1() { }
|
||||
M2() {
|
||||
return this.newFunction();
|
||||
return this./*RENAME*/newFunction();
|
||||
}
|
||||
constructor() { }
|
||||
private newFunction() {
|
||||
@@ -24,7 +24,7 @@ class C {
|
||||
class C {
|
||||
M1() { }
|
||||
M2() {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
constructor() { }
|
||||
M3() { }
|
||||
|
||||
@@ -11,7 +11,7 @@ class C {
|
||||
class C {
|
||||
M1() { }
|
||||
M2() {
|
||||
return this.newFunction();
|
||||
return this./*RENAME*/newFunction();
|
||||
}
|
||||
private newFunction() {
|
||||
return 1;
|
||||
@@ -24,7 +24,7 @@ class C {
|
||||
class C {
|
||||
M1() { }
|
||||
M2() {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
M3() { }
|
||||
constructor() { }
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace A {
|
||||
namespace B {
|
||||
function* a(z: number) {
|
||||
|
||||
return yield* newFunction();
|
||||
return yield* /*RENAME*/newFunction();
|
||||
|
||||
function* newFunction() {
|
||||
let y = 5;
|
||||
@@ -35,7 +35,7 @@ namespace A {
|
||||
namespace B {
|
||||
function* a(z: number) {
|
||||
|
||||
return yield* newFunction(z);
|
||||
return yield* /*RENAME*/newFunction(z);
|
||||
}
|
||||
|
||||
function* newFunction(z: number) {
|
||||
@@ -52,7 +52,7 @@ namespace A {
|
||||
namespace B {
|
||||
function* a(z: number) {
|
||||
|
||||
return yield* newFunction(z);
|
||||
return yield* /*RENAME*/newFunction(z);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace A {
|
||||
namespace B {
|
||||
function* a(z: number) {
|
||||
|
||||
return yield* newFunction(z, foo);
|
||||
return yield* /*RENAME*/newFunction(z, foo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace A {
|
||||
namespace B {
|
||||
async function a(z: number, z1: any) {
|
||||
|
||||
return await newFunction();
|
||||
return await /*RENAME*/newFunction();
|
||||
|
||||
async function newFunction() {
|
||||
let y = 5;
|
||||
@@ -39,7 +39,7 @@ namespace A {
|
||||
namespace B {
|
||||
async function a(z: number, z1: any) {
|
||||
|
||||
return await newFunction(z, z1);
|
||||
return await /*RENAME*/newFunction(z, z1);
|
||||
}
|
||||
|
||||
async function newFunction(z: number, z1: any) {
|
||||
@@ -58,7 +58,7 @@ namespace A {
|
||||
namespace B {
|
||||
async function a(z: number, z1: any) {
|
||||
|
||||
return await newFunction(z, z1);
|
||||
return await /*RENAME*/newFunction(z, z1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace A {
|
||||
namespace B {
|
||||
async function a(z: number, z1: any) {
|
||||
|
||||
return await newFunction(z, z1, foo);
|
||||
return await /*RENAME*/newFunction(z, z1, foo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace A {
|
||||
function a() {
|
||||
let a = 1;
|
||||
|
||||
newFunction();
|
||||
/*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
let y = 5;
|
||||
@@ -43,7 +43,7 @@ namespace A {
|
||||
function a() {
|
||||
let a = 1;
|
||||
|
||||
a = newFunction(a);
|
||||
a = /*RENAME*/newFunction(a);
|
||||
}
|
||||
|
||||
function newFunction(a: number) {
|
||||
@@ -64,7 +64,7 @@ namespace A {
|
||||
function a() {
|
||||
let a = 1;
|
||||
|
||||
a = newFunction(a);
|
||||
a = /*RENAME*/newFunction(a);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace A {
|
||||
function a() {
|
||||
let a = 1;
|
||||
|
||||
a = newFunction(x, a);
|
||||
a = /*RENAME*/newFunction(x, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace A {
|
||||
function a() {
|
||||
let a = 1;
|
||||
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
let y = 5;
|
||||
@@ -44,7 +44,7 @@ namespace A {
|
||||
let a = 1;
|
||||
|
||||
var __return: any;
|
||||
({ __return, a } = newFunction(a));
|
||||
({ __return, a } = /*RENAME*/newFunction(a));
|
||||
return __return;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace A {
|
||||
let a = 1;
|
||||
|
||||
var __return: any;
|
||||
({ __return, a } = newFunction(a));
|
||||
({ __return, a } = /*RENAME*/newFunction(a));
|
||||
return __return;
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@ namespace A {
|
||||
let a = 1;
|
||||
|
||||
var __return: any;
|
||||
({ __return, a } = newFunction(x, a));
|
||||
({ __return, a } = /*RENAME*/newFunction(x, a));
|
||||
return __return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace A {
|
||||
function a() {
|
||||
let a = 1;
|
||||
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
let y = 5;
|
||||
@@ -50,7 +50,7 @@ namespace A {
|
||||
let a = 1;
|
||||
|
||||
var __return: any;
|
||||
({ __return, a } = newFunction(a));
|
||||
({ __return, a } = /*RENAME*/newFunction(a));
|
||||
return __return;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace A {
|
||||
let a = 1;
|
||||
|
||||
var __return: any;
|
||||
({ __return, a } = newFunction(a));
|
||||
({ __return, a } = /*RENAME*/newFunction(a));
|
||||
return __return;
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ namespace A {
|
||||
let a = 1;
|
||||
|
||||
var __return: any;
|
||||
({ __return, a } = newFunction(x, a));
|
||||
({ __return, a } = /*RENAME*/newFunction(x, a));
|
||||
return __return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace A {
|
||||
namespace B {
|
||||
function a() {
|
||||
let a1 = 1;
|
||||
return newFunction() + 100;
|
||||
return /*RENAME*/newFunction() + 100;
|
||||
|
||||
function newFunction() {
|
||||
return 1 + a1 + x;
|
||||
@@ -28,7 +28,7 @@ namespace A {
|
||||
namespace B {
|
||||
function a() {
|
||||
let a1 = 1;
|
||||
return newFunction(a1) + 100;
|
||||
return /*RENAME*/newFunction(a1) + 100;
|
||||
}
|
||||
|
||||
function newFunction(a1: number) {
|
||||
@@ -42,7 +42,7 @@ namespace A {
|
||||
namespace B {
|
||||
function a() {
|
||||
let a1 = 1;
|
||||
return newFunction(a1) + 100;
|
||||
return /*RENAME*/newFunction(a1) + 100;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace A {
|
||||
namespace B {
|
||||
function a() {
|
||||
let a1 = 1;
|
||||
return newFunction(a1, x) + 100;
|
||||
return /*RENAME*/newFunction(a1, x) + 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace A {
|
||||
export interface I { x: number };
|
||||
namespace B {
|
||||
function a() {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
let a1: I = { x: 1 };
|
||||
@@ -27,7 +27,7 @@ namespace A {
|
||||
export interface I { x: number };
|
||||
namespace B {
|
||||
function a() {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
|
||||
function newFunction() {
|
||||
@@ -41,7 +41,7 @@ namespace A {
|
||||
export interface I { x: number };
|
||||
namespace B {
|
||||
function a() {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace A {
|
||||
export interface I { x: number };
|
||||
namespace B {
|
||||
function a() {
|
||||
return newFunction();
|
||||
return /*RENAME*/newFunction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,8 @@ edit.applyRefactor({
|
||||
refactorName: "Extract Method",
|
||||
actionName: "scope_1",
|
||||
actionDescription: "Extract to function in global scope",
|
||||
});
|
||||
verify.currentFileContentIs(
|
||||
`function f(x: number): number {
|
||||
return newFunction(x);
|
||||
newContent: `function f(x: number): number {
|
||||
return /*RENAME*/newFunction(x);
|
||||
}
|
||||
function newFunction(x: number) {
|
||||
switch (x) {
|
||||
@@ -21,4 +19,5 @@ function newFunction(x: number) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
`);
|
||||
`
|
||||
});
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
////// newFunction
|
||||
/////*start*/1 + 1/*end*/;
|
||||
|
||||
goTo.select('start', 'end')
|
||||
edit.applyRefactor({
|
||||
refactorName: "Extract Method",
|
||||
actionName: "scope_0",
|
||||
actionDescription: "Extract to function in global scope",
|
||||
newContent:
|
||||
`// newFunction
|
||||
/*RENAME*/newFunction_1();
|
||||
|
||||
function newFunction_1() {
|
||||
// newFunction
|
||||
1 + 1;
|
||||
}
|
||||
`
|
||||
});
|
||||
@@ -17,11 +17,10 @@ edit.applyRefactor({
|
||||
refactorName: "Extract Method",
|
||||
actionName: "scope_0",
|
||||
actionDescription: "Extract to method in class 'Foo'",
|
||||
});
|
||||
verify.currentFileContentIs(
|
||||
newContent:
|
||||
`class Foo {
|
||||
someMethod(m: number) {
|
||||
this.newFunction(m);
|
||||
this./*RENAME*/newFunction(m);
|
||||
var q = 10;
|
||||
return q;
|
||||
}
|
||||
@@ -33,4 +32,5 @@ verify.currentFileContentIs(
|
||||
var z = y + x;
|
||||
console.log(z);
|
||||
}
|
||||
}`);
|
||||
}`
|
||||
});
|
||||
|
||||
@@ -8,4 +8,11 @@ edit.applyRefactor({
|
||||
refactorName: "Extract Method",
|
||||
actionName: 'scope_0',
|
||||
actionDescription: "Extract to function in module scope",
|
||||
newContent:
|
||||
`export {}; // Make this a module
|
||||
(x => x)(/*RENAME*/newFunction())(1);
|
||||
function newFunction(): (x: any) => any {
|
||||
return x => x;
|
||||
}
|
||||
`
|
||||
});
|
||||
|
||||
@@ -14,6 +14,16 @@ edit.applyRefactor({
|
||||
refactorName: "Extract Method",
|
||||
actionName: "scope_0",
|
||||
actionDescription: "Extract to method in class 'C'",
|
||||
newContent:
|
||||
`class C {
|
||||
static j = 1 + 1;
|
||||
constructor(q: string = C./*RENAME*/newFunction()) {
|
||||
}
|
||||
|
||||
private static newFunction(): string {
|
||||
return "a" + "b";
|
||||
}
|
||||
}`
|
||||
});
|
||||
|
||||
verify.currentFileContentIs(`class C {
|
||||
@@ -31,10 +41,9 @@ edit.applyRefactor({
|
||||
refactorName: "Extract Method",
|
||||
actionName: "scope_0",
|
||||
actionDescription: "Extract to method in class 'C'",
|
||||
});
|
||||
|
||||
verify.currentFileContentIs(`class C {
|
||||
static j = C.newFunction_1();
|
||||
newContent:
|
||||
`class C {
|
||||
static j = C./*RENAME*/newFunction_1();
|
||||
constructor(q: string = C.newFunction()) {
|
||||
}
|
||||
|
||||
@@ -45,4 +54,5 @@ verify.currentFileContentIs(`class C {
|
||||
private static newFunction(): string {
|
||||
return "a" + "b";
|
||||
}
|
||||
}`);
|
||||
}`
|
||||
});
|
||||
|
||||
@@ -15,14 +15,15 @@ edit.applyRefactor({
|
||||
refactorName: "Extract Method",
|
||||
actionName: "scope_1",
|
||||
actionDescription: "Extract to function in global scope",
|
||||
});
|
||||
verify.currentFileContentIs(`function foo() {
|
||||
newContent:
|
||||
`function foo() {
|
||||
var i = 10;
|
||||
var __return: any;
|
||||
({ __return, i } = newFunction(i));
|
||||
({ __return, i } = n/*RENAME*/ewFunction(i));
|
||||
return __return;
|
||||
}
|
||||
function newFunction(i) {
|
||||
return { __return: i++, i };
|
||||
}
|
||||
`);
|
||||
`
|
||||
});
|
||||
|
||||
@@ -13,14 +13,14 @@ edit.applyRefactor({
|
||||
refactorName: "Extract Method",
|
||||
actionName: "scope_1",
|
||||
actionDescription: "Extract to function in global scope",
|
||||
});
|
||||
|
||||
verify.currentFileContentIs(`function foo() {
|
||||
newContent:
|
||||
`function foo() {
|
||||
var i = 10;
|
||||
i = newFunction(i);
|
||||
i = /*RENAME*/newFunction(i);
|
||||
}
|
||||
function newFunction(i: number) {
|
||||
i++;
|
||||
return i;
|
||||
}
|
||||
`);
|
||||
`
|
||||
});
|
||||
|
||||
@@ -13,12 +13,13 @@ edit.applyRefactor({
|
||||
refactorName: "Extract Method",
|
||||
actionName: "scope_1",
|
||||
actionDescription: "Extract to function in global scope",
|
||||
});
|
||||
verify.currentFileContentIs(`function fn() {
|
||||
newContent:
|
||||
`function fn() {
|
||||
const x = { m: 1 };
|
||||
newFunction(x);
|
||||
/*RENAME*/newFunction(x);
|
||||
}
|
||||
function newFunction(x: { m: number; }) {
|
||||
x.m = 3;
|
||||
}
|
||||
`);
|
||||
`
|
||||
});
|
||||
|
||||
@@ -13,13 +13,14 @@ edit.applyRefactor({
|
||||
refactorName: "Extract Method",
|
||||
actionName: "scope_0",
|
||||
actionDescription: "Extract to inner function in function 'fn'",
|
||||
});
|
||||
verify.currentFileContentIs(`function fn() {
|
||||
newFunction_1();
|
||||
newContent:
|
||||
`function fn() {
|
||||
/*RENAME*/newFunction_1();
|
||||
|
||||
function newFunction_1() {
|
||||
console.log("hi");
|
||||
}
|
||||
}
|
||||
|
||||
function newFunction() { }`);
|
||||
function newFunction() { }`
|
||||
});
|
||||
|
||||
@@ -14,18 +14,18 @@ edit.applyRefactor({
|
||||
refactorName: "Extract Method",
|
||||
actionName: "scope_2",
|
||||
actionDescription: "Extract to function in global scope",
|
||||
});
|
||||
verify.currentFileContentIs(
|
||||
newContent:
|
||||
`namespace NS {
|
||||
class Q {
|
||||
foo() {
|
||||
console.log('100');
|
||||
const m = 10, j = "hello", k = {x: "what"};
|
||||
const q = newFunction(m, j, k);
|
||||
const q = /*RENAME*/newFunction(m, j, k);
|
||||
}
|
||||
}
|
||||
}
|
||||
function newFunction(m: number, j: string, k: { x: string; }) {
|
||||
return m + j + k;
|
||||
}
|
||||
`);
|
||||
`
|
||||
});
|
||||
|
||||
@@ -16,14 +16,14 @@ edit.applyRefactor({
|
||||
refactorName: "Extract Method",
|
||||
actionName: "scope_0",
|
||||
actionDescription: "Extract to method in class 'Foo'",
|
||||
});
|
||||
|
||||
verify.currentFileContentIs(`class Foo {
|
||||
newContent:
|
||||
`class Foo {
|
||||
static method() {
|
||||
return Foo.newFunction();
|
||||
return Foo./*RENAME*/newFunction();
|
||||
}
|
||||
|
||||
private static newFunction() {
|
||||
return 1;
|
||||
}
|
||||
}`);
|
||||
}`
|
||||
});
|
||||
|
||||
@@ -11,13 +11,14 @@ edit.applyRefactor({
|
||||
refactorName: "Extract Method",
|
||||
actionName: "scope_1",
|
||||
actionDescription: "Extract to function in global scope",
|
||||
});
|
||||
verify.currentFileContentIs(`function M() {
|
||||
newContent:
|
||||
`function M() {
|
||||
let a = [1,2,3];
|
||||
let x = 0;
|
||||
console.log(newFunction(a, x));
|
||||
console.log(/*RENAME*/newFunction(a, x));
|
||||
}
|
||||
function newFunction(a: number[], x: number): any {
|
||||
return a[x];
|
||||
}
|
||||
`);
|
||||
`
|
||||
});
|
||||
|
||||
@@ -12,12 +12,13 @@ edit.applyRefactor({
|
||||
refactorName: "Extract Method",
|
||||
actionName: "scope_0",
|
||||
actionDescription: "Extract to inner function in function 'fn'",
|
||||
});
|
||||
verify.currentFileContentIs(`function fn() {
|
||||
var q = newFunction()
|
||||
newContent:
|
||||
`function fn() {
|
||||
var q = /*RENAME*/newFunction()
|
||||
q[0]++
|
||||
|
||||
function newFunction() {
|
||||
return [0];
|
||||
}
|
||||
}`);
|
||||
}`
|
||||
});
|
||||
|
||||
@@ -13,13 +13,12 @@ edit.applyRefactor({
|
||||
refactorName: "Extract Method",
|
||||
actionName: "scope_0",
|
||||
actionDescription: "Extract to inner function in function 'f'",
|
||||
});
|
||||
// TODO: GH#18091 (fix formatting to use `2 ? 1 :` and not `2?1:`)
|
||||
verify.currentFileContentIs(
|
||||
newContent:
|
||||
`function f() {
|
||||
var x: 1 | 2 | 3 = newFunction();
|
||||
var x: 1 | 2 | 3 = /*RENAME*/newFunction();
|
||||
|
||||
function newFunction(): 1 | 2 | 3 {
|
||||
return 1 + 1 === 2 ? 1 : 2;
|
||||
}
|
||||
}`);
|
||||
}`
|
||||
});
|
||||
|
||||
@@ -11,10 +11,11 @@ edit.applyRefactor({
|
||||
refactorName: "Extract Method",
|
||||
actionName: "scope_0",
|
||||
actionDescription: "Extract to function in global scope",
|
||||
});
|
||||
verify.currentFileContentIs(`function fn(x = newFunction()) {
|
||||
newContent:
|
||||
`function fn(x = /*RENAME*/newFunction()) {
|
||||
}
|
||||
function newFunction() {
|
||||
return 1 + 1;
|
||||
}
|
||||
`);
|
||||
`
|
||||
});
|
||||
|
||||
@@ -310,7 +310,7 @@ declare namespace FourSlashInterface {
|
||||
enableFormatting(): void;
|
||||
disableFormatting(): void;
|
||||
|
||||
applyRefactor(options: { refactorName: string, actionName: string, actionDescription: string }): void;
|
||||
applyRefactor(options: { refactorName: string, actionName: string, actionDescription: string, newContent: string }): void;
|
||||
}
|
||||
class debug {
|
||||
printCurrentParameterHelp(): void;
|
||||
|
||||
Reference in New Issue
Block a user