Skip to content

Commit bfa390e

Browse files
committed
Merge pull request microsoft#4989 from Microsoft/relativeModuleNamesInImports
record resolution for relative file name if file was found via absolu…
2 parents c55d6dc + e53646a commit bfa390e

File tree

2 files changed

+116
-46
lines changed

2 files changed

+116
-46
lines changed

src/compiler/program.ts

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,9 @@ namespace ts {
568568
}
569569

570570
function getSourceFile(fileName: string) {
571-
return filesByName.get(fileName);
571+
// first try to use file name as is to find file
572+
// then try to convert relative file name to absolute and use it to retrieve source file
573+
return filesByName.get(fileName) || filesByName.get(getNormalizedAbsolutePath(fileName, host.getCurrentDirectory()));
572574
}
573575

574576
function getDiagnosticsHelper(
@@ -775,60 +777,62 @@ namespace ts {
775777

776778
// Get source file from normalized fileName
777779
function findSourceFile(fileName: string, isDefaultLib: boolean, refFile?: SourceFile, refPos?: number, refEnd?: number): SourceFile {
778-
let canonicalName = host.getCanonicalFileName(normalizeSlashes(fileName));
779-
if (filesByName.contains(canonicalName)) {
780+
if (filesByName.contains(fileName)) {
780781
// We've already looked for this file, use cached result
781-
return getSourceFileFromCache(fileName, canonicalName, /*useAbsolutePath*/ false);
782+
return getSourceFileFromCache(fileName, /*useAbsolutePath*/ false);
783+
}
784+
785+
let normalizedAbsolutePath = getNormalizedAbsolutePath(fileName, host.getCurrentDirectory());
786+
if (filesByName.contains(normalizedAbsolutePath)) {
787+
const file = getSourceFileFromCache(normalizedAbsolutePath, /*useAbsolutePath*/ true);
788+
// we don't have resolution for this relative file name but the match was found by absolute file name
789+
// store resolution for relative name as well
790+
filesByName.set(fileName, file);
791+
return file;
782792
}
783-
else {
784-
let normalizedAbsolutePath = getNormalizedAbsolutePath(fileName, host.getCurrentDirectory());
785-
let canonicalAbsolutePath = host.getCanonicalFileName(normalizedAbsolutePath);
786-
if (filesByName.contains(canonicalAbsolutePath)) {
787-
return getSourceFileFromCache(normalizedAbsolutePath, canonicalAbsolutePath, /*useAbsolutePath*/ true);
788-
}
789793

790-
// We haven't looked for this file, do so now and cache result
791-
let file = host.getSourceFile(fileName, options.target, hostErrorMessage => {
792-
if (refFile !== undefined && refPos !== undefined && refEnd !== undefined) {
793-
fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos,
794-
Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage));
795-
}
796-
else {
797-
fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage));
798-
}
799-
});
800-
filesByName.set(canonicalName, file);
801-
if (file) {
802-
skipDefaultLib = skipDefaultLib || file.hasNoDefaultLib;
794+
// We haven't looked for this file, do so now and cache result
795+
let file = host.getSourceFile(fileName, options.target, hostErrorMessage => {
796+
if (refFile !== undefined && refPos !== undefined && refEnd !== undefined) {
797+
fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos,
798+
Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage));
799+
}
800+
else {
801+
fileProcessingDiagnostics.add(createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, fileName, hostErrorMessage));
802+
}
803+
});
804+
805+
filesByName.set(fileName, file);
806+
if (file) {
807+
skipDefaultLib = skipDefaultLib || file.hasNoDefaultLib;
803808

804-
// Set the source file for normalized absolute path
805-
filesByName.set(canonicalAbsolutePath, file);
806-
807-
let basePath = getDirectoryPath(fileName);
808-
if (!options.noResolve) {
809-
processReferencedFiles(file, basePath);
810-
}
809+
// Set the source file for normalized absolute path
810+
filesByName.set(normalizedAbsolutePath, file);
811+
812+
let basePath = getDirectoryPath(fileName);
813+
if (!options.noResolve) {
814+
processReferencedFiles(file, basePath);
815+
}
811816

812-
// always process imported modules to record module name resolutions
813-
processImportedModules(file, basePath);
817+
// always process imported modules to record module name resolutions
818+
processImportedModules(file, basePath);
814819

815-
if (isDefaultLib) {
816-
file.isDefaultLib = true;
817-
files.unshift(file);
818-
}
819-
else {
820-
files.push(file);
821-
}
820+
if (isDefaultLib) {
821+
file.isDefaultLib = true;
822+
files.unshift(file);
823+
}
824+
else {
825+
files.push(file);
822826
}
823-
824-
return file;
825827
}
826828

