Verify the emit file name is unique and doesnt overwrite input file

Fixes #4424
This commit is contained in:
Sheetal Nandi
2015-10-12 12:25:13 -07:00
parent 2c3c321593
commit 5e14edb4b7
12 changed files with 175 additions and 35 deletions

View File

@@ -32,12 +32,12 @@ namespace ts {
export function getDeclarationDiagnostics(host: EmitHost, resolver: EmitResolver, targetSourceFile: SourceFile): Diagnostic[] {
let diagnostics: Diagnostic[] = [];
let jsFilePath = getOwnEmitOutputFilePath(targetSourceFile, host, ".js");
emitDeclarations(host, resolver, diagnostics, jsFilePath, targetSourceFile);
let { declarationFilePath } = getEmitFileNames(targetSourceFile, host);
emitDeclarations(host, resolver, diagnostics, declarationFilePath, targetSourceFile);
return diagnostics;
}
function emitDeclarations(host: EmitHost, resolver: EmitResolver, diagnostics: Diagnostic[], jsFilePath: string, root?: SourceFile): DeclarationEmit {
function emitDeclarations(host: EmitHost, resolver: EmitResolver, diagnostics: Diagnostic[], declarationFilePath: string, root?: SourceFile): DeclarationEmit {
let newLine = host.getNewLine();
let compilerOptions = host.getCompilerOptions();
@@ -1603,12 +1603,10 @@ namespace ts {
function writeReferencePath(referencedFile: SourceFile) {
let declFileName = referencedFile.flags & NodeFlags.DeclarationFile
? referencedFile.fileName // Declaration file, use declaration file name
: shouldEmitToOwnFile(referencedFile, compilerOptions)
? getOwnEmitOutputFilePath(referencedFile, host, ".d.ts") // Own output file so get the .d.ts file
: removeFileExtension(compilerOptions.outFile || compilerOptions.out, getExtensionsToRemoveForEmitPath(compilerOptions)) + ".d.ts"; // Global out file
: getEmitFileNames(referencedFile, host).declarationFilePath; // declaration file name
declFileName = getRelativePathToDirectoryOrUrl(
getDirectoryPath(normalizeSlashes(jsFilePath)),
getDirectoryPath(normalizeSlashes(declarationFilePath)),
declFileName,
host.getCurrentDirectory(),
host.getCanonicalFileName,
@@ -1619,15 +1617,15 @@ namespace ts {
}
/* @internal */
export function writeDeclarationFile(jsFilePath: string, sourceFile: SourceFile, host: EmitHost, resolver: EmitResolver, diagnostics: Diagnostic[]) {
let emitDeclarationResult = emitDeclarations(host, resolver, diagnostics, jsFilePath, sourceFile);
export function writeDeclarationFile(declarationFilePath: string, sourceFile: SourceFile, host: EmitHost, resolver: EmitResolver, diagnostics: Diagnostic[]) {
let emitDeclarationResult = emitDeclarations(host, resolver, diagnostics, declarationFilePath, sourceFile);
// TODO(shkamat): Should we not write any declaration file if any of them can produce error,
// or should we just not write this file like we are doing now
if (!emitDeclarationResult.reportedDeclarationError) {
let declarationOutput = emitDeclarationResult.referencePathsOutput
+ getDeclarationOutput(emitDeclarationResult.synchronousDeclarationOutput, emitDeclarationResult.moduleElementDeclarationEmitInfo);
let compilerOptions = host.getCompilerOptions();
writeFile(host, diagnostics, removeFileExtension(jsFilePath, getExtensionsToRemoveForEmitPath(compilerOptions)) + ".d.ts", declarationOutput, compilerOptions.emitBOM);
writeFile(host, diagnostics, declarationFilePath, declarationOutput, compilerOptions.emitBOM);
}
function getDeclarationOutput(synchronousDeclarationOutput: string, moduleElementDeclarationEmitInfo: ModuleElementDeclarationEmitInfo[]) {

View File

@@ -2060,10 +2060,14 @@
"category": "Error",
"code": 5054
},
"Could not write file '{0}' which is one of the input files.": {
"Cannot write file '{0}' which is one of the input files.": {
"category": "Error",
"code": 5055
},
"Cannot write file '{0}' since one or more input files would emit into it.": {
"category": "Error",
"code": 5056
},
"Concatenate and emit output to single file.": {
"category": "Message",

View File

@@ -324,31 +324,27 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
let sourceMapDataList: SourceMapData[] = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? [] : undefined;
let diagnostics: Diagnostic[] = [];
let newLine = host.getNewLine();
let jsxDesugaring = host.getCompilerOptions().jsx !== JsxEmit.Preserve;
let shouldEmitJsx = (s: SourceFile) => (s.languageVariant === LanguageVariant.JSX && !jsxDesugaring);
if (targetSourceFile === undefined) {
forEach(host.getSourceFiles(), sourceFile => {
if (shouldEmitToOwnFile(sourceFile, compilerOptions)) {
let jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, shouldEmitJsx(sourceFile) ? ".jsx" : ".js");
emitFile(jsFilePath, sourceFile);
emitFile(getEmitFileNames(sourceFile, host), sourceFile);
}
});
if (compilerOptions.outFile || compilerOptions.out) {
emitFile(compilerOptions.outFile || compilerOptions.out);
emitFile(getBundledEmitFileNames(compilerOptions));
}
}
else {
// targetSourceFile is specified (e.g calling emitter from language service or calling getSemanticDiagnostic from language service)
if (shouldEmitToOwnFile(targetSourceFile, compilerOptions)) {
let jsFilePath = getOwnEmitOutputFilePath(targetSourceFile, host, shouldEmitJsx(targetSourceFile) ? ".jsx" : ".js");
emitFile(jsFilePath, targetSourceFile);
emitFile(getEmitFileNames(targetSourceFile, host), targetSourceFile);
}
else if (!isDeclarationFile(targetSourceFile) &&
!isJavaScript(targetSourceFile.fileName) &&
(compilerOptions.outFile || compilerOptions.out)) {
emitFile(compilerOptions.outFile || compilerOptions.out);
emitFile(getBundledEmitFileNames(compilerOptions));
}
}
@@ -7491,18 +7487,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
}
}
function emitFile(jsFilePath: string, sourceFile?: SourceFile) {
if (forEach(host.getSourceFiles(), sourceFile => jsFilePath === sourceFile.fileName)) {
// TODO(shkamat) Verify if this works if same file is referred via different paths ..\foo\a.js and a.js refering to same one
// Report error and dont emit this file
diagnostics.push(createCompilerDiagnostic(Diagnostics.Could_not_write_file_0_which_is_one_of_the_input_files, jsFilePath));
return;
function emitFile({ jsFilePath, declarationFilePath}: { jsFilePath: string, declarationFilePath: string }, sourceFile?: SourceFile) {
if (!host.isEmitBlocked(jsFilePath)) {
emitJavaScript(jsFilePath, sourceFile);
}
emitJavaScript(jsFilePath, sourceFile);
if (compilerOptions.declaration) {
writeDeclarationFile(jsFilePath, sourceFile, host, resolver, diagnostics);
if (compilerOptions.declaration && !host.isEmitBlocked(declarationFilePath)) {
writeDeclarationFile(declarationFilePath, sourceFile, host, resolver, diagnostics);
}
}
}

View File

@@ -330,6 +330,7 @@ namespace ts {
let files: SourceFile[] = [];
let fileProcessingDiagnostics = createDiagnosticCollection();
let programDiagnostics = createDiagnosticCollection();
let hasEmitBlockingDiagnostics: Map<boolean> = {}; // Map storing if there is emit blocking diagnostics for given input
let commonSourceDirectory: string;
let diagnosticsProducingTypeChecker: TypeChecker;
@@ -374,13 +375,9 @@ namespace ts {
}
}
verifyCompilerOptions();
// unconditionally set oldProgram to undefined to prevent it from being captured in closure
oldProgram = undefined;
programTime += new Date().getTime() - start;
program = {
getRootFileNames: () => rootNames,
getSourceFile: getSourceFile,
@@ -403,6 +400,11 @@ namespace ts {
getTypeCount: () => getDiagnosticsProducingTypeChecker().getTypeCount(),
getFileProcessingDiagnostics: () => fileProcessingDiagnostics
};
verifyCompilerOptions();
programTime += new Date().getTime() - start;
return program;
function getClassifiableNames() {
@@ -519,6 +521,7 @@ namespace ts {
getSourceFiles: program.getSourceFiles,
writeFile: writeFileCallback || (
(fileName, data, writeByteOrderMark, onError) => host.writeFile(fileName, data, writeByteOrderMark, onError)),
isEmitBlocked: emitFileName => hasProperty(hasEmitBlockingDiagnostics, emitFileName),
};
}
@@ -1245,6 +1248,55 @@ namespace ts {
options.target !== ScriptTarget.ES6) {
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_experimentalAsyncFunctions_cannot_be_specified_when_targeting_ES5_or_lower));
}
if (!options.noEmit) {
let emitHost = getEmitHost();
let emitFilesSeen: Map<SourceFile[]> = {};
// Build map of files seen
for (let file of files) {
let { jsFilePath, declarationFilePath } = getEmitFileNames(file, emitHost);
if (jsFilePath) {
let filesEmittingJsFilePath = lookUp(emitFilesSeen, jsFilePath);
if (!filesEmittingJsFilePath) {
emitFilesSeen[jsFilePath] = [file];
if (options.declaration) {
emitFilesSeen[declarationFilePath] = [file];
}
}
else {
filesEmittingJsFilePath.push(file);
}
}
}
// Verify that all the emit files are unique and dont overwrite input files
forEachKey(emitFilesSeen, emitFilePath => {
// Report error if the output overwrites input file
if (hasFile(files, emitFilePath)) {
createEmitBlockingDiagnostics(emitFilePath, Diagnostics.Cannot_write_file_0_which_is_one_of_the_input_files);
}
// Report error if multiple files write into same file (except if specified by --out or --outFile)
if (emitFilePath !== (options.outFile || options.out)) {
// Not --out or --outFile emit, There should be single file emitting to this file
if (emitFilesSeen[emitFilePath].length > 1) {
createEmitBlockingDiagnostics(emitFilePath, Diagnostics.Cannot_write_file_0_since_one_or_more_input_files_would_emit_into_it);
}
}
else {
// --out or --outFile, error if there exist file emitting to single file colliding with --out
if (forEach(emitFilesSeen[emitFilePath], sourceFile => shouldEmitToOwnFile(sourceFile, options))) {
createEmitBlockingDiagnostics(emitFilePath, Diagnostics.Cannot_write_file_0_since_one_or_more_input_files_would_emit_into_it);
}
}
});
}
}
function createEmitBlockingDiagnostics(emitFileName: string, message: DiagnosticMessage) {
hasEmitBlockingDiagnostics[emitFileName] = true;
programDiagnostics.add(createCompilerDiagnostic(message, emitFileName));
}
}
}

View File

@@ -39,6 +39,8 @@ namespace ts {
getCanonicalFileName(fileName: string): string;
getNewLine(): string;
isEmitBlocked(emitFileName: string): boolean;
writeFile: WriteFileCallback;
}
@@ -1766,6 +1768,44 @@ namespace ts {
return emitOutputFilePathWithoutExtension + extension;
}
export function getEmitFileNames(sourceFile: SourceFile, host: EmitHost) {
if (!isDeclarationFile(sourceFile) && !isJavaScript(sourceFile.fileName)) {
let options = host.getCompilerOptions();
let jsFilePath: string;
if (shouldEmitToOwnFile(sourceFile, options)) {
let jsFilePath = getOwnEmitOutputFilePath(sourceFile, host,
sourceFile.languageVariant === LanguageVariant.JSX && options.jsx === JsxEmit.Preserve ? ".jsx" : ".js");
return {
jsFilePath,
declarationFilePath: getDeclarationEmitFilePath(jsFilePath, options)
};
}
else if (options.outFile || options.out) {
return getBundledEmitFileNames(options);
}
}
return {
jsFilePath: undefined,
declarationFilePath: undefined
};
}
export function getBundledEmitFileNames(options: CompilerOptions) {
let jsFilePath = options.outFile || options.out;
return {
jsFilePath,
declarationFilePath: getDeclarationEmitFilePath(jsFilePath, options)
};
}
function getDeclarationEmitFilePath(jsFilePath: string, options: CompilerOptions) {
return options.declaration ? removeFileExtension(jsFilePath, getExtensionsToRemoveForEmitPath(options)) + ".d.ts" : undefined;
}
export function hasFile(sourceFiles: SourceFile[], fileName: string) {
return forEach(sourceFiles, file => file.fileName === fileName);
}
export function getSourceFilePathInNewDir(sourceFile: SourceFile, host: EmitHost, newDirPath: string) {
let sourceFilePath = getNormalizedAbsolutePath(sourceFile.fileName, host.getCurrentDirectory());
sourceFilePath = sourceFilePath.replace(host.getCommonSourceDirectory(), "");

View File

@@ -0,0 +1,10 @@
error TS5055: Cannot write file 'tests/cases/compiler/a.d.ts' which is one of the input files.
!!! error TS5055: Cannot write file 'a.d.ts' which is one of the input files.
==== tests/cases/compiler/a.ts (0 errors) ====
class c {
}
==== tests/cases/compiler/a.d.ts (0 errors) ====
declare function isC(): boolean;

View File

@@ -0,0 +1,12 @@
error TS5055: Cannot write file 'tests/cases/compiler/a.js' which is one of the input files.
!!! error TS5055: Cannot write file 'tests/cases/compiler/a.js' which is one of the input files.
==== tests/cases/compiler/a.ts (0 errors) ====
class c {
}
==== tests/cases/compiler/a.js (0 errors) ====
function foo() {
}

View File

@@ -0,0 +1,10 @@
error TS5055: Cannot write file 'tests/cases/compiler/b.d.ts' which is one of the input files.
!!! error TS5055: Cannot write file 'b.d.ts' which is one of the input files.
==== tests/cases/compiler/a.ts (0 errors) ====
class c {
}
==== tests/cases/compiler/b.d.ts (0 errors) ====
declare function foo(): boolean;

View File

@@ -1,7 +1,7 @@
error TS5055: Could not write file 'tests/cases/compiler/b.js' which is one of the input files.
error TS5055: Cannot write file 'tests/cases/compiler/b.js' which is one of the input files.
!!! error TS5055: Could not write file 'tests/cases/compiler/b.js' which is one of the input files.
!!! error TS5055: Cannot write file 'tests/cases/compiler/b.js' which is one of the input files.
==== tests/cases/compiler/a.ts (0 errors) ====
class c {
}

View File

@@ -0,0 +1,7 @@
// @declaration: true
// @filename: a.ts
class c {
}
// @filename: a.d.ts
declare function isC(): boolean;

View File

@@ -0,0 +1,8 @@
// @jsExtensions: js
// @filename: a.ts
class c {
}
// @filename: a.js
function foo() {
}

View File

@@ -0,0 +1,8 @@
// @declaration: true
// @out: tests/cases/compiler/b.js
// @filename: a.ts
class c {
}
// @filename: b.d.ts
declare function foo(): boolean;