From 166697054f4bccdd1728a1599deb720975d8d631 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 14 Jun 2018 15:39:28 -0700 Subject: [PATCH] getEditsForFileRename: For directory rename, preserve casing of suffix --- src/core/core.ts | 5 ++-- src/parser/utilities.ts | 23 ++++++++++--------- src/services/getEditsForFileRename.ts | 5 ++-- .../reference/api/tsserverlibrary.d.ts | 4 ++-- tests/baselines/reference/api/typescript.d.ts | 4 ++-- .../fourslash/getEditsForFileRename_casing.ts | 18 +++++++++++++++ 6 files changed, 38 insertions(+), 21 deletions(-) create mode 100644 tests/cases/fourslash/getEditsForFileRename_casing.ts diff --git a/src/core/core.ts b/src/core/core.ts index 20a4677d67201..5e51612dbe6d6 100644 --- a/src/core/core.ts +++ b/src/core/core.ts @@ -2084,11 +2084,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/parser/utilities.ts b/src/parser/utilities.ts index 5c0b00132c260..207854f870846 100644 --- a/src/parser/utilities.ts +++ b/src/parser/utilities.ts @@ -7461,19 +7461,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; + } + + 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. + // 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 a7e41d2014f31..ee7ee26c92174 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 45e4a4b98619a..86be0fc3b99f1 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -574,7 +574,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; @@ -7258,7 +7258,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/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 3576a4e8f6ab0..6dc4f487eb726 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -574,7 +574,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; @@ -7258,7 +7258,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 0000000000000..eceb50f4da162 --- /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";`, + }, +});