Skip to content

Commit

Permalink
Add special file name lower conversion routine and use that instead o…
Browse files Browse the repository at this point in the history
…f toLowerCase

Fixes #31819 and #35559
  • Loading branch information
sheetalkamat committed Jan 9, 2020
1 parent 6c0aea5 commit e801d79
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 20 deletions.
6 changes: 3 additions & 3 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2610,7 +2610,7 @@ namespace ts {
errors: Push<Diagnostic>,
extendedConfigCache?: Map<ExtendedConfigCacheEntry>
): ParsedTsconfig | undefined {
const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toLowerCase(extendedConfigPath);
const path = host.useCaseSensitiveFileNames ? extendedConfigPath : toFileNameLowerCase(extendedConfigPath);
let value: ExtendedConfigCacheEntry | undefined;
let extendedResult: TsConfigSourceFile;
let extendedConfig: ParsedTsconfig | undefined;
Expand Down Expand Up @@ -2914,7 +2914,7 @@ namespace ts {
export function getFileNamesFromConfigSpecs(spec: ConfigFileSpecs, basePath: string, options: CompilerOptions, host: ParseConfigHost, extraFileExtensions: readonly FileExtensionInfo[] = []): ExpandResult {
basePath = normalizePath(basePath);

const keyMapper = host.useCaseSensitiveFileNames ? identity : toLowerCase;
const keyMapper = createGetCanonicalFileName(host.useCaseSensitiveFileNames);

// Literal file names (provided via the "files" array in tsconfig.json) are stored in a
// file map with a possibly case insensitive key. We use this map later when when including
Expand Down Expand Up @@ -3083,7 +3083,7 @@ namespace ts {
const match = wildcardDirectoryPattern.exec(spec);
if (match) {
return {
key: useCaseSensitiveFileNames ? match[0] : match[0].toLowerCase(),
key: useCaseSensitiveFileNames ? match[0] : toFileNameLowerCase(match[0]),
flags: watchRecursivePattern.test(spec) ? WatchDirectoryFlags.Recursive : WatchDirectoryFlags.None
};
}
Expand Down
30 changes: 28 additions & 2 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1399,6 +1399,32 @@ namespace ts {
/** Returns lower case string */
export function toLowerCase(x: string) { return x.toLowerCase(); }

const fileNameLowerCaseRegExp = /[^İi̇ıßa-z0-9\\/:\-_\. ]+/g;
export function toFileNameLowerCase(x: string) {
// Handle special characters and make those case sensitive instead
//
// |-#--|-Character-|-Char code-|-Desc------------------------------------------------------|
// | 1. | i | 105 | Ascii i |
// | 2. | I | 73 | Ascii I |
// |-------- Special characters ------------------------------------------------------------|
// | 3. | İ | 304 | Uppper case I with dot above |
// | 4. | i̇ | 105,775 | Lower case of İ (3rd item) |
// | 5. | İ | 73,775 | Upper case of i̇ (4th item), lower case is i̇ (4th item) |
// | 6. | ı | 305 | Lower case i without dot, upper case is I (2nd item) |
// | 7. | ß | 223 | Lower case sharp s
//
// Because item 3 is special where in its lowercase character has its own
// upper case form we cant convert its case.
// Rest special characters are either already in lower case format or
// they have corresponding upper case character so they dont need special handling
//
// But to avoid having to do string building for most common cases, also ignore
// a-z, 0-9, i̇, ı, ß, \, /, ., : and space
return fileNameLowerCaseRegExp.test(x) ?
x.replace(fileNameLowerCaseRegExp, toLowerCase) :
x;
}

/** Throws an error because a function is not implemented. */
export function notImplemented(): never {
throw new Error("Not implemented");
Expand Down Expand Up @@ -1860,7 +1886,7 @@ namespace ts {

export type GetCanonicalFileName = (fileName: string) => string;
export function createGetCanonicalFileName(useCaseSensitiveFileNames: boolean): GetCanonicalFileName {
return useCaseSensitiveFileNames ? identity : toLowerCase;
return useCaseSensitiveFileNames ? identity : toFileNameLowerCase;
}

/** Represents a "prefix*suffix" pattern. */
Expand Down Expand Up @@ -2006,4 +2032,4 @@ namespace ts {
}
}
}
}
}
2 changes: 1 addition & 1 deletion src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2438,7 +2438,7 @@ namespace ts {
addFileToRefFileMap(fileName, file, refFile);

if (host.useCaseSensitiveFileNames()) {
const pathLowerCase = path.toLowerCase();
const pathLowerCase = toFileNameLowerCase(path);
// for case-sensitive file systems check if we've already seen some file with similar filename ignoring case
const existingFile = filesByNameIgnoreCase!.get(pathLowerCase);
if (existingFile) {
Expand Down
2 changes: 1 addition & 1 deletion src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3354,7 +3354,7 @@ namespace ts.server {
else {
let exclude = false;
if (typeAcquisition.enable || typeAcquisition.enableAutoDiscovery) {
const baseName = getBaseFileName(normalizedNames[i].toLowerCase());
const baseName = getBaseFileName(toFileNameLowerCase(normalizedNames[i]));
if (fileExtensionIs(baseName, "js")) {
const inferredTypingName = removeFileExtension(baseName);
const cleanedTypingName = removeMinAndVersionNumbers(inferredTypingName);
Expand Down
2 changes: 1 addition & 1 deletion src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2219,7 +2219,7 @@ namespace ts.server {
}

getCanonicalFileName(fileName: string) {
const name = this.host.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
const name = this.host.useCaseSensitiveFileNames ? fileName : toFileNameLowerCase(fileName);
return normalizePath(name);
}

Expand Down
6 changes: 3 additions & 3 deletions src/services/shims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -922,8 +922,8 @@ namespace ts {
() => {
const results = this.languageService.getDocumentHighlights(fileName, position, JSON.parse(filesToSearch));
// workaround for VS document highlighting issue - keep only items from the initial file
const normalizedName = normalizeSlashes(fileName).toLowerCase();
return filter(results, r => normalizeSlashes(r.fileName).toLowerCase() === normalizedName);
const normalizedName = toFileNameLowerCase(normalizeSlashes(fileName));
return filter(results, r => toFileNameLowerCase(normalizeSlashes(r.fileName)) === normalizedName);
});
}

Expand Down Expand Up @@ -1295,4 +1295,4 @@ namespace ts {
}
}

/* eslint-enable no-in-operator */
/* eslint-enable no-in-operator */
18 changes: 18 additions & 0 deletions src/testRunner/unittests/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,4 +289,22 @@ describe("unittests:: core paths", () => {
assert.strictEqual(ts.getRelativePathFromDirectory("file:///a/b/c", "file:///a/b", /*ignoreCase*/ false), "..");
assert.strictEqual(ts.getRelativePathFromDirectory("file:///c:", "file:///d:", /*ignoreCase*/ false), "file:///d:/");
});
it("toFileNameLowerCase", () => {
assert.strictEqual(
ts.toFileNameLowerCase("/user/UserName/projects/Project/file.ts"),
"/user/username/projects/project/file.ts"
);
assert.strictEqual(
ts.toFileNameLowerCase("/user/UserName/projects/projectß/file.ts"),
"/user/username/projects/projectß/file.ts"
);
assert.strictEqual(
ts.toFileNameLowerCase("/user/UserName/projects/İproject/file.ts"),
"/user/username/projects/İproject/file.ts"
);
assert.strictEqual(
ts.toFileNameLowerCase("/user/UserName/projects/ı/file.ts"),
"/user/username/projects/ı/file.ts"
);
});
});
19 changes: 12 additions & 7 deletions src/testRunner/unittests/tsserver/watchEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,8 @@ namespace ts.projectSystem {
});

describe("unittests:: tsserver:: watchEnvironment:: file names on case insensitive file system", () => {
function verifyFileNames(projectRoot: string) {
function verifyFileNames(projectRoot: string, projectRootPath: string) {
const keyMapper = (str: string) => str.replace(projectRoot, projectRootPath);
const file: File = {
path: `${projectRoot}/foo.ts`,
content: `import { foo } from "bar"`
Expand All @@ -580,11 +581,11 @@ namespace ts.projectSystem {
const expectedWatchFiles = [libFile.path, `${projectRoot}/tsconfig.json`, `${projectRoot}/jsconfig.json`];
checkWatchedFilesDetailed(
host,
expectedWatchFiles.map(toLowerCase),
expectedWatchFiles.map(keyMapper),
1,
arrayToMap(
expectedWatchFiles,
toLowerCase,
keyMapper,
fileName => [{
fileName,
pollingInterval: PollingInterval.Low
Expand All @@ -595,12 +596,12 @@ namespace ts.projectSystem {
const expectedWatchedDirectories = [`${projectRoot}/node_modules`, `${projectRoot}/node_modules/@types`];
checkWatchedDirectoriesDetailed(
host,
expectedWatchedDirectories.map(toLowerCase),
expectedWatchedDirectories.map(keyMapper),
1,
/*recursive*/ true,
arrayToMap(
expectedWatchedDirectories,
toLowerCase,
keyMapper,
directoryName => [{
directoryName,
fallbackPollingInterval: PollingInterval.Medium,
Expand All @@ -611,11 +612,15 @@ namespace ts.projectSystem {
}

it("project with ascii file names", () => {
verifyFileNames(`${tscWatch.projects}/I`);
verifyFileNames("/User/userName/Projects/I", "/user/username/projects/i");
});

it("project with ascii file names with i", () => {
verifyFileNames("/User/userName/Projects/i", "/user/username/projects/i");
});

it("project with unicode file names", () => {
verifyFileNames(`${tscWatch.projects}/İ`);
verifyFileNames("/User/userName/Projects/İ", "/user/username/projects/İ");
});
});
}
4 changes: 2 additions & 2 deletions src/tsserver/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,7 @@ namespace ts.server {
// //server/location
// ^ <- from 0 to this position
const firstSlash = path.indexOf(directorySeparator, 2);
return firstSlash !== -1 ? path.substring(0, firstSlash).toLowerCase() : path;
return firstSlash !== -1 ? toFileNameLowerCase(path.substring(0, firstSlash)) : path;
}
const rootLength = getRootLength(path);
if (rootLength === 0) {
Expand All @@ -801,7 +801,7 @@ namespace ts.server {
}
if (path.charCodeAt(1) === CharacterCodes.colon && path.charCodeAt(2) === CharacterCodes.slash) {
// rooted path that starts with c:/... - extract drive letter
return path.charAt(0).toLowerCase();
return toFileNameLowerCase(path.charAt(0));
}
if (path.charCodeAt(0) === CharacterCodes.slash && path.charCodeAt(1) !== CharacterCodes.slash) {
// rooted path that starts with slash - /somename - use key for current drive
Expand Down

0 comments on commit e801d79

Please sign in to comment.