diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 0dbaa3b39b2..65eea1f9f9e 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -568,7 +568,9 @@ namespace ts { } function getSourceFile(fileName: string) { - return filesByName.get(fileName); + // 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 getDiagnosticsHelper( @@ -775,60 +777,62 @@ namespace ts { // Get source file from normalized fileName function findSourceFile(fileName: string, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number): SourceFile { - let canonicalName = host.getCanonicalFileName(normalizeSlashes(fileName)); - if (filesByName.contains(canonicalName)) { + if (filesByName.contains(fileName)) { // We've already looked for this file, use cached result - return getSourceFileFromCache(fileName, canonicalName, /*useAbsolutePath*/ false); + return getSourceFileFromCache(fileName, /*useAbsolutePath*/ false); } - else { - let normalizedAbsolutePath = getNormalizedAbsolutePath(fileName, host.getCurrentDirectory()); - let canonicalAbsolutePath = host.getCanonicalFileName(normalizedAbsolutePath); - if (filesByName.contains(canonicalAbsolutePath)) { - return getSourceFileFromCache(normalizedAbsolutePath, canonicalAbsolutePath, /*useAbsolutePath*/ true); - } - - // We haven't looked for this file, do so now and cache result - let file = host.getSourceFile(fileName, options.target, hostErrorMessage => { - if (refFile !== undefined && refPos !== undefined && refEnd !== undefined) { - fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos, - Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage)); - } - else { - fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage)); - } - }); - filesByName.set(canonicalName, file); - if (file) { - skipDefaultLib = skipDefaultLib || file.hasNoDefaultLib; - - // Set the source file for normalized absolute path - filesByName.set(canonicalAbsolutePath, file); - - let basePath = getDirectoryPath(fileName); - if (!options.noResolve) { - processReferencedFiles(file, basePath); - } - - // always process imported modules to record module name resolutions - processImportedModules(file, basePath); - - if (isDefaultLib) { - file.isDefaultLib = true; - files.unshift(file); - } - else { - files.push(file); - } - } - + + let normalizedAbsolutePath = getNormalizedAbsolutePath(fileName, host.getCurrentDirectory()); + 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); return file; } - function getSourceFileFromCache(fileName: string, canonicalName: string, useAbsolutePath: boolean): SourceFile { - let file = filesByName.get(canonicalName); + // We haven't looked for this file, do so now and cache result + let file = host.getSourceFile(fileName, options.target, hostErrorMessage => { + if (refFile !== undefined && refPos !== undefined && refEnd !== undefined) { + fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos, + Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage)); + } + else { + fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage)); + } + }); + + filesByName.set(fileName, file); + if (file) { + skipDefaultLib = skipDefaultLib || file.hasNoDefaultLib; + + // Set the source file for normalized absolute path + filesByName.set(normalizedAbsolutePath, file); + + let basePath = getDirectoryPath(fileName); + if (!options.noResolve) { + processReferencedFiles(file, basePath); + } + + // always process imported modules to record module name resolutions + processImportedModules(file, basePath); + + if (isDefaultLib) { + file.isDefaultLib = true; + files.unshift(file); + } + else { + files.push(file); + } + } + + 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 (canonicalName !== sourceFileName) { + 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)); @@ -862,8 +866,8 @@ namespace ts { const importedFile = findModuleSourceFile(resolution.resolvedFileName, file.imports[i]); if (importedFile && resolution.isExternalLibraryImport) { if (!isExternalModule(importedFile)) { - let start = getTokenPosOfNode(file.imports[i], file) - fileProcessingDiagnostics.add(createFileDiagnostic(file, start, file.imports[i].end - start, Diagnostics.Exported_external_package_typings_file_0_is_not_a_module_Please_contact_the_package_author_to_update_the_package_definition, importedFile.fileName)); + let start = getTokenPosOfNode(file.imports[i], file) + fileProcessingDiagnostics.add(createFileDiagnostic(file, start, file.imports[i].end - start, Diagnostics.Exported_external_package_typings_file_0_is_not_a_module_Please_contact_the_package_author_to_update_the_package_definition, importedFile.fileName)); } else if (!fileExtensionIs(importedFile.fileName, ".d.ts")) { let start = getTokenPosOfNode(file.imports[i], file) diff --git a/tests/cases/unittests/moduleResolution.ts b/tests/cases/unittests/moduleResolution.ts index ed9f0b0a986..809782bae6f 100644 --- a/tests/cases/unittests/moduleResolution.ts +++ b/tests/cases/unittests/moduleResolution.ts @@ -163,4 +163,70 @@ module ts { ]); }); }); + + describe("Module resolution - relative imports", () => { + it("should find all modules", () => { + const options: CompilerOptions = { module: ModuleKind.CommonJS }; + const files: Map = { + "/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; + }, + getDefaultLibFileName: () => "lib.d.ts", + writeFile: (fileName, content): void => { throw new Error("NotImplemented"); }, + getCurrentDirectory: () => currentDirectory, + getCanonicalFileName: fileName => fileName.toLowerCase(), + getNewLine: () => "\r\n", + useCaseSensitiveFileNames: () => false, + fileExists: fileName => { + 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); + + 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))}`); + + // 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 }): `; + } + + let category = DiagnosticCategory[diagnostic.category].toLowerCase(); + output += `${ category } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine }`; + + return output; + } + }); } \ No newline at end of file