mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-12-12 11:36:43 -06:00
[in progress] project system work
This commit is contained in:
parent
9763dc825e
commit
c9b82eddda
@ -101,7 +101,11 @@ var servicesSources = [
|
||||
|
||||
var serverCoreSources = [
|
||||
"node.d.ts",
|
||||
"utilities.ts",
|
||||
"scriptVersionCache.ts",
|
||||
"scriptInfo.ts",
|
||||
"lsHost.ts",
|
||||
"project.ts",
|
||||
"editorServices.ts",
|
||||
"protocol.d.ts",
|
||||
"session.ts",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
160
src/server/lshost.ts
Normal file
160
src/server/lshost.ts
Normal file
@ -0,0 +1,160 @@
|
||||
/// <reference path="..\services\services.ts" />
|
||||
/// <reference path="utilities.ts" />
|
||||
/// <reference path="scriptInfo.ts" />
|
||||
|
||||
namespace ts.server {
|
||||
export class LSHost implements ts.LanguageServiceHost, ModuleResolutionHost, ServerLanguageServiceHost {
|
||||
private compilationSettings: ts.CompilerOptions;
|
||||
private readonly resolvedModuleNames: ts.FileMap<Map<ResolvedModuleWithFailedLookupLocations>>;
|
||||
private readonly resolvedTypeReferenceDirectives: ts.FileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>;
|
||||
private readonly getCanonicalFileName: (fileName: string) => string;
|
||||
|
||||
constructor(private readonly host: ServerHost, private readonly project: Project, private readonly cancellationToken: HostCancellationToken) {
|
||||
this.getCanonicalFileName = ts.createGetCanonicalFileName(this.host.useCaseSensitiveFileNames);
|
||||
this.resolvedModuleNames = createFileMap<Map<ResolvedModuleWithFailedLookupLocations>>();
|
||||
this.resolvedTypeReferenceDirectives = createFileMap<Map<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
|
||||
}
|
||||
|
||||
private resolveNamesWithLocalCache<T extends { failedLookupLocations: string[] }, R>(
|
||||
names: string[],
|
||||
containingFile: string,
|
||||
cache: ts.FileMap<Map<T>>,
|
||||
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost) => T,
|
||||
getResult: (s: T) => R): R[] {
|
||||
|
||||
const path = toPath(containingFile, this.host.getCurrentDirectory(), this.getCanonicalFileName);
|
||||
const currentResolutionsInFile = cache.get(path);
|
||||
|
||||
const newResolutions: Map<T> = {};
|
||||
const resolvedModules: R[] = [];
|
||||
const compilerOptions = this.getCompilationSettings();
|
||||
|
||||
for (const name of names) {
|
||||
// check if this is a duplicate entry in the list
|
||||
let resolution = lookUp(newResolutions, name);
|
||||
if (!resolution) {
|
||||
const existingResolution = currentResolutionsInFile && ts.lookUp(currentResolutionsInFile, name);
|
||||
if (moduleResolutionIsValid(existingResolution)) {
|
||||
// ok, it is safe to use existing name resolution results
|
||||
resolution = existingResolution;
|
||||
}
|
||||
else {
|
||||
newResolutions[name] = resolution = loader(name, containingFile, compilerOptions, this);
|
||||
}
|
||||
}
|
||||
|
||||
ts.Debug.assert(resolution !== undefined);
|
||||
|
||||
resolvedModules.push(getResult(resolution));
|
||||
}
|
||||
|
||||
// replace old results with a new one
|
||||
cache.set(path, newResolutions);
|
||||
return resolvedModules;
|
||||
|
||||
function moduleResolutionIsValid(resolution: T): boolean {
|
||||
if (!resolution) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getResult(resolution)) {
|
||||
// TODO: consider checking failedLookupLocations
|
||||
return true;
|
||||
}
|
||||
|
||||
// consider situation if we have no candidate locations as valid resolution.
|
||||
// after all there is no point to invalidate it if we have no idea where to look for the module.
|
||||
return resolution.failedLookupLocations.length === 0;
|
||||
}
|
||||
}
|
||||
|
||||
getProjectVersion() {
|
||||
return this.project.getProjectVersion();
|
||||
}
|
||||
|
||||
getCancellationToken() {
|
||||
return this.cancellationToken;
|
||||
}
|
||||
|
||||
resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[] {
|
||||
return this.resolveNamesWithLocalCache(typeDirectiveNames, containingFile, this.resolvedTypeReferenceDirectives, resolveTypeReferenceDirective, m => m.resolvedTypeReferenceDirective);
|
||||
}
|
||||
|
||||
resolveModuleNames(moduleNames: string[], containingFile: string): ResolvedModule[] {
|
||||
return this.resolveNamesWithLocalCache(moduleNames, containingFile, this.resolvedModuleNames, resolveModuleName, m => m.resolvedModule);
|
||||
}
|
||||
|
||||
getDefaultLibFileName() {
|
||||
const nodeModuleBinDir = ts.getDirectoryPath(ts.normalizePath(this.host.getExecutingFilePath()));
|
||||
return ts.combinePaths(nodeModuleBinDir, ts.getDefaultLibFileName(this.compilationSettings));
|
||||
}
|
||||
|
||||
getScriptSnapshot(filename: string): ts.IScriptSnapshot {
|
||||
const scriptInfo = this.project.getScriptInfo(filename);
|
||||
if (scriptInfo) {
|
||||
return scriptInfo.snap();
|
||||
}
|
||||
}
|
||||
|
||||
setCompilationSettings(opt: ts.CompilerOptions) {
|
||||
this.compilationSettings = opt;
|
||||
// conservatively assume that changing compiler options might affect module resolution strategy
|
||||
this.resolvedModuleNames.clear();
|
||||
this.resolvedTypeReferenceDirectives.clear();
|
||||
}
|
||||
|
||||
getCompilationSettings() {
|
||||
// change this to return active project settings for file
|
||||
return this.compilationSettings;
|
||||
}
|
||||
|
||||
getScriptFileNames() {
|
||||
return this.project.getRootFiles();
|
||||
}
|
||||
|
||||
getScriptKind(fileName: string) {
|
||||
const info = this.project.getScriptInfo(fileName);
|
||||
return info && info.scriptKind;
|
||||
}
|
||||
|
||||
getScriptVersion(filename: string) {
|
||||
return this.project.getScriptInfo(filename).getLatestVersion();
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
removeReferencedFile(info: ScriptInfo) {
|
||||
if (!info.isOpen) {
|
||||
this.resolvedModuleNames.remove(info.path);
|
||||
this.resolvedTypeReferenceDirectives.remove(info.path);
|
||||
}
|
||||
}
|
||||
|
||||
removeRoot(info: ScriptInfo) {
|
||||
this.resolvedModuleNames.remove(info.path);
|
||||
this.resolvedTypeReferenceDirectives.remove(info.path);
|
||||
}
|
||||
|
||||
resolvePath(path: string): string {
|
||||
return this.host.resolvePath(path);
|
||||
}
|
||||
|
||||
fileExists(path: string): boolean {
|
||||
return this.host.fileExists(path);
|
||||
}
|
||||
|
||||
directoryExists(path: string): boolean {
|
||||
return this.host.directoryExists(path);
|
||||
}
|
||||
|
||||
readFile(fileName: string): string {
|
||||
return this.host.readFile(fileName);
|
||||
}
|
||||
|
||||
getDirectories(path: string): string[] {
|
||||
return this.host.getDirectories(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
396
src/server/project.ts
Normal file
396
src/server/project.ts
Normal file
@ -0,0 +1,396 @@
|
||||
/// <reference path="..\services\services.ts" />
|
||||
/// <reference path="scriptInfo.ts"/>
|
||||
/// <reference path="lshost.ts"/>
|
||||
|
||||
namespace ts.server {
|
||||
export enum ProjectKind {
|
||||
Inferred,
|
||||
Configured,
|
||||
External
|
||||
}
|
||||
|
||||
export abstract class Project {
|
||||
private rootFiles: ScriptInfo[] = [];
|
||||
private rootFilesMap: FileMap<ScriptInfo> = createFileMap<ScriptInfo>();
|
||||
private lsHost: ServerLanguageServiceHost;
|
||||
protected program: ts.Program;
|
||||
private version = 0;
|
||||
|
||||
languageService: LanguageService;
|
||||
|
||||
constructor(
|
||||
readonly projectKind: ProjectKind,
|
||||
readonly projectService: ProjectService,
|
||||
private documentRegistry: ts.DocumentRegistry,
|
||||
hasExplicitListOfFiles: boolean,
|
||||
public languageServiceEnabled: boolean,
|
||||
private compilerOptions: CompilerOptions) {
|
||||
|
||||
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();
|
||||
}
|
||||
this.markAsDirty();
|
||||
}
|
||||
|
||||
getProjectVersion() {
|
||||
return this.version.toString();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
close() {
|
||||
for (const fileName of this.getFileNames()) {
|
||||
const info = this.projectKind.getScriptInfoForNormalizedPath(fileName);
|
||||
info.detachFromProject(project);
|
||||
}
|
||||
// signal language service to release files acquired from document registry
|
||||
this.languageService.dispose();
|
||||
|
||||
}
|
||||
|
||||
getCompilerOptions() {
|
||||
return this.compilerOptions;
|
||||
}
|
||||
|
||||
getRootFiles() {
|
||||
return this.rootFiles.map(info => info.fileName);
|
||||
}
|
||||
|
||||
getFileNames() {
|
||||
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));
|
||||
}
|
||||
|
||||
containsScriptInfo(info: ScriptInfo): boolean {
|
||||
return this.program && this.program.getSourceFileByPath(info.path) !== undefined;
|
||||
}
|
||||
|
||||
containsFile(filename: NormalizedPath, requireOpen?: boolean) {
|
||||
const info = this.projectService.getScriptInfoForNormalizedPath(filename);
|
||||
if (info) {
|
||||
if ((!requireOpen) || info.isOpen) {
|
||||
return this.containsScriptInfo(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isRoot(info: ScriptInfo) {
|
||||
return this.rootFilesMap.contains(info.path);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
this.markAsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
removeFile(info: ScriptInfo) {
|
||||
if (!this.removeRoot(info)) {
|
||||
this.removeReferencedFile(info)
|
||||
}
|
||||
info.detachFromProject(this);
|
||||
this.markAsDirty();
|
||||
}
|
||||
|
||||
markAsDirty() {
|
||||
this.version++;
|
||||
}
|
||||
|
||||
// remove a root file from project
|
||||
private removeRoot(info: ScriptInfo): boolean {
|
||||
if (this.isRoot(info)) {
|
||||
this.rootFiles = copyListRemovingItem(info, this.rootFiles);
|
||||
this.rootFilesMap.remove(info.path);
|
||||
this.lsHost.removeRoot(info);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private removeReferencedFile(info: ScriptInfo) {
|
||||
this.lsHost.removeReferencedFile(info)
|
||||
this.updateGraph();
|
||||
}
|
||||
|
||||
updateGraph() {
|
||||
this.program = this.languageService.getProgram();
|
||||
}
|
||||
|
||||
getScriptInfo(uncheckedFileName: string) {
|
||||
const scriptInfo = this.projectService.getOrCreateScriptInfo(toNormalizedPath(uncheckedFileName), /*openedByClient*/ false);
|
||||
if (scriptInfo.attachToProject(this)) {
|
||||
this.markAsDirty();
|
||||
}
|
||||
return scriptInfo;
|
||||
}
|
||||
|
||||
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) {
|
||||
compilerOptions.allowNonTsExtensions = true;
|
||||
this.compilerOptions = compilerOptions;
|
||||
this.lsHost.setCompilationSettings(compilerOptions);
|
||||
|
||||
this.markAsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
saveTo(filename: string, tmpfilename: string) {
|
||||
const script = this.getScriptInfo(filename);
|
||||
if (script) {
|
||||
const snap = script.snap();
|
||||
this.projectService.host.writeFile(tmpfilename, snap.getText(0, snap.getLength()));
|
||||
}
|
||||
}
|
||||
|
||||
reloadScript(filename: string, tmpfilename: string, cb: () => void) {
|
||||
const script = this.getScriptInfo(filename);
|
||||
if (script) {
|
||||
script.reloadFromFile(filename, cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class InferredProject extends Project {
|
||||
|
||||
static NextId = 0;
|
||||
|
||||
readonly inferredProjectName;
|
||||
// Used to keep track of what directories are watched for this project
|
||||
directoriesWatchedForTsconfig: string[] = [];
|
||||
|
||||
constructor(projectService: ProjectService, documentRegistry: ts.DocumentRegistry, languageServiceEnabled: boolean) {
|
||||
super(ProjectKind.Inferred,
|
||||
projectService,
|
||||
documentRegistry,
|
||||
/*files*/ undefined,
|
||||
languageServiceEnabled,
|
||||
/*compilerOptions*/ undefined);
|
||||
|
||||
this.inferredProjectName = makeInferredProjectName(InferredProject.NextId++);
|
||||
}
|
||||
|
||||
getProjectName() {
|
||||
return this.inferredProjectName;
|
||||
}
|
||||
|
||||
close() {
|
||||
super.close();
|
||||
|
||||
for (const directory of this.directoriesWatchedForTsconfig) {
|
||||
this.projectService.stopWatchingDirectory(directory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class VersionedProject extends Project {
|
||||
|
||||
private lastReportedFileNames: Map<string>;
|
||||
private lastReportedVersion: number = 0;
|
||||
currentVersion: number = 1;
|
||||
|
||||
updateGraph() {
|
||||
if (!this.languageServiceEnabled) {
|
||||
return;
|
||||
}
|
||||
const oldProgram = this.program;
|
||||
|
||||
super.updateGraph();
|
||||
|
||||
if (!oldProgram || !oldProgram.structureIsReused) {
|
||||
this.currentVersion++;
|
||||
}
|
||||
}
|
||||
|
||||
getChangesSinceVersion(lastKnownVersion?: number): protocol.ExternalProjectFiles {
|
||||
const info = {
|
||||
projectName: this.getProjectName(),
|
||||
version: this.currentVersion
|
||||
};
|
||||
if (this.lastReportedFileNames && lastKnownVersion === this.lastReportedVersion) {
|
||||
if (this.currentVersion == this.lastReportedVersion) {
|
||||
return { info };
|
||||
}
|
||||
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;
|
||||
this.lastReportedVersion = this.currentVersion;
|
||||
return { info, changes: { added, removed } };
|
||||
}
|
||||
else {
|
||||
// unknown version - return everything
|
||||
const projectFileNames = this.getFileNames();
|
||||
this.lastReportedFileNames = arrayToMap(projectFileNames, x => x);
|
||||
this.lastReportedVersion = this.currentVersion;
|
||||
return { info, files: projectFileNames };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfiguredProject extends VersionedProject {
|
||||
private projectFileWatcher: FileWatcher;
|
||||
private directoryWatcher: FileWatcher;
|
||||
private directoriesWatchedForWildcards: Map<FileWatcher>;
|
||||
/** Used for configured projects which may have multiple open roots */
|
||||
openRefCount = 0;
|
||||
|
||||
constructor(readonly configFileName: string,
|
||||
projectService: ProjectService,
|
||||
documentRegistry: ts.DocumentRegistry,
|
||||
hasExplicitListOfFiles: boolean,
|
||||
compilerOptions: CompilerOptions,
|
||||
private wildcardDirectories: Map<WatchDirectoryFlags>,
|
||||
languageServiceEnabled: boolean) {
|
||||
super(ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions);
|
||||
}
|
||||
|
||||
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);
|
||||
this.projectService.log(`Add recursive watcher for: ${directoryToWatch}`);
|
||||
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;
|
||||
this.projectService.log(`Add ${recursive ? "recursive " : ""}watcher for: ${directory}`);
|
||||
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();
|
||||
}
|
||||
|
||||
forEachValue(this.directoriesWatchedForWildcards, watcher => { watcher.close(); });
|
||||
this.directoriesWatchedForWildcards = undefined;
|
||||
|
||||
this.stopWatchingDirectory();
|
||||
}
|
||||
|
||||
addOpenRef() {
|
||||
this.openRefCount++;
|
||||
}
|
||||
|
||||
deleteOpenRef() {
|
||||
this.openRefCount--;
|
||||
return this.openRefCount;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExternalProject extends VersionedProject {
|
||||
constructor(readonly externalProjectName: string,
|
||||
projectService: ProjectService,
|
||||
documentRegistry: ts.DocumentRegistry,
|
||||
compilerOptions: CompilerOptions,
|
||||
languageServiceEnabled: boolean) {
|
||||
super(ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, languageServiceEnabled, compilerOptions);
|
||||
}
|
||||
|
||||
getProjectName() {
|
||||
return this.externalProjectName;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
src/server/protocol.d.ts
vendored
2
src/server/protocol.d.ts
vendored
@ -495,7 +495,7 @@ declare namespace ts.server.protocol {
|
||||
}
|
||||
|
||||
export interface ExternalProjectInfo {
|
||||
projectFileName: string;
|
||||
projectName: string;
|
||||
version: number;
|
||||
}
|
||||
|
||||
|
||||
151
src/server/scriptInfo.ts
Normal file
151
src/server/scriptInfo.ts
Normal file
@ -0,0 +1,151 @@
|
||||
/// <reference path="scriptVersionCache.ts"/>
|
||||
|
||||
namespace ts.server {
|
||||
|
||||
export class ScriptInfo {
|
||||
private svc: ScriptVersionCache;
|
||||
/**
|
||||
* All projects that include this file
|
||||
*/
|
||||
readonly containingProjects: Project[] = [];
|
||||
|
||||
private fileWatcher: FileWatcher;
|
||||
formatCodeSettings: ts.FormatCodeSettings;
|
||||
readonly path: Path;
|
||||
|
||||
constructor(
|
||||
private readonly host: ServerHost,
|
||||
readonly fileName: NormalizedPath,
|
||||
content: string,
|
||||
readonly scriptKind: ScriptKind,
|
||||
public isOpen = false) {
|
||||
|
||||
this.path = toPath(fileName, host.getCurrentDirectory(), createGetCanonicalFileName(host.useCaseSensitiveFileNames));
|
||||
this.svc = ScriptVersionCache.fromString(host, content);
|
||||
this.formatCodeSettings = getDefaultFormatCodeSettings(this.host);
|
||||
this.scriptKind = scriptKind && scriptKind !== ScriptKind.Unknown
|
||||
? scriptKind
|
||||
: getScriptKindFromFileName(fileName);
|
||||
}
|
||||
|
||||
attachToProject(project: Project): boolean {
|
||||
if (!contains(this.containingProjects, project)) {
|
||||
this.containingProjects.push(project);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
detachFromProject(project: Project) {
|
||||
const index = this.containingProjects.indexOf(project);
|
||||
if (index < 0) {
|
||||
// TODO: (assert?) attempt to detach file from project that didn't include this file
|
||||
return;
|
||||
}
|
||||
removeItemFromSet(this.containingProjects, project);
|
||||
}
|
||||
|
||||
detachAllProjects() {
|
||||
for (const p of this.containingProjects) {
|
||||
p.removeFile(this);
|
||||
}
|
||||
this.containingProjects.length = 0;
|
||||
}
|
||||
|
||||
getDefaultProject() {
|
||||
Debug.assert(this.containingProjects.length !== 0);
|
||||
return this.containingProjects[0];
|
||||
}
|
||||
|
||||
setFormatOptions(formatSettings: protocol.FormatOptions): void {
|
||||
if (formatSettings) {
|
||||
mergeMaps(this.formatCodeSettings, formatSettings);
|
||||
}
|
||||
}
|
||||
|
||||
setWatcher(watcher: FileWatcher): void {
|
||||
this.stopWatcher();
|
||||
this.fileWatcher = watcher;
|
||||
}
|
||||
|
||||
stopWatcher() {
|
||||
if (this.fileWatcher) {
|
||||
this.fileWatcher.close();
|
||||
this.fileWatcher = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
getLatestVersion() {
|
||||
return this.svc.latestVersion().toString();
|
||||
}
|
||||
|
||||
reload(script: string) {
|
||||
this.svc.reload(script);
|
||||
this.markContainingProjectsAsDirty();
|
||||
}
|
||||
|
||||
reloadFromFile(fileName: string, cb?: () => void) {
|
||||
this.svc.reloadFromFile(fileName, cb)
|
||||
this.markContainingProjectsAsDirty();
|
||||
}
|
||||
|
||||
snap() {
|
||||
return this.svc.getSnapshot();
|
||||
}
|
||||
|
||||
getLineInfo(line: number) {
|
||||
const snap = this.snap();
|
||||
return snap.index.lineNumberToInfo(line);
|
||||
}
|
||||
|
||||
editContent(start: number, end: number, newText: string): void {
|
||||
this.svc.edit(start, end - start, newText);
|
||||
this.markContainingProjectsAsDirty();
|
||||
}
|
||||
|
||||
markContainingProjectsAsDirty() {
|
||||
for (const p of this.containingProjects) {
|
||||
p.markAsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param line 1 based index
|
||||
*/
|
||||
lineToTextSpan(line: number) {
|
||||
const index = this.snap().index;
|
||||
const lineInfo = index.lineNumberToInfo(line + 1);
|
||||
let len: number;
|
||||
if (lineInfo.leaf) {
|
||||
len = lineInfo.leaf.text.length;
|
||||
}
|
||||
else {
|
||||
const nextLineInfo = index.lineNumberToInfo(line + 2);
|
||||
len = nextLineInfo.offset - lineInfo.offset;
|
||||
}
|
||||
return ts.createTextSpan(lineInfo.offset, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param line 1 based index
|
||||
* @param offset 1 based index
|
||||
*/
|
||||
lineOffsetToPosition(line: number, offset: number): number {
|
||||
const index = this.snap().index;
|
||||
|
||||
const lineInfo = index.lineNumberToInfo(line);
|
||||
// TODO: assert this offset is actually on the line
|
||||
return (lineInfo.offset + offset - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param line 1-based index
|
||||
* @param offset 1-based index
|
||||
*/
|
||||
positionToLineOffset(position: number): ILineInfo {
|
||||
const index = this.snap().index;
|
||||
const lineOffset = index.charOffsetToLineNumberAndPos(position);
|
||||
return { line: lineOffset.line, offset: lineOffset.offset + 1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -297,7 +297,7 @@ namespace ts.server {
|
||||
return this.currentVersion;
|
||||
}
|
||||
|
||||
reloadFromFile(filename: string, cb?: () => any) {
|
||||
reloadFromFile(filename: string, cb?: () => void) {
|
||||
let content = this.host.readFile(filename);
|
||||
// If the file doesn't exist or cannot be read, we should
|
||||
// wipe out its cached content on the server to avoid side effects.
|
||||
|
||||
@ -86,7 +86,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
export interface PendingErrorCheck {
|
||||
fileName: string;
|
||||
fileName: NormalizedPath;
|
||||
project: Project;
|
||||
}
|
||||
|
||||
@ -187,7 +187,7 @@ namespace ts.server {
|
||||
});
|
||||
}
|
||||
|
||||
private handleEvent(eventName: string, project: Project, fileName: string) {
|
||||
private handleEvent(eventName: string, project: Project, fileName: NormalizedPath) {
|
||||
if (eventName == "context") {
|
||||
this.projectService.log("got context event, updating diagnostics for" + fileName, "Info");
|
||||
this.updateErrorCheck([{ fileName, project }], this.changeSeq,
|
||||
@ -370,16 +370,12 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private getEncodedSemanticClassifications(args: protocol.FileSpanRequestArgs) {
|
||||
const file = normalizePath(args.file);
|
||||
const project = this.projectService.getProjectForFile(file);
|
||||
if (!project) {
|
||||
throw Errors.NoProject;
|
||||
}
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
return project.languageService.getEncodedSemanticClassifications(file, args);
|
||||
}
|
||||
|
||||
private getProject(projectFileName: string) {
|
||||
return projectFileName && this.projectService.getProject(projectFileName);
|
||||
return projectFileName && this.projectService.findProject(projectFileName);
|
||||
}
|
||||
|
||||
private getCompilerOptionsDiagnostics(args: protocol.ProjectRequestArgs) {
|
||||
@ -400,11 +396,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private getDiagnosticsWorker(args: protocol.FileRequestArgs, selector: (project: Project, file: string) => Diagnostic[]) {
|
||||
const file = normalizePath(args.file);
|
||||
const project = this.getProject(args.projectFileName) || this.projectService.getProjectForFile(file);
|
||||
if (!project) {
|
||||
throw Errors.NoProject;
|
||||
}
|
||||
const { project, file } = this.getFileAndProject(args);
|
||||
const scriptInfo = project.getScriptInfo(file);
|
||||
const diagnostics = selector(project, file);
|
||||
return this.convertDiagnostics(diagnostics, scriptInfo);
|
||||
@ -419,11 +411,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private getDefinition(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.FileSpan[] | DefinitionInfo[] {
|
||||
const file = ts.normalizePath(args.file);
|
||||
const project = this.projectService.getProjectForFile(file);
|
||||
if (!project) {
|
||||
throw Errors.NoProject;
|
||||
}
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
|
||||
const scriptInfo = project.getScriptInfo(file);
|
||||
const position = this.getPosition(args, scriptInfo);
|
||||
@ -780,9 +768,9 @@ namespace ts.server {
|
||||
return args.position !== undefined ? args.position : scriptInfo.lineOffsetToPosition(args.line, args.offset);
|
||||
}
|
||||
|
||||
private getFileAndProject(fileName: string) {
|
||||
const file = ts.normalizePath(fileName);
|
||||
const project = this.projectService.getProjectForFile(file);
|
||||
private getFileAndProject(args: protocol.FileLocationRequestArgs) {
|
||||
const file = ts.normalizePath(args.file);
|
||||
const project: Project = this.getProject(args.projectFileName) || this.projectService.getProjectForFile(file);
|
||||
if (!project) {
|
||||
throw Errors.NoProject;
|
||||
}
|
||||
@ -790,54 +778,49 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private getOutliningSpans(args: protocol.FileRequestArgs) {
|
||||
const { file, project } = this.getFileAndProject(args.file);
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
return project.languageService.getOutliningSpans(file);
|
||||
}
|
||||
|
||||
private getTodoComments(args: protocol.TodoCommentRequestArgs) {
|
||||
const { file, project } = this.getFileAndProject(args.file);
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
return project.languageService.getTodoComments(file, args.descriptors);
|
||||
}
|
||||
|
||||
private getDocCommentTemplate(args: protocol.FileLocationRequestArgs) {
|
||||
const { file, project } = this.getFileAndProject(args.file);
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
const scriptInfo = project.getScriptInfo(file);
|
||||
const position = this.getPosition(args, scriptInfo);
|
||||
return project.languageService.getDocCommentTemplateAtPosition(file, position);
|
||||
}
|
||||
|
||||
private getIndentation(args: protocol.IndentationRequestArgs) {
|
||||
const { file, project } = this.getFileAndProject(args.file);
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
const position = this.getPosition(args, project.getScriptInfo(file));
|
||||
const indentation = project.languageService.getIndentationAtPosition(file, position, args.options);
|
||||
return { position, indentation };
|
||||
}
|
||||
|
||||
private getBreakpointStatement(args: protocol.FileLocationRequestArgs) {
|
||||
const { file, project } = this.getFileAndProject(args.file);
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
const position = this.getPosition(args, project.getScriptInfo(file));
|
||||
return project.languageService.getBreakpointStatementAtPosition(file, position);
|
||||
}
|
||||
|
||||
private getNameOrDottedNameSpan(args: protocol.FileLocationRequestArgs) {
|
||||
const { file, project } = this.getFileAndProject(args.file);
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
const position = this.getPosition(args, project.getScriptInfo(file));
|
||||
return project.languageService.getNameOrDottedNameSpan(file, position, position);
|
||||
}
|
||||
|
||||
private isValidBraceCompletion(args: protocol.BraceCompletionRequestArgs) {
|
||||
const { file, project } = this.getFileAndProject(args.file);
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
const position = this.getPosition(args, project.getScriptInfo(file));
|
||||
return project.languageService.isValidBraceCompletionAtPostion(file, position, args.openingBrace.charCodeAt(0));
|
||||
}
|
||||
|
||||
private getQuickInfoWorker(args: protocol.FileLocationRequestArgs, simplifiedResult: boolean): protocol.QuickInfoResponseBody | QuickInfo {
|
||||
const file = ts.normalizePath(args.file);
|
||||
const project = this.projectService.getProjectForFile(file);
|
||||
if (!project) {
|
||||
throw Errors.NoProject;
|
||||
}
|
||||
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
const scriptInfo = project.getScriptInfo(file);
|
||||
const quickInfo = project.languageService.getQuickInfoAtPosition(file, this.getPosition(args, scriptInfo));
|
||||
if (!quickInfo) {
|
||||
@ -889,31 +872,17 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private getFormattingEditsForRangeFull(args: protocol.FormatRequestArgs) {
|
||||
const file = ts.normalizePath(args.file);
|
||||
const project = this.projectService.getProjectForFile(file);
|
||||
if (!project) {
|
||||
throw Errors.NoProject;
|
||||
}
|
||||
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
return project.languageService.getFormattingEditsForRange(file, args.position, args.endPosition, args.options);
|
||||
}
|
||||
|
||||
private getFormattingEditsForDocumentFull(args: protocol.FormatRequestArgs) {
|
||||
const file = ts.normalizePath(args.file);
|
||||
const project = this.projectService.getProjectForFile(file);
|
||||
if (!project) {
|
||||
throw Errors.NoProject;
|
||||
}
|
||||
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
return project.languageService.getFormattingEditsForDocument(file, args.options);
|
||||
}
|
||||
|
||||
private getFormattingEditsAfterKeystrokeFull(args: protocol.FormatOnKeyRequestArgs) {
|
||||
const file = ts.normalizePath(args.file);
|
||||
const project = this.projectService.getProjectForFile(file);
|
||||
if (!project) {
|
||||
throw Errors.NoProject;
|
||||
}
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
return project.languageService.getFormattingEditsAfterKeystroke(file, args.position, args.key, args.options);
|
||||
}
|
||||
|
||||
@ -990,11 +959,7 @@ namespace ts.server {
|
||||
|
||||
private getCompletions(args: protocol.CompletionsRequestArgs, simplifiedResult: boolean): protocol.CompletionEntry[] | CompletionInfo {
|
||||
const prefix = args.prefix || "";
|
||||
const file = ts.normalizePath(args.file);
|
||||
const project = this.projectService.getProjectForFile(file);
|
||||
if (!project) {
|
||||
throw Errors.NoProject;
|
||||
}
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
|
||||
const scriptInfo = project.getScriptInfo(file);
|
||||
const position = this.getPosition(args, scriptInfo);
|
||||
@ -1017,12 +982,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private getCompletionEntryDetails(args: protocol.CompletionDetailsRequestArgs): protocol.CompletionEntryDetails[] {
|
||||
const file = ts.normalizePath(args.file);
|
||||
const project = this.projectService.getProjectForFile(file);
|
||||
if (!project) {
|
||||
throw Errors.NoProject;
|
||||
}
|
||||
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
const scriptInfo = project.getScriptInfo(file);
|
||||
const position = this.getPosition(args, scriptInfo);
|
||||
|
||||
@ -1036,12 +996,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private getSignatureHelpItems(args: protocol.SignatureHelpRequestArgs, simplifiedResult: boolean): protocol.SignatureHelpItems | SignatureHelpItems {
|
||||
const file = ts.normalizePath(args.file);
|
||||
const project = this.projectService.getProjectForFile(file);
|
||||
if (!project) {
|
||||
throw Errors.NoProject;
|
||||
}
|
||||
|
||||
const { file, project } = this.getFileAndProject(args);
|
||||
const scriptInfo = project.getScriptInfo(file);
|
||||
const position = this.getPosition(args, scriptInfo);
|
||||
const helpItems = project.languageService.getSignatureHelpItems(file, position);
|
||||
@ -1069,6 +1024,7 @@ namespace ts.server {
|
||||
|
||||
private getDiagnostics(delay: number, fileNames: string[]) {
|
||||
const checkList = fileNames.reduce((accum: PendingErrorCheck[], fileName: string) => {
|
||||
|
||||
fileName = ts.normalizePath(fileName);
|
||||
const project = this.projectService.getProjectForFile(fileName);
|
||||
if (project) {
|
||||
|
||||
175
src/server/utilities.ts
Normal file
175
src/server/utilities.ts
Normal file
@ -0,0 +1,175 @@
|
||||
/// <reference path="..\services\services.ts" />
|
||||
/// <reference path="session.ts" />
|
||||
|
||||
namespace ts.server {
|
||||
export interface Logger {
|
||||
close(): void;
|
||||
isVerbose(): boolean;
|
||||
loggingEnabled(): boolean;
|
||||
perftrc(s: string): void;
|
||||
info(s: string): void;
|
||||
startGroup(): void;
|
||||
endGroup(): void;
|
||||
msg(s: string, type?: string): void;
|
||||
}
|
||||
|
||||
export function getDefaultFormatCodeSettings(host: ServerHost): FormatCodeSettings {
|
||||
return {
|
||||
indentSize: 4,
|
||||
tabSize: 4,
|
||||
newLineCharacter: host.newLine || "\n",
|
||||
convertTabsToSpaces: true,
|
||||
indentStyle: ts.IndentStyle.Smart,
|
||||
insertSpaceAfterCommaDelimiter: true,
|
||||
insertSpaceAfterSemicolonInForStatements: true,
|
||||
insertSpaceBeforeAndAfterBinaryOperators: true,
|
||||
insertSpaceAfterKeywordsInControlFlowStatements: true,
|
||||
insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
|
||||
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
|
||||
placeOpenBraceOnNewLineForFunctions: false,
|
||||
placeOpenBraceOnNewLineForControlBlocks: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function mergeMaps(target: Map<any>, source: Map<any>): void {
|
||||
for (const key in source) {
|
||||
if (hasProperty(source, key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function removeItemFromSet<T>(items: T[], itemToRemove: T) {
|
||||
const index = items.indexOf(itemToRemove);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
if (items.length === 0) {
|
||||
items.pop();
|
||||
}
|
||||
else {
|
||||
items[index] = items.pop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export type NormalizedPath = string & { __normalizedPathTag: any };
|
||||
|
||||
export function toNormalizedPath(fileName: string): NormalizedPath {
|
||||
return <NormalizedPath>normalizePath(fileName);
|
||||
}
|
||||
|
||||
export function asNormalizedPath(fileName: string): NormalizedPath {
|
||||
return <NormalizedPath>fileName;
|
||||
}
|
||||
|
||||
export function asNormalizedPathArray(fileNames: string[]): NormalizedPath[] {
|
||||
return <NormalizedPath[]>fileNames;
|
||||
}
|
||||
|
||||
export interface NormalizedPathMap<T> {
|
||||
get (path: NormalizedPath): T;
|
||||
set (path: NormalizedPath, value: T): void;
|
||||
contains(path: NormalizedPath): boolean;
|
||||
remove(path: NormalizedPath): void;
|
||||
}
|
||||
|
||||
export function createNormalizedPathMap<T>(): NormalizedPathMap<T> {
|
||||
const map: Map<T> = Object.create(null);
|
||||
return {
|
||||
get(path) {
|
||||
return map[path];
|
||||
},
|
||||
set(path, value) {
|
||||
map[path] = value;
|
||||
},
|
||||
contains(path) {
|
||||
return hasProperty(map, path);
|
||||
},
|
||||
remove(path) {
|
||||
delete map[path]
|
||||
}
|
||||
}
|
||||
}
|
||||
function throwLanguageServiceIsDisabledError() {;
|
||||
throw new Error("LanguageService is disabled");
|
||||
}
|
||||
|
||||
export const nullLanguageService: LanguageService = {
|
||||
cleanupSemanticCache: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getSyntacticDiagnostics: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getSemanticDiagnostics: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getCompilerOptionsDiagnostics: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getSyntacticClassifications: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getEncodedSyntacticClassifications: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getSemanticClassifications: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getEncodedSemanticClassifications: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getCompletionsAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
findReferences: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getCompletionEntryDetails: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getQuickInfoAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
findRenameLocations: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getNameOrDottedNameSpan: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getBreakpointStatementAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getBraceMatchingAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getSignatureHelpItems: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getDefinitionAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getRenameInfo: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getTypeDefinitionAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getReferencesAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getDocumentHighlights: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getOccurrencesAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getNavigateToItems: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getNavigationBarItems: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getOutliningSpans: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getTodoComments: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getIndentationAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getFormattingEditsForRange: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getFormattingEditsForDocument: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getFormattingEditsAfterKeystroke: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getDocCommentTemplateAtPosition: (): any => throwLanguageServiceIsDisabledError(),
|
||||
isValidBraceCompletionAtPostion: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getEmitOutput: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getProgram: (): any => throwLanguageServiceIsDisabledError(),
|
||||
getNonBoundSourceFile: (): any => throwLanguageServiceIsDisabledError(),
|
||||
dispose: (): any => throwLanguageServiceIsDisabledError(),
|
||||
};
|
||||
|
||||
export interface ServerLanguageServiceHost {
|
||||
getCompilationSettings(): CompilerOptions;
|
||||
setCompilationSettings(options: CompilerOptions): void;
|
||||
removeRoot(info: ScriptInfo): void;
|
||||
removeReferencedFile(info: ScriptInfo): void;
|
||||
}
|
||||
|
||||
export const nullLanguageServiceHost: ServerLanguageServiceHost = {
|
||||
getCompilationSettings: () => undefined,
|
||||
setCompilationSettings: () => undefined,
|
||||
removeRoot: () => undefined,
|
||||
removeReferencedFile: () => undefined
|
||||
};
|
||||
|
||||
export interface ProjectOptions {
|
||||
/**
|
||||
* true if config file explicitly listed files
|
||||
**/
|
||||
configHasFilesProperty?: boolean;
|
||||
/**
|
||||
* these fields can be present in the project file
|
||||
**/
|
||||
files?: string[];
|
||||
wildcardDirectories?: Map<WatchDirectoryFlags>;
|
||||
compilerOptions?: CompilerOptions;
|
||||
}
|
||||
|
||||
export function isInferredProjectName(name: string) {
|
||||
// POSIX defines /dev/null as a device - there should be no file with this prefix
|
||||
return /dev\/null\/inferredProject\d+\*/.test(name);
|
||||
}
|
||||
|
||||
export function makeInferredProjectName(counter: number) {
|
||||
return `/dev/null/inferredProject${counter}*`;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user