Path: blob/main/src/vs/workbench/services/extensions/common/extensionsProposedApi.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { isNonEmptyArray } from '../../../../base/common/arrays.js';6import { localize } from '../../../../nls.js';7import { Disposable } from '../../../../base/common/lifecycle.js';8import { ExtensionIdentifier, IExtensionDescription, IExtensionManifest } from '../../../../platform/extensions/common/extensions.js';9import { allApiProposals, ApiProposalName } from '../../../../platform/extensions/common/extensionsApiProposals.js';10import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';11import { ILogService } from '../../../../platform/log/common/log.js';12import { IProductService } from '../../../../platform/product/common/productService.js';13import { Registry } from '../../../../platform/registry/common/platform.js';14import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';15import { Extensions, IExtensionFeatureMarkdownRenderer, IExtensionFeaturesRegistry, IRenderedData } from '../../extensionManagement/common/extensionFeatures.js';16import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js';17import { Mutable } from '../../../../base/common/types.js';1819export class ExtensionsProposedApi {2021private readonly _envEnablesProposedApiForAll: boolean;22private readonly _envEnabledExtensions: Set<string>;23private readonly _productEnabledExtensions: Map<string, string[]>;2425constructor(26@ILogService private readonly _logService: ILogService,27@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,28@IProductService productService: IProductService29) {3031this._envEnabledExtensions = new Set((_environmentService.extensionEnabledProposedApi ?? []).map(id => ExtensionIdentifier.toKey(id)));3233this._envEnablesProposedApiForAll =34!_environmentService.isBuilt || // always allow proposed API when running out of sources35(_environmentService.isExtensionDevelopment && productService.quality !== 'stable') || // do not allow proposed API against stable builds when developing an extension36(this._envEnabledExtensions.size === 0 && Array.isArray(_environmentService.extensionEnabledProposedApi)); // always allow proposed API if --enable-proposed-api is provided without extension ID3738this._productEnabledExtensions = new Map<string, ApiProposalName[]>();394041// NEW world - product.json spells out what proposals each extension can use42if (productService.extensionEnabledApiProposals) {43for (const [k, value] of Object.entries(productService.extensionEnabledApiProposals)) {44const key = ExtensionIdentifier.toKey(k);45const proposalNames = value.filter(name => {46if (!allApiProposals[<ApiProposalName>name]) {47_logService.warn(`Via 'product.json#extensionEnabledApiProposals' extension '${key}' wants API proposal '${name}' but that proposal DOES NOT EXIST. Likely, the proposal has been finalized (check 'vscode.d.ts') or was abandoned.`);48return false;49}50return true;51});52this._productEnabledExtensions.set(key, proposalNames);53}54}55}5657updateEnabledApiProposals(extensions: IExtensionDescription[]): void {58for (const extension of extensions) {59this.doUpdateEnabledApiProposals(extension);60}61}6263private doUpdateEnabledApiProposals(extension: Mutable<IExtensionDescription>): void {6465const key = ExtensionIdentifier.toKey(extension.identifier);6667// warn about invalid proposal and remove them from the list68if (isNonEmptyArray(extension.enabledApiProposals)) {69extension.enabledApiProposals = extension.enabledApiProposals.filter(name => {70const result = Boolean(allApiProposals[<ApiProposalName>name]);71if (!result) {72this._logService.error(`Extension '${key}' wants API proposal '${name}' but that proposal DOES NOT EXIST. Likely, the proposal has been finalized (check 'vscode.d.ts') or was abandoned.`);73}74return result;75});76}777879if (this._productEnabledExtensions.has(key)) {80// NOTE that proposals that are listed in product.json override whatever is declared in the extension81// itself. This is needed for us to know what proposals are used "in the wild". Merging product.json-proposals82// and extension-proposals would break that.8384const productEnabledProposals = this._productEnabledExtensions.get(key)!;8586// check for difference between product.json-declaration and package.json-declaration87const productSet = new Set(productEnabledProposals);88const extensionSet = new Set(extension.enabledApiProposals);89const diff = new Set([...extensionSet].filter(a => !productSet.has(a)));90if (diff.size > 0) {91this._logService.error(`Extension '${key}' appears in product.json but enables LESS API proposals than the extension wants.\npackage.json (LOSES): ${[...extensionSet].join(', ')}\nproduct.json (WINS): ${[...productSet].join(', ')}`);9293if (this._environmentService.isExtensionDevelopment) {94this._logService.error(`Proceeding with EXTRA proposals (${[...diff].join(', ')}) because extension is in development mode. Still, this EXTENSION WILL BE BROKEN unless product.json is updated.`);95productEnabledProposals.push(...diff);96}97}9899extension.enabledApiProposals = productEnabledProposals;100return;101}102103if (this._envEnablesProposedApiForAll || this._envEnabledExtensions.has(key)) {104// proposed API usage is not restricted and allowed just like the extension105// has declared it106return;107}108109if (!extension.isBuiltin && isNonEmptyArray(extension.enabledApiProposals)) {110// restrictive: extension cannot use proposed API in this context and its declaration is nulled111this._logService.error(`Extension '${extension.identifier.value} CANNOT USE these API proposals '${extension.enabledApiProposals?.join(', ') || '*'}'. You MUST start in extension development mode or use the --enable-proposed-api command line flag`);112extension.enabledApiProposals = [];113}114}115}116117class ApiProposalsMarkdowneRenderer extends Disposable implements IExtensionFeatureMarkdownRenderer {118119readonly type = 'markdown';120121shouldRender(manifest: IExtensionManifest): boolean {122return !!manifest.originalEnabledApiProposals?.length || !!manifest.enabledApiProposals?.length;123}124125render(manifest: IExtensionManifest): IRenderedData<IMarkdownString> {126const enabledApiProposals = manifest.originalEnabledApiProposals ?? manifest.enabledApiProposals ?? [];127const data = new MarkdownString();128if (enabledApiProposals.length) {129for (const proposal of enabledApiProposals) {130data.appendMarkdown(`- \`${proposal}\`\n`);131}132}133return {134data,135dispose: () => { }136};137}138}139140Registry.as<IExtensionFeaturesRegistry>(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({141id: 'enabledApiProposals',142label: localize('enabledProposedAPIs', "API Proposals"),143access: {144canToggle: false145},146renderer: new SyncDescriptor(ApiProposalsMarkdowneRenderer),147});148149150