Skip to content

Commit d02531f

Browse files
authored
Fix compileOnSaveEmit when using source of project reference redirect with --out (#35335)
* Fix compileOnSaveEmit when using source of project reference redirect with --out Fixes #35226 * Fix typo
1 parent c447ebc commit d02531f

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 SourceFileMayBeEmittedHost {
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, SourceFileMayBeEmittedHost {
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
@@ -3643,34 +3643,36 @@ namespace ts {
36433643
* @param host An EmitHost.
36443644
* @param targetSourceFile An optional target source file to emit.
36453645
*/
3646-
export function getSourceFilesToEmit(host: EmitHost, targetSourceFile?: SourceFile): readonly SourceFile[] {
3646+
export function getSourceFilesToEmit(host: EmitHost, targetSourceFile?: SourceFile, forceDtsEmit?: boolean): readonly SourceFile[] {
36473647
const options = host.getCompilerOptions();
3648-
const isSourceFileFromExternalLibrary = (file: SourceFile) => host.isSourceFileFromExternalLibrary(file);
3649-
const getResolvedProjectReferenceToRedirect = (fileName: string) => host.getResolvedProjectReferenceToRedirect(fileName);
36503648
if (options.outFile || options.out) {
36513649
const moduleKind = getEmitModuleKind(options);
36523650
const moduleEmitEnabled = options.emitDeclarationOnly || moduleKind === ModuleKind.AMD || moduleKind === ModuleKind.System;
36533651
// Can emit only sources that are not declaration file and are either non module code or module with --module or --target es6 specified
3654-
return filter(host.getSourceFiles(), sourceFile =>
3655-
(moduleEmitEnabled || !isExternalModule(sourceFile)) && sourceFileMayBeEmitted(sourceFile, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect));
3652+
return filter(
3653+
host.getSourceFiles(),
3654+
sourceFile =>
3655+
(moduleEmitEnabled || !isExternalModule(sourceFile)) &&
3656+
sourceFileMayBeEmitted(sourceFile, host, forceDtsEmit)
3657+
);
36563658
}
36573659
else {
36583660
const sourceFiles = targetSourceFile === undefined ? host.getSourceFiles() : [targetSourceFile];
3659-
return filter(sourceFiles, sourceFile => sourceFileMayBeEmitted(sourceFile, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect));
3661+
return filter(
3662+
sourceFiles,
3663+
sourceFile => sourceFileMayBeEmitted(sourceFile, host, forceDtsEmit)
3664+
);
36603665
}
36613666
}
36623667

36633668
/** Don't call this for `--outFile`, just for `--outDir` or plain emit. `--outFile` needs additional checks. */
3664-
export function sourceFileMayBeEmitted(
3665-
sourceFile: SourceFile,
3666-
options: CompilerOptions,
3667-
isSourceFileFromExternalLibrary: (file: SourceFile) => boolean,
3668-
getResolvedProjectReferenceToRedirect: (fileName: string) => ResolvedProjectReference | undefined
3669-
) {
3669+
export function sourceFileMayBeEmitted(sourceFile: SourceFile, host: SourceFileMayBeEmittedHost, forceDtsEmit?: boolean) {
3670+
const options = host.getCompilerOptions();
36703671
return !(options.noEmitForJsFiles && isSourceFileJS(sourceFile)) &&
36713672
!sourceFile.isDeclarationFile &&
3672-
!isSourceFileFromExternalLibrary(sourceFile) &&
3673-
!(isJsonSourceFile(sourceFile) && getResolvedProjectReferenceToRedirect(sourceFile.fileName));
3673+
!host.isSourceFileFromExternalLibrary(sourceFile) &&
3674+
!(isJsonSourceFile(sourceFile) && host.getResolvedProjectReferenceToRedirect(sourceFile.fileName)) &&
3675+
(forceDtsEmit || !host.isSourceOfProjectReferenceRedirect(sourceFile.fileName));
36743676
}
36753677

36763678
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)