Cherry-pick PR #35335 into release-3.7 (#35367)

Component commits:
c44de23315 Fix compileOnSaveEmit when using source of project reference redirect with --out Fixes #35226

522efb4798 Fix typo
This commit is contained in:
TypeScript Bot 2019-11-27 13:47:36 -08:00 committed by Sheetal Nandi
parent c4d6cec389
commit 41d1ce62cd
5 changed files with 110 additions and 27 deletions

View File

@ -23,7 +23,7 @@ namespace ts {
forceDtsEmit = false,
onlyBuildInfo?: boolean,
includeBuildInfo?: boolean) {
const sourceFiles = isArray(sourceFilesOrTargetSourceFile) ? sourceFilesOrTargetSourceFile : getSourceFilesToEmit(host, sourceFilesOrTargetSourceFile);
const sourceFiles = isArray(sourceFilesOrTargetSourceFile) ? sourceFilesOrTargetSourceFile : getSourceFilesToEmit(host, sourceFilesOrTargetSourceFile, forceDtsEmit);
const options = host.getCompilerOptions();
if (options.outFile || options.out) {
const prepends = host.getPrependNodes();
@ -274,7 +274,7 @@ namespace ts {
forEachEmittedFile(
host,
emitSourceFileOrBundle,
getSourceFilesToEmit(host, targetSourceFile),
getSourceFilesToEmit(host, targetSourceFile, forceDtsEmit),
forceDtsEmit,
onlyBuildInfo,
!targetSourceFile
@ -754,6 +754,7 @@ namespace ts {
getLibFileFromReference: notImplemented,
isSourceFileFromExternalLibrary: returnFalse,
getResolvedProjectReferenceToRedirect: returnUndefined,
isSourceOfProjectReferenceRedirect: returnFalse,
writeFile: (name, text, writeByteOrderMark) => {
switch (name) {
case jsFilePath:

View File

@ -1010,15 +1010,9 @@ namespace ts {
return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
}
function isValidSourceFileForEmit(file: SourceFile) {
// source file is allowed to be emitted and its not source of project reference redirect
return sourceFileMayBeEmitted(file, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect) &&
!isSourceOfProjectReferenceRedirect(file.fileName);
}
function getCommonSourceDirectory() {
if (commonSourceDirectory === undefined) {
const emittedFiles = filter(files, file => isValidSourceFileForEmit(file));
const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, program));
if (options.rootDir && checkSourceFilesBelongToPath(emittedFiles, options.rootDir)) {
// If a rootDir is specified use it as the commonSourceDirectory
commonSourceDirectory = getNormalizedAbsolutePath(options.rootDir, currentDirectory);
@ -1477,6 +1471,7 @@ namespace ts {
getLibFileFromReference: program.getLibFileFromReference,
isSourceFileFromExternalLibrary,
getResolvedProjectReferenceToRedirect,
isSourceOfProjectReferenceRedirect,
getProbableSymlinks,
writeFile: writeFileCallback || (
(fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)),
@ -2945,7 +2940,7 @@ namespace ts {
const rootPaths = arrayToSet(rootNames, toPath);
for (const file of files) {
// Ignore file that is not emitted
if (isValidSourceFileForEmit(file) && !rootPaths.has(file.path)) {
if (sourceFileMayBeEmitted(file, program) && !rootPaths.has(file.path)) {
addProgramDiagnosticAtRefPath(
file,
rootPaths,

View File

@ -5714,13 +5714,19 @@ namespace ts {
}
/* @internal */
export interface EmitHost extends ScriptReferenceHost, ModuleSpecifierResolutionHost {
export interface SourceFileMayBeEmittedHost {
getCompilerOptions(): CompilerOptions;
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined;
isSourceOfProjectReferenceRedirect(fileName: string): boolean;
}
/* @internal */
export interface EmitHost extends ScriptReferenceHost, ModuleSpecifierResolutionHost, SourceFileMayBeEmittedHost {
getSourceFiles(): readonly SourceFile[];
useCaseSensitiveFileNames(): boolean;
getCurrentDirectory(): string;
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined;
getLibFileFromReference(ref: FileReference): SourceFile | undefined;
getCommonSourceDirectory(): string;

View File

@ -3659,34 +3659,36 @@ namespace ts {
* @param host An EmitHost.
* @param targetSourceFile An optional target source file to emit.
*/
export function getSourceFilesToEmit(host: EmitHost, targetSourceFile?: SourceFile): readonly SourceFile[] {
export function getSourceFilesToEmit(host: EmitHost, targetSourceFile?: SourceFile, forceDtsEmit?: boolean): readonly SourceFile[] {
const options = host.getCompilerOptions();
const isSourceFileFromExternalLibrary = (file: SourceFile) => host.isSourceFileFromExternalLibrary(file);
const getResolvedProjectReferenceToRedirect = (fileName: string) => host.getResolvedProjectReferenceToRedirect(fileName);
if (options.outFile || options.out) {
const moduleKind = getEmitModuleKind(options);
const moduleEmitEnabled = options.emitDeclarationOnly || moduleKind === ModuleKind.AMD || moduleKind === ModuleKind.System;
// Can emit only sources that are not declaration file and are either non module code or module with --module or --target es6 specified
return filter(host.getSourceFiles(), sourceFile =>
(moduleEmitEnabled || !isExternalModule(sourceFile)) && sourceFileMayBeEmitted(sourceFile, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect));
return filter(
host.getSourceFiles(),
sourceFile =>
(moduleEmitEnabled || !isExternalModule(sourceFile)) &&
sourceFileMayBeEmitted(sourceFile, host, forceDtsEmit)
);
}
else {
const sourceFiles = targetSourceFile === undefined ? host.getSourceFiles() : [targetSourceFile];
return filter(sourceFiles, sourceFile => sourceFileMayBeEmitted(sourceFile, options, isSourceFileFromExternalLibrary, getResolvedProjectReferenceToRedirect));
return filter(
sourceFiles,
sourceFile => sourceFileMayBeEmitted(sourceFile, host, forceDtsEmit)
);
}
}
/** Don't call this for `--outFile`, just for `--outDir` or plain emit. `--outFile` needs additional checks. */
export function sourceFileMayBeEmitted(
sourceFile: SourceFile,
options: CompilerOptions,
isSourceFileFromExternalLibrary: (file: SourceFile) => boolean,
getResolvedProjectReferenceToRedirect: (fileName: string) => ResolvedProjectReference | undefined
) {
export function sourceFileMayBeEmitted(sourceFile: SourceFile, host: SourceFileMayBeEmittedHost, forceDtsEmit?: boolean) {
const options = host.getCompilerOptions();
return !(options.noEmitForJsFiles && isSourceFileJS(sourceFile)) &&
!sourceFile.isDeclarationFile &&
!isSourceFileFromExternalLibrary(sourceFile) &&
!(isJsonSourceFile(sourceFile) && getResolvedProjectReferenceToRedirect(sourceFile.fileName));
!host.isSourceFileFromExternalLibrary(sourceFile) &&
!(isJsonSourceFile(sourceFile) && host.getResolvedProjectReferenceToRedirect(sourceFile.fileName)) &&
(forceDtsEmit || !host.isSourceOfProjectReferenceRedirect(sourceFile.fileName));
}
export function getSourceFilePathInNewDir(fileName: string, host: EmitHost, newDirPath: string): string {

View File

@ -406,4 +406,83 @@ ${appendDts}`
});
});
});
describe("unittests:: tsserver:: with project references and compile on save with external projects", () => {
it("compile on save emits same output as project build", () => {
const tsbaseJson: File = {
path: `${projectRoot}/tsbase.json`,
content: JSON.stringify({
compileOnSave: true,
compilerOptions: {
module: "none",
composite: true
}
})
};
const buttonClass = `${projectRoot}/buttonClass`;
const buttonConfig: File = {
path: `${buttonClass}/tsconfig.json`,
content: JSON.stringify({
extends: "../tsbase.json",
compilerOptions: {
outFile: "Source.js"
},
files: ["Source.ts"]
})
};
const buttonSource: File = {
path: `${buttonClass}/Source.ts`,
content: `module Hmi {
export class Button {
public static myStaticFunction() {
}
}
}`
};
const siblingClass = `${projectRoot}/SiblingClass`;
const siblingConfig: File = {
path: `${siblingClass}/tsconfig.json`,
content: JSON.stringify({
extends: "../tsbase.json",
references: [{
path: "../buttonClass/"
}],
compilerOptions: {
outFile: "Source.js"
},
files: ["Source.ts"]
})
};
const siblingSource: File = {
path: `${siblingClass}/Source.ts`,
content: `module Hmi {
export class Sibling {
public mySiblingFunction() {
}
}
}`
};
const host = createServerHost([libFile, tsbaseJson, buttonConfig, buttonSource, siblingConfig, siblingSource], { useCaseSensitiveFileNames: true });
// ts build should succeed
const solutionBuilder = tscWatch.createSolutionBuilder(host, [siblingConfig.path], {});
solutionBuilder.build();
assert.equal(host.getOutput().length, 0, JSON.stringify(host.getOutput(), /*replacer*/ undefined, " "));
const sourceJs = changeExtension(siblingSource.path, ".js");
const expectedSiblingJs = host.readFile(sourceJs);
const session = createSession(host);
openFilesForSession([siblingSource], session);
session.executeCommandSeq<protocol.CompileOnSaveEmitFileRequest>({
command: protocol.CommandTypes.CompileOnSaveEmitFile,
arguments: {
file: siblingSource.path,
projectFileName: siblingConfig.path
}
});
assert.equal(host.readFile(sourceJs), expectedSiblingJs);
});
});
}