Skip to content

Commit

Permalink
Cherry-pick PR #35335 into release-3.7
Browse files Browse the repository at this point in the history
Component commits:
c44de23 Fix compileOnSaveEmit when using source of project reference redirect with --out Fixes #35226
  • Loading branch information
sheetalkamat authored and typescript-bot committed Nov 26, 2019
1 parent 38a496a commit 4ef96f5
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 27 deletions.
5 changes: 3 additions & 2 deletions src/compiler/emitter.ts
Expand Up @@ -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();
Expand Down Expand Up @@ -274,7 +274,7 @@ namespace ts {
forEachEmittedFile(
host,
emitSourceFileOrBundle,
getSourceFilesToEmit(host, targetSourceFile),
getSourceFilesToEmit(host, targetSourceFile, forceDtsEmit),
forceDtsEmit,
onlyBuildInfo,
!targetSourceFile
Expand Down Expand Up @@ -754,6 +754,7 @@ namespace ts {
getLibFileFromReference: notImplemented,
isSourceFileFromExternalLibrary: returnFalse,
getResolvedProjectReferenceToRedirect: returnUndefined,
isSourceOfProjectReferenceRedirect: returnFalse,
writeFile: (name, text, writeByteOrderMark) => {
switch (name) {
case jsFilePath:
Expand Down
11 changes: 3 additions & 8 deletions src/compiler/program.ts
Expand Up @@ -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);
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -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,
Expand Down
12 changes: 9 additions & 3 deletions src/compiler/types.ts
Expand Up @@ -5714,13 +5714,19 @@ namespace ts {
}

/* @internal */
export interface EmitHost extends ScriptReferenceHost, ModuleSpecifierResolutionHost {
export interface SourceFileMapBeEmittedHost {
getCompilerOptions(): CompilerOptions;
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined;
isSourceOfProjectReferenceRedirect(fileName: string): boolean;
}

/* @internal */
export interface EmitHost extends ScriptReferenceHost, ModuleSpecifierResolutionHost, SourceFileMapBeEmittedHost {
getSourceFiles(): readonly SourceFile[];
useCaseSensitiveFileNames(): boolean;
getCurrentDirectory(): string;

isSourceFileFromExternalLibrary(file: SourceFile): boolean;
getResolvedProjectReferenceToRedirect(fileName: string): ResolvedProjectReference | undefined;
getLibFileFromReference(ref: FileReference): SourceFile | undefined;

getCommonSourceDirectory(): string;
Expand Down
30 changes: 16 additions & 14 deletions src/compiler/utilities.ts
Expand Up @@ -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: SourceFileMapBeEmittedHost, 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 {
Expand Down
79 changes: 79 additions & 0 deletions src/testRunner/unittests/tsserver/projectReferenceCompileOnSave.ts
Expand Up @@ -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);
});
});
}

0 comments on commit 4ef96f5

Please sign in to comment.