Skip to content

'Move to file' refactor #53542

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 49 commits into from
Apr 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
17d1a6b
tests for moce to another file
navya9singh Mar 7, 2023
98bfb65
Works in moving to an existing blank file
navya9singh Mar 7, 2023
ea8de38
new change
navya9singh Mar 7, 2023
393b093
test2 almost works
navya9singh Mar 10, 2023
fe30722
Part 2 works for test2
navya9singh Mar 13, 2023
f54ece7
generating list of file with test cases
navya9singh Mar 21, 2023
9bfd52d
Test cases working as expected
navya9singh Mar 26, 2023
58f2c9d
cleanup of tests and renaming to "move to file"
navya9singh Mar 26, 2023
e10b530
Merge branch 'main' of https://github.com/microsoft/TypeScript into t…
navya9singh Mar 26, 2023
5603210
Resolving merge conflict changes
navya9singh Mar 26, 2023
dffe218
all tests pass
navya9singh Mar 27, 2023
28d8258
duplicate code cleanup
navya9singh Mar 27, 2023
6ff1f26
deleting 'triggerReason' property
navya9singh Mar 28, 2023
4f09254
added export and deleted duplicated code
navya9singh Mar 30, 2023
0866cfc
move exports to new file, fix doChange as needed
navya9singh Mar 30, 2023
6884d00
combining createnewFile with moveToFileAndNewFile
navya9singh Mar 30, 2023
f98a457
removing `changeExistingFile` from textChanges
navya9singh Mar 31, 2023
eec2935
fix for an 'allowImportingTsExtensions' bug
navya9singh Apr 5, 2023
9fd4778
cleanup, fixes after removing `changeExistingFile`
navya9singh Apr 5, 2023
b1cf74c
fixi testcases & add getSynthesizedDeepClone
navya9singh Apr 5, 2023
2274ca1
Merge branch 'main' of https://github.com/microsoft/TypeScript into t…
navya9singh Apr 5, 2023
53c5fa2
test needs to be fixed
navya9singh Apr 5, 2023
93bca9c
Ensure cloned NodeArrays have a text range set
andrewbranch Apr 6, 2023
7cb66dd
fixes for suggesting a list of files
andrewbranch Apr 5, 2023
63d2a83
Ensure cloned NodeArrays have a text range set
andrewbranch Apr 6, 2023
a681bc9
Allow ImportAdder to insert imports into a new file
andrewbranch Apr 5, 2023
52674ac
importAdder changes for MoveToFile
navya9singh Apr 7, 2023
a7aad23
Merge branch 'task29988' of https://github.com/microsoft/TypeScript i…
navya9singh Apr 7, 2023
5666218
adding, cleaning up tests
navya9singh Apr 10, 2023
7a12e5f
Merge branch 'main' of https://github.com/microsoft/TypeScript into t…
navya9singh Apr 10, 2023
9c5efd5
Reverting changes for impoart adder for new file
navya9singh Apr 10, 2023
6068ec4
Accepting baselines
navya9singh Apr 10, 2023
9387f67
FmoduleSpecifier fixes and cleanup
navya9singh Apr 10, 2023
4862931
Not needed
navya9singh Apr 10, 2023
f0e870d
Adressing pr comments
navya9singh Apr 12, 2023
b010444
adding new tests
navya9singh Apr 12, 2023
5ecf83d
Fixing errors, accepting baselines
navya9singh Apr 13, 2023
567a3c5
Removing exports, small changes
navya9singh Apr 13, 2023
8af19ff
Adressing pr comments
navya9singh Apr 17, 2023
08b9243
Merge branch 'task29988' of https://github.com/microsoft/TypeScript i…
navya9singh Apr 17, 2023
6675cb7
Merge branch 'main' of https://github.com/microsoft/TypeScript into t…
navya9singh Apr 17, 2023
f4e73aa
Fixing baselines
navya9singh Apr 17, 2023
38471a8
adressing comments
navya9singh Apr 18, 2023
0439ddc
Merge branch 'main' of https://github.com/microsoft/TypeScript into t…
navya9singh Apr 20, 2023
9734d4d
Changes for new api protocol
navya9singh Apr 20, 2023
003113c
fixing tests
navya9singh Apr 20, 2023
28d8564
accepting baselines
navya9singh Apr 21, 2023
e61ad6d
Simplifying fileSuggestions in session.ts
navya9singh Apr 21, 2023
ea6441d
Fixing return statement
navya9singh Apr 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -7600,6 +7600,10 @@
"category": "Message",
"code": 95177
},
"Move to file": {
"category": "Message",
"code": 95178
},

