Merge pull request #20854 from mjbvz/global-plugins-for-inferred

Load global plugins for inferred projects
This commit is contained in:
Ryan Cavanaugh 2018-01-03 12:39:49 -08:00 committed by GitHub
commit dde7f03914
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 97 additions and 85 deletions

View File

@ -121,6 +121,7 @@ namespace ts.server {
private program: Program;
private externalFiles: SortedReadonlyArray<string>;
private missingFilesMap: Map<FileWatcher>;
private plugins: PluginModule[] = [];
private cachedUnresolvedImportsPerFile = new UnresolvedImportsMap();
private lastCachedUnresolvedImportsList: SortedReadonlyArray<string>;
@ -515,7 +516,18 @@ namespace ts.server {
}
getExternalFiles(): SortedReadonlyArray<string> {
return emptyArray as SortedReadonlyArray<string>;
return toSortedArray(flatMap(this.plugins, plugin => {
if (typeof plugin.getExternalFiles !== "function") return;
try {
return plugin.getExternalFiles(this);
}
catch (e) {
this.projectService.logger.info(`A plugin threw an exception in getExternalFiles: ${e}`);
if (e.stack) {
this.projectService.logger.info(e.stack);
}
}
}));
}
getSourceFile(path: Path) {
@ -1029,6 +1041,84 @@ namespace ts.server {
orderedRemoveItem(this.rootFiles, info);
this.rootFilesMap.delete(info.path);
}
protected enableGlobalPlugins() {
const host = this.projectService.host;
const options = this.getCompilationSettings();
if (!host.require) {
this.projectService.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded");
return;
}
// 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(this.projectService.getExecutingFilePath(), "../../.."), ...this.projectService.pluginProbeLocations];
if (this.projectService.globalPlugins) {
// Enable global plugins with synthetic configuration entries
for (const globalPluginName of this.projectService.globalPlugins) {
// Skip empty names from odd commandline parses
if (!globalPluginName) continue;
// 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.projectService.logger.info(`Loading global plugin ${globalPluginName}`);
this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths);
}
}
}
protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[]) {
this.projectService.logger.info(`Enabling plugin ${pluginConfigEntry.name} from candidate paths: ${searchPaths.join(",")}`);
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);
if (resolvedModule) {
this.enableProxy(resolvedModule, pluginConfigEntry);
return;
}
}
this.projectService.logger.info(`Couldn't find ${pluginConfigEntry.name}`);
}
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,
serverHost: this.projectService.host
};
const pluginModule = pluginModuleFactory({ typescript: ts });
const newLS = pluginModule.create(info);
for (const k of Object.keys(this.languageService)) {
if (!(k in newLS)) {
this.projectService.logger.info(`Plugin activation warning: Missing proxied method ${k} in created LS. Patching.`);
(newLS as any)[k] = (this.languageService as any)[k];
}
}
this.projectService.logger.info(`Plugin validation succeded`);
this.languageService = newLS;
this.plugins.push(pluginModule);
}
catch (e) {
this.projectService.logger.info(`Plugin activation failed: ${e}`);
}
}
}
/**
@ -1092,6 +1182,7 @@ namespace ts.server {
projectService.host,
currentDirectory);
this.projectRootPath = projectRootPath && projectService.toCanonicalFileName(projectRootPath);
this.enableGlobalPlugins();
}
addRoot(info: ScriptInfo) {
@ -1153,8 +1244,6 @@ namespace ts.server {
/*@internal*/
configFileSpecs: ConfigFileSpecs;
private plugins: PluginModule[] = [];
/** Ref count to the project when opened from external project */
private externalProjectRefCount = 0;
@ -1236,69 +1325,7 @@ namespace ts.server {
}
}
if (this.projectService.globalPlugins) {
// Enable global plugins with synthetic configuration entries
for (const globalPluginName of this.projectService.globalPlugins) {
// Skip empty names from odd commandline parses
if (!globalPluginName) continue;
// 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.projectService.logger.info(`Loading global plugin ${globalPluginName}`);
this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths);
}
}
}
private enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[]) {
this.projectService.logger.info(`Enabling plugin ${pluginConfigEntry.name} from candidate paths: ${searchPaths.join(",")}`);
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);
if (resolvedModule) {
this.enableProxy(resolvedModule, pluginConfigEntry);
return;
}
}
this.projectService.logger.info(`Couldn't find ${pluginConfigEntry.name}`);
}
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,
serverHost: this.projectService.host
};
const pluginModule = pluginModuleFactory({ typescript: ts });
const newLS = pluginModule.create(info);
for (const k of Object.keys(this.languageService)) {
if (!(k in newLS)) {
this.projectService.logger.info(`Plugin activation warning: Missing proxied method ${k} in created LS. Patching.`);
(newLS as any)[k] = (this.languageService as any)[k];
}
}
this.projectService.logger.info(`Plugin validation succeded`);
this.languageService = newLS;
this.plugins.push(pluginModule);
}
catch (e) {
this.projectService.logger.info(`Plugin activation failed: ${e}`);
}
this.enableGlobalPlugins();
}
/**
@ -1327,21 +1354,6 @@ namespace ts.server {
return this.typeAcquisition;
}
getExternalFiles(): SortedReadonlyArray<string> {
return toSortedArray(flatMap(this.plugins, plugin => {
if (typeof plugin.getExternalFiles !== "function") return;
try {
return plugin.getExternalFiles(this);
}
catch (e) {
this.projectService.logger.info(`A plugin threw an exception in getExternalFiles: ${e}`);
if (e.stack) {
this.projectService.logger.info(e.stack);
}
}
}));
}
/*@internal*/
watchWildcards(wildcardDirectories: Map<WatchDirectoryFlags>) {
updateWatchingWildcardDirectories(

View File

@ -7230,6 +7230,7 @@ declare namespace ts.server {
private program;
private externalFiles;
private missingFilesMap;
private plugins;
private cachedUnresolvedImportsPerFile;
private lastCachedUnresolvedImportsList;
protected languageService: LanguageService;
@ -7345,6 +7346,9 @@ declare namespace ts.server {
filesToString(writeProjectFileNames: boolean): string;
setCompilerOptions(compilerOptions: CompilerOptions): void;
protected removeRoot(info: ScriptInfo): void;
protected enableGlobalPlugins(): void;
protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[]): void;
private enableProxy(pluginModuleFactory, configEntry);
}
/**
* If a file is opened and no tsconfig (or jsconfig) is found,
@ -7373,7 +7377,6 @@ declare namespace ts.server {
private typeAcquisition;
private directoriesWatchedForWildcards;
readonly canonicalConfigFilePath: NormalizedPath;
private plugins;
/** Ref count to the project when opened from external project */
private externalProjectRefCount;
private projectErrors;
@ -7384,8 +7387,6 @@ declare namespace ts.server {
updateGraph(): boolean;
getConfigFilePath(): NormalizedPath;
enablePlugins(): void;
private enablePlugin(pluginConfigEntry, searchPaths);
private enableProxy(pluginModuleFactory, configEntry);
/**
* Get the errors that dont have any file name associated
*/
@ -7397,7 +7398,6 @@ declare namespace ts.server {
setProjectErrors(projectErrors: Diagnostic[]): void;
setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void;
getTypeAcquisition(): TypeAcquisition;
getExternalFiles(): SortedReadonlyArray<string>;
close(): void;
getEffectiveTypeRoots(): string[];
}