diff --git a/src/vs/workbench/node/pluginHostMain.ts b/src/vs/workbench/node/extensionHostMain.ts similarity index 96% rename from src/vs/workbench/node/pluginHostMain.ts rename to src/vs/workbench/node/extensionHostMain.ts index a28c5261bad9e98842894210da7fa7de614c0ba9..b1dcc6e5c43c6a0a80c08c0fdfbc0c32f6bd473c 100644 --- a/src/vs/workbench/node/pluginHostMain.ts +++ b/src/vs/workbench/node/extensionHostMain.ts @@ -26,7 +26,7 @@ import {ExtHostTelemetryService} from 'vs/workbench/api/node/extHostTelemetry'; import {BaseRequestService} from 'vs/platform/request/common/baseRequestService'; import {BaseWorkspaceContextService} from 'vs/platform/workspace/common/baseWorkspaceContextService'; import {ModeServiceImpl} from 'vs/editor/common/services/modeServiceImpl'; -import {PluginScanner, MessagesCollector} from 'vs/workbench/node/extensionPoints'; +import {ExtensionScanner, MessagesCollector} from 'vs/workbench/node/extensionPoints'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { Client } from 'vs/base/node/service.net'; import { IExtensionsService } from 'vs/workbench/parts/extensions/common/extensions'; @@ -153,9 +153,9 @@ export class PluginHostMain { } private static scanPlugins(collector: MessagesCollector, builtinPluginsPath: string, userInstallPath: string, pluginDevelopmentPath: string, version: string): TPromise { - const builtinPlugins = PluginScanner.scanPlugins(version, collector, builtinPluginsPath, true); - const userPlugins = !userInstallPath ? TPromise.as([]) : PluginScanner.scanPlugins(version, collector, userInstallPath, false); - const developedPlugins = !pluginDevelopmentPath ? TPromise.as([]) : PluginScanner.scanOneOrMultiplePlugins(version, collector, pluginDevelopmentPath, false); + const builtinPlugins = ExtensionScanner.scanExtensions(version, collector, builtinPluginsPath, true); + const userPlugins = !userInstallPath ? TPromise.as([]) : ExtensionScanner.scanExtensions(version, collector, userInstallPath, false); + const developedPlugins = !pluginDevelopmentPath ? TPromise.as([]) : ExtensionScanner.scanOneOrMultipleExtensions(version, collector, pluginDevelopmentPath, false); return TPromise.join([builtinPlugins, userPlugins, developedPlugins]).then((_: IExtensionDescription[][]) => { let builtinPlugins = _[0]; diff --git a/src/vs/workbench/node/extensionPoints.ts b/src/vs/workbench/node/extensionPoints.ts index 05724bfbd3ce2e5115b1b3df16344635cbc4c187..d59e2f6bf37447e3e871b1ae2f72480ffcdba2f4 100644 --- a/src/vs/workbench/node/extensionPoints.ts +++ b/src/vs/workbench/node/extensionPoints.ts @@ -69,7 +69,74 @@ export class MessagesCollector { } } -export class PluginScanner { +abstract class ExtensionManifestHandler { + + protected _ourVersion: string; + protected _collector: MessagesCollector; + protected _absoluteFolderPath: string; + protected _isBuiltin: boolean; + protected _absoluteManifestPath: string; + + constructor(ourVersion: string, collector: MessagesCollector, absoluteFolderPath:string, isBuiltin:boolean) { + this._ourVersion = ourVersion; + this._collector = collector; + this._absoluteFolderPath = absoluteFolderPath; + this._isBuiltin = isBuiltin; + this._absoluteManifestPath = paths.join(absoluteFolderPath, MANIFEST_FILE); + } +} + +class ExtensionManifestParser extends ExtensionManifestHandler { + public parse(): TPromise { + return pfs.readFile(this._absoluteManifestPath).then((manifestContents) => { + let errors: string[] = []; + let extensionDescription: IExtensionDescription = json.parse(manifestContents.toString(), errors); + if (errors.length > 0) { + errors.forEach((error) => { + this._collector.error(this._absoluteFolderPath, 'Failed to parse ' + this._absoluteManifestPath + ': ' + error); + }); + return null; + } + return extensionDescription; + }, (err) => { + this._collector.error(this._absoluteFolderPath, 'Cannot read file ' + this._absoluteManifestPath + ': ' + err.message); + return null; + }); + } +} + +class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { + + public replaceNLS(extensionDescription:IExtensionDescription): TPromise { + let extension = paths.extname(this._absoluteManifestPath); + let basename = this._absoluteManifestPath.substr(0, this._absoluteManifestPath.length - extension.length); + + return pfs.fileExists(basename + '.nls' + extension).then(exists => { + if (!exists) { + return extensionDescription; + } + return ExtensionManifestNLSReplacer.findMessageBundle(basename).then(messageBundle => { + if (!messageBundle) { + return extensionDescription; + } + return pfs.readFile(messageBundle).then(messageBundleContent => { + let errors: string[] = []; + let messages: { [key: string]: string; } = json.parse(messageBundleContent.toString(), errors); + if (errors.length > 0) { + errors.forEach((error) => { + this._collector.error(this._absoluteFolderPath, 'Failed to parse ' + messageBundle + ': ' + error); + }); + return extensionDescription; + } + ExtensionManifestNLSReplacer._replaceNLStrings(extensionDescription, messages, this._collector, this._absoluteFolderPath); + return extensionDescription; + }, (err) => { + this._collector.error(this._absoluteFolderPath, 'Cannot read file ' + messageBundle + ': ' + err.message); + return null; + }); + }); + }); + } private static findMessageBundle(basename: string): TPromise { return new TPromise((c ,e, p) => { @@ -123,23 +190,56 @@ export class PluginScanner { } } } else if (Types.isObject(value)) { - PluginScanner._replaceNLStrings(value, messages, collector, messageScope); + ExtensionManifestNLSReplacer._replaceNLStrings(value, messages, collector, messageScope); } else if (Types.isArray(value)) { (value).forEach(element => { if (Types.isObject(element)) { - PluginScanner._replaceNLStrings(element, messages, collector, messageScope); + ExtensionManifestNLSReplacer._replaceNLStrings(element, messages, collector, messageScope); } }); } } }); } +} + +class ExtensionManifestValidator extends ExtensionManifestHandler { + validate(extensionDescription:IExtensionDescription): IExtensionDescription { + extensionDescription.isBuiltin = this._isBuiltin; + + let notices: string[] = []; + if (!isValidExtensionDescription(this._ourVersion, this._absoluteFolderPath, extensionDescription, notices)) { + notices.forEach((error) => { + this._collector.error(this._absoluteFolderPath, error); + }); + return null; + } + + // in this case the notices are warnings + notices.forEach((error) => { + this._collector.warn(this._absoluteFolderPath, error); + }); + + // id := `publisher.name` + extensionDescription.id = `${ extensionDescription.publisher }.${ extensionDescription.name }`; + + // main := absolutePath(`main`) + if (extensionDescription.main) { + extensionDescription.main = paths.normalize(paths.join(this._absoluteFolderPath, extensionDescription.main)); + } + + extensionDescription.extensionFolderPath = this._absoluteFolderPath; + + return extensionDescription; + } +} +export class ExtensionScanner { /** - * Scan the plugin defined in `absoluteFolderPath` + * Read the extension defined in `absoluteFolderPath` */ - public static scanPlugin( + public static scanExtension( version: string, collector: MessagesCollector, absoluteFolderPath:string, @@ -147,97 +247,29 @@ export class PluginScanner { ) : TPromise { absoluteFolderPath = paths.normalize(absoluteFolderPath); - let absoluteManifestPath = paths.join(absoluteFolderPath, MANIFEST_FILE); - let parseJSONManifest = (manifestContents:string): IExtensionDescription => { - let errors: string[] = []; - let pluginDescFromFile: IExtensionDescription = json.parse(manifestContents, errors); - if (errors.length > 0) { - errors.forEach((error) => { - collector.error(absoluteFolderPath, 'Failed to parse ' + absoluteManifestPath + ': ' + error); - }); + let parser = new ExtensionManifestParser(version, collector, absoluteFolderPath, isBuiltin); + return parser.parse().then((extensionDescription) => { + if (extensionDescription === null) { return null; } - return pluginDescFromFile; - }; - let replaceNLStrings = (pluginDescFromFile:IExtensionDescription): TPromise => { - let extension = paths.extname(absoluteManifestPath); - let basename = absoluteManifestPath.substr(0, absoluteManifestPath.length - extension.length); - - return pfs.fileExists(basename + '.nls' + extension).then(exists => { - if (!exists) { - return pluginDescFromFile; - } - return PluginScanner.findMessageBundle(basename).then(messageBundle => { - if (!messageBundle) { - return pluginDescFromFile; - } - return pfs.readFile(messageBundle).then(messageBundleContent => { - let errors: string[] = []; - let messages: { [key: string]: string; } = json.parse(messageBundleContent.toString(), errors); - if (errors.length > 0) { - errors.forEach((error) => { - collector.error(absoluteFolderPath, 'Failed to parse ' + messageBundle + ': ' + error); - }); - return pluginDescFromFile; - } - PluginScanner._replaceNLStrings(pluginDescFromFile, messages, collector, absoluteFolderPath); - return pluginDescFromFile; - }); - }); - }); - }; - - return pfs.readFile(absoluteManifestPath).then((manifestContents) => { - let pluginDescFromFile = parseJSONManifest(manifestContents.toString()); - if (pluginDescFromFile === null) { + let nlsReplacer = new ExtensionManifestNLSReplacer(version, collector, absoluteFolderPath, isBuiltin); + return nlsReplacer.replaceNLS(extensionDescription); + }).then((extensionDescription) => { + if (extensionDescription === null) { return null; } - return replaceNLStrings(pluginDescFromFile); - - }).then((pluginDescFromFile) => { - if (pluginDescFromFile === null) { - return null; - } - - pluginDescFromFile.isBuiltin = isBuiltin; - - let notices: string[] = []; - if (!isValidExtensionDescription(version, absoluteFolderPath, pluginDescFromFile, notices)) { - notices.forEach((error) => { - collector.error(absoluteFolderPath, error); - }); - return null; - } - - // in this case the notices are warnings - notices.forEach((error) => { - collector.warn(absoluteFolderPath, error); - }); - - // id := `publisher.name` - pluginDescFromFile.id = `${ pluginDescFromFile.publisher }.${ pluginDescFromFile.name }`; - - // main := absolutePath(`main`) - if (pluginDescFromFile.main) { - pluginDescFromFile.main = paths.normalize(paths.join(absoluteFolderPath, pluginDescFromFile.main)); - } - - pluginDescFromFile.extensionFolderPath = absoluteFolderPath; - - return pluginDescFromFile; - }, (err) => { - collector.error(absoluteFolderPath, 'Cannot read file ' + absoluteManifestPath + ': ' + err.message); - return null; + let validator = new ExtensionManifestValidator(version, collector, absoluteFolderPath, isBuiltin); + return validator.validate(extensionDescription); }); } /** * Scan a list of extensions defined in `absoluteFolderPath` */ - public static scanPlugins( + public static scanExtensions( version: string, collector: MessagesCollector, absoluteFolderPath:string, @@ -254,13 +286,13 @@ export class PluginScanner { return obsolete.then(obsolete => { return pfs.readDirsInDir(absoluteFolderPath) - .then(folders => TPromise.join(folders.map(f => this.scanPlugin(version, collector, paths.join(absoluteFolderPath, f), isBuiltin)))) - .then(plugins => plugins.filter(item => item !== null)) + .then(folders => TPromise.join(folders.map(f => this.scanExtension(version, collector, paths.join(absoluteFolderPath, f), isBuiltin)))) + .then(extensionDescriptions => extensionDescriptions.filter(item => item !== null)) // TODO: align with extensionsService - .then(plugins => plugins.filter(p => !obsolete[`${ p.publisher }.${ p.name }-${ p.version }`])) - .then(plugins => { - const pluginsById = values(groupBy(plugins, p => p.id)); - return pluginsById.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0]); + .then(extensionDescriptions => extensionDescriptions.filter(p => !obsolete[`${ p.publisher }.${ p.name }-${ p.version }`])) + .then(extensionDescriptions => { + const extensionDescriptionsById = values(groupBy(extensionDescriptions, p => p.id)); + return extensionDescriptionsById.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0]); }) .then(null, err => { collector.error(absoluteFolderPath, err); @@ -270,10 +302,10 @@ export class PluginScanner { } /** - * Combination of scanPlugin and scanPlugins: If a plugin manifest is found at root, we load just this plugin, otherwise we assume - * the folder contains multiple extensions. + * Combination of scanExtension and scanExtensions: If an extension manifest is found at root, we load just this extension, + * otherwise we assume the folder contains multiple extensions. */ - public static scanOneOrMultiplePlugins( + public static scanOneOrMultipleExtensions( version: string, collector: MessagesCollector, absoluteFolderPath:string, @@ -282,14 +314,14 @@ export class PluginScanner { { return pfs.fileExists(paths.join(absoluteFolderPath, MANIFEST_FILE)).then((exists) => { if (exists) { - return this.scanPlugin(version, collector, absoluteFolderPath, isBuiltin).then((extensionDescription) => { + return this.scanExtension(version, collector, absoluteFolderPath, isBuiltin).then((extensionDescription) => { if (extensionDescription === null) { return []; } return [extensionDescription]; }); } - return this.scanPlugins(version, collector, absoluteFolderPath, isBuiltin); + return this.scanExtensions(version, collector, absoluteFolderPath, isBuiltin); }, (err) => { collector.error(absoluteFolderPath, err); return []; diff --git a/src/vs/workbench/node/pluginHostProcess.ts b/src/vs/workbench/node/pluginHostProcess.ts index 7d355beb0fc5dc119277d98d4b60ccfacb42fb92..2301967f8bc0965f4b22ab7b4bf3b5ce4ac624ce 100644 --- a/src/vs/workbench/node/pluginHostProcess.ts +++ b/src/vs/workbench/node/pluginHostProcess.ts @@ -7,7 +7,7 @@ import {onUnexpectedError} from 'vs/base/common/errors'; import { TPromise } from 'vs/base/common/winjs.base'; -import { PluginHostMain, createServices, IInitData, exit } from 'vs/workbench/node/pluginHostMain'; +import { PluginHostMain, createServices, IInitData, exit } from 'vs/workbench/node/extensionHostMain'; import { Client, connect } from 'vs/base/node/service.net'; import { create as createIPC, IPluginsIPC } from 'vs/platform/extensions/common/ipcRemoteCom'; import marshalling = require('vs/base/common/marshalling');