Skip to content
This repository was archived by the owner on Oct 16, 2020. It is now read-only.

Commit d807ec3

Browse files
committed
fix: Switch to didChangeConfiguration, always use local filesystem
Also add 'node_modules' to resolvedPath Don't give up when resolveJavaScriptModule fails
1 parent 791074d commit d807ec3

File tree

5 files changed

+61
-33
lines changed

5 files changed

+61
-33
lines changed

src/plugins.ts

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import * as fs from 'mz/fs';
2+
import * as path from 'path';
13
import * as ts from 'typescript';
24
import { Logger, NoopLogger } from './logging';
35
import { combinePaths } from './match-files';
4-
import { InitializationOptions } from './request-type';
6+
import { PluginSettings } from './request-type';
7+
import { toUnixPath } from './util';
58

69
// Based on types and logic from TypeScript server/project.ts @
710
// https://github.com/Microsoft/TypeScript/blob/711e890e59e10aa05a43cb938474a3d9c2270429/src/server/project.ts
@@ -58,11 +61,17 @@ export class PluginLoader {
5861
private globalPlugins: string[] = [];
5962
private pluginProbeLocations: string[] = [];
6063

61-
constructor(private rootFilePath: string, private fs: ts.ModuleResolutionHost, initializationOptions?: InitializationOptions, private logger = new NoopLogger(), private requireModule: (moduleName: string) => any = require) {
62-
if (initializationOptions) {
63-
this.allowLocalPluginLoads = initializationOptions.allowLocalPluginLoads || false;
64-
this.globalPlugins = initializationOptions.globalPlugins || [];
65-
this.pluginProbeLocations = initializationOptions.pluginProbeLocations || [];
64+
constructor(
65+
private rootFilePath: string,
66+
private fs: ts.ModuleResolutionHost,
67+
pluginSettings?: PluginSettings,
68+
private logger = new NoopLogger(),
69+
private resolutionHost = new LocalModuleResolutionHost(),
70+
private requireModule: (moduleName: string) => any = require) {
71+
if (pluginSettings) {
72+
this.allowLocalPluginLoads = pluginSettings.allowLocalPluginLoads || false;
73+
this.globalPlugins = pluginSettings.globalPlugins || [];
74+
this.pluginProbeLocations = pluginSettings.pluginProbeLocations || [];
6675
}
6776
}
6877

@@ -117,7 +126,7 @@ export class PluginLoader {
117126
return;
118127
}
119128
}
120-
this.logger.info(`Couldn't find ${pluginConfigEntry.name} anywhere in paths: ${searchPaths.join(',')}`);
129+
this.logger.error(`Couldn't find ${pluginConfigEntry.name} anywhere in paths: ${searchPaths.join(',')}`);
121130
}
122131

123132
/**
@@ -126,10 +135,11 @@ export class PluginLoader {
126135
* @param initialDir
127136
*/
128137
private resolveModule(moduleName: string, initialDir: string): {} | undefined {
129-
this.logger.info(`Loading ${moduleName} from ${initialDir}`);
130-
const result = this.requirePlugin(initialDir, moduleName);
138+
const resolvedPath = toUnixPath(path.resolve(combinePaths(initialDir, 'node_modules')));
139+
this.logger.info(`Loading ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`);
140+
const result = this.requirePlugin(resolvedPath, moduleName);
131141
if (result.error) {
132-
this.logger.info(`Failed to load module: ${JSON.stringify(result.error)}`);
142+
this.logger.error(`Failed to load module: ${JSON.stringify(result.error)}`);
133143
return undefined;
134144
}
135145
return result.module;
@@ -141,8 +151,8 @@ export class PluginLoader {
141151
* @param moduleName
142152
*/
143153
private requirePlugin(initialDir: string, moduleName: string): RequireResult {
144-
const modulePath = this.resolveJavaScriptModule(moduleName, initialDir, this.fs);
145154
try {
155+
const modulePath = this.resolveJavaScriptModule(moduleName, initialDir, this.fs);
146156
return { module: this.requireModule(modulePath), error: undefined };
147157
} catch (error) {
148158
return { module: undefined, error };
@@ -158,11 +168,23 @@ export class PluginLoader {
158168
private resolveJavaScriptModule(moduleName: string, initialDir: string, host: ts.ModuleResolutionHost): string {
159169
// TODO: this should set jsOnly=true to the internal resolver, but this parameter is not exposed on a public api.
160170
const result =
161-
ts.nodeModuleNameResolver(moduleName, /* containingFile */ initialDir.replace('\\', '/') + '/package.json', { moduleResolution: ts.ModuleResolutionKind.NodeJs, allowJs: true }, this.fs, undefined);
171+
ts.nodeModuleNameResolver(moduleName, /* containingFile */ initialDir.replace('\\', '/') + '/package.json', { moduleResolution: ts.ModuleResolutionKind.NodeJs, allowJs: true }, this.resolutionHost, undefined);
162172
if (!result.resolvedModule) {
163173
// this.logger.error(result.failedLookupLocations);
164174
throw new Error(`Could not resolve JS module ${moduleName} starting at ${initialDir}.`);
165175
}
166176
return result.resolvedModule.resolvedFileName;
167177
}
168178
}
179+
180+
/**
181+
* A local filesystem-based ModuleResolutionHost for plugin loading.
182+
*/
183+
export class LocalModuleResolutionHost implements ts.ModuleResolutionHost {
184+
fileExists(fileName: string): boolean {
185+
return fs.existsSync(fileName);
186+
}
187+
readFile(fileName: string): string {
188+
return fs.readFileSync(fileName, 'utf8');
189+
}
190+
}

src/project-manager.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { FileSystemUpdater } from './fs';
1010
import { Logger, NoopLogger } from './logging';
1111
import { InMemoryFileSystem } from './memfs';
1212
import { PluginCreateInfo, PluginLoader, PluginModuleFactory } from './plugins';
13-
import { InitializationOptions } from './request-type';
13+
import { PluginSettings } from './request-type';
1414
import { traceObservable, traceSync } from './tracing';
1515
import {
1616
isConfigFile,
@@ -105,7 +105,7 @@ export class ProjectManager implements Disposable {
105105
/**
106106
* Options passed to the language server at startup
107107
*/
108-
private initializationOptions?: InitializationOptions;
108+
private pluginSettings?: PluginSettings;
109109

110110
/**
111111
* @param rootPath root path as passed to `initialize`
@@ -118,14 +118,14 @@ export class ProjectManager implements Disposable {
118118
inMemoryFileSystem: InMemoryFileSystem,
119119
updater: FileSystemUpdater,
120120
traceModuleResolution?: boolean,
121-
initializationOptions?: InitializationOptions,
121+
pluginSettings?: PluginSettings,
122122
protected logger: Logger = new NoopLogger()
123123
) {
124124
this.rootPath = rootPath;
125125
this.updater = updater;
126126
this.inMemoryFs = inMemoryFileSystem;
127127
this.versions = new Map<string, number>();
128-
this.initializationOptions = initializationOptions;
128+
this.pluginSettings = pluginSettings;
129129
this.traceModuleResolution = traceModuleResolution || false;
130130

131131
// Share DocumentRegistry between all ProjectConfigurations
@@ -153,7 +153,7 @@ export class ProjectManager implements Disposable {
153153
'',
154154
tsConfig,
155155
this.traceModuleResolution,
156-
this.initializationOptions,
156+
this.pluginSettings,
157157
this.logger
158158
);
159159
configs.set(trimmedRootPath, config);
@@ -183,7 +183,7 @@ export class ProjectManager implements Disposable {
183183
filePath,
184184
undefined,
185185
this.traceModuleResolution,
186-
this.initializationOptions,
186+
this.pluginSettings,
187187
this.logger
188188
));
189189
// Remove catch-all config (if exists)
@@ -813,7 +813,7 @@ export class ProjectConfiguration {
813813
configFilePath: string,
814814
configContent?: any,
815815
traceModuleResolution?: boolean,
816-
private initializationOptions?: InitializationOptions,
816+
private pluginSettings?: PluginSettings,
817817
private logger: Logger = new NoopLogger()
818818
) {
819819
this.fs = fs;
@@ -922,8 +922,8 @@ export class ProjectConfiguration {
922922
this.logger
923923
);
924924
this.service = ts.createLanguageService(this.host, this.documentRegistry);
925-
const pluginLoader = new PluginLoader(this.rootFilePath, this.fs, this.initializationOptions, this.logger);
926-
pluginLoader.loadPlugins(options, (factory, config) => this.enableProxy(factory, config));
925+
const pluginLoader = new PluginLoader(this.rootFilePath, this.fs, this.pluginSettings, this.logger);
926+
pluginLoader.loadPlugins(options, this.enableProxy.bind(this) /* (factory, config) => this.enableProxy(factory, config)) */);
927927
this.initialized = true;
928928
}
929929

src/request-type.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import * as vscode from 'vscode-languageserver';
33

44
export interface InitializeParams extends vscode.InitializeParams {
55
capabilities: ClientCapabilities;
6-
initializationOptions?: InitializationOptions;
76
}
87

9-
export interface InitializationOptions {
8+
/**
9+
* Settings to enable plugin loading
10+
*/
11+
export interface PluginSettings {
1012
allowLocalPluginLoads: boolean;
1113
globalPlugins: string[];
1214
pluginProbeLocations: string[];

src/test/plugins.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as sinon from 'sinon';
33
import * as ts from 'typescript';
44
import {InMemoryFileSystem} from '../memfs';
55
import {PluginLoader, PluginModule, PluginModuleFactory} from '../plugins';
6-
import {InitializationOptions} from '../request-type';
6+
import {PluginSettings} from '../request-type';
77
import { path2uri } from '../util';
88

99
describe('plugins', () => {
@@ -24,14 +24,14 @@ describe('plugins', () => {
2424
const peerPackagesUri = path2uri(peerPackagesPath);
2525
memfs.add(peerPackagesUri + '/node_modules/some-plugin/package.json', '{ "name": "some-plugin", "version": "0.1.1", "main": "plugin.js"}');
2626
memfs.add(peerPackagesUri + '/node_modules/some-plugin/plugin.js', '');
27-
const initializationOptions: InitializationOptions = {
27+
const pluginSettings: PluginSettings = {
2828
globalPlugins: ['some-plugin'],
2929
allowLocalPluginLoads: false,
3030
pluginProbeLocations: []
3131
};
3232
const pluginFactoryFunc = (modules: any) => 5;
3333
const fakeRequire = (path: string) => pluginFactoryFunc;
34-
const loader = new PluginLoader('/', memfs, initializationOptions, undefined, fakeRequire);
34+
const loader = new PluginLoader('/', memfs, pluginSettings, undefined, memfs, fakeRequire);
3535
const compilerOptions: ts.CompilerOptions = {};
3636
const applyProxy = sinon.spy();
3737
loader.loadPlugins(compilerOptions, applyProxy);
@@ -43,14 +43,14 @@ describe('plugins', () => {
4343
const memfs = new InMemoryFileSystem('/some-project');
4444
memfs.add('file:///some-project/node_modules/some-plugin/package.json', '{ "name": "some-plugin", "version": "0.1.1", "main": "plugin.js"}');
4545
memfs.add('file:///some-project/node_modules/some-plugin/plugin.js', '');
46-
const initializationOptions: InitializationOptions = {
46+
const pluginSettings: PluginSettings = {
4747
globalPlugins: [],
4848
allowLocalPluginLoads: true,
4949
pluginProbeLocations: []
5050
};
5151
const pluginFactoryFunc = (modules: any) => 5;
5252
const fakeRequire = (path: string) => pluginFactoryFunc;
53-
const loader = new PluginLoader('/some-project', memfs, initializationOptions, undefined, fakeRequire);
53+
const loader = new PluginLoader('/some-project', memfs, pluginSettings, undefined, memfs, fakeRequire);
5454
const pluginOption: ts.PluginImport = {
5555
name: 'some-plugin'
5656
};

src/typescript-service.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {
4949
InitializeResult,
5050
PackageDescriptor,
5151
PackageInformation,
52+
PluginSettings,
5253
ReferenceInformation,
5354
SymbolDescriptor,
5455
SymbolLocationInformation,
@@ -86,9 +87,9 @@ export type TypeScriptServiceFactory = (client: LanguageClient, options?: TypeSc
8687
/**
8788
* Settings synced through `didChangeConfiguration`
8889
*/
89-
export interface Settings {
90-
format: ts.FormatCodeSettings;
91-
}
90+
export type Settings = {
91+
format: ts.FormatCodeSettings
92+
} & PluginSettings;
9293

9394
/**
9495
* Handles incoming requests and return responses. There is a one-to-one-to-one
@@ -171,7 +172,10 @@ export class TypeScriptService {
171172
insertSpaceBeforeFunctionParenthesis: false,
172173
placeOpenBraceOnNewLineForFunctions: false,
173174
placeOpenBraceOnNewLineForControlBlocks: false
174-
}
175+
},
176+
allowLocalPluginLoads: false,
177+
globalPlugins: [],
178+
pluginProbeLocations: []
175179
};
176180

177181
/**
@@ -223,7 +227,7 @@ export class TypeScriptService {
223227
this.inMemoryFileSystem,
224228
this.updater,
225229
this.traceModuleResolution,
226-
params.initializationOptions,
230+
this.settings,
227231
this.logger
228232
);
229233
this.packageManager = new PackageManager(this.updater, this.inMemoryFileSystem, this.logger);

0 commit comments

Comments
 (0)