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

Commit e517506

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 e570f53 commit e517506

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
@@ -9,7 +9,7 @@ import { FileSystemUpdater } from './fs';
99
import { Logger, NoopLogger } from './logging';
1010
import { InMemoryFileSystem } from './memfs';
1111
import { PluginCreateInfo, PluginLoader, PluginModuleFactory } from './plugins';
12-
import { InitializationOptions } from './request-type';
12+
import { PluginSettings } from './request-type';
1313
import { traceObservable, traceSync } from './tracing';
1414
import {
1515
isConfigFile,
@@ -99,7 +99,7 @@ export class ProjectManager implements Disposable {
9999
/**
100100
* Options passed to the language server at startup
101101
*/
102-
private initializationOptions?: InitializationOptions;
102+
private pluginSettings?: PluginSettings;
103103

104104
/**
105105
* @param rootPath root path as passed to `initialize`
@@ -112,14 +112,14 @@ export class ProjectManager implements Disposable {
112112
inMemoryFileSystem: InMemoryFileSystem,
113113
updater: FileSystemUpdater,
114114
traceModuleResolution?: boolean,
115-
initializationOptions?: InitializationOptions,
115+
pluginSettings?: PluginSettings,
116116
protected logger: Logger = new NoopLogger()
117117
) {
118118
this.rootPath = rootPath;
119119
this.updater = updater;
120120
this.inMemoryFs = inMemoryFileSystem;
121121
this.versions = new Map<string, number>();
122-
this.initializationOptions = initializationOptions;
122+
this.pluginSettings = pluginSettings;
123123
this.traceModuleResolution = traceModuleResolution || false;
124124

125125
// Share DocumentRegistry between all ProjectConfigurations
@@ -147,7 +147,7 @@ export class ProjectManager implements Disposable {
147147
'',
148148
tsConfig,
149149
this.traceModuleResolution,
150-
this.initializationOptions,
150+
this.pluginSettings,
151151
this.logger
152152
);
153153
configs.set(trimmedRootPath, config);
@@ -177,7 +177,7 @@ export class ProjectManager implements Disposable {
177177
filePath,
178178
undefined,
179179
this.traceModuleResolution,
180-
this.initializationOptions,
180+
this.pluginSettings,
181181
this.logger
182182
));
183183
// Remove catch-all config (if exists)
@@ -760,7 +760,7 @@ export class ProjectConfiguration {
760760
configFilePath: string,
761761
configContent?: any,
762762
traceModuleResolution?: boolean,
763-
private initializationOptions?: InitializationOptions,
763+
private pluginSettings?: PluginSettings,
764764
private logger: Logger = new NoopLogger()
765765
) {
766766
this.fs = fs;
@@ -864,8 +864,8 @@ export class ProjectConfiguration {
864864
this.logger
865865
);
866866
this.service = ts.createLanguageService(this.host, this.documentRegistry);
867-
const pluginLoader = new PluginLoader(this.rootFilePath, this.fs, this.initializationOptions, this.logger);
868-
pluginLoader.loadPlugins(options, (factory, config) => this.enableProxy(factory, config));
867+
const pluginLoader = new PluginLoader(this.rootFilePath, this.fs, this.pluginSettings, this.logger);
868+
pluginLoader.loadPlugins(options, this.enableProxy.bind(this) /* (factory, config) => this.enableProxy(factory, config)) */);
869869
this.initialized = true;
870870
}
871871

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)