Skip to content

Commit c44de23

Browse files
committed
Fix compileOnSaveEmit when using source of project reference redirect with --out
Fixes #35226
1 parent ea4e6b3 commit c44de23

File tree

5 files changed

+110
-27
lines changed

5 files changed

+110
-27
lines changed

src/compiler/emitter.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ namespace ts {
2323
forceDtsEmit = false,
2424
onlyBuildInfo?: boolean,
2525
includeBuildInfo?: boolean) {
26-
const sourceFiles = isArray(sourceFilesOrTargetSourceFile) ? sourceFilesOrTargetSourceFile : getSourceFilesToEmit(host, sourceFilesOrTargetSourceFile);
26+
const sourceFiles = isArray(sourceFilesOrTargetSourceFile) ? sourceFilesOrTargetSourceFile : getSourceFilesToEmit(host, sourceFilesOrTargetSourceFile, forceDtsEmit);
2727
const options = host.getCompilerOptions();
2828
if (options.outFile || options.out) {
2929
const prepends = host.getPrependNodes();
@@ -274,7 +274,7 @@ namespace ts {
274274
forEachEmittedFile(
275275
host,
276276
emitSourceFileOrBundle,
277-
getSourceFilesToEmit(host, targetSourceFile),
277+
getSourceFilesToEmit(host, targetSourceFile, forceDtsEmit),
278278
forceDtsEmit,
279279
onlyBuildInfo,
280280
!targetSourceFile
@@ -754,6 +754,7 @@ namespace ts {
754754
getLibFileFromReference: notImplemented,
755755
isSourceFileFromExternalLibrary: returnFalse,
756756
getResolvedProjectReferenceToRedirect: returnUndefined,
757+
isSourceOfProjectReferenceRedirect: returnFalse,
757758
writeFile: (name, text, writeByteOrderMark) => {
758759
switch (name) {
759760
case jsFilePath:

src/compiler/program.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,15 +1004,9 @@ namespace ts {
10041004
return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
10051005
}
10061006

1007-
function isValidSourceFileForEmit(file: SourceFile) {
1008-
// source file is allowed to be emitted and its not source of project reference redirect
1009-
return sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect) &&
1010-
!isSourceOfProjectReferenceRedirect(file.fileName);
1011-
}
1012-
10131007
function getCommonSourceDirectory() {
10141008
if (commonSourceDirectory === undefined) {
1015-
const emittedFiles = filter(files, file => isValidSourceFileForEmit(file));
1009+
const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, program));
10161010
if (options.rootDir && checkSourceFilesBelongToPath(emittedFiles, options.rootDir)) {
10171011
// If a rootDir is specified use it as the commonSourceDirectory
10181012
commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory);
@@ -1471,6 +1465,7 @@ namespace ts {
14711465
getLibFileFromReference: program.getLibFileFromReference,
14721466
isSourceFileFromExternalLibrary,
14731467
getResolvedProjectReferenceToRedirect,
1468+
isSourceOfProjectReferenceRedirect,
14741469
getProbableSymlinks,
14751470
writeFile: writeFileCallback || (
14761471
(fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)),
@@ -2953,7 +2948,7 @@ namespace ts {
29532948
const rootPaths = arrayToSet(rootNames, toPath);
29542949
for (const file of files) {
29552950
// Ignore file that is not emitted
2956-
if (isValidSourceFileForEmit(file) && !rootPaths.has(file.path)) {
2951+
if (sourceFileMayBeEmitted(file, program) && !rootPaths.has(file.path)) {
29572952
addProgramDiagnosticAtRefPath(
29582953
file,
29592954
rootPaths,

src/compiler/types.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5719,13 +5719,19 @@ namespace ts {
57195719
}
57205720

57215721
/* @internal */
5722-
export interface EmitHost extends ScriptReferenceHost, ModuleSpecifierResolutionHost {
5722+
export interface SourceFileMapBeEmittedHost {
5723+
getCompilerOptions(): CompilerOptions;
5724+
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
5725+
getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined;
5726+
isSourceOfProjectReferenceRedirect(fileName: string): boolean;
5727+
}
5728+
5729+
/* @internal */
5730+
export interface EmitHost extends ScriptReferenceHost, ModuleSpecifierResolutionHost, SourceFileMapBeEmittedHost {
57235731
getSourceFiles(): readonly SourceFile[];
57245732
useCaseSensitiveFileNames(): boolean;
57255733
getCurrentDirectory(): string;
57265734

5727-
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
5728-
getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined;
57295735
getLibFileFromReference(ref: FileReference): SourceFile | undefined;
57305736

57315737
getCommonSourceDirectory(): string;

src/compiler/utilities.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3656,34 +3656,36 @@ namespace ts {
36563656
* @param host An EmitHost.
36573657
* @param targetSourceFile An optional target source file to emit.
36583658
*/
3659-
export function getSourceFilesToEmit(host: EmitHost, targetSourceFile?: SourceFile): readonly SourceFile[] {
3659+
export function getSourceFilesToEmit(host: EmitHost, targetSourceFile?: SourceFile, forceDtsEmit?: boolean): readonly SourceFile[] {
36603660
const options = host.getCompilerOptions();
3661-
const isSourceFileFromExternalLibrary = (file: SourceFile) => host.isSourceFileFromExternalLibrary(file);
3662-
const getResolvedProjectReferenceToRedirect = (fileName: string) => host.getResolvedProjectReferenceToRedirect(fileName);
36633661
if (options.outFile || options.out) {
36643662
const moduleKind = getEmitModuleKind(options);
36653663
const moduleEmitEnabled = options.emitDeclarationOnly || moduleKind === ModuleKind.AMD || moduleKind === ModuleKind.System;
36663664
// Can emit only sources that are not declaration file and are either non module code or module with --module or --target es6 specified
3667-
return filter(host.getSourceFiles(), sourceFile =>
3668-
(moduleEmitEnabled || !isExternalModule(sourceFile)) && sourceFileMayBeEmitted(sourceFile, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect));
3665+
return filter(
3666+
host.getSourceFiles(),
3667+
sourceFile =>
3668+
(moduleEmitEnabled || !isExternalModule(sourceFile)) &&
3669+
sourceFileMayBeEmitted(sourceFile, host, forceDtsEmit)
3670+
);
36693671
}
36703672
else {
36713673
const sourceFiles = targetSourceFile === undefined ? host.getSourceFiles() : [targetSourceFile];
3672-
return filter(sourceFiles, sourceFile => sourceFileMayBeEmitted(sourceFile, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect));
3674+
return filter(
3675+
sourceFiles,
3676+
sourceFile => sourceFileMayBeEmitted(sourceFile, host, forceDtsEmit)
3677+
);
36733678
}
36743679
}
36753680

36763681
/** Don't call this for `--outFile`, just for `--outDir` or plain emit. `--outFile` needs additional checks. */
3677-
export function sourceFileMayBeEmitted(
3678-
sourceFile: SourceFile,
3679-
options: CompilerOptions,
3680-
isSourceFileFromExternalLibrary: (file: SourceFile) => boolean,
3681-
getResolvedProjectReferenceToRedirect: (fileName: string) => ResolvedProjectReference | undefined
3682-
) {
3682+
export function sourceFileMayBeEmitted(sourceFile: SourceFile, host: SourceFileMapBeEmittedHost, forceDtsEmit?: boolean) {
3683+
const options = host.getCompilerOptions();
36833684
return !(options.noEmitForJsFiles && isSourceFileJS(sourceFile)) &&
36843685
!sourceFile.isDeclarationFile &&
3685-
!isSourceFileFromExternalLibrary(sourceFile) &&
3686-
!(isJsonSourceFile(sourceFile) && getResolvedProjectReferenceToRedirect(sourceFile.fileName));
3686+
!host.isSourceFileFromExternalLibrary(sourceFile) &&
3687+
!(isJsonSourceFile(sourceFile) && host.getResolvedProjectReferenceToRedirect(sourceFile.fileName)) &&
3688+
(forceDtsEmit || !host.isSourceOfProjectReferenceRedirect(sourceFile.fileName));
36873689
}
36883690

36893691
export function getSourceFilePathInNewDir(fileName: string, host: EmitHost, newDirPath: string): string {

src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,4 +406,83 @@ ${appendDts}`
406406
});
407407
});
408408
});
409+
410+
describe("unittests:: tsserver:: with project references and compile on save with external projects", () => {
411+
it("compile on save emits same output as project build", () => {
412+
const tsbaseJson: File = {
413+
path: `${projectRoot}/tsbase.json`,
414+
content: JSON.stringify({
415+
compileOnSave: true,
416+
compilerOptions: {
417+
module: "none",
418+
composite: true
419+
}
420+
})
421+
};
422+
const buttonClass = `${projectRoot}/buttonClass`;
423+
const buttonConfig: File = {
424+
path: `${buttonClass}/tsconfig.json`,
425+
content: JSON.stringify({
426+
extends: "../tsbase.json",
427+
compilerOptions: {
428+
outFile: "Source.js"
429+
},
430+
files: ["Source.ts"]
431+
})
432+
};
433+
const buttonSource: File = {
434+
path: `${buttonClass}/Source.ts`,
435+
content: `module Hmi {
436+
export class Button {
437+
public static myStaticFunction() {
438+
}
439+
}
440+
}`
441+
};
442+
443+
const siblingClass = `${projectRoot}/SiblingClass`;
444+
const siblingConfig: File = {
445+
path: `${siblingClass}/tsconfig.json`,
446+
content: JSON.stringify({
447+
extends: "../tsbase.json",
448+
references: [{
449+
path: "../buttonClass/"
450+
}],
451+
compilerOptions: {
452+
outFile: "Source.js"
453+
},
454+
files: ["Source.ts"]
455+
})
456+
};
457+
const siblingSource: File = {
458+
path: `${siblingClass}/Source.ts`,
459+
content: `module Hmi {
460+
export class Sibling {
461+
public mySiblingFunction() {
462+
}
463+
}
464+
}`
465+
};
466+
const host = createServerHost([libFile, tsbaseJson, buttonConfig, buttonSource, siblingConfig, siblingSource], { useCaseSensitiveFileNames: true });
467+
468+
// ts build should succeed
469+
const solutionBuilder = tscWatch.createSolutionBuilder(host, [siblingConfig.path], {});
470+
solutionBuilder.build();
471+
assert.equal(host.getOutput().length, 0, JSON.stringify(host.getOutput(), /*replacer*/ undefined, " "));
472+
const sourceJs = changeExtension(siblingSource.path, ".js");
473+
const expectedSiblingJs = host.readFile(sourceJs);
474+
475+
const session = createSession(host);
476+
openFilesForSession([siblingSource], session);
477+
478+
session.executeCommandSeq<protocol.CompileOnSaveEmitFileRequest>({
479+
command: protocol.CommandTypes.CompileOnSaveEmitFile,
480+
arguments: {
481+
file: siblingSource.path,
482+
projectFileName: siblingConfig.path
483+
}
484+
});
485+
assert.equal(host.readFile(sourceJs), expectedSiblingJs);
486+
});
487+
});
409488
}

0 commit comments

Comments
 (0)