Change:
Following the addition of implicit activation events in the November release, we'd like to provide lint warnings via the extension-editing extension when an extension declares activation events which are already implied by its declared contributions.
cc as a heads up--we previously discussed this in and at the time agreed that a simple warning for the reserved/internal event was sufficient. We've since updated the docs and extension samples to mention that you don't need to explicitly list many events anymore. In discussion with Kai, the next step to round out this work and make extension authors aware of the new behavior is to provide lint warnings for all redundant events in the extension-editing extension. Let me know if you have any questions or concerns.

Discussion:
const allowedBadgeProvidersRegex: RegExp[] = (product.extensionAllowedBadgeProvidersRegex || []).map((r: string) => new RegExp(r)); const extensionEnabledApiProposals: Record<string, string[]> = product.extensionEnabledApiProposals ?? {}; const reservedImplicitActivationEventPrefixes = ['onNotebookSerializer:']; <ins> const redundantImplicitActivationEventPrefixes = ['onLanguage:', 'onView:', 'onAuthenticationRequest:', 'onCommand:', 'onCustomEditor:']; </ins> function isTrustedSVGSource(uri: Uri): boolean { return allowedBadgeProviders.includes(uri.authority.toLowerCase()) || allowedBadgeProvidersRegex.some(r => r.test(uri.toString()));

const relativeBadgeUrlRequiresHttpsRepository = l10n.t("Relative badge URLs require a repository with HTTPS protocol to be specified in this package.json."); const apiProposalNotListed = l10n.t("This proposal cannot be used because for this extension the product defines a fixed set of API proposals. You can test your extension but before publishing you MUST reach out to the VS Code team."); const implicitActivationEvent = l10n.t("This activation event cannot be explicitly listed by your extension."); <ins> const redundantImplicitActivationEvent = l10n.t("This activation event can be removed as VS Code generates these automatically from your package.json contribution declarations."); </ins> enum Context { ICON,

isExtension: boolean; hasHttpsRepository: boolean; repository: Uri; <ins> implicitActivationEvents: Set<string> | undefined; </ins> } export class ExtensionLinter {

} } } <del> const activationEventsNode = findNodeAtLocation(tree, ['activationEvents']); if (activationEventsNode?.type === 'array' && activationEventsNode.children) { for (const activationEventNode of activationEventsNode.children) { const activationEvent = getNodeValue(activationEventNode); </del> <ins> } const activationEventsNode = findNodeAtLocation(tree, ['activationEvents']); if (activationEventsNode?.type === 'array' && activationEventsNode.children) { for (const activationEventNode of activationEventsNode.children) { const activationEvent = getNodeValue(activationEventNode); if (info.implicitActivationEvents?.has(activationEvent) && redundantImplicitActivationEventPrefixes.some((prefix) => activationEvent.startsWith(prefix))) { const start = document.positionAt(activationEventNode.offset); const end = document.positionAt(activationEventNode.offset + activationEventNode.length); diagnostics.push(new Diagnostic(new Range(start, end), redundantImplicitActivationEvent, DiagnosticSeverity.Warning)); } else { </ins> for (const implicitActivationEventPrefix of reservedImplicitActivationEventPrefixes) { if (activationEvent.startsWith(implicitActivationEventPrefix)) { const start = document.positionAt(activationEventNode.offset);

} } } } this.diagnosticsCollection.set(document.uri, diagnostics); });

const engine = tree && findNodeAtLocation(tree, ['engines', 'vscode']); const repo = tree && findNodeAtLocation(tree, ['repository', 'url']); const uri = repo && parseUri(repo.value); <ins> const activationEvents = tree && parseImplicitActivationEvents(tree); </ins> const info: PackageJsonInfo = { isExtension: !!(engine && engine.type === 'string'), hasHttpsRepository: !!(repo && repo.type === 'string' && repo.value && uri && uri.scheme.toLowerCase() === 'https'), <del> repository: uri! </del> <ins> repository: uri!, implicitActivationEvents: activationEvents </ins> }; const str = folder.toString(); const oldInfo = this.folderToPackageJsonInfo[str];

} } } <ins> function parseImplicitActivationEvents(tree: JsonNode): Set<string> { const activationEvents = new Set<string>(); // commands const commands = findNodeAtLocation(tree, ['contributes', 'commands']); commands?.children?.forEach(child => { const command = findNodeAtLocation(child, ['command']); if (command && command.type === 'string') { activationEvents.add(`onCommand:${command.value}`); } }); // authenticationProviders const authenticationProviders = findNodeAtLocation(tree, ['contributes', 'authentication']); authenticationProviders?.children?.forEach(child => { const id = findNodeAtLocation(child, ['id']); if (id && id.type === 'string') { activationEvents.add(`onAuthenticationRequest:${id.value}`); } }); // languages const languageContributions = findNodeAtLocation(tree, ['contributes', 'languages']); languageContributions?.children?.forEach(child => { const id = findNodeAtLocation(child, ['id']); if (id && id.type === 'string') { activationEvents.add(`onLanguage:${id.value}`); } }); // customEditors const customEditors = findNodeAtLocation(tree, ['contributes', 'customEditors']); customEditors?.children?.forEach(child => { const viewType = findNodeAtLocation(child, ['viewType']); if (viewType && viewType.type === 'string') { activationEvents.add(`onCustomEditor:${viewType.value}`); } }); // views const viewContributions = findNodeAtLocation(tree, ['contributes', 'views']); viewContributions?.children?.forEach(viewContribution => { const views = viewContribution.children?.find((node) => node.type === 'array'); views?.children?.forEach(view => { const id = findNodeAtLocation(view, ['id']); if (id && id.type === 'string') { activationEvents.add(`onView:${id.value}`); } }); }); return activationEvents; } </ins>