"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
Expand Down
21 changes: 16 additions & 5 deletions src/compiler/moduleSpecifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,18 @@ function getPreferences(
? [ModuleSpecifierEnding.JsExtension, ModuleSpecifierEnding.Index]
: [ModuleSpecifierEnding.Index, ModuleSpecifierEnding.JsExtension];
}
const allowImportingTsExtension = shouldAllowImportingTsExtension(compilerOptions, importingSourceFile.fileName);
switch (preferredEnding) {
case ModuleSpecifierEnding.JsExtension: return [ModuleSpecifierEnding.JsExtension, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.Index];
case ModuleSpecifierEnding.JsExtension: return allowImportingTsExtension
? [ModuleSpecifierEnding.JsExtension, ModuleSpecifierEnding.TsExtension, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.Index]
: [ModuleSpecifierEnding.JsExtension, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.Index];
case ModuleSpecifierEnding.TsExtension: return [ModuleSpecifierEnding.TsExtension, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.JsExtension, ModuleSpecifierEnding.Index];
case ModuleSpecifierEnding.Index: return [ModuleSpecifierEnding.Index, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.JsExtension];
case ModuleSpecifierEnding.Minimal: return [ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.Index, ModuleSpecifierEnding.JsExtension];
case ModuleSpecifierEnding.Index: return allowImportingTsExtension
? [ModuleSpecifierEnding.Index, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.TsExtension, ModuleSpecifierEnding.JsExtension]
: [ModuleSpecifierEnding.Index, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.JsExtension];
case ModuleSpecifierEnding.Minimal: return allowImportingTsExtension
? [ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.Index, ModuleSpecifierEnding.TsExtension, ModuleSpecifierEnding.JsExtension]
: [ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.Index, ModuleSpecifierEnding.JsExtension];
default: Debug.assertNever(preferredEnding);
}
},
Expand Down Expand Up @@ -1046,7 +1053,12 @@ function processEnding(fileName: string, allowedEndings: readonly ModuleSpecifie
return fileName;
}

