Merge pull request #5275 from Microsoft/fixCasingAndPathFormat

use absolute path as key to store files, correctly handle scenarios w…
This commit is contained in:
Vladimir Matveev 2015-10-27 14:02:35 -07:00
commit e811fecda6
7 changed files with 274 additions and 129 deletions

View File

@ -247,7 +247,12 @@ namespace ts {
},
description: Diagnostics.Specifies_module_resolution_strategy_Colon_node_Node_js_or_classic_TypeScript_pre_1_6,
error: Diagnostics.Argument_for_moduleResolution_option_must_be_node_or_classic,
}
},
{
name: "forceConsistentCasingInFileNames",
type: "boolean",
description: Diagnostics.Disallow_inconsistently_cased_references_to_the_same_file
},
];
/* @internal */

View File

@ -2290,7 +2290,10 @@
"category": "Message",
"code": 6072
},
"Disallow inconsistently-cased references to the same file.": {
"category": "Message",
"code": 6073
},
"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
"code": 7005

View File

@ -207,7 +207,6 @@ namespace ts {
};
export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost {
let currentDirectory: string;
let existingDirectories: Map<boolean> = {};
function getCanonicalFileName(fileName: string): string {
@ -277,7 +276,7 @@ namespace ts {
getSourceFile,
getDefaultLibFileName: options => combinePaths(getDirectoryPath(normalizePath(sys.getExecutingFilePath())), getDefaultLibFileName(options)),
writeFile,
getCurrentDirectory: () => currentDirectory || (currentDirectory = sys.getCurrentDirectory()),
getCurrentDirectory: memoize(() => sys.getCurrentDirectory()),
useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames,
getCanonicalFileName,
getNewLine: () => newLine,
@ -342,11 +341,15 @@ namespace ts {
host = host || createCompilerHost(options);
const currentDirectory = host.getCurrentDirectory();
const resolveModuleNamesWorker = host.resolveModuleNames
? ((moduleNames: string[], containingFile: string) => host.resolveModuleNames(moduleNames, containingFile))
: ((moduleNames: string[], containingFile: string) => map(moduleNames, moduleName => resolveModuleName(moduleName, containingFile, options, host).resolvedModule));
let filesByName = createFileMap<SourceFile>(fileName => host.getCanonicalFileName(fileName));
let filesByName = createFileMap<SourceFile>(getCanonicalFileName);
// stores 'filename -> file association' ignoring case
// used to track cases when two file names differ only in casing
let filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createFileMap<SourceFile>(fileName => fileName.toLowerCase()) : undefined;
if (oldProgram) {
// check properties that can affect structure of the program or module resolution strategy
@ -394,7 +397,7 @@ namespace ts {
getDiagnosticsProducingTypeChecker,
getCommonSourceDirectory: () => commonSourceDirectory,
emit,
getCurrentDirectory: () => host.getCurrentDirectory(),
getCurrentDirectory: () => currentDirectory,
getNodeCount: () => getDiagnosticsProducingTypeChecker().getNodeCount(),
getIdentifierCount: () => getDiagnosticsProducingTypeChecker().getIdentifierCount(),
getSymbolCount: () => getDiagnosticsProducingTypeChecker().getSymbolCount(),
@ -432,13 +435,18 @@ namespace ts {
// check if program source files has changed in the way that can affect structure of the program
let newSourceFiles: SourceFile[] = [];
let normalizedAbsoluteFileNames: string[] = [];
let modifiedSourceFiles: SourceFile[] = [];
for (let oldSourceFile of oldProgram.getSourceFiles()) {
let newSourceFile = host.getSourceFile(oldSourceFile.fileName, options.target);
if (!newSourceFile) {
return false;
}
const normalizedAbsolutePath = getNormalizedAbsolutePath(newSourceFile.fileName, currentDirectory);
normalizedAbsoluteFileNames.push(normalizedAbsolutePath);
if (oldSourceFile !== newSourceFile) {
if (oldSourceFile.hasNoDefaultLib !== newSourceFile.hasNoDefaultLib) {
// value of no-default-lib has changed
@ -461,7 +469,7 @@ namespace ts {
if (resolveModuleNamesWorker) {
let moduleNames = map(newSourceFile.imports, name => name.text);
let resolutions = resolveModuleNamesWorker(moduleNames, newSourceFile.fileName);
let resolutions = resolveModuleNamesWorker(moduleNames, normalizedAbsolutePath);
// ensure that module resolution results are still correct
for (let i = 0; i < moduleNames.length; ++i) {
let newResolution = resolutions[i];
@ -491,8 +499,8 @@ namespace ts {
}
// update fileName -> file mapping
for (let file of newSourceFiles) {
filesByName.set(file.fileName, file);
for (let i = 0, len = newSourceFiles.length; i < len; ++i) {
filesByName.set(normalizedAbsoluteFileNames[i], newSourceFiles[i]);
}
files = newSourceFiles;
@ -508,10 +516,10 @@ namespace ts {
function getEmitHost(writeFileCallback?: WriteFileCallback): EmitHost {
return {
getCanonicalFileName: fileName => host.getCanonicalFileName(fileName),
getCanonicalFileName,
getCommonSourceDirectory: program.getCommonSourceDirectory,
getCompilerOptions: program.getCompilerOptions,
getCurrentDirectory: () => host.getCurrentDirectory(),
getCurrentDirectory: () => currentDirectory,
getNewLine: () => host.getNewLine(),
getSourceFile: program.getSourceFile,
getSourceFiles: program.getSourceFiles,
@ -561,10 +569,8 @@ namespace ts {
return emitResult;
}
function getSourceFile(fileName: string) {
// first try to use file name as is to find file
// then try to convert relative file name to absolute and use it to retrieve source file
return filesByName.get(fileName) || filesByName.get(getNormalizedAbsolutePath(fileName, host.getCurrentDirectory()));
function getSourceFile(fileName: string): SourceFile {
return filesByName.get(getNormalizedAbsolutePath(fileName, currentDirectory));
}
function getDiagnosticsHelper(
@ -735,7 +741,7 @@ namespace ts {
diagnostic = Diagnostics.File_0_has_unsupported_extension_The_only_supported_extensions_are_1;
diagnosticArgument = [fileName, "'" + supportedExtensions.join("', '") + "'"];
}
else if (!findSourceFile(fileName, isDefaultLib, refFile, refPos, refEnd)) {
else if (!findSourceFile(fileName, getNormalizedAbsolutePath(fileName, currentDirectory), isDefaultLib, refFile, refPos, refEnd)) {
diagnostic = Diagnostics.File_0_not_found;
diagnosticArgument = [fileName];
}
@ -745,13 +751,13 @@ namespace ts {
}
}
else {
let nonTsFile: SourceFile = options.allowNonTsExtensions && findSourceFile(fileName, isDefaultLib, refFile, refPos, refEnd);
let nonTsFile: SourceFile = options.allowNonTsExtensions && findSourceFile(fileName, getNormalizedAbsolutePath(fileName, currentDirectory), isDefaultLib, refFile, refPos, refEnd);
if (!nonTsFile) {
if (options.allowNonTsExtensions) {
diagnostic = Diagnostics.File_0_not_found;
diagnosticArgument = [fileName];
}
else if (!forEach(supportedExtensions, extension => findSourceFile(fileName + extension, isDefaultLib, refFile, refPos, refEnd))) {
else if (!forEach(supportedExtensions, extension => findSourceFile(fileName + extension, getNormalizedAbsolutePath(fileName + extension, currentDirectory), isDefaultLib, refFile, refPos, refEnd))) {
diagnostic = Diagnostics.File_0_not_found;
fileName += ".ts";
diagnosticArgument = [fileName];
@ -769,19 +775,26 @@ namespace ts {
}
}
// Get source file from normalized fileName
function findSourceFile(fileName: string, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number): SourceFile {
if (filesByName.contains(fileName)) {
// We've already looked for this file, use cached result
return getSourceFileFromCache(fileName, /*useAbsolutePath*/ false);
function reportFileNamesDifferOnlyInCasingError(fileName: string, existingFileName: string, refFile: SourceFile, refPos: number, refEnd: number): void {
if (refFile !== undefined && refPos !== undefined && refEnd !== undefined) {
fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos,
Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, fileName, existingFileName));
}
else {
fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, fileName, existingFileName));
}
}
let normalizedAbsolutePath = getNormalizedAbsolutePath(fileName, host.getCurrentDirectory());
// Get source file from normalized fileName
function findSourceFile(fileName: string, normalizedAbsolutePath: string, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number): SourceFile {
if (filesByName.contains(normalizedAbsolutePath)) {
const file = getSourceFileFromCache(normalizedAbsolutePath, /*useAbsolutePath*/ true);
// we don't have resolution for this relative file name but the match was found by absolute file name
// store resolution for relative name as well
filesByName.set(fileName, file);
const file = filesByName.get(normalizedAbsolutePath);
// try to check if we've already seen this file but with a different casing in path
// NOTE: this only makes sense for case-insensitive file systems
if (file && options.forceConsistentCasingInFileNames && getNormalizedAbsolutePath(file.fileName, currentDirectory) !== normalizedAbsolutePath) {
reportFileNamesDifferOnlyInCasingError(fileName, file.fileName, refFile, refPos, refEnd);
}
return file;
}
@ -796,12 +809,20 @@ namespace ts {
}
});
filesByName.set(fileName, file);
filesByName.set(normalizedAbsolutePath, file);
if (file) {
skipDefaultLib = skipDefaultLib || file.hasNoDefaultLib;
if (host.useCaseSensitiveFileNames()) {
// for case-sensitive file systems check if we've already seen some file with similar filename ignoring case
const existingFile = filesByNameIgnoreCase.get(normalizedAbsolutePath);
if (existingFile) {
reportFileNamesDifferOnlyInCasingError(fileName, existingFile.fileName, refFile, refPos, refEnd);
}
else {
filesByNameIgnoreCase.set(normalizedAbsolutePath, file);
}
}
// Set the source file for normalized absolute path
filesByName.set(normalizedAbsolutePath, file);
skipDefaultLib = skipDefaultLib || file.hasNoDefaultLib;
let basePath = getDirectoryPath(fileName);
if (!options.noResolve) {
@ -821,23 +842,6 @@ namespace ts {
}
return file;
function getSourceFileFromCache(fileName: string, useAbsolutePath: boolean): SourceFile {
let file = filesByName.get(fileName);
if (file && host.useCaseSensitiveFileNames()) {
let sourceFileName = useAbsolutePath ? getNormalizedAbsolutePath(file.fileName, host.getCurrentDirectory()) : file.fileName;
if (normalizeSlashes(fileName) !== normalizeSlashes(sourceFileName)) {
if (refFile !== undefined && refPos !== undefined && refEnd !== undefined) {
fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos,
Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, fileName, sourceFileName));
}
else {
fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, fileName, sourceFileName));
}
}
}
return file;
}
}
function processReferencedFiles(file: SourceFile, basePath: string) {
@ -847,17 +851,29 @@ namespace ts {
});
}
function getCanonicalFileName(fileName: string): string {
return host.getCanonicalFileName(fileName);
}
function processImportedModules(file: SourceFile, basePath: string) {
collectExternalModuleReferences(file);
if (file.imports.length) {
file.resolvedModules = {};
let moduleNames = map(file.imports, name => name.text);
let resolutions = resolveModuleNamesWorker(moduleNames, file.fileName);
let resolutions = resolveModuleNamesWorker(moduleNames, getNormalizedAbsolutePath(file.fileName, currentDirectory));
for (let i = 0; i < file.imports.length; ++i) {
let resolution = resolutions[i];
setResolvedModule(file, moduleNames[i], resolution);
if (resolution && !options.noResolve) {
const importedFile = findModuleSourceFile(resolution.resolvedFileName, file.imports[i]);
const absoluteImportPath = isRootedDiskPath(resolution.resolvedFileName)
? resolution.resolvedFileName
: getNormalizedAbsolutePath(resolution.resolvedFileName, currentDirectory);
// convert an absolute import path to path that is relative to current directory
// this was host still can locate it but files names in user output will be shorter (and thus look nicer).
const relativePath = getRelativePathToDirectoryOrUrl(currentDirectory, absoluteImportPath, currentDirectory, getCanonicalFileName, false);
const importedFile = findSourceFile(relativePath, absoluteImportPath, /* isDefaultLib */ false, file, skipTrivia(file.text, file.imports[i].pos), file.imports[i].end);
if (importedFile && resolution.isExternalLibraryImport) {
if (!isExternalModule(importedFile)) {
let start = getTokenPosOfNode(file.imports[i], file);
@ -876,15 +892,10 @@ namespace ts {
file.resolvedModules = undefined;
}
return;
function findModuleSourceFile(fileName: string, nameLiteral: Expression) {
return findSourceFile(fileName, /* isDefaultLib */ false, file, skipTrivia(file.text, nameLiteral.pos), nameLiteral.end);
}
}
function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string {
let commonPathComponents: string[];
let currentDirectory = host.getCurrentDirectory();
forEach(files, sourceFile => {
// Each file contributes into common source file path
if (isDeclarationFile(sourceFile)) {
@ -925,7 +936,6 @@ namespace ts {
function checkSourceFilesBelongToPath(sourceFiles: SourceFile[], rootDirectory: string): boolean {
let allFilesBelongToPath = true;
if (sourceFiles) {
let currentDirectory = host.getCurrentDirectory();
let absoluteRootDirectoryPath = host.getCanonicalFileName(getNormalizedAbsolutePath(rootDirectory, currentDirectory));
for (var sourceFile of sourceFiles) {
@ -1030,7 +1040,7 @@ namespace ts {
if (options.rootDir && checkSourceFilesBelongToPath(files, options.rootDir)) {
// If a rootDir is specified and is valid use it as the commonSourceDirectory
commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, host.getCurrentDirectory());
commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory);
}
else {
// Compute the commonSourceDirectory from the input files

View File

@ -2092,6 +2092,7 @@ namespace ts {
experimentalDecorators?: boolean;
emitDecoratorMetadata?: boolean;
moduleResolution?: ModuleResolutionKind;
forceConsistentCasingInFileNames?: boolean;
/* @internal */ stripInternal?: boolean;
// Skip checking lib.d.ts to help speed up tests.

View File

@ -923,7 +923,9 @@ namespace Harness {
function register(file: { unitName: string; content: string; }) {
if (file.content !== undefined) {
let fileName = ts.normalizePath(file.unitName);
filemap[getCanonicalFileName(fileName)] = createSourceFileAndAssertInvariants(fileName, file.content, scriptTarget);
const sourceFile = createSourceFileAndAssertInvariants(fileName, file.content, scriptTarget);
filemap[getCanonicalFileName(fileName)] = sourceFile;
filemap[getCanonicalFileName(ts.getNormalizedAbsolutePath(fileName, getCurrentDirectory()))] = sourceFile;
}
};
inputFiles.forEach(register);

View File

@ -127,7 +127,7 @@ class ProjectRunner extends RunnerBase {
}
function compileProjectFiles(moduleKind: ts.ModuleKind, getInputFiles: () => string[],
getSourceFileText: (fileName: string) => string,
getSourceFileTextImpl: (fileName: string) => string,
writeFile: (fileName: string, data: string, writeByteOrderMark: boolean) => void): CompileProjectFilesResult {
let program = ts.createProgram(getInputFiles(), createCompilerOptions(), createCompilerHost());
@ -170,6 +170,11 @@ class ProjectRunner extends RunnerBase {
};
}
function getSourceFileText(fileName: string): string {
const text = getSourceFileTextImpl(fileName);
return text !== undefined ? text : getSourceFileTextImpl(ts.getNormalizedAbsolutePath(fileName, getCurrentDirectory()));
}
function getSourceFile(fileName: string, languageVersion: ts.ScriptTarget): ts.SourceFile {
let sourceFile: ts.SourceFile = undefined;
if (fileName === Harness.Compiler.defaultLibFileName) {
@ -194,7 +199,7 @@ class ProjectRunner extends RunnerBase {
getCanonicalFileName: Harness.Compiler.getCanonicalFileName,
useCaseSensitiveFileNames: () => Harness.IO.useCaseSensitiveFileNames(),
getNewLine: () => Harness.IO.newLine(),
fileExists: fileName => getSourceFile(fileName, ts.ScriptTarget.ES5) !== undefined,
fileExists: fileName => fileName === Harness.Compiler.defaultLibFileName || getSourceFileText(fileName) !== undefined,
readFile: fileName => Harness.IO.readFile(fileName)
};
}
@ -318,7 +323,16 @@ class ProjectRunner extends RunnerBase {
return ts.map(allInputFiles, outputFile => outputFile.emittedFileName);
}
function getSourceFileText(fileName: string): string {
return ts.forEach(allInputFiles, inputFile => inputFile.emittedFileName === fileName ? inputFile.code : undefined);
for (const inputFile of allInputFiles) {
const isMatchingFile = ts.isRootedDiskPath(fileName)
? ts.getNormalizedAbsolutePath(inputFile.emittedFileName, getCurrentDirectory()) === fileName
: inputFile.emittedFileName === fileName;
if (isMatchingFile) {
return inputFile.code;
}
}
return undefined;
}
function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) {

View File

@ -6,21 +6,35 @@ declare namespace chai.assert {
}
module ts {
function diagnosticToString(diagnostic: Diagnostic) {
let output = "";
if (diagnostic.file) {
let loc = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
output += `${diagnostic.file.fileName}(${loc.line + 1},${loc.character + 1}): `;
}
let category = DiagnosticCategory[diagnostic.category].toLowerCase();
output += `${category} TS${diagnostic.code}: ${flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine)}${sys.newLine}`;
return output;
}
interface File {
name: string
content?: string
content?: string
}
function createModuleResolutionHost(...files: File[]): ModuleResolutionHost {
let map = arrayToMap(files, f => f.name);
return { fileExists, readFile };
function fileExists(path: string): boolean {
return hasProperty(map, path);
}
function readFile(path: string): string {
return hasProperty(map, path) ? map[path].content : undefined;
}
@ -28,18 +42,18 @@ module ts {
function splitPath(path: string): { dir: string; rel: string } {
let index = path.indexOf(directorySeparator);
return index === -1
return index === -1
? { dir: path, rel: undefined }
: { dir: path.substr(0, index), rel: path.substr(index + 1) };
}
describe("Node module resolution - relative paths", () => {
function testLoadAsFile(containingFileName: string, moduleFileNameNoExt: string, moduleName: string): void {
for (let ext of supportedExtensions) {
let containingFile = { name: containingFileName }
let moduleFile = { name: moduleFileNameNoExt + ext }
let resolution = nodeModuleNameResolver(moduleName, containingFile.name, createModuleResolutionHost(containingFile, moduleFile));
let resolution = nodeModuleNameResolver(moduleName, containingFile.name, createModuleResolutionHost(containingFile, moduleFile));
assert.equal(resolution.resolvedModule.resolvedFileName, moduleFile.name);
assert.equal(!!resolution.resolvedModule.isExternalLibraryImport, false);
@ -53,11 +67,11 @@ module ts {
failedLookupLocations.push(normalizePath(getRootLength(moduleName) === 0 ? combinePaths(dir, moduleName) : moduleName) + e);
}
}
assert.deepEqual(resolution.failedLookupLocations, failedLookupLocations);
}
}
it("module name that starts with './' resolved as relative file name", () => {
testLoadAsFile("/foo/bar/baz.ts", "/foo/bar/foo", "./foo");
});
@ -73,7 +87,7 @@ module ts {
it("module name that starts with 'c:/' script extension resolved as relative file name", () => {
testLoadAsFile("c:/foo/bar/baz.ts", "c:/foo", "c:/foo");
});
function testLoadingFromPackageJson(containingFileName: string, packageJsonFileName: string, fieldRef: string, moduleFileName: string, moduleName: string): void {
let containingFile = { name: containingFileName };
let packageJson = { name: packageJsonFileName, content: JSON.stringify({ "typings": fieldRef }) };
@ -84,17 +98,17 @@ module ts {
// expect three failed lookup location - attempt to load module as file with all supported extensions
assert.equal(resolution.failedLookupLocations.length, 3);
}
it("module name as directory - load from typings", () => {
testLoadingFromPackageJson("/a/b/c/d.ts", "/a/b/c/bar/package.json", "c/d/e.d.ts", "/a/b/c/bar/c/d/e.d.ts", "./bar");
testLoadingFromPackageJson("/a/b/c/d.ts", "/a/bar/package.json", "e.d.ts", "/a/bar/e.d.ts", "../../bar");
testLoadingFromPackageJson("/a/b/c/d.ts", "/bar/package.json", "e.d.ts", "/bar/e.d.ts", "/bar");
testLoadingFromPackageJson("c:/a/b/c/d.ts", "c:/bar/package.json", "e.d.ts", "c:/bar/e.d.ts", "c:/bar");
});
it ("module name as directory - load index.d.ts", () => {
let containingFile = {name: "/a/b/c.ts"};
let packageJson = {name: "/a/b/foo/package.json", content: JSON.stringify({main: "/c/d"})};
it("module name as directory - load index.d.ts", () => {
let containingFile = { name: "/a/b/c.ts" };
let packageJson = { name: "/a/b/foo/package.json", content: JSON.stringify({ main: "/c/d" }) };
let indexFile = { name: "/a/b/foo/index.d.ts" };
let resolution = nodeModuleNameResolver("./foo", containingFile.name, createModuleResolutionHost(containingFile, packageJson, indexFile));
assert.equal(resolution.resolvedModule.resolvedFileName, indexFile.name);
@ -108,7 +122,7 @@ module ts {
]);
});
});
describe("Node module resolution - non-relative paths", () => {
it("load module as file - ts files not loaded", () => {
let containingFile = { name: "/a/b/c/d/e.ts" };
@ -140,7 +154,7 @@ module ts {
assert.equal(resolution.resolvedModule.resolvedFileName, moduleFile.name);
assert.equal(resolution.resolvedModule.isExternalLibraryImport, true);
});
it("load module as directory", () => {
let containingFile = { name: "/a/node_modules/b/c/node_modules/d/e.ts" };
let moduleFile = { name: "/a/node_modules/foo/index.d.ts" };
@ -178,31 +192,15 @@ module ts {
]);
});
});
describe("Module resolution - relative imports", () => {
it("should find all modules", () => {
const options: CompilerOptions = { module: ModuleKind.CommonJS };
const files: Map<string> = {
"/a/b/c/first/shared.ts": `
class A {}
export = A`,
"/a/b/c/first/second/class_a.ts": `
import Shared = require('../shared');
import C = require('../../third/class_c');
class B {}
export = B;`,
"/a/b/c/third/class_c.ts":`
import Shared = require('../first/shared');
class C {}
export = C;
`
};
const currentDirectory = "/a/b/c/first/second";
const host: CompilerHost = {
getSourceFile: (fileName: string, languageVersion: ScriptTarget) => {
let path = normalizePath(combinePaths(currentDirectory, fileName));
return hasProperty(files, path) ? createSourceFile(fileName, files[path], languageVersion) : undefined;
},
function test(files: Map<string>, currentDirectory: string, rootFiles: string[], expectedFilesCount: number, relativeNamesToCheck: string[]) {
const options: CompilerOptions = { module: ModuleKind.CommonJS };
const host: CompilerHost = {
getSourceFile: (fileName: string, languageVersion: ScriptTarget) => {
let path = normalizePath(combinePaths(currentDirectory, fileName));
return hasProperty(files, path) ? createSourceFile(fileName, files[path], languageVersion) : undefined;
},
getDefaultLibFileName: () => "lib.d.ts",
writeFile: (fileName, content): void => { throw new Error("NotImplemented"); },
getCurrentDirectory: () => currentDirectory,
@ -210,38 +208,150 @@ export = C;
getNewLine: () => "\r\n",
useCaseSensitiveFileNames: () => false,
fileExists: fileName => {
let path = normalizePath(combinePaths(currentDirectory, fileName));
return hasProperty(files, path);
let path = normalizePath(combinePaths(currentDirectory, fileName));
return hasProperty(files, path);
},
readFile: (fileName): string => { throw new Error("NotImplemented"); }
};
};
const program = createProgram(["class_a.ts"], options, host);
const program = createProgram(rootFiles, options, host);
assert.equal(program.getSourceFiles().length, 3);
const syntacticDiagnostics = program.getSyntacticDiagnostics();
assert.equal(syntacticDiagnostics.length, 0, `expect no syntactic diagnostics, got: ${JSON.stringify(syntacticDiagnostics.map(diagnosticToString))}`);
const semanticDiagnostics = program.getSemanticDiagnostics();
assert.equal(semanticDiagnostics.length, 0, `expect no semantic diagnostics, got: ${JSON.stringify(semanticDiagnostics.map(diagnosticToString))}`);
assert.equal(program.getSourceFiles().length, expectedFilesCount);
const syntacticDiagnostics = program.getSyntacticDiagnostics();
assert.equal(syntacticDiagnostics.length, 0, `expect no syntactic diagnostics, got: ${JSON.stringify(syntacticDiagnostics.map(diagnosticToString))}`);
const semanticDiagnostics = program.getSemanticDiagnostics();
assert.equal(semanticDiagnostics.length, 0, `expect no semantic diagnostics, got: ${JSON.stringify(semanticDiagnostics.map(diagnosticToString))}`);
// try to get file using a relative name
const fileC = program.getSourceFile("../../../c/third/class_c.ts");
assert.isTrue(fileC !== undefined, `expected to get file by relative name, got ${fileC}`);
});
function diagnosticToString(diagnostic: Diagnostic) {
let output = "";
if (diagnostic.file) {
let loc = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
output += `${ diagnostic.file.fileName }(${ loc.line + 1 },${ loc.character + 1 }): `;
// try to get file using a relative name
for (const relativeFileName of relativeNamesToCheck) {
assert.isTrue(program.getSourceFile(relativeFileName) !== undefined, `expected to get file by relative name, got undefined`);
}
let category = DiagnosticCategory[diagnostic.category].toLowerCase();
output += `${ category } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine }`;
return output;
}
it("should find all modules", () => {
const files: Map<string> = {
"/a/b/c/first/shared.ts": `
class A {}
export = A`,
"/a/b/c/first/second/class_a.ts": `
import Shared = require('../shared');
import C = require('../../third/class_c');
class B {}
export = B;`,
"/a/b/c/third/class_c.ts": `
import Shared = require('../first/shared');
class C {}
export = C;
`
};
test(files, "/a/b/c/first/second", ["class_a.ts"], 3, ["../../../c/third/class_c.ts"]);
});
it("should find modules in node_modules", () => {
const files: Map<string> = {
"/parent/node_modules/mod/index.d.ts": "export var x",
"/parent/app/myapp.ts": `import {x} from "mod"`
};
test(files, "/parent/app",["myapp.ts"], 2, []);
});
it("should find file referenced via absolute and relative names", () => {
const files: Map<string> = {
"/a/b/c.ts": `/// <reference path="b.ts"/>`,
"/a/b/b.ts": "var x"
};
test(files, "/a/b", ["c.ts", "/a/b/b.ts"], 2, []);
});
});
describe("Files with different casing", () => {
const library = createSourceFile("lib.d.ts", "", ScriptTarget.ES5);
function test(files: Map<string>, options: CompilerOptions, currentDirectory: string, useCaseSensitiveFileNames: boolean, rootFiles: string[], diagnosticCodes: number[]): void {
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
if (!useCaseSensitiveFileNames) {
let f: Map<string> = {};
for (let fileName in files) {
f[getCanonicalFileName(fileName)] = files[fileName];
}
files = f;
}
const host: CompilerHost = {
getSourceFile: (fileName: string, languageVersion: ScriptTarget) => {
if (fileName === "lib.d.ts") {
return library;
}
let path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName)));
return hasProperty(files, path) ? createSourceFile(fileName, files[path], languageVersion) : undefined;
},
getDefaultLibFileName: () => "lib.d.ts",
writeFile: (fileName, content): void => { throw new Error("NotImplemented"); },
getCurrentDirectory: () => currentDirectory,
getCanonicalFileName,
getNewLine: () => "\r\n",
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
fileExists: fileName => {
let path = getCanonicalFileName(normalizePath(combinePaths(currentDirectory, fileName)));
return hasProperty(files, path);
},
readFile: (fileName): string => { throw new Error("NotImplemented"); }
};
const program = createProgram(rootFiles, options, host);
const diagnostics = sortAndDeduplicateDiagnostics(program.getSemanticDiagnostics().concat(program.getOptionsDiagnostics()));
assert.equal(diagnostics.length, diagnosticCodes.length, `Incorrect number of expected diagnostics, expected ${diagnosticCodes.length}, got '${map(diagnostics, diagnosticToString).join("\r\n")}'`);
for (let i = 0; i < diagnosticCodes.length; ++i) {
assert.equal(diagnostics[i].code, diagnosticCodes[i], `Expected diagnostic code ${diagnosticCodes[i]}, got '${diagnostics[i].code}': '${diagnostics[i].messageText}'`);
}
}
it("should succeed when the same file is referenced using absolute and relative names", () => {
const files: Map<string> = {
"/a/b/c.ts": `/// <reference path="d.ts"/>`,
"/a/b/d.ts": "var x"
};
test(files, { module: ts.ModuleKind.AMD }, "/a/b", /* useCaseSensitiveFileNames */ false, ["c.ts", "/a/b/d.ts"], []);
});
it("should fail when two files used in program differ only in casing (tripleslash references)", () => {
const files: Map<string> = {
"/a/b/c.ts": `/// <reference path="D.ts"/>`,
"/a/b/d.ts": "var x"
};
test(files, { module: ts.ModuleKind.AMD, forceConsistentCasingInFileNames: true }, "/a/b", /* useCaseSensitiveFileNames */ false, ["c.ts", "d.ts"], [1149]);
});
it("should fail when two files used in program differ only in casing (imports)", () => {
const files: Map<string> = {
"/a/b/c.ts": `import {x} from "D"`,
"/a/b/d.ts": "export var x"
};
test(files, { module: ts.ModuleKind.AMD, forceConsistentCasingInFileNames: true }, "/a/b", /* useCaseSensitiveFileNames */ false, ["c.ts", "d.ts"], [1149]);
});
it("should fail when two files used in program differ only in casing (imports, relative module names)", () => {
const files: Map<string> = {
"moduleA.ts": `import {x} from "./ModuleB"`,
"moduleB.ts": "export var x"
};
test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "", /* useCaseSensitiveFileNames */ false, ["moduleA.ts", "moduleB.ts"], [1149]);
});
it("should fail when two files exist on disk that differs only in casing", () => {
const files: Map<string> = {
"/a/b/c.ts": `import {x} from "D"`,
"/a/b/D.ts": "export var x",
"/a/b/d.ts": "export var y"
};
test(files, { module: ts.ModuleKind.AMD }, "/a/b", /* useCaseSensitiveFileNames */ true, ["c.ts", "d.ts"], [1149]);
});
it("should fail when module name in 'require' calls has inconsistent casing", () => {
const files: Map<string> = {
"moduleA.ts": `import a = require("./ModuleC")`,
"moduleB.ts": `import a = require("./moduleC")`,
"moduleC.ts": "export var x"
};
test(files, { module: ts.ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, "", /* useCaseSensitiveFileNames */ false, ["moduleA.ts", "moduleB.ts", "moduleC.ts"], [1149, 1149]);
})
});
}