827-
function getSourceFileFromCache(fileName: string, canonicalName: string, useAbsolutePath: boolean): SourceFile {
828-
let file = filesByName.get(canonicalName);
829+
return file;
830+
831+
function getSourceFileFromCache(fileName: string, useAbsolutePath: boolean): SourceFile {
832+
let file = filesByName.get(fileName);
829833
if (file && host.useCaseSensitiveFileNames()) {
830834
let sourceFileName = useAbsolutePath ? getNormalizedAbsolutePath(file.fileName, host.getCurrentDirectory()) : file.fileName;
831-
if (canonicalName !== sourceFileName) {
835+
if (normalizeSlashes(fileName) !== normalizeSlashes(sourceFileName)) {
832836
if (refFile !== undefined && refPos !== undefined && refEnd !== undefined) {
833837
fileProcessingDiagnostics.add(createFileDiagnostic(refFile, refPos, refEnd - refPos,
834838
Diagnostics.File_name_0_differs_from_already_included_file_name_1_only_in_casing, fileName, sourceFileName));
@@ -862,8 +866,8 @@ namespace ts {
862866
const importedFile = findModuleSourceFile(resolution.resolvedFileName, file.imports[i]);
863867
if (importedFile && resolution.isExternalLibraryImport) {
864868
if (!isExternalModule(importedFile)) {
865-
let start = getTokenPosOfNode(file.imports[i], file)
866-
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));
869+
let start = getTokenPosOfNode(file.imports[i], file)
870+
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));
867871
}
868872
else if (!fileExtensionIs(importedFile.fileName, ".d.ts")) {
869873
let start = getTokenPosOfNode(file.imports[i], file)

tests/cases/unittests/moduleResolution.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,70 @@ module ts {
163163
]);
164164
});
165165
});
166+
167+
describe("Module resolution - relative imports", () => {
168+
it("should find all modules", () => {
169+
const options: CompilerOptions = { module: ModuleKind.CommonJS };
170+
const files: Map<string> = {
171+
"/a/b/c/first/shared.ts": `
172+
class A {}
173+
export = A`,
174+
"/a/b/c/first/second/class_a.ts": `
175+
import Shared = require('../shared');
176+
import C = require('../../third/class_c');
177+
class B {}
178+
export = B;`,
179+
"/a/b/c/third/class_c.ts":`
180+
import Shared = require('../first/shared');
181+
class C {}
182+
export = C;
183+
`
184+
};
185+
const currentDirectory = "/a/b/c/first/second";
186+
const host: CompilerHost = {
187+
getSourceFile: (fileName: string, languageVersion: ScriptTarget) => {
188+
let path = normalizePath(combinePaths(currentDirectory, fileName));
189+
return hasProperty(files, path) ? createSourceFile(fileName, files[path], languageVersion) : undefined;
190+
},
191+
getDefaultLibFileName: () => "lib.d.ts",
192+
writeFile: (fileName, content): void => { throw new Error("NotImplemented"); },
193+
getCurrentDirectory: () => currentDirectory,
194+
getCanonicalFileName: fileName => fileName.toLowerCase(),
195+
getNewLine: () => "\r\n",
196+
useCaseSensitiveFileNames: () => false,
197+
fileExists: fileName => {
198+
let path = normalizePath(combinePaths(currentDirectory, fileName));
199+
return hasProperty(files, path);
200+
},
201+
readFile: (fileName): string => { throw new Error("NotImplemented"); }
202+
};
203+
204+
const program = createProgram(["class_a.ts"], options, host);
205+
206+
assert.equal(program.getSourceFiles().length, 3);
207+
const syntacticDiagnostics = program.getSyntacticDiagnostics();
208+
assert.equal(syntacticDiagnostics.length, 0, `expect no syntactic diagnostics, got: ${JSON.stringify(syntacticDiagnostics.map(diagnosticToString))}`);
209+
const semanticDiagnostics = program.getSemanticDiagnostics();
210+
assert.equal(semanticDiagnostics.length, 0, `expect no semantic diagnostics, got: ${JSON.stringify(semanticDiagnostics.map(diagnosticToString))}`);
211+
212+
// try to get file using a relative name
213+
const fileC = program.getSourceFile("../../../c/third/class_c.ts");
214+
assert.isTrue(fileC !== undefined, `expected to get file by relative name, got ${fileC}`);
215+
});
216+
217+
function diagnosticToString(diagnostic: Diagnostic) {
218+
let output = "";
219+
220+
if (diagnostic.file) {
221+
let loc = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
222+
223+
output += `${ diagnostic.file.fileName }(${ loc.line + 1 },${ loc.character + 1 }): `;
224+
}
225+
226+
let category = DiagnosticCategory[diagnostic.category].toLowerCase();
227+
output += `${ category } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine }`;
228+
229+
return output;
230+
}
231+
});
166232
}

0 commit comments

Comments
 (0)