diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 8560ccde32c..72ece69ccd1 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1952,6 +1952,15 @@ namespace ts { return normalized; } + export function normalizePathAndRoot(path: string): string { + const normalized = normalizePathAndParts(path).path; + const rootLength = getRootLength(normalized); + if (rootLength === 0) { + return normalized; + } + return path.substr(0, rootLength).toLowerCase() + path.substr(rootLength); + } + export function normalizePath(path: string): string { return normalizePathAndParts(path).path; } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 7a1d88d3bfd..f421a180257 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -37,7 +37,7 @@ namespace ts { for (const sourceFile of sourceFiles) { const jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, getOutputExtension(sourceFile, options)); const sourceMapFilePath = getSourceMapFilePath(jsFilePath, options); - const declarationFilePath = !isSourceFileJavaScript(sourceFile) && (emitOnlyDtsFiles || options.declaration) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined; + const declarationFilePath = !isSourceFileJavaScript(sourceFile) && (emitOnlyDtsFiles || options.declaration || options.referenceTarget) ? getDeclarationEmitOutputFilePath(sourceFile, host) : undefined; action({ jsFilePath, sourceMapFilePath, declarationFilePath }, sourceFile, emitOnlyDtsFiles); } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 464e5073bc3..4d848f20655 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -614,8 +614,8 @@ namespace ts { // List of collected files is complete; validate exhautiveness if this is a project with a file list if (options.referenceTarget && rootNames.length < files.length) { - const normalizedRootNames = rootNames.map(r => normalizePath(r)); - const sourceFiles = files.filter(f => !f.isDeclarationFile).map(f => normalizePath(f.path)); + const normalizedRootNames = rootNames.map(r => normalizePathAndRoot(r)); + const sourceFiles = files.filter(f => !f.isDeclarationFile).map(f => normalizePathAndRoot(f.path)); for (const file of sourceFiles) { if (normalizedRootNames.every(r => r !== file)) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_0_is_not_in_project_file_list_Projects_must_list_all_files_or_use_an_include_pattern, file)); @@ -687,7 +687,7 @@ namespace ts { const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary)); if (options.referenceTarget) { // Project compilations never infer their root from the input source paths - commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir || ".", currentDirectory); + commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir || getDirectoryPath(options.configFilePath), currentDirectory); } else if (options.rootDir && checkSourceFilesBelongToPath(emittedFiles, options.rootDir)) { // If a rootDir is specified and is valid use it as the commonSourceDirectory diff --git a/src/harness/unittests/projectReferences.ts b/src/harness/unittests/projectReferences.ts index 882e8ba6e2e..14fc0fdf503 100644 --- a/src/harness/unittests/projectReferences.ts +++ b/src/harness/unittests/projectReferences.ts @@ -45,7 +45,7 @@ namespace ts { return names.map((n, i) => `import * as mod_${i} from ${n}`).join("\r\n"); } - function testProjectReferences(spec: TestSpecification, entryPointConfigFileName: string, checkResult: (prog: Program) => void) { + function testProjectReferences(spec: TestSpecification, entryPointConfigFileName: string, checkResult: (prog: Program, host: Utils.MockProjectReferenceCompilerHost) => void) { const files = createMap(); for (const key in spec) { const sp = spec[key]; @@ -81,7 +81,7 @@ namespace ts { const file = ts.parseJsonConfigFileContent(config, host.configHost, getDirectoryPath(entryPointConfigFileName), {}, entryPointConfigFileName); file.options.configFilePath = entryPointConfigFileName; const prog = ts.createProgram(file.fileNames, file.options, host); - checkResult(prog); + checkResult(prog, host); } describe("project-references meta check", () => { @@ -232,4 +232,26 @@ namespace ts { }); }); }); + + /** + * referenceTarget behavior + */ + describe("project-references behavior changes under referenceTarget: true", () => { + it("doesn't infer the rootDir from source paths", () => { + const spec: TestSpecification = { + "/alpha": { + files: { "/alpha/src/a.ts": "export const m: number = 3;" }, + options: { + outDir: "bin" + }, + references: [] + } + }; + testProjectReferences(spec, "/alpha/tsconfig.json", (program, host) => { + program.emit(); + assert.deepEqual(host.emittedFiles.map(e => e.fileName).sort(), ["/alpha/bin/src/a.d.ts", "/alpha/bin/src/a.js"]); + }); + }); + }); + } diff --git a/src/harness/virtualFileSystem.ts b/src/harness/virtualFileSystem.ts index b52a5730bf5..1e19efa303e 100644 --- a/src/harness/virtualFileSystem.ts +++ b/src/harness/virtualFileSystem.ts @@ -222,9 +222,16 @@ namespace Utils { } export class MockProjectReferenceCompilerHost implements ts.CompilerHost { - public configHost: ts.ParseConfigHost = new MockParseConfigHost(this.currentDirectory, this.ignoreCase, this.files); + public emittedFiles: {fileName: string, contents: string}[] = []; + public configHost: ts.ParseConfigHost; private readonly getCanonicalFileNameImpl = ts.createGetCanonicalFileName(!this.ignoreCase); constructor(private currentDirectory: string, private ignoreCase: boolean, private files: ts.Map | string[]) { + this.reset(); + } + + reset() { + this.configHost = new MockParseConfigHost(this.currentDirectory, this.ignoreCase, this.files); + this.emittedFiles = []; } getCanonicalFileName = (fileName: string): string => { @@ -269,7 +276,11 @@ namespace Utils { getDefaultLibFileName(options: ts.CompilerOptions): string { return ts.getDefaultLibFileName(options); } - writeFile: ts.WriteFileCallback; + + writeFile: ts.WriteFileCallback = (fileName, contents) => { + this.emittedFiles.push({ fileName, contents }); + } + getCurrentDirectory = (): string => { return this.currentDirectory; }