diff --git a/src/server/builder.ts b/src/server/builder.ts
index 285ab9ece60..001bd708b2e 100644
--- a/src/server/builder.ts
+++ b/src/server/builder.ts
@@ -3,48 +3,45 @@
///
namespace ts.server {
- export function shouldEmitFile(scriptInfo: ScriptInfo) {
- return !scriptInfo.hasMixedContent;
- }
-
export interface Builder {
/**
* This is the callback when file infos in the builder are updated
*/
- onProjectUpdateGraph(): void;
- getFilesAffectedBy(scriptInfo: ScriptInfo): string[];
- /**
- * @returns {boolean} whether the emit was conducted or not
- */
- emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean;
+ onProgramUpdateGraph(program: Program): void;
+ getFilesAffectedBy(program: Program, path: Path): string[];
+ emitFile(program: Program, path: Path): EmitOutput;
clear(): void;
}
interface EmitHandler {
- addScriptInfo(scriptInfo: ScriptInfo): void;
+ addScriptInfo(program: Program, sourceFile: SourceFile): void;
removeScriptInfo(path: Path): void;
- updateScriptInfo(scriptInfo: ScriptInfo): void;
+ updateScriptInfo(program: Program, sourceFile: SourceFile): void;
/**
* Gets the files affected by the script info which has updated shape from the known one
*/
- getFilesAffectedByUpdatedShape(scriptInfo: ScriptInfo, singleFileResult: string[]): string[];
+ getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[];
}
- export function createBuilder(project: Project): Builder {
+ export function createBuilder(
+ getCanonicalFileName: (fileName: string) => string,
+ getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles?: boolean) => EmitOutput,
+ computeHash: (data: string) => string,
+ shouldEmitFile: (sourceFile: SourceFile) => boolean
+ ): Builder {
let isModuleEmit: boolean | undefined;
- let projectVersionForDependencyGraph: string;
// Last checked shape signature for the file info
let fileInfos: Map;
let emitHandler: EmitHandler;
return {
- onProjectUpdateGraph,
+ onProgramUpdateGraph,
getFilesAffectedBy,
emitFile,
clear
};
- function createProjectGraph() {
- const currentIsModuleEmit = project.getCompilerOptions().module !== ModuleKind.None;
+ function createProgramGraph(program: Program) {
+ const currentIsModuleEmit = program.getCompilerOptions().module !== ModuleKind.None;
if (isModuleEmit !== currentIsModuleEmit) {
isModuleEmit = currentIsModuleEmit;
emitHandler = isModuleEmit ? getModuleEmitHandler() : getNonModuleEmitHandler();
@@ -52,77 +49,55 @@ namespace ts.server {
}
fileInfos = mutateExistingMap(
- fileInfos, arrayToMap(project.getScriptInfos(), info => info.path),
- (_path, info) => {
- emitHandler.addScriptInfo(info);
+ fileInfos, arrayToMap(program.getSourceFiles(), sourceFile => sourceFile.path),
+ (_path, sourceFile) => {
+ emitHandler.addScriptInfo(program, sourceFile);
return "";
},
(path: Path, _value) => emitHandler.removeScriptInfo(path),
/*isSameValue*/ undefined,
/*OnDeleteExistingMismatchValue*/ undefined,
- (_prevValue, scriptInfo) => emitHandler.updateScriptInfo(scriptInfo)
+ (_prevValue, sourceFile) => emitHandler.updateScriptInfo(program, sourceFile)
);
- projectVersionForDependencyGraph = project.getProjectVersion();
}
- function ensureFileInfos() {
+ function ensureProgramGraph(program: Program) {
if (!emitHandler) {
- createProjectGraph();
+ createProgramGraph(program);
}
- Debug.assert(projectVersionForDependencyGraph === project.getProjectVersion());
}
- function onProjectUpdateGraph() {
+ function onProgramUpdateGraph(program: Program) {
if (emitHandler) {
- createProjectGraph();
+ createProgramGraph(program);
}
}
- function getFilesAffectedBy(scriptInfo: ScriptInfo): string[] {
- ensureFileInfos();
+ function getFilesAffectedBy(program: Program, path: Path): string[] {
+ ensureProgramGraph(program);
- const singleFileResult = scriptInfo.hasMixedContent ? [] : [scriptInfo.fileName];
- const path = scriptInfo.path;
- if (!fileInfos || !fileInfos.has(path) || !updateShapeSignature(scriptInfo)) {
+ const sourceFile = program.getSourceFile(path);
+ const singleFileResult = sourceFile && shouldEmitFile(sourceFile) ? [sourceFile.fileName] : [];
+ if (!fileInfos || !fileInfos.has(path) || !updateShapeSignature(program, sourceFile)) {
return singleFileResult;
}
- return emitHandler.getFilesAffectedByUpdatedShape(scriptInfo, singleFileResult);
+ return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile, singleFileResult);
}
- /**
- * @returns {boolean} whether the emit was conducted or not
- */
- function emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean {
- ensureFileInfos();
- if (!fileInfos || !fileInfos.has(scriptInfo.path)) {
- return false;
+ function emitFile(program: Program, path: Path): EmitOutput {
+ ensureProgramGraph(program);
+ if (!fileInfos || !fileInfos.has(path)) {
+ return { outputFiles: [], emitSkipped: true };
}
- const { emitSkipped, outputFiles } = project.getFileEmitOutput(scriptInfo, /*emitOnlyDtsFiles*/ false);
- if (!emitSkipped) {
- const projectRootPath = project.getProjectRootPath();
- for (const outputFile of outputFiles) {
- const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, projectRootPath ? projectRootPath : getDirectoryPath(scriptInfo.fileName));
- writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark);
- }
- }
- return !emitSkipped;
+ return getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false);
}
function clear() {
isModuleEmit = undefined;
emitHandler = undefined;
fileInfos = undefined;
- projectVersionForDependencyGraph = undefined;
- }
-
- function getSourceFile(path: Path) {
- return project.getSourceFile(path);
- }
-
- function getScriptInfo(path: Path) {
- return project.projectService.getScriptInfoForPath(path);
}
function isExternalModuleOrHasOnlyAmbientExternalModules(sourceFile: SourceFile) {
@@ -147,12 +122,8 @@ namespace ts.server {
/**
* @return {boolean} indicates if the shape signature has changed since last update.
*/
- function updateShapeSignature(scriptInfo: ScriptInfo) {
- const path = scriptInfo.path;
- const sourceFile = getSourceFile(path);
- if (!sourceFile) {
- return true;
- }
+ function updateShapeSignature(program: Program, sourceFile: SourceFile) {
+ const path = sourceFile.path;
const prevSignature = fileInfos.get(path);
let latestSignature = prevSignature;
@@ -161,7 +132,7 @@ namespace ts.server {
fileInfos.set(path, latestSignature);
}
else {
- const emitOutput = project.getFileEmitOutput(scriptInfo, /*emitOnlyDtsFiles*/ true);
+ const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true);
if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) {
latestSignature = computeHash(emitOutput.outputFiles[0].text);
fileInfos.set(path, latestSignature);
@@ -171,8 +142,67 @@ namespace ts.server {
return !prevSignature || latestSignature !== prevSignature;
}
- function computeHash(text: string) {
- return project.projectService.host.createHash(text);
+ /**
+ * Gets the referenced files for a file from the program
+ * @param program
+ * @param path
+ */
+ function getReferencedFiles(program: Program, sourceFile: SourceFile): Map {
+ const referencedFiles = createMap();
+ // We need to use a set here since the code can contain the same import twice,
+ // but that will only be one dependency.
+ // To avoid invernal conversion, the key of the referencedFiles map must be of type Path
+ if (sourceFile.imports && sourceFile.imports.length > 0) {
+ const checker: TypeChecker = program.getTypeChecker();
+ for (const importName of sourceFile.imports) {
+ const symbol = checker.getSymbolAtLocation(importName);
+ if (symbol && symbol.declarations && symbol.declarations[0]) {
+ const declarationSourceFile = symbol.declarations[0].getSourceFile();
+ if (declarationSourceFile) {
+ referencedFiles.set(declarationSourceFile.path, true);
+ }
+ }
+ }
+ }
+
+ const sourceFileDirectory = getDirectoryPath(sourceFile.path);
+ // Handle triple slash references
+ if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) {
+ for (const referencedFile of sourceFile.referencedFiles) {
+ const referencedPath = toPath(referencedFile.fileName, sourceFileDirectory, getCanonicalFileName);
+ referencedFiles.set(referencedPath, true);
+ }
+ }
+
+ // Handle type reference directives
+ if (sourceFile.resolvedTypeReferenceDirectiveNames) {
+ sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => {
+ if (!resolvedTypeReferenceDirective) {
+ return;
+ }
+
+ const fileName = resolvedTypeReferenceDirective.resolvedFileName;
+ const typeFilePath = toPath(fileName, sourceFileDirectory, getCanonicalFileName);
+ referencedFiles.set(typeFilePath, true);
+ });
+ }
+
+ return referencedFiles;
+ }
+
+ /**
+ * Gets all the emittable files from the program
+ */
+ function getAllEmittableFiles(program: Program) {
+ const defaultLibraryFileName = getDefaultLibFileName(program.getCompilerOptions());
+ const sourceFiles = program.getSourceFiles();
+ const result: string[] = [];
+ for (const sourceFile of sourceFiles) {
+ if (getBaseFileName(sourceFile.fileName) !== defaultLibraryFileName && shouldEmitFile(sourceFile)) {
+ result.push(sourceFile.fileName);
+ }
+ }
+ return result;
}
function noop() { }
@@ -185,14 +215,14 @@ namespace ts.server {
getFilesAffectedByUpdatedShape
};
- function getFilesAffectedByUpdatedShape(_scriptInfo: ScriptInfo, singleFileResult: string[]): string[] {
- const options = project.getCompilerOptions();
+ function getFilesAffectedByUpdatedShape(program: Program, _sourceFile: SourceFile, singleFileResult: string[]): string[] {
+ const options = program.getCompilerOptions();
// If `--out` or `--outFile` is specified, any new emit will result in re-emitting the entire project,
// so returning the file itself is good enough.
if (options && (options.out || options.outFile)) {
return singleFileResult;
}
- return project.getAllEmittableFiles();
+ return getAllEmittableFiles(program);
}
}
@@ -207,19 +237,22 @@ namespace ts.server {
getFilesAffectedByUpdatedShape
};
- function setReferences(path: Path, latestVersion: string, existingMap: Map) {
- existingMap = mutateExistingMapWithNewSet(existingMap, project.getReferencedFiles(path),
+ function setReferences(program: Program, sourceFile: SourceFile, existingMap: Map) {
+ const path = sourceFile.path;
+ existingMap = mutateExistingMapWithNewSet(
+ existingMap,
+ getReferencedFiles(program, sourceFile),
// Creating new Reference: Also add referenced by
key => { referencedBy.add(key, path); return true; },
// Remove existing reference
(key, _existingValue) => { referencedBy.remove(key, path); }
);
references.set(path, existingMap);
- scriptVersionForReferences.set(path, latestVersion);
+ scriptVersionForReferences.set(path, sourceFile.version);
}
- function addScriptInfo(info: ScriptInfo) {
- setReferences(info.path, info.getLatestVersion(), undefined);
+ function addScriptInfo(program: Program, sourceFile: SourceFile) {
+ setReferences(program, sourceFile, undefined);
}
function removeScriptInfo(path: Path) {
@@ -227,12 +260,11 @@ namespace ts.server {
scriptVersionForReferences.delete(path);
}
- function updateScriptInfo(scriptInfo: ScriptInfo) {
- const path = scriptInfo.path;
+ function updateScriptInfo(program: Program, sourceFile: SourceFile) {
+ const path = sourceFile.path;
const lastUpdatedVersion = scriptVersionForReferences.get(path);
- const latestVersion = scriptInfo.getLatestVersion();
- if (lastUpdatedVersion !== latestVersion) {
- setReferences(path, latestVersion, references.get(path));
+ if (lastUpdatedVersion !== sourceFile.version) {
+ setReferences(program, sourceFile, references.get(path));
}
}
@@ -240,14 +272,12 @@ namespace ts.server {
return referencedBy.get(path) || [];
}
- function getFilesAffectedByUpdatedShape(scriptInfo: ScriptInfo, singleFileResult: string[]): string[] {
- const path = scriptInfo.path;
- const sourceFile = getSourceFile(path);
+ function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] {
if (!isExternalModuleOrHasOnlyAmbientExternalModules(sourceFile)) {
- return project.getAllEmittableFiles();
+ return getAllEmittableFiles(program);
}
- const options = project.getCompilerOptions();
+ const options = program.getCompilerOptions();
if (options && (options.isolatedModules || options.out || options.outFile)) {
return singleFileResult;
}
@@ -256,22 +286,23 @@ namespace ts.server {
// Because if so, its own referencedBy files need to be saved as well to make the
// emitting result consistent with files on disk.
- const fileNamesMap = createMap();
- const setFileName = (path: Path, scriptInfo: ScriptInfo) => {
- fileNamesMap.set(path, scriptInfo && shouldEmitFile(scriptInfo) ? scriptInfo.fileName : undefined);
+ const fileNamesMap = createMap();
+ const setFileName = (path: Path, sourceFile: SourceFile) => {
+ fileNamesMap.set(path, sourceFile && shouldEmitFile(sourceFile) ? sourceFile.fileName : undefined);
};
// Start with the paths this file was referenced by
- setFileName(path, scriptInfo);
+ const path = sourceFile.path;
+ setFileName(path, sourceFile);
const queue = getReferencedByPaths(path).slice();
while (queue.length > 0) {
const currentPath = queue.pop();
if (!fileNamesMap.has(currentPath)) {
- const currentScriptInfo = getScriptInfo(currentPath);
- if (currentScriptInfo && updateShapeSignature(currentScriptInfo)) {
+ const currentSourceFile = program.getSourceFileByPath(currentPath);
+ if (currentSourceFile && updateShapeSignature(program, currentSourceFile)) {
queue.push(...getReferencedByPaths(currentPath));
}
- setFileName(currentPath, currentScriptInfo);
+ setFileName(currentPath, currentSourceFile);
}
}
diff --git a/src/server/project.ts b/src/server/project.ts
index d7c062178c4..851a26a5d4a 100644
--- a/src/server/project.ts
+++ b/src/server/project.ts
@@ -219,7 +219,6 @@ namespace ts.server {
this.disableLanguageService();
}
- this.builder = createBuilder(this);
this.markAsDirty();
}
@@ -247,12 +246,41 @@ namespace ts.server {
return this.languageService;
}
+ private ensureBuilder() {
+ if (!this.builder) {
+ this.builder = createBuilder(
+ this.projectService.toCanonicalFileName,
+ (_program, sourceFile, emitOnlyDts) => this.getFileEmitOutput(sourceFile, emitOnlyDts),
+ data => this.projectService.host.createHash(data),
+ sourceFile => !this.projectService.getScriptInfoForPath(sourceFile.path).hasMixedContent
+ );
+ }
+ }
+
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] {
if (!this.languageServiceEnabled) {
return [];
}
this.updateGraph();
- return this.builder.getFilesAffectedBy(scriptInfo);
+ this.ensureBuilder();
+ return this.builder.getFilesAffectedBy(this.program, scriptInfo.path);
+ }
+
+ /**
+ * Returns true if emit was conducted
+ */
+ emitFile(scriptInfo: ScriptInfo, writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void): boolean {
+ this.ensureBuilder();
+ const { emitSkipped, outputFiles } = this.builder.emitFile(this.program, scriptInfo.path);
+ if (!emitSkipped) {
+ const projectRootPath = this.getProjectRootPath();
+ for (const outputFile of outputFiles) {
+ const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, projectRootPath ? projectRootPath : getDirectoryPath(scriptInfo.fileName));
+ writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark);
+ }
+ }
+
+ return !emitSkipped;
}
getProjectVersion() {
@@ -390,11 +418,11 @@ namespace ts.server {
});
}
- getFileEmitOutput(info: ScriptInfo, emitOnlyDtsFiles: boolean) {
+ private getFileEmitOutput(sourceFile: SourceFile, emitOnlyDtsFiles: boolean) {
if (!this.languageServiceEnabled) {
return undefined;
}
- return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles);
+ return this.getLanguageService().getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles);
}
getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) {
@@ -453,21 +481,6 @@ namespace ts.server {
return false;
}
- getAllEmittableFiles() {
- if (!this.languageServiceEnabled) {
- return [];
- }
- const defaultLibraryFileName = getDefaultLibFileName(this.compilerOptions);
- const infos = this.getScriptInfos();
- const result: string[] = [];
- for (const info of infos) {
- if (getBaseFileName(info.fileName) !== defaultLibraryFileName && shouldEmitFile(info)) {
- result.push(info.fileName);
- }
- }
- return result;
- }
-
containsScriptInfo(info: ScriptInfo): boolean {
return this.isRoot(info) || (this.program && this.program.getSourceFileByPath(info.path) !== undefined);
}
@@ -594,11 +607,13 @@ namespace ts.server {
// update builder only if language service is enabled
// otherwise tell it to drop its internal state
- if (this.languageServiceEnabled && this.compileOnSaveEnabled) {
- this.builder.onProjectUpdateGraph();
- }
- else {
- this.builder.clear();
+ if (this.builder) {
+ if (this.languageServiceEnabled && this.compileOnSaveEnabled) {
+ this.builder.onProgramUpdateGraph(this.program);
+ }
+ else {
+ this.builder.clear();
+ }
}
if (hasChanges) {
diff --git a/src/server/session.ts b/src/server/session.ts
index 1e851d09615..31cb8c1e15d 100644
--- a/src/server/session.ts
+++ b/src/server/session.ts
@@ -1223,7 +1223,7 @@ namespace ts.server {
return false;
}
const scriptInfo = project.getScriptInfo(file);
- return project.builder.emitFile(scriptInfo, (path, data, writeByteOrderMark) => this.host.writeFile(path, data, writeByteOrderMark));
+ return project.emitFile(scriptInfo, (path, data, writeByteOrderMark) => this.host.writeFile(path, data, writeByteOrderMark));
}
private getSignatureHelpItems(args: protocol.SignatureHelpRequestArgs, simplifiedResult: boolean): protocol.SignatureHelpItems | SignatureHelpItems {