2016-08-24 01:11:52 +02:00
|
|
|
|
/// <reference path="..\services\services.ts" />
|
2016-06-23 01:51:09 +02:00
|
|
|
|
/// <reference path="utilities.ts"/>
|
2016-06-22 02:31:54 +02:00
|
|
|
|
/// <reference path="scriptInfo.ts"/>
|
2016-06-25 02:27:18 +02:00
|
|
|
|
/// <reference path="lsHost.ts"/>
|
2016-08-12 20:04:43 +02:00
|
|
|
|
/// <reference path="typingsCache.ts"/>
|
2016-08-24 01:11:52 +02:00
|
|
|
|
/// <reference path="builder.ts"/>
|
2016-06-22 02:31:54 +02:00
|
|
|
|
|
|
|
|
|
namespace ts.server {
|
2016-06-24 23:30:45 +02:00
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
|
export enum ProjectKind {
|
|
|
|
|
Inferred,
|
|
|
|
|
Configured,
|
|
|
|
|
External
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-24 23:55:12 +02:00
|
|
|
|
function remove<T>(items: T[], item: T) {
|
2016-06-24 23:30:45 +02:00
|
|
|
|
const index = items.indexOf(item);
|
|
|
|
|
if (index >= 0) {
|
|
|
|
|
items.splice(index, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-06-24 23:55:12 +02:00
|
|
|
|
|
2016-08-24 00:15:12 +02:00
|
|
|
|
const jsOrDts = [".js", ".d.ts"];
|
|
|
|
|
|
2016-08-24 22:37:03 +02:00
|
|
|
|
export function allRootFilesAreJsOrDts(project: Project): boolean {
|
|
|
|
|
return project.getRootScriptInfos().every(f => fileExtensionIsAny(f.fileName, jsOrDts));
|
2016-08-24 00:15:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-26 23:37:49 +02:00
|
|
|
|
export interface ProjectFilesWithTSDiagnostics extends protocol.ProjectFiles {
|
|
|
|
|
projectErrors: Diagnostic[];
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
|
export abstract class Project {
|
2016-06-28 00:29:04 +02:00
|
|
|
|
private rootFiles: ScriptInfo[] = [];
|
|
|
|
|
private rootFilesMap: FileMap<ScriptInfo> = createFileMap<ScriptInfo>();
|
2016-06-22 02:31:54 +02:00
|
|
|
|
private lsHost: ServerLanguageServiceHost;
|
2016-06-23 01:51:09 +02:00
|
|
|
|
private program: ts.Program;
|
2016-06-22 02:31:54 +02:00
|
|
|
|
|
2016-07-23 01:01:54 +02:00
|
|
|
|
private languageService: LanguageService;
|
2016-08-24 01:11:52 +02:00
|
|
|
|
builder: Builder;
|
2016-06-23 01:51:09 +02:00
|
|
|
|
/**
|
|
|
|
|
* Set of files that was returned from the last call to getChangesSinceVersion.
|
|
|
|
|
*/
|
|
|
|
|
private lastReportedFileNames: Map<string>;
|
|
|
|
|
/**
|
|
|
|
|
* Last version that was reported.
|
|
|
|
|
*/
|
|
|
|
|
private lastReportedVersion = 0;
|
|
|
|
|
/**
|
|
|
|
|
* Current project structure version.
|
|
|
|
|
* This property is changed in 'updateGraph' based on the set of files in program
|
|
|
|
|
*/
|
|
|
|
|
private projectStructureVersion = 0;
|
|
|
|
|
/**
|
|
|
|
|
* Current version of the project state. It is changed when:
|
|
|
|
|
* - new root file was added/removed
|
|
|
|
|
* - edit happen in some file that is currently included in the project.
|
|
|
|
|
* This property is different from projectStructureVersion since in most cases edits don't affect set of files in the project
|
|
|
|
|
*/
|
|
|
|
|
private projectStateVersion = 0;
|
|
|
|
|
|
2016-08-18 01:42:05 +02:00
|
|
|
|
private typingFiles: TypingsArray;
|
2016-08-12 20:04:43 +02:00
|
|
|
|
|
2016-08-26 23:37:49 +02:00
|
|
|
|
protected projectErrors: Diagnostic[];
|
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
|
constructor(
|
|
|
|
|
readonly projectKind: ProjectKind,
|
|
|
|
|
readonly projectService: ProjectService,
|
|
|
|
|
private documentRegistry: ts.DocumentRegistry,
|
|
|
|
|
hasExplicitListOfFiles: boolean,
|
|
|
|
|
public languageServiceEnabled: boolean,
|
2016-08-24 01:11:52 +02:00
|
|
|
|
private compilerOptions: CompilerOptions,
|
|
|
|
|
public compileOnSaveEnabled: boolean) {
|
2016-06-22 02:31:54 +02:00
|
|
|
|
|
|
|
|
|
if (!this.compilerOptions) {
|
|
|
|
|
this.compilerOptions = ts.getDefaultCompilerOptions();
|
|
|
|
|
this.compilerOptions.allowNonTsExtensions = true;
|
|
|
|
|
this.compilerOptions.allowJs = true;
|
|
|
|
|
}
|
|
|
|
|
else if (hasExplicitListOfFiles) {
|
|
|
|
|
// If files are listed explicitly, allow all extensions
|
|
|
|
|
this.compilerOptions.allowNonTsExtensions = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (languageServiceEnabled) {
|
|
|
|
|
this.enableLanguageService();
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
this.disableLanguageService();
|
|
|
|
|
}
|
2016-08-24 01:11:52 +02:00
|
|
|
|
|
|
|
|
|
this.builder = createBuilder(this);
|
2016-06-22 02:31:54 +02:00
|
|
|
|
this.markAsDirty();
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-26 23:37:49 +02:00
|
|
|
|
getProjectErrors() {
|
|
|
|
|
return this.projectErrors;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-12 20:04:43 +02:00
|
|
|
|
getLanguageService(ensureSynchronized = true): LanguageService {
|
2016-07-23 01:01:54 +02:00
|
|
|
|
if (ensureSynchronized) {
|
|
|
|
|
this.updateGraph();
|
|
|
|
|
}
|
|
|
|
|
return this.languageService;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-24 01:11:52 +02:00
|
|
|
|
getCompileOnSaveAffectedFileList(scriptInfo: ScriptInfo): string[] {
|
|
|
|
|
if (!this.languageServiceEnabled) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
this.updateGraph();
|
|
|
|
|
return this.builder.getFilesAffectedBy(scriptInfo);
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
|
getProjectVersion() {
|
2016-06-23 01:51:09 +02:00
|
|
|
|
return this.projectStateVersion.toString();
|
2016-06-22 02:31:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enableLanguageService() {
|
|
|
|
|
const lsHost = new LSHost(this.projectService.host, this, this.projectService.cancellationToken);
|
|
|
|
|
lsHost.setCompilationSettings(this.compilerOptions);
|
|
|
|
|
this.languageService = ts.createLanguageService(lsHost, this.documentRegistry);
|
|
|
|
|
|
|
|
|
|
this.lsHost = lsHost;
|
|
|
|
|
this.languageServiceEnabled = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
disableLanguageService() {
|
|
|
|
|
this.languageService = nullLanguageService;
|
|
|
|
|
this.lsHost = nullLanguageServiceHost;
|
|
|
|
|
this.languageServiceEnabled = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
abstract getProjectName(): string;
|
2016-08-24 00:15:12 +02:00
|
|
|
|
abstract getTypingOptions(): TypingOptions;
|
2016-06-22 02:31:54 +02:00
|
|
|
|
|
2016-08-24 01:11:52 +02:00
|
|
|
|
getSourceFile(path: Path) {
|
|
|
|
|
if (!this.program) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
return this.program.getSourceFileByPath(path);
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
|
close() {
|
2016-06-28 00:29:04 +02:00
|
|
|
|
if (this.program) {
|
|
|
|
|
// if we have a program - release all files that are enlisted in program
|
|
|
|
|
for (const f of this.program.getSourceFiles()) {
|
|
|
|
|
const info = this.projectService.getScriptInfo(f.fileName);
|
|
|
|
|
info.detachFromProject(this);
|
|
|
|
|
}
|
2016-06-22 02:31:54 +02:00
|
|
|
|
}
|
2016-06-28 00:29:04 +02:00
|
|
|
|
else {
|
|
|
|
|
// release all root files
|
|
|
|
|
for (const root of this.rootFiles) {
|
|
|
|
|
root.detachFromProject(this);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.rootFiles = undefined;
|
|
|
|
|
this.rootFilesMap = undefined;
|
|
|
|
|
this.program = undefined;
|
|
|
|
|
|
|
|
|
|
// signal language service to release source files acquired from document registry
|
2016-06-22 02:31:54 +02:00
|
|
|
|
this.languageService.dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getCompilerOptions() {
|
|
|
|
|
return this.compilerOptions;
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-27 23:25:43 +02:00
|
|
|
|
hasRoots() {
|
2016-06-29 02:45:29 +02:00
|
|
|
|
return this.rootFiles && this.rootFiles.length > 0;
|
2016-06-27 23:25:43 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
|
getRootFiles() {
|
2016-06-29 02:45:29 +02:00
|
|
|
|
return this.rootFiles && this.rootFiles.map(info => info.fileName);
|
2016-06-22 02:31:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-12 20:04:43 +02:00
|
|
|
|
getRootFilesLSHost() {
|
|
|
|
|
const result: string[] = [];
|
|
|
|
|
if (this.rootFiles) {
|
|
|
|
|
for (const f of this.rootFiles) {
|
|
|
|
|
result.push(f.fileName);
|
|
|
|
|
}
|
|
|
|
|
if (this.typingFiles) {
|
|
|
|
|
for (const f of this.typingFiles) {
|
|
|
|
|
result.push(f);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-06 00:51:39 +02:00
|
|
|
|
getRootScriptInfos() {
|
|
|
|
|
return this.rootFiles;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-24 01:11:52 +02:00
|
|
|
|
getScriptInfos() {
|
|
|
|
|
return map(this.program.getSourceFiles(), sourceFile => this.getScriptInfoLSHost(sourceFile.path));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getFileEmitOutput(info: ScriptInfo, emitOnlyDtsFiles: boolean) {
|
|
|
|
|
if (!this.languageServiceEnabled) {
|
|
|
|
|
return undefined;
|
|
|
|
|
}
|
|
|
|
|
return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles);
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
|
getFileNames() {
|
2016-06-23 01:51:09 +02:00
|
|
|
|
if (!this.program) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
|
if (!this.languageServiceEnabled) {
|
|
|
|
|
// if language service is disabled assume that all files in program are root files + default library
|
|
|
|
|
let rootFiles = this.getRootFiles();
|
|
|
|
|
if (this.compilerOptions) {
|
|
|
|
|
const defaultLibrary = getDefaultLibFilePath(this.compilerOptions);
|
|
|
|
|
if (defaultLibrary) {
|
|
|
|
|
(rootFiles || (rootFiles = [])).push(asNormalizedPath(defaultLibrary));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return rootFiles;
|
|
|
|
|
}
|
|
|
|
|
const sourceFiles = this.program.getSourceFiles();
|
|
|
|
|
return sourceFiles.map(sourceFile => asNormalizedPath(sourceFile.fileName));
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-24 01:11:52 +02:00
|
|
|
|
getFileNamesWithoutDefaultLib() {
|
|
|
|
|
if (!this.languageServiceEnabled) {
|
|
|
|
|
return this.getRootFiles();
|
|
|
|
|
}
|
|
|
|
|
const defaultLibraryFileName = getDefaultLibFileName(this.compilerOptions);
|
|
|
|
|
return filter(this.getFileNames(), file => getBaseFileName(file) !== defaultLibraryFileName);
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
|
containsScriptInfo(info: ScriptInfo): boolean {
|
2016-06-28 00:29:04 +02:00
|
|
|
|
return this.isRoot(info) || (this.program && this.program.getSourceFileByPath(info.path) !== undefined);
|
2016-06-22 02:31:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
containsFile(filename: NormalizedPath, requireOpen?: boolean) {
|
|
|
|
|
const info = this.projectService.getScriptInfoForNormalizedPath(filename);
|
2016-06-24 23:30:45 +02:00
|
|
|
|
if (info && (info.isOpen || !requireOpen)) {
|
|
|
|
|
return this.containsScriptInfo(info);
|
2016-06-22 02:31:54 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isRoot(info: ScriptInfo) {
|
2016-06-29 02:45:29 +02:00
|
|
|
|
return this.rootFilesMap && this.rootFilesMap.contains(info.path);
|
2016-06-22 02:31:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// add a root file to project
|
|
|
|
|
addRoot(info: ScriptInfo) {
|
|
|
|
|
if (!this.isRoot(info)) {
|
|
|
|
|
this.rootFiles.push(info);
|
|
|
|
|
this.rootFilesMap.set(info.path, info);
|
|
|
|
|
info.attachToProject(this);
|
2016-06-24 23:55:12 +02:00
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
|
this.markAsDirty();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-24 23:55:12 +02:00
|
|
|
|
removeFile(info: ScriptInfo, detachFromProject = true) {
|
2016-06-24 23:30:45 +02:00
|
|
|
|
this.removeRootFileIfNecessary(info);
|
|
|
|
|
this.lsHost.notifyFileRemoved(info);
|
|
|
|
|
|
2016-06-23 01:51:09 +02:00
|
|
|
|
if (detachFromProject) {
|
|
|
|
|
info.detachFromProject(this);
|
|
|
|
|
}
|
2016-06-24 23:30:45 +02:00
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
|
this.markAsDirty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
markAsDirty() {
|
2016-06-23 01:51:09 +02:00
|
|
|
|
this.projectStateVersion++;
|
2016-06-22 02:31:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-06-30 22:23:55 +02:00
|
|
|
|
/**
|
|
|
|
|
* Updates set of files that contribute to this project
|
|
|
|
|
* @returns: true if set of files in the project stays the same and false - otherwise.
|
|
|
|
|
*/
|
2016-06-29 02:45:29 +02:00
|
|
|
|
updateGraph(): boolean {
|
2016-06-23 01:51:09 +02:00
|
|
|
|
if (!this.languageServiceEnabled) {
|
2016-06-29 02:45:29 +02:00
|
|
|
|
return true;
|
2016-06-23 01:51:09 +02:00
|
|
|
|
}
|
2016-08-18 01:42:05 +02:00
|
|
|
|
let hasChanges = this.updateGraphWorker();
|
|
|
|
|
const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this);
|
|
|
|
|
if (this.setTypings(cachedTypings)) {
|
|
|
|
|
hasChanges = this.updateGraphWorker() || hasChanges;
|
|
|
|
|
}
|
2016-08-12 20:04:43 +02:00
|
|
|
|
if (hasChanges) {
|
|
|
|
|
this.projectStructureVersion++;
|
|
|
|
|
}
|
2016-08-12 23:01:23 +02:00
|
|
|
|
return !hasChanges;
|
2016-08-12 20:04:43 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-18 01:42:05 +02:00
|
|
|
|
private setTypings(typings: TypingsArray): boolean {
|
|
|
|
|
if (arrayIsEqualTo(this.typingFiles, typings)) {
|
2016-08-12 20:04:43 +02:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
this.typingFiles = typings;
|
|
|
|
|
this.markAsDirty();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2016-06-23 01:51:09 +02:00
|
|
|
|
|
2016-08-12 20:04:43 +02:00
|
|
|
|
private updateGraphWorker() {
|
2016-06-23 01:51:09 +02:00
|
|
|
|
const oldProgram = this.program;
|
2016-06-22 02:31:54 +02:00
|
|
|
|
this.program = this.languageService.getProgram();
|
2016-06-23 01:51:09 +02:00
|
|
|
|
|
2016-08-12 20:04:43 +02:00
|
|
|
|
let hasChanges = false;
|
2016-06-23 01:51:09 +02:00
|
|
|
|
// bump up the version if
|
|
|
|
|
// - oldProgram is not set - this is a first time updateGraph is called
|
|
|
|
|
// - newProgram is different from the old program and structure of the old program was not reused.
|
|
|
|
|
if (!oldProgram || (this.program !== oldProgram && !oldProgram.structureIsReused)) {
|
2016-08-12 20:04:43 +02:00
|
|
|
|
hasChanges = true;
|
2016-06-24 23:30:45 +02:00
|
|
|
|
if (oldProgram) {
|
|
|
|
|
for (const f of oldProgram.getSourceFiles()) {
|
2016-06-29 02:45:29 +02:00
|
|
|
|
if (this.program.getSourceFileByPath(f.path)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// new program does not contain this file - detach it from the project
|
|
|
|
|
const scriptInfoToDetach = this.projectService.getScriptInfo(f.fileName);
|
|
|
|
|
if (scriptInfoToDetach) {
|
|
|
|
|
scriptInfoToDetach.detachFromProject(this);
|
2016-06-24 23:30:45 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-06-23 01:51:09 +02:00
|
|
|
|
}
|
2016-08-24 01:11:52 +02:00
|
|
|
|
this.builder.onProjectUpdateGraph();
|
2016-08-12 20:04:43 +02:00
|
|
|
|
return hasChanges;
|
2016-06-22 02:31:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-06-24 23:30:45 +02:00
|
|
|
|
getScriptInfoLSHost(fileName: string) {
|
|
|
|
|
const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false);
|
|
|
|
|
if (scriptInfo) {
|
|
|
|
|
scriptInfo.attachToProject(this);
|
|
|
|
|
}
|
|
|
|
|
return scriptInfo;
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-23 02:02:08 +02:00
|
|
|
|
getScriptInfoForNormalizedPath(fileName: NormalizedPath) {
|
2016-06-23 01:51:09 +02:00
|
|
|
|
const scriptInfo = this.projectService.getOrCreateScriptInfoForNormalizedPath(fileName, /*openedByClient*/ false);
|
2016-08-26 23:37:49 +02:00
|
|
|
|
if (scriptInfo && !scriptInfo.isAttached(this)) {
|
|
|
|
|
return Errors.ThrowProjectDoesNotContainDocument(fileName, this);
|
|
|
|
|
}
|
2016-06-22 02:31:54 +02:00
|
|
|
|
return scriptInfo;
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-23 01:51:09 +02:00
|
|
|
|
getScriptInfo(uncheckedFileName: string) {
|
2016-06-23 02:02:08 +02:00
|
|
|
|
return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
|
2016-06-23 01:51:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
|
filesToString() {
|
|
|
|
|
if (!this.program) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
let strBuilder = "";
|
|
|
|
|
for (const file of this.program.getSourceFiles()) {
|
|
|
|
|
strBuilder += `${file.fileName}\n`;
|
|
|
|
|
}
|
|
|
|
|
return strBuilder;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setCompilerOptions(compilerOptions: CompilerOptions) {
|
|
|
|
|
if (compilerOptions) {
|
2016-08-01 07:06:41 +02:00
|
|
|
|
if (this.projectKind === ProjectKind.Inferred) {
|
|
|
|
|
compilerOptions.allowJs = true;
|
|
|
|
|
}
|
2016-06-22 02:31:54 +02:00
|
|
|
|
compilerOptions.allowNonTsExtensions = true;
|
|
|
|
|
this.compilerOptions = compilerOptions;
|
|
|
|
|
this.lsHost.setCompilationSettings(compilerOptions);
|
|
|
|
|
|
|
|
|
|
this.markAsDirty();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-24 23:30:45 +02:00
|
|
|
|
reloadScript(filename: NormalizedPath): boolean {
|
|
|
|
|
const script = this.projectService.getScriptInfoForNormalizedPath(filename);
|
2016-06-22 02:31:54 +02:00
|
|
|
|
if (script) {
|
2016-06-24 23:30:45 +02:00
|
|
|
|
Debug.assert(script.isAttached(this));
|
|
|
|
|
script.reloadFromFile();
|
|
|
|
|
return true;
|
2016-06-22 02:31:54 +02:00
|
|
|
|
}
|
2016-06-24 23:30:45 +02:00
|
|
|
|
return false;
|
2016-06-22 02:31:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-26 23:37:49 +02:00
|
|
|
|
getChangesSinceVersion(lastKnownVersion?: number): ProjectFilesWithTSDiagnostics {
|
2016-07-23 01:01:54 +02:00
|
|
|
|
this.updateGraph();
|
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
|
const info = {
|
|
|
|
|
projectName: this.getProjectName(),
|
2016-06-23 01:51:09 +02:00
|
|
|
|
version: this.projectStructureVersion,
|
2016-08-02 02:12:15 +02:00
|
|
|
|
isInferred: this.projectKind === ProjectKind.Inferred,
|
|
|
|
|
options: this.getCompilerOptions()
|
2016-06-22 02:31:54 +02:00
|
|
|
|
};
|
2016-06-23 01:51:09 +02:00
|
|
|
|
// check if requested version is the same that we have reported last time
|
2016-06-22 02:31:54 +02:00
|
|
|
|
if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) {
|
2016-06-23 01:51:09 +02:00
|
|
|
|
// if current structure version is the same - return info witout any changes
|
|
|
|
|
if (this.projectStructureVersion == this.lastReportedVersion) {
|
2016-08-26 23:37:49 +02:00
|
|
|
|
return { info, projectErrors: this.projectErrors };
|
2016-06-22 02:31:54 +02:00
|
|
|
|
}
|
2016-06-23 01:51:09 +02:00
|
|
|
|
// compute and return the difference
|
2016-06-22 02:31:54 +02:00
|
|
|
|
const lastReportedFileNames = this.lastReportedFileNames;
|
|
|
|
|
const currentFiles = arrayToMap(this.getFileNames(), x => x);
|
|
|
|
|
|
|
|
|
|
const added: string[] = [];
|
|
|
|
|
const removed: string[] = [];
|
|
|
|
|
for (const id in currentFiles) {
|
|
|
|
|
if (hasProperty(currentFiles, id) && !hasProperty(lastReportedFileNames, id)) {
|
|
|
|
|
added.push(id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (const id in lastReportedFileNames) {
|
|
|
|
|
if (hasProperty(lastReportedFileNames, id) && !hasProperty(currentFiles, id)) {
|
|
|
|
|
removed.push(id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.lastReportedFileNames = currentFiles;
|
|
|
|
|
|
|
|
|
|
this.lastReportedFileNames = currentFiles;
|
2016-06-23 01:51:09 +02:00
|
|
|
|
this.lastReportedVersion = this.projectStructureVersion;
|
2016-08-26 23:37:49 +02:00
|
|
|
|
return { info, changes: { added, removed }, projectErrors: this.projectErrors };
|
2016-06-22 02:31:54 +02:00
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// unknown version - return everything
|
|
|
|
|
const projectFileNames = this.getFileNames();
|
|
|
|
|
this.lastReportedFileNames = arrayToMap(projectFileNames, x => x);
|
2016-06-23 01:51:09 +02:00
|
|
|
|
this.lastReportedVersion = this.projectStructureVersion;
|
2016-08-26 23:37:49 +02:00
|
|
|
|
return { info, files: projectFileNames, projectErrors: this.projectErrors };
|
2016-06-22 02:31:54 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-06-23 02:02:08 +02:00
|
|
|
|
|
2016-08-24 01:11:52 +02:00
|
|
|
|
getReferencedFiles(path: Path): Path[] {
|
|
|
|
|
if (!this.languageServiceEnabled) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sourceFile = this.getSourceFile(path);
|
|
|
|
|
if (!sourceFile) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
// 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
|
|
|
|
|
const referencedFiles = createMap<boolean>();
|
|
|
|
|
if (sourceFile.imports) {
|
|
|
|
|
const checker: TypeChecker = this.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[declarationSourceFile.path] = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const currentDirectory = getDirectoryPath(path);
|
|
|
|
|
const getCanonicalFileName = createGetCanonicalFileName(this.projectService.host.useCaseSensitiveFileNames);
|
|
|
|
|
// Handle triple slash references
|
|
|
|
|
if (sourceFile.referencedFiles) {
|
|
|
|
|
for (const referencedFile of sourceFile.referencedFiles) {
|
|
|
|
|
const referencedPath = toPath(referencedFile.fileName, currentDirectory, getCanonicalFileName);
|
|
|
|
|
referencedFiles[referencedPath] = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle type reference directives
|
|
|
|
|
if (sourceFile.resolvedTypeReferenceDirectiveNames) {
|
|
|
|
|
for (const typeName in sourceFile.resolvedTypeReferenceDirectiveNames) {
|
|
|
|
|
const resolvedTypeReferenceDirective = sourceFile.resolvedTypeReferenceDirectiveNames[typeName];
|
|
|
|
|
if (!resolvedTypeReferenceDirective) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fileName = resolvedTypeReferenceDirective.resolvedFileName;
|
|
|
|
|
const typeFilePath = toPath(fileName, currentDirectory, getCanonicalFileName);
|
|
|
|
|
referencedFiles[typeFilePath] = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return map(Object.keys(referencedFiles), key => <Path>key);
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-23 02:02:08 +02:00
|
|
|
|
// remove a root file from project
|
2016-06-24 23:30:45 +02:00
|
|
|
|
private removeRootFileIfNecessary(info: ScriptInfo): void {
|
2016-06-23 02:02:08 +02:00
|
|
|
|
if (this.isRoot(info)) {
|
2016-06-24 23:30:45 +02:00
|
|
|
|
remove(this.rootFiles, info);
|
2016-06-23 02:02:08 +02:00
|
|
|
|
this.rootFilesMap.remove(info.path);
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-06-22 02:31:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-06-23 01:51:09 +02:00
|
|
|
|
export class InferredProject extends Project {
|
|
|
|
|
|
|
|
|
|
private static NextId = 1;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Unique name that identifies this particular inferred project
|
|
|
|
|
*/
|
|
|
|
|
private readonly inferredProjectName: string;
|
|
|
|
|
|
|
|
|
|
// Used to keep track of what directories are watched for this project
|
|
|
|
|
directoriesWatchedForTsconfig: string[] = [];
|
|
|
|
|
|
2016-08-25 19:46:41 +02:00
|
|
|
|
constructor(projectService: ProjectService, documentRegistry: ts.DocumentRegistry, languageServiceEnabled: boolean, compilerOptions: CompilerOptions, public compileOnSaveEnabled: boolean) {
|
2016-06-23 01:51:09 +02:00
|
|
|
|
super(ProjectKind.Inferred,
|
|
|
|
|
projectService,
|
|
|
|
|
documentRegistry,
|
|
|
|
|
/*files*/ undefined,
|
|
|
|
|
languageServiceEnabled,
|
2016-08-24 01:11:52 +02:00
|
|
|
|
compilerOptions,
|
2016-08-25 19:46:41 +02:00
|
|
|
|
compileOnSaveEnabled);
|
2016-06-23 01:51:09 +02:00
|
|
|
|
|
2016-06-24 23:55:12 +02:00
|
|
|
|
this.inferredProjectName = makeInferredProjectName(InferredProject.NextId);
|
|
|
|
|
InferredProject.NextId++;
|
2016-06-23 01:51:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getProjectName() {
|
|
|
|
|
return this.inferredProjectName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
close() {
|
|
|
|
|
super.close();
|
|
|
|
|
|
|
|
|
|
for (const directory of this.directoriesWatchedForTsconfig) {
|
|
|
|
|
this.projectService.stopWatchingDirectory(directory);
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-08-24 00:15:12 +02:00
|
|
|
|
|
|
|
|
|
getTypingOptions(): TypingOptions {
|
|
|
|
|
return {
|
2016-08-24 22:37:03 +02:00
|
|
|
|
enableAutoDiscovery: allRootFilesAreJsOrDts(this),
|
2016-08-24 00:15:12 +02:00
|
|
|
|
include: [],
|
|
|
|
|
exclude: []
|
|
|
|
|
};
|
|
|
|
|
}
|
2016-06-23 01:51:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class ConfiguredProject extends Project {
|
2016-08-24 22:37:03 +02:00
|
|
|
|
private typingOptions: TypingOptions;
|
2016-06-22 02:31:54 +02:00
|
|
|
|
private projectFileWatcher: FileWatcher;
|
|
|
|
|
private directoryWatcher: FileWatcher;
|
|
|
|
|
private directoriesWatchedForWildcards: Map<FileWatcher>;
|
|
|
|
|
/** Used for configured projects which may have multiple open roots */
|
|
|
|
|
openRefCount = 0;
|
|
|
|
|
|
2016-06-23 01:51:09 +02:00
|
|
|
|
constructor(readonly configFileName: NormalizedPath,
|
2016-06-22 02:31:54 +02:00
|
|
|
|
projectService: ProjectService,
|
|
|
|
|
documentRegistry: ts.DocumentRegistry,
|
|
|
|
|
hasExplicitListOfFiles: boolean,
|
|
|
|
|
compilerOptions: CompilerOptions,
|
2016-08-17 23:38:30 +02:00
|
|
|
|
private wildcardDirectories: Map<WatchDirectoryFlags>,
|
2016-08-24 01:11:52 +02:00
|
|
|
|
languageServiceEnabled: boolean,
|
2016-08-25 19:46:41 +02:00
|
|
|
|
public compileOnSaveEnabled: boolean) {
|
2016-08-24 01:11:52 +02:00
|
|
|
|
super(ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled);
|
2016-06-22 02:31:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-26 23:37:49 +02:00
|
|
|
|
setProjectErrors(projectErrors: Diagnostic[]) {
|
|
|
|
|
this.projectErrors = projectErrors;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-24 00:15:12 +02:00
|
|
|
|
setTypingOptions(newTypingOptions: TypingOptions): void {
|
|
|
|
|
this.typingOptions = newTypingOptions;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-12 20:04:43 +02:00
|
|
|
|
getTypingOptions() {
|
|
|
|
|
return this.typingOptions;
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
|
getProjectName() {
|
|
|
|
|
return this.configFileName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watchConfigFile(callback: (project: ConfiguredProject) => void) {
|
|
|
|
|
this.projectFileWatcher = this.projectService.host.watchFile(this.configFileName, _ => callback(this));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watchConfigDirectory(callback: (project: ConfiguredProject, path: string) => void) {
|
|
|
|
|
if (this.directoryWatcher) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const directoryToWatch = getDirectoryPath(this.configFileName);
|
2016-07-12 04:44:23 +02:00
|
|
|
|
this.projectService.logger.info(`Add recursive watcher for: ${directoryToWatch}`);
|
2016-06-22 02:31:54 +02:00
|
|
|
|
this.directoryWatcher = this.projectService.host.watchDirectory(directoryToWatch, path => callback(this, path), /*recursive*/ true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
watchWildcards(callback: (project: ConfiguredProject, path: string) => void) {
|
|
|
|
|
if (!this.wildcardDirectories) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const configDirectoryPath = getDirectoryPath(this.configFileName);
|
|
|
|
|
this.directoriesWatchedForWildcards = reduceProperties(this.wildcardDirectories, (watchers, flag, directory) => {
|
|
|
|
|
if (comparePaths(configDirectoryPath, directory, ".", !this.projectService.host.useCaseSensitiveFileNames) !== Comparison.EqualTo) {
|
|
|
|
|
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
|
2016-07-12 04:44:23 +02:00
|
|
|
|
this.projectService.logger.info(`Add ${recursive ? "recursive " : ""}watcher for: ${directory}`);
|
2016-06-22 02:31:54 +02:00
|
|
|
|
watchers[directory] = this.projectService.host.watchDirectory(
|
|
|
|
|
directory,
|
|
|
|
|
path => callback(this, path),
|
|
|
|
|
recursive
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return watchers;
|
|
|
|
|
}, <Map<FileWatcher>>{});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stopWatchingDirectory() {
|
|
|
|
|
if (this.directoryWatcher) {
|
|
|
|
|
this.directoryWatcher.close();
|
|
|
|
|
this.directoryWatcher = undefined;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
close() {
|
|
|
|
|
super.close();
|
|
|
|
|
|
|
|
|
|
if (this.projectFileWatcher) {
|
|
|
|
|
this.projectFileWatcher.close();
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-17 23:38:30 +02:00
|
|
|
|
for (const id in this.directoriesWatchedForWildcards) {
|
|
|
|
|
this.directoriesWatchedForWildcards[id].close();
|
|
|
|
|
}
|
2016-06-22 02:31:54 +02:00
|
|
|
|
this.directoriesWatchedForWildcards = undefined;
|
|
|
|
|
|
|
|
|
|
this.stopWatchingDirectory();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addOpenRef() {
|
|
|
|
|
this.openRefCount++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deleteOpenRef() {
|
|
|
|
|
this.openRefCount--;
|
|
|
|
|
return this.openRefCount;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-23 01:51:09 +02:00
|
|
|
|
export class ExternalProject extends Project {
|
2016-08-24 00:15:12 +02:00
|
|
|
|
private typingOptions: TypingOptions;
|
2016-06-22 02:31:54 +02:00
|
|
|
|
constructor(readonly externalProjectName: string,
|
|
|
|
|
projectService: ProjectService,
|
|
|
|
|
documentRegistry: ts.DocumentRegistry,
|
|
|
|
|
compilerOptions: CompilerOptions,
|
2016-08-24 01:11:52 +02:00
|
|
|
|
languageServiceEnabled: boolean,
|
2016-08-25 19:46:41 +02:00
|
|
|
|
public compileOnSaveEnabled: boolean) {
|
2016-08-24 01:11:52 +02:00
|
|
|
|
super(ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, languageServiceEnabled, compilerOptions, compileOnSaveEnabled);
|
2016-08-24 00:15:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getTypingOptions() {
|
|
|
|
|
return this.typingOptions;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-26 23:37:49 +02:00
|
|
|
|
setProjectErrors(projectErrors: Diagnostic[]) {
|
|
|
|
|
this.projectErrors = projectErrors;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-24 00:15:12 +02:00
|
|
|
|
setTypingOptions(newTypingOptions: TypingOptions): void {
|
|
|
|
|
if (!newTypingOptions) {
|
|
|
|
|
// set default typings options
|
|
|
|
|
newTypingOptions = {
|
2016-08-24 22:37:03 +02:00
|
|
|
|
enableAutoDiscovery: allRootFilesAreJsOrDts(this),
|
2016-08-24 00:15:12 +02:00
|
|
|
|
include: [],
|
|
|
|
|
exclude: []
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (newTypingOptions.enableAutoDiscovery === undefined) {
|
|
|
|
|
// if autoDiscovery was not specified by the caller - set it based on the content of the project
|
2016-08-24 22:37:03 +02:00
|
|
|
|
newTypingOptions.enableAutoDiscovery = allRootFilesAreJsOrDts(this);
|
2016-08-24 00:15:12 +02:00
|
|
|
|
}
|
|
|
|
|
if (!newTypingOptions.include) {
|
|
|
|
|
newTypingOptions.include = [];
|
|
|
|
|
}
|
|
|
|
|
if (!newTypingOptions.exclude) {
|
|
|
|
|
newTypingOptions.exclude = [];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.typingOptions = newTypingOptions;
|
2016-06-22 02:31:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getProjectName() {
|
|
|
|
|
return this.externalProjectName;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|