2017-03-01 18:28:51 +01: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
|
|
|
|
}
|
|
|
|
|
2017-05-25 22:30:27 +02:00
|
|
|
/* @internal */
|
|
|
|
export function countEachFileTypes(infos: ScriptInfo[]): FileStats {
|
2016-10-12 04:19:40 +02:00
|
|
|
const result = { js: 0, jsx: 0, ts: 0, tsx: 0, dts: 0 };
|
|
|
|
for (const info of infos) {
|
|
|
|
switch (info.scriptKind) {
|
|
|
|
case ScriptKind.JS:
|
|
|
|
result.js += 1;
|
|
|
|
break;
|
|
|
|
case ScriptKind.JSX:
|
|
|
|
result.jsx += 1;
|
|
|
|
break;
|
|
|
|
case ScriptKind.TS:
|
2017-06-10 04:32:44 +02:00
|
|
|
fileExtensionIs(info.fileName, Extension.Dts)
|
2016-10-12 04:19:40 +02:00
|
|
|
? result.dts += 1
|
|
|
|
: result.ts += 1;
|
|
|
|
break;
|
|
|
|
case ScriptKind.TSX:
|
|
|
|
result.tsx += 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
function hasOneOrMoreJsAndNoTsFiles(project: Project) {
|
|
|
|
const counts = countEachFileTypes(project.getScriptInfos());
|
|
|
|
return counts.js > 0 && counts.ts === 0 && counts.tsx === 0;
|
2016-09-02 00:17:38 +02:00
|
|
|
}
|
|
|
|
|
2016-08-24 22:37:03 +02:00
|
|
|
export function allRootFilesAreJsOrDts(project: Project): boolean {
|
2016-10-12 04:19:40 +02:00
|
|
|
const counts = countEachFileTypes(project.getRootScriptInfos());
|
|
|
|
return counts.ts === 0 && counts.tsx === 0;
|
2016-09-02 00:17:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export function allFilesAreJsOrDts(project: Project): boolean {
|
2016-10-12 04:19:40 +02:00
|
|
|
const counts = countEachFileTypes(project.getScriptInfos());
|
|
|
|
return counts.ts === 0 && counts.tsx === 0;
|
2016-08-24 00:15:12 +02:00
|
|
|
}
|
|
|
|
|
2017-01-05 00:56:16 +01:00
|
|
|
/* @internal */
|
2016-08-26 23:37:49 +02:00
|
|
|
export interface ProjectFilesWithTSDiagnostics extends protocol.ProjectFiles {
|
|
|
|
projectErrors: Diagnostic[];
|
|
|
|
}
|
|
|
|
|
2016-10-26 00:24:21 +02:00
|
|
|
export class UnresolvedImportsMap {
|
2017-06-28 22:15:34 +02:00
|
|
|
readonly perFileMap = createMap<ReadonlyArray<string>>();
|
2016-10-26 00:24:21 +02:00
|
|
|
private version = 0;
|
|
|
|
|
|
|
|
public clear() {
|
|
|
|
this.perFileMap.clear();
|
|
|
|
this.version = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
public getVersion() {
|
|
|
|
return this.version;
|
|
|
|
}
|
|
|
|
|
|
|
|
public remove(path: Path) {
|
2017-06-28 22:15:34 +02:00
|
|
|
this.perFileMap.delete(path);
|
2016-10-26 00:24:21 +02:00
|
|
|
this.version++;
|
|
|
|
}
|
|
|
|
|
|
|
|
public get(path: Path) {
|
|
|
|
return this.perFileMap.get(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
public set(path: Path, value: ReadonlyArray<string>) {
|
|
|
|
this.perFileMap.set(path, value);
|
|
|
|
this.version++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-14 22:35:16 +01:00
|
|
|
export interface PluginCreateInfo {
|
|
|
|
project: Project;
|
|
|
|
languageService: LanguageService;
|
|
|
|
languageServiceHost: LanguageServiceHost;
|
|
|
|
serverHost: ServerHost;
|
|
|
|
config: any;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface PluginModule {
|
|
|
|
create(createInfo: PluginCreateInfo): LanguageService;
|
|
|
|
getExternalFiles?(proj: Project): string[];
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface PluginModuleFactory {
|
|
|
|
(mod: { typescript: typeof ts }): PluginModule;
|
|
|
|
}
|
|
|
|
|
2017-07-06 03:03:11 +02:00
|
|
|
/**
|
|
|
|
* The project root can be script info - if root is present,
|
|
|
|
* or it could be just normalized path if root wasnt present on the host(only for non inferred project)
|
|
|
|
*/
|
|
|
|
export type ProjectRoot = ScriptInfo | NormalizedPath;
|
|
|
|
/* @internal */
|
|
|
|
export function isScriptInfo(value: ProjectRoot): value is ScriptInfo {
|
|
|
|
return value instanceof ScriptInfo;
|
|
|
|
}
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
export abstract class Project {
|
2016-06-28 00:29:04 +02:00
|
|
|
private rootFiles: ScriptInfo[] = [];
|
2017-07-06 03:03:11 +02:00
|
|
|
private rootFilesMap: Map<ProjectRoot> = createMap<ProjectRoot>();
|
2017-07-13 22:08:59 +02:00
|
|
|
private program: Program;
|
2017-04-22 00:20:55 +02:00
|
|
|
private externalFiles: SortedReadonlyArray<string>;
|
2017-07-14 05:08:57 +02:00
|
|
|
private missingFilesMap: Map<FileWatcher>;
|
2016-06-22 02:31:54 +02:00
|
|
|
|
2016-10-26 00:24:21 +02:00
|
|
|
private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap();
|
|
|
|
private lastCachedUnresolvedImportsList: SortedReadonlyArray<string>;
|
|
|
|
|
2017-02-14 22:35:16 +01:00
|
|
|
// wrapper over the real language service that will suppress all semantic operations
|
|
|
|
protected languageService: LanguageService;
|
2016-11-12 02:35:11 +01:00
|
|
|
|
|
|
|
public languageServiceEnabled = true;
|
|
|
|
|
2017-07-12 01:10:44 +02:00
|
|
|
/*@internal*/
|
|
|
|
lsHost: LSHost;
|
2017-02-14 22:35:16 +01:00
|
|
|
|
2016-08-24 01:11:52 +02:00
|
|
|
builder: Builder;
|
2016-12-09 02:56:08 +01:00
|
|
|
/**
|
|
|
|
* Set of files names that were updated since the last call to getChangesSinceVersion.
|
|
|
|
*/
|
2017-07-07 19:34:36 +02:00
|
|
|
private updatedFileNames: Map<true>;
|
2016-06-23 01:51:09 +02:00
|
|
|
/**
|
|
|
|
* Set of files that was returned from the last call to getChangesSinceVersion.
|
|
|
|
*/
|
2017-07-07 19:34:36 +02:00
|
|
|
private lastReportedFileNames: Map<true>;
|
2016-06-23 01:51:09 +02:00
|
|
|
/**
|
|
|
|
* 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-10-26 00:24:21 +02:00
|
|
|
private typingFiles: SortedReadonlyArray<string>;
|
2016-08-12 20:04:43 +02:00
|
|
|
|
2016-08-26 23:37:49 +02:00
|
|
|
protected projectErrors: Diagnostic[];
|
|
|
|
|
2016-09-21 01:52:34 +02:00
|
|
|
public typesVersion = 0;
|
|
|
|
|
2016-10-12 04:19:40 +02:00
|
|
|
public isNonTsProject() {
|
2016-09-02 00:17:38 +02:00
|
|
|
this.updateGraph();
|
|
|
|
return allFilesAreJsOrDts(this);
|
|
|
|
}
|
|
|
|
|
2016-10-12 04:19:40 +02:00
|
|
|
public isJsOnlyProject() {
|
|
|
|
this.updateGraph();
|
|
|
|
return hasOneOrMoreJsAndNoTsFiles(this);
|
|
|
|
}
|
|
|
|
|
2016-10-26 00:24:21 +02:00
|
|
|
public getCachedUnresolvedImportsPerFile_TestOnly() {
|
|
|
|
return this.cachedUnresolvedImportsPerFile;
|
|
|
|
}
|
|
|
|
|
2017-02-14 22:35:16 +01:00
|
|
|
public static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void): {} {
|
|
|
|
const resolvedPath = normalizeSlashes(host.resolvePath(combinePaths(initialDir, "node_modules")));
|
|
|
|
log(`Loading ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`);
|
|
|
|
const result = host.require(resolvedPath, moduleName);
|
|
|
|
if (result.error) {
|
|
|
|
log(`Failed to load module: ${JSON.stringify(result.error)}`);
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
return result.module;
|
|
|
|
}
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
constructor(
|
2016-11-18 00:12:32 +01:00
|
|
|
private readonly projectName: string,
|
2016-06-22 02:31:54 +02:00
|
|
|
readonly projectKind: ProjectKind,
|
|
|
|
readonly projectService: ProjectService,
|
2017-07-13 22:08:59 +02:00
|
|
|
private documentRegistry: DocumentRegistry,
|
2016-06-22 02:31:54 +02:00
|
|
|
hasExplicitListOfFiles: boolean,
|
2016-11-12 02:35:11 +01:00
|
|
|
languageServiceEnabled: boolean,
|
2016-08-24 01:11:52 +02:00
|
|
|
private compilerOptions: CompilerOptions,
|
2017-07-12 01:10:44 +02:00
|
|
|
public compileOnSaveEnabled: boolean,
|
|
|
|
host: ServerHost) {
|
2016-06-22 02:31:54 +02:00
|
|
|
|
|
|
|
if (!this.compilerOptions) {
|
2017-07-13 22:08:59 +02:00
|
|
|
this.compilerOptions = getDefaultCompilerOptions();
|
2016-06-22 02:31:54 +02:00
|
|
|
this.compilerOptions.allowNonTsExtensions = true;
|
|
|
|
this.compilerOptions.allowJs = true;
|
|
|
|
}
|
2017-02-10 02:06:47 +01:00
|
|
|
else if (hasExplicitListOfFiles || this.compilerOptions.allowJs) {
|
|
|
|
// If files are listed explicitly or allowJs is specified, allow all extensions
|
2016-06-22 02:31:54 +02:00
|
|
|
this.compilerOptions.allowNonTsExtensions = true;
|
|
|
|
}
|
|
|
|
|
2016-11-30 08:30:14 +01:00
|
|
|
this.setInternalCompilerOptionsForEmittingJsFiles();
|
2016-11-04 05:13:41 +01:00
|
|
|
|
2017-07-12 01:10:44 +02:00
|
|
|
this.lsHost = new LSHost(host, this, this.projectService.cancellationToken);
|
2016-11-12 02:35:11 +01:00
|
|
|
this.lsHost.setCompilationSettings(this.compilerOptions);
|
|
|
|
|
2017-07-13 22:08:59 +02:00
|
|
|
this.languageService = createLanguageService(this.lsHost, this.documentRegistry);
|
2016-11-12 02:35:11 +01:00
|
|
|
|
|
|
|
if (!languageServiceEnabled) {
|
2016-06-22 02:31:54 +02:00
|
|
|
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-11-30 08:30:14 +01:00
|
|
|
private setInternalCompilerOptionsForEmittingJsFiles() {
|
|
|
|
if (this.projectKind === ProjectKind.Inferred || this.projectKind === ProjectKind.External) {
|
|
|
|
this.compilerOptions.noEmitForJsFiles = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-24 00:38:03 +02:00
|
|
|
/**
|
|
|
|
* Get the errors that dont have any file name associated
|
|
|
|
*/
|
|
|
|
getGlobalProjectErrors() {
|
|
|
|
return filter(this.projectErrors, diagnostic => !diagnostic.file);
|
|
|
|
}
|
|
|
|
|
|
|
|
getAllProjectErrors() {
|
2016-08-26 23:37:49 +02:00
|
|
|
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();
|
|
|
|
}
|
2016-12-13 22:21:32 +01:00
|
|
|
return this.languageService;
|
2016-07-23 01:01:54 +02:00
|
|
|
}
|
|
|
|
|
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() {
|
2016-11-12 02:35:11 +01:00
|
|
|
if (this.languageServiceEnabled) {
|
|
|
|
return;
|
|
|
|
}
|
2016-06-22 02:31:54 +02:00
|
|
|
this.languageServiceEnabled = true;
|
2016-11-12 02:35:11 +01:00
|
|
|
this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ true);
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
disableLanguageService() {
|
2016-11-12 02:35:11 +01:00
|
|
|
if (!this.languageServiceEnabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.languageService.cleanupSemanticCache();
|
2016-06-22 02:31:54 +02:00
|
|
|
this.languageServiceEnabled = false;
|
2016-11-12 02:35:11 +01:00
|
|
|
this.projectService.onUpdateLanguageServiceStateForProject(this, /*languageServiceEnabled*/ false);
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
|
|
|
|
2016-11-18 00:12:32 +01:00
|
|
|
getProjectName() {
|
|
|
|
return this.projectName;
|
|
|
|
}
|
2016-09-14 01:20:42 +02:00
|
|
|
abstract getProjectRootPath(): string | undefined;
|
2016-11-19 02:46:06 +01:00
|
|
|
abstract getTypeAcquisition(): TypeAcquisition;
|
2016-06-22 02:31:54 +02:00
|
|
|
|
2017-04-22 00:20:55 +02:00
|
|
|
getExternalFiles(): SortedReadonlyArray<string> {
|
|
|
|
return emptyArray as SortedReadonlyArray<string>;
|
2017-02-14 22:35:16 +01:00
|
|
|
}
|
|
|
|
|
2016-08-24 01:11:52 +02:00
|
|
|
getSourceFile(path: Path) {
|
|
|
|
if (!this.program) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
return this.program.getSourceFileByPath(path);
|
|
|
|
}
|
|
|
|
|
2016-09-21 01:52:34 +02:00
|
|
|
updateTypes() {
|
|
|
|
this.typesVersion++;
|
|
|
|
this.markAsDirty();
|
|
|
|
}
|
|
|
|
|
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);
|
2017-07-13 08:15:03 +02:00
|
|
|
// We might not find the script info in case its not associated with the project any more
|
|
|
|
// and project graph was not updated (eg delayed update graph in case of files changed/deleted on the disk)
|
|
|
|
if (info) {
|
|
|
|
info.detachFromProject(this);
|
|
|
|
}
|
2016-06-28 00:29:04 +02:00
|
|
|
}
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
2016-12-21 04:25:25 +01:00
|
|
|
if (!this.program || !this.languageServiceEnabled) {
|
|
|
|
// release all root files either if there is no program or language service is disabled.
|
|
|
|
// in the latter case set of root files can be larger than the set of files in program.
|
2016-06-28 00:29:04 +02:00
|
|
|
for (const root of this.rootFiles) {
|
|
|
|
root.detachFromProject(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.rootFiles = undefined;
|
|
|
|
this.rootFilesMap = undefined;
|
|
|
|
this.program = undefined;
|
2017-06-13 01:31:49 +02:00
|
|
|
this.builder = undefined;
|
|
|
|
this.cachedUnresolvedImportsPerFile = undefined;
|
|
|
|
this.projectErrors = undefined;
|
|
|
|
this.lsHost.dispose();
|
|
|
|
this.lsHost = undefined;
|
2016-06-28 00:29:04 +02:00
|
|
|
|
2017-06-21 01:30:33 +02:00
|
|
|
// Clean up file watchers waiting for missing files
|
2017-07-14 05:08:57 +02:00
|
|
|
cleanExistingMap(this.missingFilesMap, (missingFilePath, fileWatcher) => {
|
|
|
|
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.ProjectClose);
|
|
|
|
});
|
2017-06-21 01:30:33 +02:00
|
|
|
this.missingFilesMap = undefined;
|
|
|
|
|
2016-06-28 00:29:04 +02:00
|
|
|
// signal language service to release source files acquired from document registry
|
2016-06-22 02:31:54 +02:00
|
|
|
this.languageService.dispose();
|
2017-06-13 01:31:49 +02:00
|
|
|
this.languageService = undefined;
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2017-07-06 03:03:11 +02:00
|
|
|
this.rootFilesMap.forEach((value, _path) => {
|
|
|
|
const f: ScriptInfo = isScriptInfo(value) && value;
|
|
|
|
if (this.languageServiceEnabled || (f && f.isScriptOpen())) {
|
2016-12-13 22:21:32 +01:00
|
|
|
// if language service is disabled - process only files that are open
|
2017-07-06 03:03:11 +02:00
|
|
|
result.push(f ? f.fileName : value as NormalizedPath);
|
2016-12-13 22:21:32 +01:00
|
|
|
}
|
2017-07-06 03:03:11 +02:00
|
|
|
});
|
2016-08-12 20:04:43 +02:00
|
|
|
if (this.typingFiles) {
|
|
|
|
for (const f of this.typingFiles) {
|
|
|
|
result.push(f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-07-06 03:03:11 +02:00
|
|
|
getRootFilesMap() {
|
|
|
|
return this.rootFilesMap;
|
|
|
|
}
|
|
|
|
|
2016-07-06 00:51:39 +02:00
|
|
|
getRootScriptInfos() {
|
|
|
|
return this.rootFiles;
|
|
|
|
}
|
|
|
|
|
2016-08-24 01:11:52 +02:00
|
|
|
getScriptInfos() {
|
2016-12-13 22:21:32 +01:00
|
|
|
if (!this.languageServiceEnabled) {
|
|
|
|
// if language service is not enabled - return just root files
|
|
|
|
return this.rootFiles;
|
|
|
|
}
|
2016-09-30 22:19:54 +02:00
|
|
|
return map(this.program.getSourceFiles(), sourceFile => {
|
|
|
|
const scriptInfo = this.projectService.getScriptInfoForPath(sourceFile.path);
|
|
|
|
if (!scriptInfo) {
|
|
|
|
Debug.assert(false, `scriptInfo for a file '${sourceFile.fileName}' is missing.`);
|
|
|
|
}
|
|
|
|
return scriptInfo;
|
|
|
|
});
|
2016-08-24 01:11:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
getFileEmitOutput(info: ScriptInfo, emitOnlyDtsFiles: boolean) {
|
|
|
|
if (!this.languageServiceEnabled) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles);
|
|
|
|
}
|
|
|
|
|
2017-05-15 22:13:00 +02:00
|
|
|
getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) {
|
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;
|
|
|
|
}
|
2016-11-08 06:13:11 +01:00
|
|
|
const result: NormalizedPath[] = [];
|
|
|
|
for (const f of this.program.getSourceFiles()) {
|
|
|
|
if (excludeFilesFromExternalLibraries && this.program.isSourceFileFromExternalLibrary(f)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
result.push(asNormalizedPath(f.fileName));
|
|
|
|
}
|
2017-05-15 22:13:00 +02:00
|
|
|
if (!excludeConfigFiles) {
|
|
|
|
const configFile = this.program.getCompilerOptions().configFile;
|
|
|
|
if (configFile) {
|
|
|
|
result.push(asNormalizedPath(configFile.fileName));
|
|
|
|
if (configFile.extendedSourceFiles) {
|
|
|
|
for (const f of configFile.extendedSourceFiles) {
|
|
|
|
result.push(asNormalizedPath(f));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-11-08 06:13:11 +01:00
|
|
|
return result;
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
|
|
|
|
2017-05-24 00:38:03 +02:00
|
|
|
hasConfigFile(configFilePath: NormalizedPath) {
|
|
|
|
if (this.program && this.languageServiceEnabled) {
|
|
|
|
const configFile = this.program.getCompilerOptions().configFile;
|
|
|
|
if (configFile) {
|
|
|
|
if (configFilePath === asNormalizedPath(configFile.fileName)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (configFile.extendedSourceFiles) {
|
|
|
|
for (const f of configFile.extendedSourceFiles) {
|
|
|
|
if (configFilePath === asNormalizedPath(f)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-08-31 21:11:32 +02:00
|
|
|
getAllEmittableFiles() {
|
2016-08-24 01:11:52 +02:00
|
|
|
if (!this.languageServiceEnabled) {
|
2016-08-31 21:11:32 +02:00
|
|
|
return [];
|
2016-08-24 01:11:52 +02:00
|
|
|
}
|
|
|
|
const defaultLibraryFileName = getDefaultLibFileName(this.compilerOptions);
|
2016-08-31 21:11:32 +02:00
|
|
|
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;
|
2016-08-24 01:11:52 +02:00
|
|
|
}
|
|
|
|
|
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-12-09 01:17:42 +01:00
|
|
|
if (info && (info.isScriptOpen() || !requireOpen)) {
|
2016-06-24 23:30:45 +02:00
|
|
|
return this.containsScriptInfo(info);
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
isRoot(info: ScriptInfo) {
|
2017-07-06 03:03:11 +02:00
|
|
|
return this.rootFilesMap && this.rootFilesMap.get(info.path) === info;
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-06 03:03:11 +02:00
|
|
|
// add a root file to project
|
|
|
|
addMissingFileRoot(fileName: NormalizedPath) {
|
2017-07-15 07:35:07 +02:00
|
|
|
const path = this.projectService.toPath(fileName);
|
2017-07-06 03:03:11 +02:00
|
|
|
this.rootFilesMap.set(path, fileName);
|
|
|
|
this.markAsDirty();
|
|
|
|
}
|
|
|
|
|
2016-06-24 23:55:12 +02:00
|
|
|
removeFile(info: ScriptInfo, detachFromProject = true) {
|
2016-12-30 02:00:04 +01:00
|
|
|
if (this.isRoot(info)) {
|
|
|
|
this.removeRoot(info);
|
|
|
|
}
|
2016-06-24 23:30:45 +02:00
|
|
|
this.lsHost.notifyFileRemoved(info);
|
2016-10-26 00:24:21 +02:00
|
|
|
this.cachedUnresolvedImportsPerFile.remove(info.path);
|
2016-06-24 23:30:45 +02:00
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2016-12-09 02:56:08 +01:00
|
|
|
registerFileUpdate(fileName: string) {
|
2017-07-07 19:34:36 +02:00
|
|
|
(this.updatedFileNames || (this.updatedFileNames = createMap<true>())).set(fileName, true);
|
2016-12-09 02:56:08 +01:00
|
|
|
}
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
markAsDirty() {
|
2016-06-23 01:51:09 +02:00
|
|
|
this.projectStateVersion++;
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
|
|
|
|
2016-10-26 00:24:21 +02:00
|
|
|
private extractUnresolvedImportsFromSourceFile(file: SourceFile, result: string[]) {
|
|
|
|
const cached = this.cachedUnresolvedImportsPerFile.get(file.path);
|
|
|
|
if (cached) {
|
|
|
|
// found cached result - use it and return
|
|
|
|
for (const f of cached) {
|
|
|
|
result.push(f);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let unresolvedImports: string[];
|
|
|
|
if (file.resolvedModules) {
|
2016-12-05 23:13:32 +01:00
|
|
|
file.resolvedModules.forEach((resolvedModule, name) => {
|
2016-10-26 00:24:21 +02:00
|
|
|
// pick unresolved non-relative names
|
2016-12-05 23:13:32 +01:00
|
|
|
if (!resolvedModule && !isExternalModuleNameRelative(name)) {
|
2016-10-26 00:24:21 +02:00
|
|
|
// for non-scoped names extract part up-to the first slash
|
|
|
|
// for scoped names - extract up to the second slash
|
|
|
|
let trimmed = name.trim();
|
|
|
|
let i = trimmed.indexOf("/");
|
|
|
|
if (i !== -1 && trimmed.charCodeAt(0) === CharacterCodes.at) {
|
|
|
|
i = trimmed.indexOf("/", i + 1);
|
|
|
|
}
|
|
|
|
if (i !== -1) {
|
|
|
|
trimmed = trimmed.substr(0, i);
|
|
|
|
}
|
|
|
|
(unresolvedImports || (unresolvedImports = [])).push(trimmed);
|
|
|
|
result.push(trimmed);
|
|
|
|
}
|
2016-12-05 23:13:32 +01:00
|
|
|
});
|
2016-10-26 00:24:21 +02:00
|
|
|
}
|
|
|
|
this.cachedUnresolvedImportsPerFile.set(file.path, unresolvedImports || emptyArray);
|
|
|
|
}
|
|
|
|
|
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-10-26 00:24:21 +02:00
|
|
|
this.lsHost.startRecordingFilesWithChangedResolutions();
|
|
|
|
|
2016-08-18 01:42:05 +02:00
|
|
|
let hasChanges = this.updateGraphWorker();
|
2016-10-26 00:24:21 +02:00
|
|
|
|
|
|
|
const changedFiles: ReadonlyArray<Path> = this.lsHost.finishRecordingFilesWithChangedResolutions() || emptyArray;
|
|
|
|
|
|
|
|
for (const file of changedFiles) {
|
|
|
|
// delete cached information for changed files
|
|
|
|
this.cachedUnresolvedImportsPerFile.remove(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 1. no changes in structure, no changes in unresolved imports - do nothing
|
2016-12-05 23:13:32 +01:00
|
|
|
// 2. no changes in structure, unresolved imports were changed - collect unresolved imports for all files
|
2016-10-26 00:24:21 +02:00
|
|
|
// (can reuse cached imports for files that were not changed)
|
|
|
|
// 3. new files were added/removed, but compilation settings stays the same - collect unresolved imports for all new/modified files
|
|
|
|
// (can reuse cached imports for files that were not changed)
|
|
|
|
// 4. compilation settings were changed in the way that might affect module resolution - drop all caches and collect all data from the scratch
|
|
|
|
let unresolvedImports: SortedReadonlyArray<string>;
|
|
|
|
if (hasChanges || changedFiles.length) {
|
|
|
|
const result: string[] = [];
|
|
|
|
for (const sourceFile of this.program.getSourceFiles()) {
|
|
|
|
this.extractUnresolvedImportsFromSourceFile(sourceFile, result);
|
|
|
|
}
|
|
|
|
this.lastCachedUnresolvedImportsList = toSortedReadonlyArray(result);
|
|
|
|
}
|
|
|
|
unresolvedImports = this.lastCachedUnresolvedImportsList;
|
|
|
|
|
|
|
|
const cachedTypings = this.projectService.typingsCache.getTypingsForProject(this, unresolvedImports, hasChanges);
|
2016-08-18 01:42:05 +02:00
|
|
|
if (this.setTypings(cachedTypings)) {
|
|
|
|
hasChanges = this.updateGraphWorker() || hasChanges;
|
|
|
|
}
|
2016-12-13 22:21:32 +01:00
|
|
|
|
|
|
|
// update builder only if language service is enabled
|
|
|
|
// otherwise tell it to drop its internal state
|
|
|
|
if (this.languageServiceEnabled) {
|
|
|
|
this.builder.onProjectUpdateGraph();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.builder.clear();
|
|
|
|
}
|
|
|
|
|
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-10-26 00:24:21 +02:00
|
|
|
private setTypings(typings: SortedReadonlyArray<string>): boolean {
|
2016-08-18 01:42:05 +02:00
|
|
|
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
|
|
|
|
|
|
|
// 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.
|
2017-06-22 22:47:54 +02:00
|
|
|
const hasChanges = !oldProgram || (this.program !== oldProgram && !(oldProgram.structureIsReused & StructureIsReused.Completely));
|
|
|
|
|
|
|
|
if (hasChanges) {
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-04-22 00:20:55 +02:00
|
|
|
|
2017-06-22 22:47:54 +02:00
|
|
|
const missingFilePaths = this.program.getMissingFilePaths();
|
2017-07-14 05:08:57 +02:00
|
|
|
const newMissingFilePathMap = arrayToSet(missingFilePaths);
|
|
|
|
// Update the missing file paths watcher
|
|
|
|
this.missingFilesMap = mutateExistingMapWithNewSet(
|
|
|
|
this.missingFilesMap, newMissingFilePathMap,
|
|
|
|
// Watch the missing files
|
|
|
|
missingFilePath => {
|
|
|
|
const fileWatcher = this.projectService.addFileWatcher(
|
|
|
|
WatchType.MissingFilePath, this, missingFilePath,
|
|
|
|
(filename: string, eventKind: FileWatcherEventKind) => {
|
|
|
|
if (eventKind === FileWatcherEventKind.Created && this.missingFilesMap.has(missingFilePath)) {
|
|
|
|
this.missingFilesMap.delete(missingFilePath);
|
|
|
|
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.FileCreated);
|
|
|
|
|
|
|
|
if (this.projectKind === ProjectKind.Configured) {
|
|
|
|
const absoluteNormalizedPath = getNormalizedAbsolutePath(filename, getDirectoryPath(missingFilePath));
|
|
|
|
(this.lsHost.host as CachedServerHost).addOrDeleteFileOrFolder(toNormalizedPath(absoluteNormalizedPath));
|
|
|
|
}
|
|
|
|
|
|
|
|
// When a missing file is created, we should update the graph.
|
|
|
|
this.markAsDirty();
|
|
|
|
this.projectService.delayUpdateProjectGraphAndInferredProjectsRefresh(this);
|
2017-07-12 01:10:44 +02:00
|
|
|
}
|
2017-06-21 00:54:43 +02:00
|
|
|
}
|
2017-07-14 05:08:57 +02:00
|
|
|
);
|
|
|
|
return fileWatcher;
|
|
|
|
},
|
|
|
|
// Files that are no longer missing (e.g. because they are no longer required)
|
|
|
|
// should no longer be watched.
|
|
|
|
(missingFilePath, fileWatcher) => {
|
|
|
|
this.projectService.closeFileWatcher(WatchType.MissingFilePath, this, missingFilePath, fileWatcher, WatcherCloseReason.NotNeeded);
|
2017-06-21 00:54:43 +02:00
|
|
|
}
|
2017-07-14 05:08:57 +02:00
|
|
|
);
|
2017-06-21 00:54:43 +02:00
|
|
|
}
|
|
|
|
|
2017-04-22 00:20:55 +02:00
|
|
|
const oldExternalFiles = this.externalFiles || emptyArray as SortedReadonlyArray<string>;
|
|
|
|
this.externalFiles = this.getExternalFiles();
|
|
|
|
enumerateInsertsAndDeletes(this.externalFiles, oldExternalFiles,
|
|
|
|
// Ensure a ScriptInfo is created for new external files. This is performed indirectly
|
|
|
|
// by the LSHost for files in the program when the program is retrieved above but
|
|
|
|
// the program doesn't contain external files so this must be done explicitly.
|
|
|
|
inserted => {
|
|
|
|
const scriptInfo = this.projectService.getOrCreateScriptInfo(inserted, /*openedByClient*/ false);
|
|
|
|
scriptInfo.attachToProject(this);
|
|
|
|
},
|
|
|
|
removed => {
|
|
|
|
const scriptInfoToDetach = this.projectService.getScriptInfo(removed);
|
|
|
|
if (scriptInfoToDetach) {
|
|
|
|
scriptInfoToDetach.detachFromProject(this);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-08-12 20:04:43 +02:00
|
|
|
return hasChanges;
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
|
|
|
|
2017-06-21 00:54:43 +02:00
|
|
|
isWatchedMissingFile(path: Path) {
|
2017-07-14 05:08:57 +02:00
|
|
|
return this.missingFilesMap && this.missingFilesMap.has(path);
|
2017-06-21 00:54:43 +02:00
|
|
|
}
|
|
|
|
|
2016-06-24 23:30:45 +02:00
|
|
|
getScriptInfoLSHost(fileName: string) {
|
|
|
|
const scriptInfo = this.projectService.getOrCreateScriptInfo(fileName, /*openedByClient*/ false);
|
|
|
|
if (scriptInfo) {
|
2017-07-06 03:03:11 +02:00
|
|
|
const existingValue = this.rootFilesMap.get(scriptInfo.path);
|
|
|
|
if (existingValue !== undefined && existingValue !== scriptInfo) {
|
|
|
|
// This was missing path earlier but now the file exists. Update the root
|
|
|
|
this.rootFiles.push(scriptInfo);
|
|
|
|
this.rootFilesMap.set(scriptInfo.path, scriptInfo);
|
|
|
|
}
|
2016-06-24 23:30:45 +02:00
|
|
|
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()) {
|
2017-07-12 19:42:05 +02:00
|
|
|
strBuilder += `\t${file.fileName}\n`;
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
|
|
|
return strBuilder;
|
|
|
|
}
|
|
|
|
|
|
|
|
setCompilerOptions(compilerOptions: CompilerOptions) {
|
|
|
|
if (compilerOptions) {
|
|
|
|
compilerOptions.allowNonTsExtensions = true;
|
2016-10-26 00:24:21 +02:00
|
|
|
if (changesAffectModuleResolution(this.compilerOptions, compilerOptions)) {
|
|
|
|
// reset cached unresolved imports if changes in compiler options affected module resolution
|
|
|
|
this.cachedUnresolvedImportsPerFile.clear();
|
|
|
|
this.lastCachedUnresolvedImportsList = undefined;
|
|
|
|
}
|
2016-06-22 02:31:54 +02:00
|
|
|
this.compilerOptions = compilerOptions;
|
2016-11-30 08:30:14 +01:00
|
|
|
this.setInternalCompilerOptionsForEmittingJsFiles();
|
2016-06-22 02:31:54 +02:00
|
|
|
this.lsHost.setCompilationSettings(compilerOptions);
|
|
|
|
|
|
|
|
this.markAsDirty();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-18 08:15:22 +02:00
|
|
|
reloadScript(filename: NormalizedPath, tempFileName?: NormalizedPath): boolean {
|
2016-06-24 23:30:45 +02:00
|
|
|
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));
|
2016-10-18 08:15:22 +02:00
|
|
|
script.reloadFromFile(tempFileName);
|
2016-06-24 23:30:45 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2017-01-05 00:56:16 +01:00
|
|
|
/* @internal */
|
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,
|
2016-12-13 22:21:32 +01:00
|
|
|
options: this.getCompilerOptions(),
|
|
|
|
languageServiceDisabled: !this.languageServiceEnabled
|
2016-06-22 02:31:54 +02:00
|
|
|
};
|
2016-12-09 23:44:08 +01:00
|
|
|
const updatedFileNames = this.updatedFileNames;
|
|
|
|
this.updatedFileNames = undefined;
|
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-12-09 23:44:08 +01:00
|
|
|
// if current structure version is the same - return info without any changes
|
2017-04-05 00:51:13 +02:00
|
|
|
if (this.projectStructureVersion === this.lastReportedVersion && !updatedFileNames) {
|
2017-05-24 00:38:03 +02:00
|
|
|
return { info, projectErrors: this.getGlobalProjectErrors() };
|
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;
|
2017-07-07 19:34:36 +02:00
|
|
|
const currentFiles = arrayToSet(this.getFileNames());
|
2016-06-22 02:31:54 +02:00
|
|
|
|
|
|
|
const added: string[] = [];
|
|
|
|
const removed: string[] = [];
|
2017-05-25 23:00:11 +02:00
|
|
|
const updated: string[] = updatedFileNames ? arrayFrom(updatedFileNames.keys()) : [];
|
2016-12-12 16:50:09 +01:00
|
|
|
|
2016-12-28 18:16:38 +01:00
|
|
|
forEachKey(currentFiles, id => {
|
2016-12-05 23:13:32 +01:00
|
|
|
if (!lastReportedFileNames.has(id)) {
|
2016-06-22 02:31:54 +02:00
|
|
|
added.push(id);
|
|
|
|
}
|
2016-12-05 23:13:32 +01:00
|
|
|
});
|
2016-12-28 18:16:38 +01:00
|
|
|
forEachKey(lastReportedFileNames, id => {
|
2016-12-05 23:13:32 +01:00
|
|
|
if (!currentFiles.has(id)) {
|
2016-06-22 02:31:54 +02:00
|
|
|
removed.push(id);
|
|
|
|
}
|
2016-12-05 23:13:32 +01:00
|
|
|
});
|
2016-06-22 02:31:54 +02:00
|
|
|
this.lastReportedFileNames = currentFiles;
|
2016-06-23 01:51:09 +02:00
|
|
|
this.lastReportedVersion = this.projectStructureVersion;
|
2017-05-24 00:38:03 +02:00
|
|
|
return { info, changes: { added, removed, updated }, projectErrors: this.getGlobalProjectErrors() };
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// unknown version - return everything
|
|
|
|
const projectFileNames = this.getFileNames();
|
2017-07-07 19:34:36 +02:00
|
|
|
this.lastReportedFileNames = arrayToSet(projectFileNames);
|
2016-06-23 01:51:09 +02:00
|
|
|
this.lastReportedVersion = this.projectStructureVersion;
|
2017-05-24 00:38:03 +02:00
|
|
|
return { info, files: projectFileNames, projectErrors: this.getGlobalProjectErrors() };
|
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
|
2016-10-28 00:50:21 +02:00
|
|
|
const referencedFiles = createMap<boolean>();
|
2016-08-30 20:47:08 +02:00
|
|
|
if (sourceFile.imports && sourceFile.imports.length > 0) {
|
2016-08-24 01:11:52 +02:00
|
|
|
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) {
|
2016-12-05 23:13:32 +01:00
|
|
|
referencedFiles.set(declarationSourceFile.path, true);
|
2016-08-24 01:11:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const currentDirectory = getDirectoryPath(path);
|
|
|
|
// Handle triple slash references
|
2016-08-30 20:47:08 +02:00
|
|
|
if (sourceFile.referencedFiles && sourceFile.referencedFiles.length > 0) {
|
2016-08-24 01:11:52 +02:00
|
|
|
for (const referencedFile of sourceFile.referencedFiles) {
|
2017-07-15 07:35:07 +02:00
|
|
|
const referencedPath = this.projectService.toPath(referencedFile.fileName, currentDirectory);
|
2016-12-05 23:13:32 +01:00
|
|
|
referencedFiles.set(referencedPath, true);
|
2016-08-24 01:11:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle type reference directives
|
|
|
|
if (sourceFile.resolvedTypeReferenceDirectiveNames) {
|
2016-12-05 23:13:32 +01:00
|
|
|
sourceFile.resolvedTypeReferenceDirectiveNames.forEach((resolvedTypeReferenceDirective) => {
|
2016-08-24 01:11:52 +02:00
|
|
|
if (!resolvedTypeReferenceDirective) {
|
2016-12-05 23:13:32 +01:00
|
|
|
return;
|
2016-08-24 01:11:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const fileName = resolvedTypeReferenceDirective.resolvedFileName;
|
2017-07-15 07:35:07 +02:00
|
|
|
const typeFilePath = this.projectService.toPath(fileName, currentDirectory);
|
2016-12-05 23:13:32 +01:00
|
|
|
referencedFiles.set(typeFilePath, true);
|
2017-02-10 21:24:10 +01:00
|
|
|
});
|
2016-08-24 01:11:52 +02:00
|
|
|
}
|
|
|
|
|
2016-12-28 18:05:52 +01:00
|
|
|
const allFileNames = arrayFrom(referencedFiles.keys()) as Path[];
|
2017-07-12 01:10:44 +02:00
|
|
|
return filter(allFileNames, file => this.lsHost.host.fileExists(file));
|
2016-08-24 01:11:52 +02:00
|
|
|
}
|
|
|
|
|
2016-06-23 02:02:08 +02:00
|
|
|
// remove a root file from project
|
2016-12-30 02:00:04 +01:00
|
|
|
protected removeRoot(info: ScriptInfo): void {
|
2017-05-24 17:42:02 +02:00
|
|
|
orderedRemoveItem(this.rootFiles, info);
|
2017-06-28 22:15:34 +02:00
|
|
|
this.rootFilesMap.delete(info.path);
|
2016-06-23 02:02:08 +02:00
|
|
|
}
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
|
|
|
|
2017-05-25 22:30:27 +02:00
|
|
|
/**
|
|
|
|
* If a file is opened and no tsconfig (or jsconfig) is found,
|
|
|
|
* the file and its imports/references are put into an InferredProject.
|
|
|
|
*/
|
2016-06-23 01:51:09 +02:00
|
|
|
export class InferredProject extends Project {
|
|
|
|
|
2016-11-18 00:12:32 +01:00
|
|
|
private static newName = (() => {
|
|
|
|
let nextId = 1;
|
|
|
|
return () => {
|
|
|
|
const id = nextId;
|
|
|
|
nextId++;
|
|
|
|
return makeInferredProjectName(id);
|
2017-02-10 21:24:10 +01:00
|
|
|
};
|
2016-11-18 00:12:32 +01:00
|
|
|
})();
|
2016-06-23 01:51:09 +02:00
|
|
|
|
2016-12-28 23:46:58 +01:00
|
|
|
private _isJsInferredProject = false;
|
2016-12-29 19:26:34 +01:00
|
|
|
|
2016-12-30 02:00:04 +01:00
|
|
|
toggleJsInferredProject(isJsInferredProject: boolean) {
|
|
|
|
if (isJsInferredProject !== this._isJsInferredProject) {
|
|
|
|
this._isJsInferredProject = isJsInferredProject;
|
|
|
|
this.setCompilerOptions();
|
|
|
|
}
|
2016-12-28 23:46:58 +01:00
|
|
|
}
|
|
|
|
|
2016-12-30 02:00:04 +01:00
|
|
|
setCompilerOptions(options?: CompilerOptions) {
|
|
|
|
// Avoid manipulating the given options directly
|
2017-05-27 02:06:15 +02:00
|
|
|
const newOptions = options ? cloneCompilerOptions(options) : this.getCompilerOptions();
|
2016-12-28 23:46:58 +01:00
|
|
|
if (!newOptions) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._isJsInferredProject && typeof newOptions.maxNodeModuleJsDepth !== "number") {
|
|
|
|
newOptions.maxNodeModuleJsDepth = 2;
|
|
|
|
}
|
2016-12-30 02:00:04 +01:00
|
|
|
else if (!this._isJsInferredProject) {
|
|
|
|
newOptions.maxNodeModuleJsDepth = undefined;
|
|
|
|
}
|
2016-12-28 23:46:58 +01:00
|
|
|
newOptions.allowJs = true;
|
|
|
|
super.setCompilerOptions(newOptions);
|
|
|
|
}
|
|
|
|
|
2016-06-23 01:51:09 +02:00
|
|
|
// Used to keep track of what directories are watched for this project
|
|
|
|
directoriesWatchedForTsconfig: string[] = [];
|
|
|
|
|
2017-07-13 22:08:59 +02:00
|
|
|
constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions) {
|
2016-11-18 00:12:32 +01:00
|
|
|
super(InferredProject.newName(),
|
|
|
|
ProjectKind.Inferred,
|
2016-06-23 01:51:09 +02:00
|
|
|
projectService,
|
|
|
|
documentRegistry,
|
|
|
|
/*files*/ undefined,
|
2016-11-12 02:35:11 +01:00
|
|
|
/*languageServiceEnabled*/ true,
|
2016-08-24 01:11:52 +02:00
|
|
|
compilerOptions,
|
2017-07-12 01:10:44 +02:00
|
|
|
/*compileOnSaveEnabled*/ false,
|
|
|
|
projectService.host);
|
2016-06-23 01:51:09 +02:00
|
|
|
}
|
|
|
|
|
2016-12-30 02:00:04 +01:00
|
|
|
addRoot(info: ScriptInfo) {
|
|
|
|
if (!this._isJsInferredProject && info.isJavaScript()) {
|
|
|
|
this.toggleJsInferredProject(/*isJsInferredProject*/ true);
|
|
|
|
}
|
|
|
|
super.addRoot(info);
|
|
|
|
}
|
|
|
|
|
|
|
|
removeRoot(info: ScriptInfo) {
|
2017-07-06 03:03:11 +02:00
|
|
|
super.removeRoot(info);
|
2016-12-30 02:00:04 +01:00
|
|
|
if (this._isJsInferredProject && info.isJavaScript()) {
|
2017-07-06 03:03:11 +02:00
|
|
|
if (!some(this.getRootScriptInfos(), info => info.isJavaScript())) {
|
2016-12-30 02:00:04 +01:00
|
|
|
this.toggleJsInferredProject(/*isJsInferredProject*/ false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-15 07:35:07 +02:00
|
|
|
isProjectWithSingleRoot() {
|
|
|
|
// - when useSingleInferredProject is not set, we can guarantee that this will be the only root
|
|
|
|
// - other wise it has single root if it has single root script info
|
|
|
|
return !this.projectService.useSingleInferredProject || this.getRootScriptInfos().length === 1;
|
|
|
|
}
|
|
|
|
|
2016-09-14 01:20:42 +02:00
|
|
|
getProjectRootPath() {
|
|
|
|
// Single inferred project does not have a project root.
|
|
|
|
if (this.projectService.useSingleInferredProject) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
const rootFiles = this.getRootFiles();
|
|
|
|
return getDirectoryPath(rootFiles[0]);
|
|
|
|
}
|
|
|
|
|
2016-06-23 01:51:09 +02:00
|
|
|
close() {
|
|
|
|
super.close();
|
|
|
|
|
|
|
|
for (const directory of this.directoriesWatchedForTsconfig) {
|
|
|
|
this.projectService.stopWatchingDirectory(directory);
|
|
|
|
}
|
|
|
|
}
|
2016-08-24 00:15:12 +02:00
|
|
|
|
2016-11-19 02:46:06 +01:00
|
|
|
getTypeAcquisition(): TypeAcquisition {
|
2016-08-24 00:15:12 +02:00
|
|
|
return {
|
2016-11-19 02:46:06 +01:00
|
|
|
enable: allRootFilesAreJsOrDts(this),
|
2016-08-24 00:15:12 +02:00
|
|
|
include: [],
|
|
|
|
exclude: []
|
|
|
|
};
|
|
|
|
}
|
2016-06-23 01:51:09 +02:00
|
|
|
}
|
|
|
|
|
2017-07-07 23:43:00 +02:00
|
|
|
type WildCardDirectoryWatchers = { watcher: FileWatcher, recursive: boolean };
|
|
|
|
|
2017-05-25 22:30:27 +02:00
|
|
|
/**
|
|
|
|
* If a file is opened, the server will look for a tsconfig (or jsconfig)
|
|
|
|
* and if successfull create a ConfiguredProject for it.
|
|
|
|
* Otherwise it will create an InferredProject.
|
|
|
|
*/
|
2016-06-23 01:51:09 +02:00
|
|
|
export class ConfiguredProject extends Project {
|
2016-11-19 02:46:06 +01:00
|
|
|
private typeAcquisition: TypeAcquisition;
|
2017-07-14 05:08:57 +02:00
|
|
|
/* @internal */
|
|
|
|
configFileWatcher: FileWatcher;
|
2017-07-07 23:43:00 +02:00
|
|
|
private directoriesWatchedForWildcards: Map<WildCardDirectoryWatchers>;
|
2017-07-14 05:08:57 +02:00
|
|
|
private typeRootsWatchers: Map<FileWatcher>;
|
2016-11-23 21:34:00 +01:00
|
|
|
readonly canonicalConfigFilePath: NormalizedPath;
|
2016-09-02 00:17:38 +02:00
|
|
|
|
2017-07-13 08:15:03 +02:00
|
|
|
/* @internal */
|
|
|
|
pendingReload: boolean;
|
|
|
|
|
2017-07-11 22:38:12 +02:00
|
|
|
/*@internal*/
|
|
|
|
configFileSpecs: ConfigFileSpecs;
|
|
|
|
|
2017-02-14 22:35:16 +01:00
|
|
|
private plugins: PluginModule[] = [];
|
|
|
|
|
2016-06-22 02:31:54 +02:00
|
|
|
/** Used for configured projects which may have multiple open roots */
|
|
|
|
openRefCount = 0;
|
|
|
|
|
2017-04-14 01:16:57 +02:00
|
|
|
constructor(configFileName: NormalizedPath,
|
2016-06-22 02:31:54 +02:00
|
|
|
projectService: ProjectService,
|
2017-07-13 22:08:59 +02:00
|
|
|
documentRegistry: DocumentRegistry,
|
2016-06-22 02:31:54 +02:00
|
|
|
hasExplicitListOfFiles: boolean,
|
|
|
|
compilerOptions: CompilerOptions,
|
2016-08-24 01:11:52 +02:00
|
|
|
languageServiceEnabled: boolean,
|
2017-07-12 01:10:44 +02:00
|
|
|
public compileOnSaveEnabled: boolean,
|
|
|
|
cachedServerHost: CachedServerHost) {
|
|
|
|
super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, cachedServerHost);
|
2016-11-23 21:34:00 +01:00
|
|
|
this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName));
|
2017-02-14 22:35:16 +01:00
|
|
|
this.enablePlugins();
|
2016-11-18 00:12:32 +01:00
|
|
|
}
|
|
|
|
|
2017-07-13 08:15:03 +02:00
|
|
|
/**
|
|
|
|
* Checks if the project has reload from disk pending, if thats pending, it reloads (and then updates graph as part of that) instead of just updating the graph
|
|
|
|
* @returns: true if set of files in the project stays the same and false - otherwise.
|
|
|
|
*/
|
|
|
|
updateGraph(): boolean {
|
|
|
|
if (this.pendingReload) {
|
|
|
|
this.pendingReload = false;
|
|
|
|
this.projectService.reloadConfiguredProject(this);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return super.updateGraph();
|
|
|
|
}
|
|
|
|
|
2017-07-12 01:10:44 +02:00
|
|
|
getCachedServerHost() {
|
|
|
|
return this.lsHost.host as CachedServerHost;
|
|
|
|
}
|
|
|
|
|
2016-11-18 00:12:32 +01:00
|
|
|
getConfigFilePath() {
|
2017-07-14 05:08:57 +02:00
|
|
|
return asNormalizedPath(this.getProjectName());
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
|
|
|
|
2017-02-14 22:35:16 +01:00
|
|
|
enablePlugins() {
|
|
|
|
const host = this.projectService.host;
|
|
|
|
const options = this.getCompilerOptions();
|
|
|
|
|
|
|
|
if (!host.require) {
|
|
|
|
this.projectService.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-04-14 01:16:57 +02:00
|
|
|
// Search our peer node_modules, then any globally-specified probe paths
|
|
|
|
// ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/
|
|
|
|
const searchPaths = [combinePaths(host.getExecutingFilePath(), "../../.."), ...this.projectService.pluginProbeLocations];
|
|
|
|
|
2017-05-18 04:52:57 +02:00
|
|
|
if (this.projectService.allowLocalPluginLoads) {
|
|
|
|
const local = getDirectoryPath(this.canonicalConfigFilePath);
|
|
|
|
this.projectService.logger.info(`Local plugin loading enabled; adding ${local} to search paths`);
|
|
|
|
searchPaths.unshift(local);
|
|
|
|
}
|
|
|
|
|
2017-04-14 01:16:57 +02:00
|
|
|
// Enable tsconfig-specified plugins
|
2017-04-14 01:02:24 +02:00
|
|
|
if (options.plugins) {
|
|
|
|
for (const pluginConfigEntry of options.plugins) {
|
|
|
|
this.enablePlugin(pluginConfigEntry, searchPaths);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.projectService.globalPlugins) {
|
|
|
|
// Enable global plugins with synthetic configuration entries
|
|
|
|
for (const globalPluginName of this.projectService.globalPlugins) {
|
|
|
|
// Skip already-locally-loaded plugins
|
|
|
|
if (options.plugins && options.plugins.some(p => p.name === globalPluginName)) continue;
|
|
|
|
|
|
|
|
// Provide global: true so plugins can detect why they can't find their config
|
|
|
|
this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths);
|
|
|
|
}
|
2017-04-14 01:16:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[]) {
|
|
|
|
const log = (message: string) => {
|
|
|
|
this.projectService.logger.info(message);
|
|
|
|
};
|
|
|
|
|
|
|
|
for (const searchPath of searchPaths) {
|
|
|
|
const resolvedModule = <PluginModuleFactory>Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log);
|
2017-02-14 22:35:16 +01:00
|
|
|
if (resolvedModule) {
|
|
|
|
this.enableProxy(resolvedModule, pluginConfigEntry);
|
2017-04-14 01:16:57 +02:00
|
|
|
return;
|
2017-02-14 22:35:16 +01:00
|
|
|
}
|
|
|
|
}
|
2017-04-14 01:16:57 +02:00
|
|
|
this.projectService.logger.info(`Couldn't find ${pluginConfigEntry.name} anywhere in paths: ${searchPaths.join(",")}`);
|
2017-02-14 22:35:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private enableProxy(pluginModuleFactory: PluginModuleFactory, configEntry: PluginImport) {
|
|
|
|
try {
|
|
|
|
if (typeof pluginModuleFactory !== "function") {
|
|
|
|
this.projectService.logger.info(`Skipped loading plugin ${configEntry.name} because it did expose a proper factory function`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const info: PluginCreateInfo = {
|
|
|
|
config: configEntry,
|
|
|
|
project: this,
|
|
|
|
languageService: this.languageService,
|
|
|
|
languageServiceHost: this.lsHost,
|
|
|
|
serverHost: this.projectService.host
|
|
|
|
};
|
|
|
|
|
|
|
|
const pluginModule = pluginModuleFactory({ typescript: ts });
|
|
|
|
this.languageService = pluginModule.create(info);
|
|
|
|
this.plugins.push(pluginModule);
|
|
|
|
}
|
|
|
|
catch (e) {
|
|
|
|
this.projectService.logger.info(`Plugin activation failed: ${e}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-14 01:20:42 +02:00
|
|
|
getProjectRootPath() {
|
2016-11-18 00:12:32 +01:00
|
|
|
return getDirectoryPath(this.getConfigFilePath());
|
2016-09-14 01:20:42 +02:00
|
|
|
}
|
|
|
|
|
2016-08-26 23:37:49 +02:00
|
|
|
setProjectErrors(projectErrors: Diagnostic[]) {
|
|
|
|
this.projectErrors = projectErrors;
|
|
|
|
}
|
|
|
|
|
2016-11-19 02:46:06 +01:00
|
|
|
setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void {
|
|
|
|
this.typeAcquisition = newTypeAcquisition;
|
2016-08-24 00:15:12 +02:00
|
|
|
}
|
|
|
|
|
2016-11-19 02:46:06 +01:00
|
|
|
getTypeAcquisition() {
|
|
|
|
return this.typeAcquisition;
|
2016-08-12 20:04:43 +02:00
|
|
|
}
|
|
|
|
|
2017-04-22 00:20:55 +02:00
|
|
|
getExternalFiles(): SortedReadonlyArray<string> {
|
2017-06-05 23:11:43 +02:00
|
|
|
return toSortedReadonlyArray(flatMap(this.plugins, plugin => {
|
|
|
|
if (typeof plugin.getExternalFiles !== "function") return;
|
|
|
|
try {
|
|
|
|
return plugin.getExternalFiles(this);
|
2017-02-14 22:35:16 +01:00
|
|
|
}
|
2017-06-05 23:11:43 +02:00
|
|
|
catch (e) {
|
|
|
|
this.projectService.logger.info(`A plugin threw an exception in getExternalFiles: ${e}`);
|
|
|
|
}
|
|
|
|
}));
|
2017-02-14 22:35:16 +01:00
|
|
|
}
|
|
|
|
|
2017-07-07 23:43:00 +02:00
|
|
|
watchWildcards(wildcardDirectories: Map<WatchDirectoryFlags>) {
|
2017-07-14 05:08:57 +02:00
|
|
|
this.directoriesWatchedForWildcards = mutateExistingMap(
|
|
|
|
this.directoriesWatchedForWildcards, wildcardDirectories,
|
|
|
|
// Watcher is same if the recursive flags match
|
|
|
|
({ recursive: existingRecursive }, flag) => {
|
|
|
|
// If the recursive dont match, it needs update
|
|
|
|
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
|
|
|
|
return existingRecursive !== recursive;
|
|
|
|
},
|
2017-07-15 07:35:07 +02:00
|
|
|
// Create new watch and recursive info
|
2017-07-14 05:08:57 +02:00
|
|
|
(directory, flag) => {
|
|
|
|
const recursive = (flag & WatchDirectoryFlags.Recursive) !== 0;
|
|
|
|
return {
|
|
|
|
watcher: this.projectService.addDirectoryWatcher(
|
|
|
|
WatchType.WildCardDirectories, this, directory,
|
|
|
|
path => this.projectService.onFileAddOrRemoveInWatchedDirectoryOfProject(this, path),
|
|
|
|
recursive
|
|
|
|
),
|
|
|
|
recursive
|
|
|
|
};
|
|
|
|
},
|
|
|
|
// Close existing watch thats not needed any more
|
|
|
|
(directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher(
|
|
|
|
WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.NotNeeded
|
|
|
|
),
|
|
|
|
// Close existing watch that doesnt match in recursive flag
|
|
|
|
(directory, { watcher, recursive }) => this.projectService.closeDirectoryWatcher(
|
|
|
|
WatchType.WildCardDirectories, this, directory, watcher, recursive, WatcherCloseReason.RecursiveChanged
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
stopWatchingWildCards(reason: WatcherCloseReason) {
|
|
|
|
cleanExistingMap(
|
|
|
|
this.directoriesWatchedForWildcards,
|
|
|
|
(directory, { watcher, recursive }) =>
|
|
|
|
this.projectService.closeDirectoryWatcher(WatchType.WildCardDirectories, this,
|
|
|
|
directory, watcher, recursive, reason)
|
|
|
|
);
|
|
|
|
this.directoriesWatchedForWildcards = undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
watchTypeRoots() {
|
|
|
|
const newTypeRoots = arrayToSet(this.getEffectiveTypeRoots(), dir => this.projectService.toCanonicalFileName(dir));
|
|
|
|
this.typeRootsWatchers = mutateExistingMapWithNewSet(
|
|
|
|
this.typeRootsWatchers, newTypeRoots,
|
|
|
|
// Create new watch
|
|
|
|
root => this.projectService.addDirectoryWatcher(WatchType.TypeRoot, this, root,
|
|
|
|
path => this.projectService.onTypeRootFileChanged(this, path), /*recursive*/ false
|
|
|
|
),
|
|
|
|
// Close existing watch thats not needed any more
|
|
|
|
(directory, watcher) => this.projectService.closeDirectoryWatcher(
|
|
|
|
WatchType.TypeRoot, this, directory, watcher, /*recursive*/ false, WatcherCloseReason.NotNeeded
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
stopWatchingTypeRoots(reason: WatcherCloseReason) {
|
|
|
|
cleanExistingMap(
|
|
|
|
this.typeRootsWatchers,
|
|
|
|
(directory, watcher) =>
|
|
|
|
this.projectService.closeDirectoryWatcher(WatchType.TypeRoot, this,
|
|
|
|
directory, watcher, /*recursive*/ false, reason)
|
|
|
|
);
|
|
|
|
this.typeRootsWatchers = undefined;
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
close() {
|
|
|
|
super.close();
|
|
|
|
|
2017-07-07 23:43:00 +02:00
|
|
|
if (this.configFileWatcher) {
|
2017-07-14 05:08:57 +02:00
|
|
|
this.projectService.closeFileWatcher(WatchType.ConfigFilePath, this, this.getConfigFilePath(), this.configFileWatcher, WatcherCloseReason.ProjectClose);
|
2017-07-07 23:43:00 +02:00
|
|
|
this.configFileWatcher = undefined;
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
|
|
|
|
2017-07-14 05:08:57 +02:00
|
|
|
this.stopWatchingTypeRoots(WatcherCloseReason.ProjectClose);
|
|
|
|
this.stopWatchingWildCards(WatcherCloseReason.ProjectClose);
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
addOpenRef() {
|
|
|
|
this.openRefCount++;
|
|
|
|
}
|
|
|
|
|
|
|
|
deleteOpenRef() {
|
|
|
|
this.openRefCount--;
|
|
|
|
return this.openRefCount;
|
|
|
|
}
|
2016-09-20 01:47:15 +02:00
|
|
|
|
|
|
|
getEffectiveTypeRoots() {
|
2017-07-13 23:13:14 +02:00
|
|
|
return getEffectiveTypeRoots(this.getCompilerOptions(), this.lsHost.host) || [];
|
2016-09-20 01:47:15 +02:00
|
|
|
}
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
|
|
|
|
2017-05-25 22:30:27 +02:00
|
|
|
/**
|
|
|
|
* Project whose configuration is handled externally, such as in a '.csproj'.
|
|
|
|
* These are created only if a host explicitly calls `openExternalProject`.
|
|
|
|
*/
|
2016-06-23 01:51:09 +02:00
|
|
|
export class ExternalProject extends Project {
|
2016-11-19 02:46:06 +01:00
|
|
|
private typeAcquisition: TypeAcquisition;
|
2017-03-17 20:56:31 +01:00
|
|
|
constructor(public externalProjectName: string,
|
2016-06-22 02:31:54 +02:00
|
|
|
projectService: ProjectService,
|
2017-07-13 22:08:59 +02:00
|
|
|
documentRegistry: DocumentRegistry,
|
2016-06-22 02:31:54 +02:00
|
|
|
compilerOptions: CompilerOptions,
|
2016-08-24 01:11:52 +02:00
|
|
|
languageServiceEnabled: boolean,
|
2016-09-14 01:20:42 +02:00
|
|
|
public compileOnSaveEnabled: boolean,
|
|
|
|
private readonly projectFilePath?: string) {
|
2017-07-12 01:10:44 +02:00
|
|
|
super(externalProjectName, ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, languageServiceEnabled, compilerOptions, compileOnSaveEnabled, projectService.host);
|
2016-08-24 00:15:12 +02:00
|
|
|
}
|
|
|
|
|
2016-09-14 01:20:42 +02:00
|
|
|
getProjectRootPath() {
|
|
|
|
if (this.projectFilePath) {
|
|
|
|
return getDirectoryPath(this.projectFilePath);
|
|
|
|
}
|
|
|
|
// if the projectFilePath is not given, we make the assumption that the project name
|
|
|
|
// is the path of the project file. AS the project name is provided by VS, we need to
|
|
|
|
// normalize slashes before using it as a file name.
|
2016-11-18 00:12:32 +01:00
|
|
|
return getDirectoryPath(normalizeSlashes(this.getProjectName()));
|
2016-09-14 01:20:42 +02:00
|
|
|
}
|
|
|
|
|
2016-11-19 02:46:06 +01:00
|
|
|
getTypeAcquisition() {
|
|
|
|
return this.typeAcquisition;
|
2016-08-24 00:15:12 +02:00
|
|
|
}
|
|
|
|
|
2016-08-26 23:37:49 +02:00
|
|
|
setProjectErrors(projectErrors: Diagnostic[]) {
|
|
|
|
this.projectErrors = projectErrors;
|
|
|
|
}
|
|
|
|
|
2016-11-19 02:46:06 +01:00
|
|
|
setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void {
|
|
|
|
if (!newTypeAcquisition) {
|
2016-08-24 00:15:12 +02:00
|
|
|
// set default typings options
|
2016-11-19 02:46:06 +01:00
|
|
|
newTypeAcquisition = {
|
|
|
|
enable: allRootFilesAreJsOrDts(this),
|
2016-08-24 00:15:12 +02:00
|
|
|
include: [],
|
|
|
|
exclude: []
|
|
|
|
};
|
|
|
|
}
|
|
|
|
else {
|
2016-11-19 02:46:06 +01:00
|
|
|
if (newTypeAcquisition.enable === undefined) {
|
2016-08-24 00:15:12 +02:00
|
|
|
// if autoDiscovery was not specified by the caller - set it based on the content of the project
|
2016-11-19 02:46:06 +01:00
|
|
|
newTypeAcquisition.enable = allRootFilesAreJsOrDts(this);
|
2016-08-24 00:15:12 +02:00
|
|
|
}
|
2016-11-19 02:46:06 +01:00
|
|
|
if (!newTypeAcquisition.include) {
|
|
|
|
newTypeAcquisition.include = [];
|
2016-08-24 00:15:12 +02:00
|
|
|
}
|
2016-11-19 02:46:06 +01:00
|
|
|
if (!newTypeAcquisition.exclude) {
|
|
|
|
newTypeAcquisition.exclude = [];
|
2016-08-24 00:15:12 +02:00
|
|
|
}
|
|
|
|
}
|
2016-11-19 02:46:06 +01:00
|
|
|
this.typeAcquisition = newTypeAcquisition;
|
2016-06-22 02:31:54 +02:00
|
|
|
}
|
|
|
|
}
|
2017-05-24 00:38:03 +02:00
|
|
|
}
|