if (fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Dcts, Extension.Cts])) {
const jsPriority = allowedEndings.indexOf(ModuleSpecifierEnding.JsExtension);
const tsPriority = allowedEndings.indexOf(ModuleSpecifierEnding.TsExtension);
if (fileExtensionIsOneOf(fileName, [Extension.Mts, Extension.Cts]) && tsPriority !== -1 && tsPriority < jsPriority) {
return fileName;
}
else if (fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Dcts, Extension.Cts])) {
return noExtension + getJSExtensionForFile(fileName, options);
}
else if (!fileExtensionIsOneOf(fileName, [Extension.Dts]) && fileExtensionIsOneOf(fileName, [Extension.Ts]) && stringContains(fileName, ".d.")) {
Expand All @@ -1072,7 +1084,6 @@ function processEnding(fileName: string, allowedEndings: readonly ModuleSpecifie
// know if a .d.ts extension is valid, so use no extension or a .js extension
if (isDeclarationFileName(fileName)) {
const extensionlessPriority = allowedEndings.findIndex(e => e === ModuleSpecifierEnding.Minimal || e === ModuleSpecifierEnding.Index);
const jsPriority = allowedEndings.indexOf(ModuleSpecifierEnding.JsExtension);
return extensionlessPriority !== -1 && extensionlessPriority < jsPriority
? noExtension
: noExtension + getJSExtensionForFile(fileName, options);
Expand Down
1 change: 0 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4239,7 +4239,6 @@ export interface SourceFileLike {
getPositionOfLineAndCharacter?(line: number, character: number, allowEdits?: true): number;
}


/** @internal */
export interface RedirectInfo {
/** Source file this redirects to. */
Expand Down
10 changes: 9 additions & 1 deletion src/harness/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,14 @@ export class SessionClient implements LanguageService {
return response.body!; // TODO: GH#18217
}

getMoveToRefactoringFileSuggestions(fileName: string, positionOrRange: number | TextRange): { newFileName: string; files: string[]; } {
const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName);

const request = this.processRequest<protocol.GetMoveToRefactoringFileSuggestionsRequest>(protocol.CommandTypes.GetMoveToRefactoringFileSuggestions, args);
const response = this.processResponse<protocol.GetMoveToRefactoringFileSuggestions>(request);
return { newFileName: response.body?.newFileName, files:response.body?.files }!;
}

getEditsForRefactor(
fileName: string,
_formatOptions: FormatCodeSettings,
Expand All @@ -816,7 +824,7 @@ export class SessionClient implements LanguageService {
const renameFilename: string | undefined = response.body.renameFilename;
let renameLocation: number | undefined;
if (renameFilename !== undefined) {
renameLocation = this.lineOffsetToPosition(renameFilename, response.body.renameLocation!); // TODO: GH#18217
renameLocation = this.lineOffsetToPosition(renameFilename, response.body.renameLocation!);
}

return {
Expand Down
21 changes: 16 additions & 5 deletions src/harness/fourslashImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3293,7 +3293,6 @@ export class TestState {
ts.Debug.fail(`Did not expect a change in ${change.fileName}`);
}
const oldText = this.tryGetFileContent(change.fileName);
ts.Debug.assert(!!change.isNewFile === (oldText === undefined));
const newContent = change.isNewFile ? ts.first(change.textChanges).newText : ts.textChanges.applyChanges(oldText!, change.textChanges);
this.verifyTextMatches(newContent, /*includeWhitespace*/ true, expectedNewContent);
}
Expand Down Expand Up @@ -3906,6 +3905,18 @@ export class TestState {
this.verifyNewContent({ newFileContent: options.newFileContents }, editInfo.edits);
}

public moveToFile(options: FourSlashInterface.MoveToFileOptions): void {
assert(this.getRanges().length === 1, "Must have exactly one fourslash range (source enclosed between '[|' and '|]' delimiters) in the source file");
const range = this.getRanges()[0];
const refactor = ts.find(this.getApplicableRefactors(range, { allowTextChangesInNewFiles: true }, /*triggerReason*/ undefined, /*kind*/ undefined, /*includeInteractiveActions*/ true), r => r.name === "Move to file")!;
assert(refactor.actions.length === 1);
const action = ts.first(refactor.actions);
assert(action.name === "Move to file" && action.description === "Move to file");

const editInfo = this.languageService.getEditsForRefactor(range.fileName, this.formatCodeSettings, range, refactor.name, action.name, options.preferences || ts.emptyOptions, options.interactiveRefactorArguments)!;
this.verifyNewContent({ newFileContent: options.newFileContents }, editInfo.edits);
}

private testNewFileContents(edits: readonly ts.FileTextChanges[], newFileContents: { [fileName: string]: string }, description: string): void {
for (const { fileName, textChanges } of edits) {
const newContent = newFileContents[fileName];
Expand Down Expand Up @@ -4211,11 +4222,11 @@ export class TestState {
private getApplicableRefactorsAtSelection(triggerReason: ts.RefactorTriggerReason = "implicit", kind?: string, preferences = ts.emptyOptions) {
return this.getApplicableRefactorsWorker(this.getSelection(), this.activeFile.fileName, preferences, triggerReason, kind);
}
private getApplicableRefactors(rangeOrMarker: Range | Marker, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason = "implicit", kind?: string): readonly ts.ApplicableRefactorInfo[] {
return this.getApplicableRefactorsWorker("position" in rangeOrMarker ? rangeOrMarker.position : rangeOrMarker, rangeOrMarker.fileName, preferences, triggerReason, kind); // eslint-disable-line local/no-in-operator
private getApplicableRefactors(rangeOrMarker: Range | Marker, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason = "implicit", kind?: string, includeInteractiveActions?: boolean): readonly ts.ApplicableRefactorInfo[] {
return this.getApplicableRefactorsWorker("position" in rangeOrMarker ? rangeOrMarker.position : rangeOrMarker, rangeOrMarker.fileName, preferences, triggerReason, kind, includeInteractiveActions); // eslint-disable-line local/no-in-operator
}
private getApplicableRefactorsWorker(positionOrRange: number | ts.TextRange, fileName: string, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason, kind?: string): readonly ts.ApplicableRefactorInfo[] {
return this.languageService.getApplicableRefactors(fileName, positionOrRange, preferences, triggerReason, kind) || ts.emptyArray;
private getApplicableRefactorsWorker(positionOrRange: number | ts.TextRange, fileName: string, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason, kind?: string, includeInteractiveActions?: boolean): readonly ts.ApplicableRefactorInfo[] {
return this.languageService.getApplicableRefactors(fileName, positionOrRange, preferences, triggerReason, kind, includeInteractiveActions) || ts.emptyArray;
}

public configurePlugin(pluginName: string, configuration: any): void {
Expand Down
10 changes: 10 additions & 0 deletions src/harness/fourslashInterfaceImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,10 @@ export class Verify extends VerifyNegatable {
this.state.moveToNewFile(options);
}

public moveToFile(options: MoveToFileOptions): void {
this.state.moveToFile(options);
}

public noMoveToNewFile(): void {
this.state.noMoveToNewFile();
}
Expand Down Expand Up @@ -1896,6 +1900,12 @@ export interface MoveToNewFileOptions {
readonly preferences?: ts.UserPreferences;
}

export interface MoveToFileOptions {
readonly newFileContents: { readonly [fileName: string]: string };
readonly interactiveRefactorArguments: ts.InteractiveRefactorArguments;
readonly preferences?: ts.UserPreferences;
}

export type RenameLocationsOptions = readonly RenameLocationOptions[] | {
readonly findInStrings?: boolean;
readonly findInComments?: boolean;
Expand Down
3 changes: 3 additions & 0 deletions src/harness/harnessLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,9 @@ class LanguageServiceShimProxy implements ts.LanguageService {
getApplicableRefactors(): ts.ApplicableRefactorInfo[] {
throw new Error("Not supported on the shim.");
}
getMoveToRefactoringFileSuggestions(): { newFileName: string, files: string[] } {
throw new Error("Not supported on the shim.");
}
organizeImports(_args: ts.OrganizeImportsArgs, _formatOptions: ts.FormatCodeSettings): readonly ts.FileTextChanges[] {
throw new Error("Not supported on the shim.");
}
Expand Down
25 changes: 25 additions & 0 deletions src/server/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
EndOfLineState,
FileExtensionInfo,
HighlightSpanKind,
InteractiveRefactorArguments,
MapLike,
OutliningSpanKind,
OutputFile,
Expand Down Expand Up @@ -142,6 +143,7 @@ export const enum CommandTypes {

GetApplicableRefactors = "getApplicableRefactors",
GetEditsForRefactor = "getEditsForRefactor",
GetMoveToRefactoringFileSuggestions = "getMoveToRefactoringFileSuggestions",
/** @internal */
GetEditsForRefactorFull = "getEditsForRefactor-full",

Expand Down Expand Up @@ -606,6 +608,27 @@ export interface GetApplicableRefactorsResponse extends Response {
body?: ApplicableRefactorInfo[];
}

/**
* Request refactorings at a given position or selection area to move to an existing file.
*/
export interface GetMoveToRefactoringFileSuggestionsRequest extends Request {
command: CommandTypes.GetMoveToRefactoringFileSuggestions;
arguments: GetMoveToRefactoringFileSuggestionsRequestArgs;
}
export type GetMoveToRefactoringFileSuggestionsRequestArgs = FileLocationOrRangeRequestArgs & {
kind?: string;
};
/**
* Response is a list of available files.
* Each refactoring exposes one or more "Actions"; a user selects one action to invoke a refactoring
*/
export interface GetMoveToRefactoringFileSuggestions extends Response {
body: {
newFileName: string;
files: string[];
};
}

/**
* A set of one or more available refactoring actions, grouped under a parent refactoring.
*/
Expand Down Expand Up @@ -680,6 +703,8 @@ export type GetEditsForRefactorRequestArgs = FileLocationOrRangeRequestArgs & {
refactor: string;
/* The 'name' property from the refactoring action */
action: string;
/* Arguments for interactive action */
interactiveRefactorArguments?: InteractiveRefactorArguments;
};


Expand Down
21 changes: 17 additions & 4 deletions src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,7 @@ const invalidPartialSemanticModeCommands: readonly protocol.CommandTypes[] = [
protocol.CommandTypes.ApplyCodeActionCommand,
protocol.CommandTypes.GetSupportedCodeFixes,
protocol.CommandTypes.GetApplicableRefactors,
protocol.CommandTypes.GetMoveToRefactoringFileSuggestions,
protocol.CommandTypes.GetEditsForRefactor,
protocol.CommandTypes.GetEditsForRefactorFull,
protocol.CommandTypes.OrganizeImports,
Expand Down Expand Up @@ -2688,6 +2689,7 @@ export class Session<TMessage = string> implements EventSender {
args.refactor,
args.action,
this.getPreferences(file),
args.interactiveRefactorArguments
);

if (result === undefined) {
Expand All @@ -2703,11 +2705,19 @@ export class Session<TMessage = string> implements EventSender {
const renameScriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(renameFilename))!;
mappedRenameLocation = getLocationInNewDocument(getSnapshotText(renameScriptInfo.getSnapshot()), renameFilename, renameLocation, edits);
}
return { renameLocation: mappedRenameLocation, renameFilename, edits: this.mapTextChangesToCodeEdits(edits) };
}
else {
return result;
return {
renameLocation: mappedRenameLocation,
renameFilename,
edits: this.mapTextChangesToCodeEdits(edits)
};
}
return result;
}

private getMoveToRefactoringFileSuggestions(args: protocol.GetMoveToRefactoringFileSuggestionsRequestArgs): { newFileName: string, files: string[] }{
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file)!;
return project.getLanguageService().getMoveToRefactoringFileSuggestions(file, this.extractPositionOrRange(args, scriptInfo), this.getPreferences(file));
}

private organizeImports(args: protocol.OrganizeImportsRequestArgs, simplifiedResult: boolean): readonly protocol.FileCodeEdits[] | readonly FileTextChanges[] {
Expand Down Expand Up @@ -3430,6 +3440,9 @@ export class Session<TMessage = string> implements EventSender {
[protocol.CommandTypes.GetEditsForRefactor]: (request: protocol.GetEditsForRefactorRequest) => {
return this.requiredResponse(this.getEditsForRefactor(request.arguments, /*simplifiedResult*/ true));
},
[protocol.CommandTypes.GetMoveToRefactoringFileSuggestions]: (request: protocol.GetMoveToRefactoringFileSuggestionsRequest) => {
return this.requiredResponse(this.getMoveToRefactoringFileSuggestions(request.arguments));
},
[protocol.CommandTypes.GetEditsForRefactorFull]: (request: protocol.GetEditsForRefactorRequest) => {
return this.requiredResponse(this.getEditsForRefactor(request.arguments, /*simplifiedResult*/ false));
},
Expand Down
1 change: 1 addition & 0 deletions src/services/_namespaces/ts.refactor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from "../refactors/convertImport";
export * from "../refactors/extractType";
export * from "../refactors/helpers";
export * from "../refactors/moveToNewFile";
export * from "../refactors/moveToFile";
import * as addOrRemoveBracesToArrowFunction from "./ts.refactor.addOrRemoveBracesToArrowFunction";
export { addOrRemoveBracesToArrowFunction };
import * as convertArrowFunctionOrFunctionExpression from "./ts.refactor.convertArrowFunctionOrFunctionExpression";
Expand Down
5 changes: 3 additions & 2 deletions src/services/refactorProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
ApplicableRefactorInfo,
arrayFrom,
flatMapIterator,
InteractiveRefactorArguments,
Refactor,
RefactorContext,
RefactorEditInfo,
Expand Down Expand Up @@ -30,7 +31,7 @@ export function getApplicableRefactors(context: RefactorContext, includeInteract
}

/** @internal */
export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined {
export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string, interactiveRefactorArguments?: InteractiveRefactorArguments): RefactorEditInfo | undefined {
const refactor = refactors.get(refactorName);
return refactor && refactor.getEditsForAction(context, actionName);
return refactor && refactor.getEditsForAction(context, actionName, interactiveRefactorArguments);
}
Loading