From 77c955a048c901a973d8646763920ba4deb1c35a Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 5 Jun 2018 12:34:10 -0700 Subject: [PATCH 1/3] Add 'fileToRename' property to RenameInfo --- src/harness/fourslash.ts | 7 ++++--- src/server/client.ts | 1 + src/server/protocol.ts | 6 ++++++ src/server/session.ts | 2 +- src/services/rename.ts | 20 +++++++++++++++++-- src/services/types.ts | 5 +++++ .../reference/api/tsserverlibrary.d.ts | 10 ++++++++++ tests/baselines/reference/api/typescript.d.ts | 5 +++++ tests/cases/fourslash/fourslash.ts | 2 +- tests/cases/fourslash/renameFile.ts | 10 ++++++++++ 10 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 tests/cases/fourslash/renameFile.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 2ace3df3b42b6..6ee598bb01015 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -1544,7 +1544,7 @@ Actual: ${stringify(fullActual)}`); } } - public verifyRenameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string) { + public verifyRenameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string, fileToRename?: string): void { const renameInfo = this.languageService.getRenameInfo(this.activeFile.fileName, this.currentCaretPosition); if (!renameInfo.canRename) { this.raiseError("Rename did not succeed"); @@ -1554,6 +1554,7 @@ Actual: ${stringify(fullActual)}`); this.validate("fullDisplayName", fullDisplayName, renameInfo.fullDisplayName); this.validate("kind", kind, renameInfo.kind); this.validate("kindModifiers", kindModifiers, renameInfo.kindModifiers); + this.validate("fileToRename", fileToRename, renameInfo.fileToRename); if (this.getRanges().length !== 1) { this.raiseError("Expected a single range to be selected in the test file."); @@ -4404,8 +4405,8 @@ namespace FourSlashInterface { this.state.verifySemanticClassifications(classifications); } - public renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string) { - this.state.verifyRenameInfoSucceeded(displayName, fullDisplayName, kind, kindModifiers); + public renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string, fileToRename?: string) { + this.state.verifyRenameInfoSucceeded(displayName, fullDisplayName, kind, kindModifiers, fileToRename); } public renameInfoFailed(message?: string) { diff --git a/src/server/client.ts b/src/server/client.ts index e797cadb8ca01..30cebe46633f4 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -394,6 +394,7 @@ namespace ts.server { return this.lastRenameEntry = { canRename: body.info.canRename, + fileToRename: body.info.fileToRename, displayName: body.info.displayName, fullDisplayName: body.info.fullDisplayName, kind: body.info.kind, diff --git a/src/server/protocol.ts b/src/server/protocol.ts index be1a6247fb0bc..d48d7b1509e8e 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -1063,6 +1063,12 @@ namespace ts.server.protocol { */ canRename: boolean; + /** + * fileName to rename. + * If set, `getEditsForFileRename` should be called instead of `findRenameLocations`. + */ + fileToRename?: string; + /** * Error message if item can not be renamed. */ diff --git a/src/server/session.ts b/src/server/session.ts index 3a368587d4b1d..7d887eb85920b 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -878,7 +878,7 @@ namespace ts.server { return projectInfo; } - private getRenameInfo(args: protocol.FileLocationRequestArgs) { + private getRenameInfo(args: protocol.FileLocationRequestArgs): RenameInfo { const { file, project } = this.getFileAndProject(args); const position = this.getPositionInFile(args, file); return project.getLanguageService().getRenameInfo(file, position); diff --git a/src/services/rename.ts b/src/services/rename.ts index cd557f5aaa47e..57550162c7b63 100644 --- a/src/services/rename.ts +++ b/src/services/rename.ts @@ -36,8 +36,9 @@ namespace ts.Rename { return undefined; } - // Can't rename a module name. - if (isStringLiteralLike(node) && tryGetImportFromModuleSpecifier(node)) return undefined; + if (isStringLiteralLike(node) && tryGetImportFromModuleSpecifier(node)) { + return getRenameInfoForModule(node, sourceFile, symbol); + } const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, node); const specifierName = (isImportOrExportSpecifierName(node) || isStringOrNumericLiteral(node) && node.parent.kind === SyntaxKind.ComputedPropertyName) @@ -48,9 +49,24 @@ namespace ts.Rename { return getRenameInfoSuccess(displayName, fullDisplayName, kind, SymbolDisplay.getSymbolModifiers(symbol), node, sourceFile); } + function getRenameInfoForModule(node: StringLiteralLike, sourceFile: SourceFile, moduleSymbol: Symbol): RenameInfo | undefined { + const moduleSourceFile = find(moduleSymbol.declarations, isSourceFile); + return moduleSourceFile && { + canRename: true, + fileToRename: moduleSourceFile.fileName, + kind: ScriptElementKind.moduleElement, + displayName: moduleSourceFile.fileName, + localizedErrorMessage: undefined, + fullDisplayName: moduleSourceFile.fileName, + kindModifiers: ScriptElementKindModifier.none, + triggerSpan: createTriggerSpanForNode(node, sourceFile), + }; + } + function getRenameInfoSuccess(displayName: string, fullDisplayName: string, kind: ScriptElementKind, kindModifiers: string, node: Node, sourceFile: SourceFile): RenameInfo { return { canRename: true, + fileToRename: undefined, kind, displayName, localizedErrorMessage: undefined, diff --git a/src/services/types.ts b/src/services/types.ts index 7d829132d47e0..e3111fd633e38 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -737,6 +737,11 @@ namespace ts { export interface RenameInfo { canRename: boolean; + /** + * fileName to rename. + * If set, `getEditsForFileRename` should be called instead of `findRenameLocations`. + */ + fileToRename?: string; localizedErrorMessage?: string; displayName: string; fullDisplayName: string; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 76a5d890fa71a..c02720bc37c29 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4919,6 +4919,11 @@ declare namespace ts { } interface RenameInfo { canRename: boolean; + /** + * fileName to rename. + * If set, `getEditsForFileRename` should be called instead of `findRenameLocations`. + */ + fileToRename?: string; localizedErrorMessage?: string; displayName: string; fullDisplayName: string; @@ -6343,6 +6348,11 @@ declare namespace ts.server.protocol { * True if item can be renamed. */ canRename: boolean; + /** + * fileName to rename. + * If set, `getEditsForFileRename` should be called instead of `findRenameLocations`. + */ + fileToRename?: string; /** * Error message if item can not be renamed. */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 51bd949ae4d0e..574f42eda124a 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4919,6 +4919,11 @@ declare namespace ts { } interface RenameInfo { canRename: boolean; + /** + * fileName to rename. + * If set, `getEditsForFileRename` should be called instead of `findRenameLocations`. + */ + fileToRename?: string; localizedErrorMessage?: string; displayName: string; fullDisplayName: string; diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index a104e2fde0fdc..7f57a4b3ae9c1 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -310,7 +310,7 @@ declare namespace FourSlashInterface { text: string; textSpan?: TextSpan; }[]): void; - renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string): void; + renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string, fileToRename?: string): void; renameInfoFailed(message?: string): void; renameLocations(startRanges: ArrayOrSingle, options: Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges: Range[] }): void; diff --git a/tests/cases/fourslash/renameFile.ts b/tests/cases/fourslash/renameFile.ts new file mode 100644 index 0000000000000..e30c7456dfcb6 --- /dev/null +++ b/tests/cases/fourslash/renameFile.ts @@ -0,0 +1,10 @@ +/// + +// @Filename: /a.ts +////export const a = 0; + +// @Filename: /b.ts +////import { a } from "[|/**/./a|]"; + +goTo.marker(); +verify.renameInfoSucceeded(/*displayName*/ "/a.ts", /*fullDisplayName*/ "/a.ts", /*kind*/ "module", /*kindModifiers*/ "", /*fileToRename*/ "/a.ts"); From b8f2fe3d8cd19f3fc27a6285d64c60d352d40074 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 5 Jun 2018 13:29:21 -0700 Subject: [PATCH 2/3] Update tests --- src/harness/fourslash.ts | 20 ++++++++++--------- .../findAllRefs_importType_exportEquals.ts | 2 +- tests/cases/fourslash/fourslash.ts | 4 ++-- tests/cases/fourslash/renameFile.ts | 10 ---------- tests/cases/fourslash/renameImport.ts | 6 ++++-- 5 files changed, 18 insertions(+), 24 deletions(-) delete mode 100644 tests/cases/fourslash/renameFile.ts diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 6ee598bb01015..33d87730af10f 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -434,12 +434,12 @@ namespace FourSlash { } } - public goToEachRange(action: () => void) { + public goToEachRange(action: (range: Range) => void) { const ranges = this.getRanges(); assert(ranges.length); for (const range of ranges) { this.selectRange(range); - action(); + action(range); } } @@ -1544,7 +1544,7 @@ Actual: ${stringify(fullActual)}`); } } - public verifyRenameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string, fileToRename?: string): void { + public verifyRenameInfoSucceeded(displayName: string | undefined, fullDisplayName: string | undefined, kind: string | undefined, kindModifiers: string | undefined, fileToRename: string | undefined, expectedRange: Range | undefined): void { const renameInfo = this.languageService.getRenameInfo(this.activeFile.fileName, this.currentCaretPosition); if (!renameInfo.canRename) { this.raiseError("Rename did not succeed"); @@ -1556,11 +1556,13 @@ Actual: ${stringify(fullActual)}`); this.validate("kindModifiers", kindModifiers, renameInfo.kindModifiers); this.validate("fileToRename", fileToRename, renameInfo.fileToRename); - if (this.getRanges().length !== 1) { - this.raiseError("Expected a single range to be selected in the test file."); + if (!expectedRange) { + if (this.getRanges().length !== 1) { + this.raiseError("Expected a single range to be selected in the test file."); + } + expectedRange = this.getRanges()[0]; } - const expectedRange = this.getRanges()[0]; if (renameInfo.triggerSpan.start !== expectedRange.pos || ts.textSpanEnd(renameInfo.triggerSpan) !== expectedRange.end) { this.raiseError("Expected triggerSpan [" + expectedRange.pos + "," + expectedRange.end + "). Got [" + @@ -3934,7 +3936,7 @@ namespace FourSlashInterface { this.state.goToRangeStart(range); } - public eachRange(action: () => void) { + public eachRange(action: (range: FourSlash.Range) => void) { this.state.goToEachRange(action); } @@ -4405,8 +4407,8 @@ namespace FourSlashInterface { this.state.verifySemanticClassifications(classifications); } - public renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string, fileToRename?: string) { - this.state.verifyRenameInfoSucceeded(displayName, fullDisplayName, kind, kindModifiers, fileToRename); + public renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string, fileToRename?: string, expectedRange?: FourSlash.Range) { + this.state.verifyRenameInfoSucceeded(displayName, fullDisplayName, kind, kindModifiers, fileToRename, expectedRange); } public renameInfoFailed(message?: string) { diff --git a/tests/cases/fourslash/findAllRefs_importType_exportEquals.ts b/tests/cases/fourslash/findAllRefs_importType_exportEquals.ts index 010f7f3189092..35b7a2fafe180 100644 --- a/tests/cases/fourslash/findAllRefs_importType_exportEquals.ts +++ b/tests/cases/fourslash/findAllRefs_importType_exportEquals.ts @@ -27,5 +27,5 @@ verify.renameLocations(r1, [r1, r2]); verify.renameLocations(r2, [r0, r1, r2]); for (const range of [r3, r4]) { goTo.rangeStart(range); - verify.renameInfoFailed(); + verify.renameInfoSucceeded(/*displayName*/ "/a.ts", /*fullDisplayName*/ "/a.ts", /*kind*/ "module", /*kindModifiers*/ "", /*fileToRename*/ "/a.ts", range); } diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 7f57a4b3ae9c1..b704fd72630ad 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -129,7 +129,7 @@ declare namespace FourSlashInterface { eachMarker(markers: ReadonlyArray, action: (marker: Marker, index: number) => void): void; eachMarker(action: (marker: Marker, index: number) => void): void; rangeStart(range: Range): void; - eachRange(action: () => void): void; + eachRange(action: (range: Range) => void): void; bof(): void; eof(): void; implementation(): void; @@ -310,7 +310,7 @@ declare namespace FourSlashInterface { text: string; textSpan?: TextSpan; }[]): void; - renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string, fileToRename?: string): void; + renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string, fileToRename?: string, range?: Range): void; renameInfoFailed(message?: string): void; renameLocations(startRanges: ArrayOrSingle, options: Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges: Range[] }): void; diff --git a/tests/cases/fourslash/renameFile.ts b/tests/cases/fourslash/renameFile.ts deleted file mode 100644 index e30c7456dfcb6..0000000000000 --- a/tests/cases/fourslash/renameFile.ts +++ /dev/null @@ -1,10 +0,0 @@ -/// - -// @Filename: /a.ts -////export const a = 0; - -// @Filename: /b.ts -////import { a } from "[|/**/./a|]"; - -goTo.marker(); -verify.renameInfoSucceeded(/*displayName*/ "/a.ts", /*fullDisplayName*/ "/a.ts", /*kind*/ "module", /*kindModifiers*/ "", /*fileToRename*/ "/a.ts"); diff --git a/tests/cases/fourslash/renameImport.ts b/tests/cases/fourslash/renameImport.ts index 84d3ca3d335e6..adcc24dddffc0 100644 --- a/tests/cases/fourslash/renameImport.ts +++ b/tests/cases/fourslash/renameImport.ts @@ -7,10 +7,12 @@ // @Filename: /b.ts ////import * as a from "[|./a|]"; -////import a2 = require("[|./a"|]); +////import a2 = require("[|./a|]"); // @Filename: /c.js ////const a = require("[|./a|]"); verify.noErrors(); -goTo.eachRange(() => { verify.renameInfoFailed(); }); +goTo.eachRange(range => { + verify.renameInfoSucceeded(/*displayName*/ "/a.ts", /*fullDisplayName*/ "/a.ts", /*kind*/ "module", /*kindModifiers*/ "", /*fileToRename*/ "/a.ts", range); +}); From db345fc7576b4b09f6ab64e89ce1911ad640c321 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 10 Sep 2018 10:52:57 -0700 Subject: [PATCH 3/3] Support directory rename --- src/server/protocol.ts | 2 +- src/services/rename.ts | 14 +++++++++----- src/services/types.ts | 2 +- tests/baselines/reference/api/tsserverlibrary.d.ts | 4 ++-- tests/baselines/reference/api/typescript.d.ts | 2 +- tests/cases/fourslash/renameImport.ts | 10 +++++++++- 6 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index ed391dbdcb9de..2804b27deaf1a 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -1094,7 +1094,7 @@ namespace ts.server.protocol { canRename: boolean; /** - * fileName to rename. + * File or directory to rename. * If set, `getEditsForFileRename` should be called instead of `findRenameLocations`. */ fileToRename?: string; diff --git a/src/services/rename.ts b/src/services/rename.ts index 336f5205e448c..649ed33786f81 100644 --- a/src/services/rename.ts +++ b/src/services/rename.ts @@ -40,13 +40,17 @@ namespace ts.Rename { function getRenameInfoForModule(node: StringLiteralLike, sourceFile: SourceFile, moduleSymbol: Symbol): RenameInfo | undefined { const moduleSourceFile = find(moduleSymbol.declarations, isSourceFile); - return moduleSourceFile && { + if (!moduleSourceFile) return undefined; + const withoutIndex = node.text.endsWith("/index") || node.text.endsWith("/index.js") ? undefined : tryRemoveSuffix(removeFileExtension(moduleSourceFile.fileName), "/index"); + const name = withoutIndex === undefined ? moduleSourceFile.fileName : withoutIndex; + const kind = withoutIndex === undefined ? ScriptElementKind.moduleElement : ScriptElementKind.directory; + return { canRename: true, - fileToRename: moduleSourceFile.fileName, - kind: ScriptElementKind.moduleElement, - displayName: moduleSourceFile.fileName, + fileToRename: name, + kind, + displayName: name, localizedErrorMessage: undefined, - fullDisplayName: moduleSourceFile.fileName, + fullDisplayName: name, kindModifiers: ScriptElementKindModifier.none, triggerSpan: createTriggerSpanForNode(node, sourceFile), }; diff --git a/src/services/types.ts b/src/services/types.ts index 065d436816ae0..08a09d7f91240 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -785,7 +785,7 @@ namespace ts { export interface RenameInfo { canRename: boolean; /** - * fileName to rename. + * File or directory to rename. * If set, `getEditsForFileRename` should be called instead of `findRenameLocations`. */ fileToRename?: string; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 1a22bfd174b10..672b93e55daf3 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5102,7 +5102,7 @@ declare namespace ts { interface RenameInfo { canRename: boolean; /** - * fileName to rename. + * File or directory to rename. * If set, `getEditsForFileRename` should be called instead of `findRenameLocations`. */ fileToRename?: string; @@ -6428,7 +6428,7 @@ declare namespace ts.server.protocol { */ canRename: boolean; /** - * fileName to rename. + * File or directory to rename. * If set, `getEditsForFileRename` should be called instead of `findRenameLocations`. */ fileToRename?: string; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 35cba52df6c92..5d093382f5429 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5102,7 +5102,7 @@ declare namespace ts { interface RenameInfo { canRename: boolean; /** - * fileName to rename. + * File or directory to rename. * If set, `getEditsForFileRename` should be called instead of `findRenameLocations`. */ fileToRename?: string; diff --git a/tests/cases/fourslash/renameImport.ts b/tests/cases/fourslash/renameImport.ts index adcc24dddffc0..f3221f3584e0a 100644 --- a/tests/cases/fourslash/renameImport.ts +++ b/tests/cases/fourslash/renameImport.ts @@ -5,14 +5,22 @@ // @Filename: /a.ts ////export const x = 0; +// @Filename: /dir/index.ts +////export const x = 0; + // @Filename: /b.ts ////import * as a from "[|./a|]"; ////import a2 = require("[|./a|]"); +////import * as dir from "[|{| "target": "dir" |}./dir|]"; +////import * as dir2 from "[|{| "target": "dir/index" |}./dir/index|]"; // @Filename: /c.js ////const a = require("[|./a|]"); verify.noErrors(); goTo.eachRange(range => { - verify.renameInfoSucceeded(/*displayName*/ "/a.ts", /*fullDisplayName*/ "/a.ts", /*kind*/ "module", /*kindModifiers*/ "", /*fileToRename*/ "/a.ts", range); + const target = range.marker && range.marker.data && range.marker.data.target; + const name = target === "dir" ? "/dir" : target === "dir/index" ? "/dir/index.ts" : "/a.ts"; + const kind = target === "dir" ? "directory" : "module"; + verify.renameInfoSucceeded(/*displayName*/ name, /*fullDisplayName*/ name, /*kind*/ kind, /*kindModifiers*/ "", /*fileToRename*/ name, range); });