UNPKG

javascript-typescript-langserver

Version:

Implementation of the Language Server Protocol for JavaScript and TypeScript

130 lines 5.81 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("mz/fs"); const path = require("path"); const ts = require("typescript"); const logging_1 = require("./logging"); const match_files_1 = require("./match-files"); const util_1 = require("./util"); /** * A local filesystem-based ModuleResolutionHost for plugin loading. */ class LocalModuleResolutionHost { fileExists(fileName) { return fs.existsSync(fileName); } readFile(fileName) { return fs.readFileSync(fileName, 'utf8'); } } exports.LocalModuleResolutionHost = LocalModuleResolutionHost; class PluginLoader { constructor(rootFilePath, fs, pluginSettings, logger = new logging_1.NoopLogger(), resolutionHost = new LocalModuleResolutionHost(), requireModule = require) { this.rootFilePath = rootFilePath; this.fs = fs; this.logger = logger; this.resolutionHost = resolutionHost; this.requireModule = requireModule; this.allowLocalPluginLoads = false; this.globalPlugins = []; this.pluginProbeLocations = []; if (pluginSettings) { this.allowLocalPluginLoads = pluginSettings.allowLocalPluginLoads || false; this.globalPlugins = pluginSettings.globalPlugins || []; this.pluginProbeLocations = pluginSettings.pluginProbeLocations || []; } } loadPlugins(options, applyProxy) { // Search our peer node_modules, then any globally-specified probe paths // ../../.. to walk from X/node_modules/javascript-typescript-langserver/lib/project-manager.js to X/node_modules/ const searchPaths = [match_files_1.combinePaths(__filename, '../../..'), ...this.pluginProbeLocations]; // Corresponds to --allowLocalPluginLoads, opt-in to avoid remote code execution. if (this.allowLocalPluginLoads) { const local = this.rootFilePath; this.logger.info(`Local plugin loading enabled; adding ${local} to search paths`); searchPaths.unshift(local); } let pluginImports = []; if (options.plugins) { pluginImports = options.plugins; } // Enable tsconfig-specified plugins if (options.plugins) { for (const pluginConfigEntry of pluginImports) { this.enablePlugin(pluginConfigEntry, searchPaths, applyProxy); } } if (this.globalPlugins) { // Enable global plugins with synthetic configuration entries for (const globalPluginName of this.globalPlugins) { // Skip already-locally-loaded plugins if (!pluginImports || pluginImports.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 }, searchPaths, applyProxy); } } } /** * Tries to load and enable a single plugin * @param pluginConfigEntry * @param searchPaths */ enablePlugin(pluginConfigEntry, searchPaths, enableProxy) { for (const searchPath of searchPaths) { const resolvedModule = this.resolveModule(pluginConfigEntry.name, searchPath); if (resolvedModule) { enableProxy(resolvedModule, pluginConfigEntry); return; } } this.logger.error(`Couldn't find ${pluginConfigEntry.name} anywhere in paths: ${searchPaths.join(',')}`); } /** * Load a plugin using a node require * @param moduleName * @param initialDir */ resolveModule(moduleName, initialDir) { const resolvedPath = util_1.toUnixPath(path.resolve(match_files_1.combinePaths(initialDir, 'node_modules'))); this.logger.info(`Loading ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`); const result = this.requirePlugin(resolvedPath, moduleName); if (result.error) { this.logger.error(`Failed to load module: ${JSON.stringify(result.error)}`); return undefined; } return result.module; } /** * Resolves a loads a plugin function relative to initialDir * @param initialDir * @param moduleName */ requirePlugin(initialDir, moduleName) { try { const modulePath = this.resolveJavaScriptModule(moduleName, initialDir, this.fs); return { module: this.requireModule(modulePath), error: undefined }; } catch (error) { return { module: undefined, error }; } } /** * Expose resolution logic to allow us to use Node module resolution logic from arbitrary locations. * No way to do this with `require()`: https://github.com/nodejs/node/issues/5963 * Throws an error if the module can't be resolved. * stolen from moduleNameResolver.ts because marked as internal */ resolveJavaScriptModule(moduleName, initialDir, host) { // TODO: this should set jsOnly=true to the internal resolver, but this parameter is not exposed on a public api. const result = ts.nodeModuleNameResolver(moduleName, initialDir.replace('\\', '/') + '/package.json' /* containingFile */, { moduleResolution: ts.ModuleResolutionKind.NodeJs, allowJs: true }, this.resolutionHost, undefined); if (!result.resolvedModule) { // this.logger.error(result.failedLookupLocations); throw new Error(`Could not resolve JS module ${moduleName} starting at ${initialDir}.`); } return result.resolvedModule.resolvedFileName; } } exports.PluginLoader = PluginLoader; //# sourceMappingURL=plugins.js.map