Addressing some comments

- Use result value instead of try/catch (`ImportPluginResult`)
- Add awaits
- Add logging
This commit is contained in:
Matt Bierner 2022-03-07 18:42:57 -08:00
parent 34a83a370d
commit 51680a1faa
No known key found for this signature in database
GPG Key ID: 099C331567E11888
3 changed files with 37 additions and 23 deletions

View File

@ -1545,7 +1545,7 @@ namespace ts.server {
return !!this.program && this.program.isSourceOfProjectReferenceRedirect(fileName);
}
protected enableGlobalPlugins(options: CompilerOptions, pluginConfigOverrides: Map<any> | undefined) {
protected async enableGlobalPlugins(options: CompilerOptions, pluginConfigOverrides: Map<any> | undefined): Promise<void> {
const host = this.projectService.host;
if (!host.require) {
@ -1555,9 +1555,9 @@ namespace ts.server {
// Search any globally-specified probe paths, then our peer node_modules
const searchPaths = [
...this.projectService.pluginProbeLocations,
// ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/
combinePaths(this.projectService.getExecutingFilePath(), "../../.."),
...this.projectService.pluginProbeLocations,
// ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/
combinePaths(this.projectService.getExecutingFilePath(), "../../.."),
];
if (this.projectService.globalPlugins) {
@ -1572,12 +1572,12 @@ namespace ts.server {
// 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, pluginConfigOverrides);
await this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths, pluginConfigOverrides);
}
}
}
protected async enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<any> | undefined) {
protected async enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<any> | undefined): Promise<void> {
this.projectService.logger.info(`Enabling plugin ${pluginConfigEntry.name} from candidate paths: ${searchPaths.join(",")}`);
if (!pluginConfigEntry.name || parsePackageName(pluginConfigEntry.name).rest) {
this.projectService.logger.info(`Skipped loading plugin ${pluginConfigEntry.name || JSON.stringify(pluginConfigEntry)} because only package name is allowed plugin name`);
@ -1593,21 +1593,20 @@ namespace ts.server {
let resolvedModule: any | undefined;
if (this.projectService.host.importServicePlugin) {
for (const searchPath of searchPaths) {
try {
resolvedModule = await this.projectService.host.importServicePlugin(searchPath, pluginConfigEntry.name);
const result = await this.projectService.host.importServicePlugin(searchPath, pluginConfigEntry.name);
if (result.error) {
logError(result.error.toString());
}
catch (e) {
// TODO: log this?
continue;
}
if (resolvedModule) {
else {
resolvedModule = result.module;
break;
}
}
}
else {
resolvedModule = firstDefined(searchPaths, searchPath =>
Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log, logError) as PluginModuleFactory | undefined);
Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log, logError) as PluginModuleFactory | undefined);
}
if (resolvedModule) {
@ -2290,7 +2289,7 @@ namespace ts.server {
}
/*@internal*/
enablePluginsWithOptions(options: CompilerOptions, pluginConfigOverrides: ESMap<string, any> | undefined) {
async enablePluginsWithOptions(options: CompilerOptions, pluginConfigOverrides: ESMap<string, any> | undefined): Promise<void> {
const host = this.projectService.host;
if (!host.require) {
@ -2311,11 +2310,11 @@ namespace ts.server {
// Enable tsconfig-specified plugins
if (options.plugins) {
for (const pluginConfigEntry of options.plugins) {
this.enablePlugin(pluginConfigEntry, searchPaths, pluginConfigOverrides);
await this.enablePlugin(pluginConfigEntry, searchPaths, pluginConfigOverrides);
}
}
this.enableGlobalPlugins(options, pluginConfigOverrides);
return this.enableGlobalPlugins(options, pluginConfigOverrides);
}
/**

View File

@ -6,6 +6,8 @@ declare namespace ts.server {
}
export type RequireResult = { module: {}, error: undefined } | { module: undefined, error: { stack?: string, message?: string } };
export type ImportPluginResult = { module: {}, error: undefined } | { module: undefined, error: { stack?: string, message: string } };
export interface ServerHost extends System {
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher;
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher;
@ -16,6 +18,6 @@ declare namespace ts.server {
gc?(): void;
trace?(s: string): void;
require?(initialPath: string, moduleName: string): RequireResult;
importServicePlugin?(root: string, moduleName: string): Promise<any>;
importServicePlugin?(root: string, moduleName: string): Promise<ImportPluginResult>;
}
}

View File

@ -139,21 +139,34 @@ namespace ts.server {
/* eslint-enable no-restricted-globals */
require: () => ({ module: undefined, error: new Error("Not implemented") }),
importServicePlugin: async (root: string, moduleName: string) => {
importServicePlugin: async (root: string, moduleName: string): Promise<ImportPluginResult> => {
const packageRoot = combinePaths(root, "node_modules", moduleName);
const packageJsonResponse = await fetch(combinePaths(packageRoot, "package.json"));
const packageJson = await packageJsonResponse.json();
let packageJson: any | undefined;
try {
const packageJsonResponse = await fetch(combinePaths(packageRoot, "package.json"));
packageJson = await packageJsonResponse.json();
}
catch (e) {
return { module: undefined, error: new Error("Could not load plugin. Could not load 'package.json'.") };
}
const browser = packageJson.browser;
if (!browser) {
throw new Error("Could not load plugin. No 'browser' field found in package.json.");
return { module: undefined, error: new Error("Could not load plugin. No 'browser' field found in package.json.") };
}
const scriptPath = combinePaths(packageRoot, browser);
// TODO: TS rewrites `import(...)` to `require`. Use eval to bypass this
// eslint-disable-next-line no-eval
return (await eval(`import(${JSON.stringify(scriptPath)})`)).default;
try {
const module = (await eval(`import(${JSON.stringify(scriptPath)})`)).default;
return { module, error: undefined };
}
catch (e) {
return { module: undefined, error: e };
}
},
exit: notImplemented,