Skip to content

Commit

Permalink
exthost: allow aliasing required modules for back compat
Browse files Browse the repository at this point in the history
Fixes #140585
  • Loading branch information
connor4312 committed Jan 12, 2022
1 parent 08b9273 commit 3d1576a
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 17 deletions.
78 changes: 70 additions & 8 deletions src/vs/workbench/api/common/extHostRequireInterceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,20 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { platform } from 'vs/base/common/process';
import { ILogService } from 'vs/platform/log/common/log';
import { escapeRegExpCharacters } from 'vs/base/common/strings';


interface LoadFunction {
(request: string): any;
}

interface INodeModuleFactory {
interface IAlternativeModuleProvider {
alternativeModuleName(name: string): string | undefined;
}

interface INodeModuleFactory extends Partial<IAlternativeModuleProvider> {
readonly nodeModuleName: string | string[];
load(request: string, parent: URI, original: LoadFunction): any;
alternativeModuleName?(name: string): string | undefined;
}

export abstract class RequireInterceptor {
Expand Down Expand Up @@ -60,21 +64,25 @@ export abstract class RequireInterceptor {

this.register(new VSCodeNodeModuleFactory(this._apiFactory, extensionPaths, this._extensionRegistry, configProvider, this._logService));
this.register(this._instaService.createInstance(KeytarNodeModuleFactory, extensionPaths));
this.register(this._instaService.createInstance(NodeModuleAliasingModuleFactory));
if (this._initData.remote.isRemote) {
this.register(this._instaService.createInstance(OpenNodeModuleFactory, extensionPaths, this._initData.environment.appUriScheme));
}
}

protected abstract _installInterceptor(): void;

public register(interceptor: INodeModuleFactory): void {
if (Array.isArray(interceptor.nodeModuleName)) {
for (let moduleName of interceptor.nodeModuleName) {
this._factories.set(moduleName, interceptor);
public register(interceptor: INodeModuleFactory | IAlternativeModuleProvider): void {
if ('nodeModuleName' in interceptor) {
if (Array.isArray(interceptor.nodeModuleName)) {
for (let moduleName of interceptor.nodeModuleName) {
this._factories.set(moduleName, interceptor);
}
} else {
this._factories.set(interceptor.nodeModuleName, interceptor);
}
} else {
this._factories.set(interceptor.nodeModuleName, interceptor);
}

if (typeof interceptor.alternativeModuleName === 'function') {
this._alternatives.push((moduleName) => {
return interceptor.alternativeModuleName!(moduleName);
Expand All @@ -83,6 +91,60 @@ export abstract class RequireInterceptor {
}
}

//#region --- module renames

class NodeModuleAliasingModuleFactory implements IAlternativeModuleProvider {
/**
* Map of aliased internal node_modules, used to allow for modules to be
* renamed without breaking extensions. In the form "original -> new name".
*/
private static readonly aliased: ReadonlyMap<string, string> = new Map([
['vscode-ripgrep', '@vscode/ripgrep'],
]);

private readonly re?: RegExp;

constructor(@IExtHostInitDataService initData: IExtHostInitDataService) {
if (initData.environment.appRoot && NodeModuleAliasingModuleFactory.aliased.size) {
const root = escapeRegExpCharacters(this.forceForwardSlashes(initData.environment.appRoot.fsPath));
// decompose ${appRoot}/node_modules/foo/bin to ['${appRoot}/node_modules/', 'foo', '/bin'],
// and likewise the more complex form ${appRoot}/node_modules.asar.unpacked/@vcode/foo/bin
// to ['${appRoot}/node_modules.asar.unpacked/',' @vscode/foo', '/bin'].
const npmIdChrs = `[a-z0-9_.-]`;
const npmModuleName = `@${npmIdChrs}+\\/${npmIdChrs}+|${npmIdChrs}+`;
const moduleFolders = 'node_modules|node_modules\\.asar(?:\\.unpacked)?';
this.re = new RegExp(`^(${root}/${moduleFolders}\\/)(${npmModuleName})(.*)$`, 'i');
}
}

public alternativeModuleName(name: string): string | undefined {
if (!this.re) {
return;
}

const result = this.re.exec(this.forceForwardSlashes(name));
if (!result) {
return;
}

const [, prefix, moduleName, suffix] = result;
const dealiased = NodeModuleAliasingModuleFactory.aliased.get(moduleName);
if (dealiased === undefined) {
return;
}

console.warn(`${moduleName} as been renamed to ${dealiased}, please update your imports`);

return prefix + dealiased + suffix;
}

private forceForwardSlashes(str: string) {
return str.replace(/\\/g, '/');
}
}

//#endregion

//#region --- vscode-module

class VSCodeNodeModuleFactory implements INodeModuleFactory {
Expand Down
28 changes: 19 additions & 9 deletions src/vs/workbench/api/node/extHostExtensionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,33 @@ class NodeModuleRequireInterceptor extends RequireInterceptor {
protected _installInterceptor(): void {
const that = this;
const node_module = <any>require.__$__nodeRequire('module');
const original = node_module._load;
const originalLoad = node_module._load;
node_module._load = function load(request: string, parent: { filename: string; }, isMain: boolean) {
request = applyAlternatives(request);
if (!that._factories.has(request)) {
return originalLoad.apply(this, arguments);
}
return that._factories.get(request)!.load(
request,
URI.file(realpathSync(parent.filename)),
request => originalLoad.apply(this, [request, parent, isMain])
);
};

const originalLookup = node_module._resolveLookupPaths;
node_module._resolveLookupPaths = (request: string, parent: unknown) => {
return originalLookup.call(this, applyAlternatives(request), parent);
};

const applyAlternatives = (request: string) => {
for (let alternativeModuleName of that._alternatives) {
let alternative = alternativeModuleName(request);
if (alternative) {
request = alternative;
break;
}
}
if (!that._factories.has(request)) {
return original.apply(this, arguments);
}
return that._factories.get(request)!.load(
request,
URI.file(realpathSync(parent.filename)),
request => original.apply(this, [request, parent, isMain])
);
return request;
};
}
}
Expand Down

0 comments on commit 3d1576a

Please sign in to comment.