Skip to content

Commit

Permalink
fix: #394 - Handle inconsistent file path casings on case insensitive…
Browse files Browse the repository at this point in the history
… file systems.
  • Loading branch information
dsherret committed Mar 2, 2019
1 parent dd9ab6e commit f7f6a3c
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 1 deletion.
5 changes: 5 additions & 0 deletions src/fileSystem/DefaultFileSystemHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ export class DefaultFileSystemHost implements FileSystemHost {
});
}

isCaseSensitive() {
const platform = process.platform;
return platform !== "win32" && platform !== "darwin";

This comment has been minimized.

Copy link
@dsherret

dsherret Mar 2, 2019

Author Owner

I'm not sure if there's a better way of doing this, but this should cover most scenarios.

}

private getDirectoryNotFoundErrorIfNecessary(err: any, path: string) {
return FileUtils.isNotExistsError(err) ? new errors.DirectoryNotFoundError(path) : err;
}
Expand Down
2 changes: 2 additions & 0 deletions src/fileSystem/FileSystemHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@
directoryExistsSync(dirPath: string): boolean;
getCurrentDirectory(): string;
glob(patterns: ReadonlyArray<string>): string[];
/** Gets if this file system is case sensitive. Defaults to true if not implemented. */
isCaseSensitive?(): boolean;
}
22 changes: 21 additions & 1 deletion src/fileSystem/FileSystemWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,10 @@ class Directory {
*/
export class FileSystemWrapper {
private readonly directories = new KeyValueCache<string, Directory>();
private readonly pathCasingMaintainer: PathCasingMaintainer;

constructor(private readonly fileSystem: FileSystemHost) {
this.pathCasingMaintainer = new PathCasingMaintainer(fileSystem);
}

queueFileDelete(filePath: string) {
Expand Down Expand Up @@ -603,7 +605,8 @@ export class FileSystemWrapper {
}

getStandardizedAbsolutePath(fileOrDirPath: string, relativeBase?: string) {
return FileUtils.getStandardizedAbsolutePath(this.fileSystem, fileOrDirPath, relativeBase);
fileOrDirPath = FileUtils.getStandardizedAbsolutePath(this.fileSystem, fileOrDirPath, relativeBase);
return this.pathCasingMaintainer.getPath(fileOrDirPath);
}

readFileOrNotExists(filePath: string, encoding: string) {
Expand Down Expand Up @@ -772,3 +775,20 @@ export class FileSystemWrapper {
}
}
}

/** Maintains the file or dir path casing by using the first file path found for case insensistive file systems. */
class PathCasingMaintainer {
private readonly caseInsensitiveMappings: KeyValueCache<string, string> | undefined;

constructor(fileSystem: FileSystemHost) {
if (fileSystem.isCaseSensitive != null && !fileSystem.isCaseSensitive())
this.caseInsensitiveMappings = new KeyValueCache();
}

getPath(fileOrDirPath: string) {
if (this.caseInsensitiveMappings == null)
return fileOrDirPath;

return this.caseInsensitiveMappings.getOrCreate(fileOrDirPath.toLowerCase(), () => fileOrDirPath);
}
}
1 change: 1 addition & 0 deletions src/next-major-deprecations.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
* `InitializerGetExpressionableNode` to `InitializerExpressionGetableNode` for consistency with `ExportGetableNode`
* Deprecate `InitializerSetExpressionableNode`
* Remove default export from library.
* Make `isCaseSensitive` required on `FileSystemHost`

37 changes: 37 additions & 0 deletions src/tests/issues/394tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { expect } from "chai";
import { SourceFile } from "../../compiler";
import { Project } from "../../Project";
import { VirtualFileSystemHost, FileSystemHost } from "../../fileSystem";

describe("tests for issue #394", () => {
it("should get the original source file with correct casing when the module specifier has incorrect casing and using a case insensitive file system", () => {
const fileSystem: FileSystemHost = new VirtualFileSystemHost();
const sourceFiles: SourceFile[] = [];
fileSystem.fileExistsSync = filePath => sourceFiles.some(s => s.getFilePath().toLowerCase() === filePath.toLowerCase());
fileSystem.readFileSync = filePath => {
const searchingFile = sourceFiles.find(s => s.getFilePath().toLowerCase() === filePath.toLowerCase());
return searchingFile == null ? "" : searchingFile.getFullText();
};
fileSystem.isCaseSensitive = () => false;

const project = new Project({ fileSystem });
const interfaceSourceFile = project.createSourceFile("/folder/MyInterface.ts", "export interface MyInterface {}");
const sourceFile = project.createSourceFile("/folder/main.ts", "import { MyInterface } from './myInterface';\n\nlet myVar: MyInterface;");
sourceFiles.push(interfaceSourceFile, sourceFile);
const varDeclaration = sourceFile.getVariableDeclarationOrThrow("myVar");
const declarations = varDeclaration.getType().getSymbolOrThrow().getDeclarations();

expect(declarations[0].getSourceFile().getFilePath()).to.equal("/folder/MyInterface.ts");
expect(declarations[0].getSourceFile()).to.equal(interfaceSourceFile);
});

it("should not be able to get the source file symbol when the module specifier has incorrect casing and using a case sensitive file system", () => {
const project = new Project({ useVirtualFileSystem: true });
const interfaceSourceFile = project.createSourceFile("/folder/MyInterface.ts", "export interface MyInterface {}");
const sourceFile = project.createSourceFile("/folder/main.ts", "import { MyInterface } from './myInterface';\n\nlet myVar: MyInterface;");
const varDeclaration = sourceFile.getVariableDeclarationOrThrow("myVar");
const symbol = varDeclaration.getType().getSymbol();

expect(symbol).to.be.undefined;
});
});

0 comments on commit f7f6a3c

Please sign in to comment.