From e3e4c5dd2ec3952844956234185a1e0295686572 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 21 Jun 2018 11:46:13 -0700 Subject: [PATCH] getEditsForFileRename: For directory rename, preserve casing of suffix (#24975) --- src/compiler/core.ts | 5 ++-- src/compiler/utilities.ts | 23 ++++++++++--------- src/services/getEditsForFileRename.ts | 5 ++-- .../reference/api/tsserverlibrary.d.ts | 4 ++-- .../fourslash/getEditsForFileRename_casing.ts | 18 +++++++++++++++ 5 files changed, 36 insertions(+), 19 deletions(-) create mode 100644 tests/cases/fourslash/getEditsForFileRename_casing.ts diff --git a/src/compiler/core.ts b/src/compiler/core.ts index c2946aa5a2..2ea1b32a1e 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2087,11 +2087,10 @@ namespace ts { return startsWith(str, prefix) ? str.substr(prefix.length) : str; } - export function tryRemovePrefix(str: string, prefix: string): string | undefined { - return startsWith(str, prefix) ? str.substring(prefix.length) : undefined; + export function tryRemovePrefix(str: string, prefix: string, getCanonicalFileName: GetCanonicalFileName = identity): string | undefined { + return startsWith(getCanonicalFileName(str), getCanonicalFileName(prefix)) ? str.substring(prefix.length) : undefined; } - function isPatternMatch({ prefix, suffix }: Pattern, candidate: string) { return candidate.length >= prefix.length + suffix.length && startsWith(candidate, prefix) && diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 93415815e7..2c62e44fad 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7490,19 +7490,20 @@ namespace ts { return true; } - export function tryRemoveDirectoryPrefix(path: string, dirPath: string): string | undefined { - const a = tryRemovePrefix(path, dirPath); - if (a === undefined) return undefined; - switch (a.charCodeAt(0)) { - case CharacterCodes.slash: - case CharacterCodes.backslash: - return a.slice(1); - default: - return undefined; - } + function isDirectorySeparator(charCode: number): boolean { + return charCode === CharacterCodes.slash || charCode === CharacterCodes.backslash; } - // Reserved characters, forces escaping of any non-word (or digit), non-whitespace character. + function stripLeadingDirectorySeparator(s: string): string | undefined { + return isDirectorySeparator(s.charCodeAt(0)) ? s.slice(1) : undefined; + } + + export function tryRemoveDirectoryPrefix(path: string, dirPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined { + const withoutPrefix = tryRemovePrefix(path, dirPath, getCanonicalFileName); + return withoutPrefix === undefined ? undefined : stripLeadingDirectorySeparator(withoutPrefix); + } + + // Reserved characters, forces escaping of any non-word (or digit), non-whitespace character. // It may be inefficient (we could just match (/[-[\]{}()*+?.,\\^$|#\s]/g), but this is future // proof. const reservedCharacterPattern = /[^\w\s\/]/g; diff --git a/src/services/getEditsForFileRename.ts b/src/services/getEditsForFileRename.ts index 8960950bf3..b9b8ba4e3a 100644 --- a/src/services/getEditsForFileRename.ts +++ b/src/services/getEditsForFileRename.ts @@ -16,9 +16,8 @@ namespace ts { function getPathUpdater(oldFileOrDirPath: string, newFileOrDirPath: string, getCanonicalFileName: GetCanonicalFileName): PathUpdater { const canonicalOldPath = getCanonicalFileName(oldFileOrDirPath); return path => { - const canonicalPath = getCanonicalFileName(path); - if (canonicalPath === canonicalOldPath) return newFileOrDirPath; - const suffix = tryRemoveDirectoryPrefix(canonicalPath, canonicalOldPath); + if (getCanonicalFileName(path) === canonicalOldPath) return newFileOrDirPath; + const suffix = tryRemoveDirectoryPrefix(path, canonicalOldPath, getCanonicalFileName); return suffix === undefined ? undefined : newFileOrDirPath + "/" + suffix; }; } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 2946e354f4..a7d4b73c53 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -515,7 +515,7 @@ declare namespace ts { function findBestPatternMatch(values: ReadonlyArray, getPattern: (value: T) => Pattern, candidate: string): T | undefined; function startsWith(str: string, prefix: string): boolean; function removePrefix(str: string, prefix: string): string; - function tryRemovePrefix(str: string, prefix: string): string | undefined; + function tryRemovePrefix(str: string, prefix: string, getCanonicalFileName?: GetCanonicalFileName): string | undefined; function and(f: (arg: T) => boolean, g: (arg: T) => boolean): (arg: T) => boolean; function or(f: (arg: T) => boolean, g: (arg: T) => boolean): (arg: T) => boolean; function assertTypeIsNever(_: never): void; @@ -7269,7 +7269,7 @@ declare namespace ts { function comparePaths(a: string, b: string, currentDirectory: string, ignoreCase?: boolean): Comparison; function containsPath(parent: string, child: string, ignoreCase?: boolean): boolean; function containsPath(parent: string, child: string, currentDirectory: string, ignoreCase?: boolean): boolean; - function tryRemoveDirectoryPrefix(path: string, dirPath: string): string | undefined; + function tryRemoveDirectoryPrefix(path: string, dirPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined; function hasExtension(fileName: string): boolean; const commonPackageFolders: ReadonlyArray; function getRegularExpressionForWildcard(specs: ReadonlyArray | undefined, basePath: string, usage: "files" | "directories" | "exclude"): string | undefined; diff --git a/tests/cases/fourslash/getEditsForFileRename_casing.ts b/tests/cases/fourslash/getEditsForFileRename_casing.ts new file mode 100644 index 0000000000..eceb50f4da --- /dev/null +++ b/tests/cases/fourslash/getEditsForFileRename_casing.ts @@ -0,0 +1,18 @@ +/// + +// @Filename: /a.ts +////import { foo } from "./dir/fOo"; + +// @Filename: /dir/fOo.ts +////export const foo = 0; + +// On a case-insensitive file system (like fourslash uses), there was a bug where we used the canonicalized path suffix. + +verify.getEditsForFileRename({ + oldPath: "/dir", + newPath: "/newDir", + newFileContents: { + "/a.ts": +`import { foo } from "./newDir/fOo";`, + }, +});