mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-20 13:45:34 -05:00
Verify the emit file name is unique and doesnt overwrite input file
Fixes #4424
This commit is contained in:
@@ -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[]) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(), "");
|
||||
|
||||
@@ -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;
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// @declaration: true
|
||||
// @filename: a.ts
|
||||
class c {
|
||||
}
|
||||
|
||||
// @filename: a.d.ts
|
||||
declare function isC(): boolean;
|
||||
@@ -0,0 +1,8 @@
|
||||
// @jsExtensions: js
|
||||
// @filename: a.ts
|
||||
class c {
|
||||
}
|
||||
|
||||
// @filename: a.js
|
||||
function foo() {
|
||||
}
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user