Skip to content

Commit 41d1ce6

Browse files
TypeScript Botsheetalkamat
TypeScript Bot
authored andcommitted
Cherry-pick PR #35335 into release-3.7 (#35367)
Component commits: c44de23 Fix compileOnSaveEmit when using source of project reference redirect with --out Fixes #35226 522efb4 Fix typo
1 parent c4d6cec commit 41d1ce6

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
@@ -1010,15 +1010,9 @@ namespace ts {
10101010
return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
10111011
}
10121012

1013-
function isValidSourceFileForEmit(file: SourceFile) {
1014-
// source file is allowed to be emitted and its not source of project reference redirect
1015-
return sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect) &&
1016-
!isSourceOfProjectReferenceRedirect(file.fileName);
1017-
}
1018-
10191013
function getCommonSourceDirectory() {
10201014
if (commonSourceDirectory === undefined) {
1021-
const emittedFiles = filter(files, file => isValidSourceFileForEmit(file));
1015+
const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, program));
10221016
if (options.rootDir && checkSourceFilesBelongToPath(emittedFiles, options.rootDir)) {
10231017
// If a rootDir is specified use it as the commonSourceDirectory
10241018
commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory);
@@ -1477,6 +1471,7 @@ namespace ts {
14771471
getLibFileFromReference: program.getLibFileFromReference,
14781472
isSourceFileFromExternalLibrary,
14791473
getResolvedProjectReferenceToRedirect,
1474+
isSourceOfProjectReferenceRedirect,
14801475
getProbableSymlinks,
14811476
writeFile: writeFileCallback || (
14821477
(fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)),
@@ -2945,7 +2940,7 @@ namespace ts {
29452940
const rootPaths = arrayToSet(rootNames, toPath);
29462941
for (const file of files) {
29472942
// Ignore file that is not emitted
2948-
if (isValidSourceFileForEmit(file) && !rootPaths.has(file.path)) {
2943+
if (sourceFileMayBeEmitted(file, program) && !rootPaths.has(file.path)) {
29492944
addProgramDiagnosticAtRefPath(
29502945
file,
29512946
rootPaths,

src/compiler/types.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5714,13 +5714,19 @@ namespace ts {
57145714
}
57155715

57165716
/* @internal */
5717-
export interface EmitHost extends ScriptReferenceHost, ModuleSpecifierResolutionHost {
5717+
export interface SourceFileMayBeEmittedHost {
5718+
getCompilerOptions(): CompilerOptions;
5719+
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
5720+
getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined;
5721+
isSourceOfProjectReferenceRedirect(fileName: string): boolean;
5722+
}
5723+
5724+
/* @internal */
5725+
export interface EmitHost extends ScriptReferenceHost, ModuleSpecifierResolutionHost, SourceFileMayBeEmittedHost {
57185726
getSourceFiles(): readonly SourceFile[];
57195727
useCaseSensitiveFileNames(): boolean;
57205728
getCurrentDirectory(): string;
57215729

5722-
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
5723-
getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined;
57245730
getLibFileFromReference(ref: FileReference): SourceFile | undefined;
57255731

57265732
getCommonSourceDirectory(): string;

src/compiler/utilities.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3659,34 +3659,36 @@ namespace ts {
36593659
* @param host An EmitHost.
36603660
* @param targetSourceFile An optional target source file to emit.
36613661
*/
3662-
export function getSourceFilesToEmit(host: EmitHost, targetSourceFile?: SourceFile): readonly SourceFile[] {
3662+
export function getSourceFilesToEmit(host: EmitHost, targetSourceFile?: SourceFile, forceDtsEmit?: boolean): readonly SourceFile[] {
36633663
const options = host.getCompilerOptions();
3664-
const isSourceFileFromExternalLibrary = (file: SourceFile) => host.isSourceFileFromExternalLibrary(file);
3665-
const getResolvedProjectReferenceToRedirect = (fileName: string) => host.getResolvedProjectReferenceToRedirect(fileName);
36663664
if (options.outFile || options.out) {
36673665
const moduleKind = getEmitModuleKind(options);
36683666
const moduleEmitEnabled = options.emitDeclarationOnly || moduleKind === ModuleKind.AMD || moduleKind === ModuleKind.System;
36693667
// Can emit only sources that are not declaration file and are either non module code or module with --module or --target es6 specified
3670-
return filter(host.getSourceFiles(), sourceFile =>
3671-
(moduleEmitEnabled || !isExternalModule(sourceFile)) && sourceFileMayBeEmitted(sourceFile, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect));
3668+
return filter(
3669+
host.getSourceFiles(),
3670+
sourceFile =>
3671+
(moduleEmitEnabled || !isExternalModule(sourceFile)) &&
3672+
sourceFileMayBeEmitted(sourceFile, host, forceDtsEmit)
3673+
);
36723674
}
36733675
else {
36743676
const sourceFiles = targetSourceFile === undefined ? host.getSourceFiles() : [targetSourceFile];
3675-
return filter(sourceFiles, sourceFile => sourceFileMayBeEmitted(sourceFile, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect));
3677+
return filter(
3678+
sourceFiles,
3679+
sourceFile => sourceFileMayBeEmitted(sourceFile, host, forceDtsEmit)
3680+
);
36763681
}
36773682
}
36783683

36793684
/** Don't call this for `--outFile`, just for `--outDir` or plain emit. `--outFile` needs additional checks. */
3680-
export function sourceFileMayBeEmitted(
3681-
sourceFile: SourceFile,
3682-
options: CompilerOptions,
3683-
isSourceFileFromExternalLibrary: (file: SourceFile) => boolean,
3684-
getResolvedProjectReferenceToRedirect: (fileName: string) => ResolvedProjectReference | undefined
3685-
) {
3685+
export function sourceFileMayBeEmitted(sourceFile: SourceFile, host: SourceFileMayBeEmittedHost, forceDtsEmit?: boolean) {
3686+
const options = host.getCompilerOptions();
36863687
return !(options.noEmitForJsFiles && isSourceFileJS(sourceFile)) &&
36873688
!sourceFile.isDeclarationFile &&
3688-
!isSourceFileFromExternalLibrary(sourceFile) &&
3689-
!(isJsonSourceFile(sourceFile) && getResolvedProjectReferenceToRedirect(sourceFile.fileName));
3689+
!host.isSourceFileFromExternalLibrary(sourceFile) &&
3690+
!(isJsonSourceFile(sourceFile) && host.getResolvedProjectReferenceToRedirect(sourceFile.fileName)) &&
3691+
(forceDtsEmit || !host.isSourceOfProjectReferenceRedirect(sourceFile.fileName));
36903692
}
36913693

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