Skip to content

Commit 2541755

Browse files
committed
Adopt case insensitive extension identifiers
1 parent 5adc152 commit 2541755

57 files changed

Lines changed: 483 additions & 343 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/vs/platform/configuration/common/configurationRegistry.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
1010
import * as types from 'vs/base/common/types';
1111
import * as strings from 'vs/base/common/strings';
1212
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
13+
import { CanonicalExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
1314

1415
export const Extensions = {
1516
Configuration: 'base.contributions.configuration'
@@ -93,7 +94,7 @@ export interface IConfigurationNode {
9394
}
9495

9596
export interface IDefaultConfigurationExtension {
96-
id: string;
97+
id: CanonicalExtensionIdentifier;
9798
name: string;
9899
defaults: { [key: string]: {} };
99100
}

src/vs/platform/extensions/common/extensions.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
77
import { IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement';
88
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
9+
import * as strings from 'vs/base/common/strings';
910

1011
export const MANIFEST_CACHE_FOLDER = 'CachedExtensions';
1112
export const USER_MANIFEST_CACHE_FILE = 'user';
@@ -31,3 +32,58 @@ export function isUIExtension(manifest: IExtensionManifest, configurationService
3132
default: return uiExtensions.has(extensionId) || !manifest.main;
3233
}
3334
}
35+
36+
/**
37+
* **!Do not construct directly!**
38+
*
39+
* **!Only static methods because it gets serialized!**
40+
*
41+
* This represents the "canonical" version for an extension identifier. Extension ids
42+
* have to be case-insensitive (due to the marketplace), but we must ensure case
43+
* preservation because the extension API is already public at this time.
44+
*
45+
* For example, given an extension with the publisher `"Hello"` and the name `"World"`,
46+
* its canonical extension identifier is `"Hello.World"`. This extension could be
47+
* referenced in some other extension's dependencies using the string `"hello.world"`.
48+
*
49+
* To make matters more complicated, an extension can optionally have an UUID. When two
50+
* extensions have the same UUID, they are considered equal even if their identifier is different.
51+
*/
52+
export class CanonicalExtensionIdentifier {
53+
public readonly value: string;
54+
private readonly _lower: string;
55+
56+
constructor(value: string) {
57+
this.value = value;
58+
this._lower = value.toLowerCase();
59+
}
60+
61+
public static equals(a: CanonicalExtensionIdentifier | string | null | undefined, b: CanonicalExtensionIdentifier | string | null | undefined) {
62+
if (typeof a === 'undefined' || a === null) {
63+
return (typeof b === 'undefined' || b === null);
64+
}
65+
if (typeof b === 'undefined' || b === null) {
66+
return false;
67+
}
68+
if (typeof a === 'string' || typeof b === 'string') {
69+
// At least one of the arguments is an extension id in string form,
70+
// so we have to use the string comparison which ignores case.
71+
let aValue = (typeof a === 'string' ? a : a.value);
72+
let bValue = (typeof b === 'string' ? b : b.value);
73+
return strings.equalsIgnoreCase(aValue, bValue);
74+
}
75+
76+
// Now we know both arguments are CanonicalExtensionIdentifier
77+
return (a._lower === b._lower);
78+
}
79+
80+
/**
81+
* Gives the value by which to index (for equality).
82+
*/
83+
public static toKey(id: CanonicalExtensionIdentifier | string): string {
84+
if (typeof id === 'string') {
85+
return id.toLowerCase();
86+
}
87+
return id._lower;
88+
}
89+
}

src/vs/platform/statusbar/common/statusbar.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
77
import { IDisposable } from 'vs/base/common/lifecycle';
88
import { ThemeColor } from 'vs/platform/theme/common/themeService';
9+
import { CanonicalExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
910

1011
export const IStatusbarService = createDecorator<IStatusbarService>('statusbarService');
1112

@@ -48,7 +49,7 @@ export interface IStatusbarEntry {
4849
/**
4950
* An optional extension ID if this entry is provided from an extension.
5051
*/
51-
readonly extensionId?: string;
52+
readonly extensionId?: CanonicalExtensionIdentifier;
5253

5354
/**
5455
* Wether to show a beak above the status bar entry.

src/vs/workbench/api/browser/viewsContainersExtensionPoint.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
3030
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
3131
import { URI } from 'vs/base/common/uri';
3232
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
33+
import { CanonicalExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
3334

3435
export interface IUserFriendlyViewsContainerDescriptor {
3536
id: string;
@@ -147,12 +148,12 @@ class ViewsContainersExtensionHandler implements IWorkbenchContribution {
147148
containers.forEach(descriptor => {
148149
const cssClass = `extensionViewlet-${descriptor.id}`;
149150
const icon = resources.joinPath(extension.extensionLocation, descriptor.icon);
150-
this.registerCustomViewlet({ id: `workbench.view.extension.${descriptor.id}`, title: descriptor.title, icon }, order++, cssClass, extension.id);
151+
this.registerCustomViewlet({ id: `workbench.view.extension.${descriptor.id}`, title: descriptor.title, icon }, order++, cssClass, extension.identifier);
151152
});
152153
return order;
153154
}
154155

155-
private registerCustomViewlet(descriptor: IUserFriendlyViewsContainerDescriptor2, order: number, cssClass: string, extensionId: string): void {
156+
private registerCustomViewlet(descriptor: IUserFriendlyViewsContainerDescriptor2, order: number, cssClass: string, extensionId: CanonicalExtensionIdentifier): void {
156157
const viewContainersRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
157158
const viewletRegistry = Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets);
158159
const id = descriptor.id;

src/vs/workbench/api/browser/viewsExtensionPoint.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
1919
import { VIEWLET_ID as EXPLORER } from 'vs/workbench/parts/files/common/files';
2020
import { VIEWLET_ID as SCM } from 'vs/workbench/parts/scm/common/scm';
2121
import { VIEWLET_ID as DEBUG } from 'vs/workbench/parts/debug/common/debug';
22+
import { CanonicalExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
2223

2324
interface IUserFriendlyViewDescriptor {
2425
id: string;
@@ -136,7 +137,7 @@ class ViewsContainersExtensionHandler implements IWorkbenchContribution {
136137
canToggleVisibility: true,
137138
collapsed: this.showCollapsed(container),
138139
treeView: this.instantiationService.createInstance(CustomTreeView, item.id, container),
139-
order: extension.description.id === container.extensionId ? index + 1 : void 0
140+
order: CanonicalExtensionIdentifier.equals(extension.description.identifier, container.extensionId) ? index + 1 : void 0
140141
};
141142

142143
viewIds.push(viewDescriptor.id);

src/vs/workbench/api/electron-browser/mainThreadComments.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
1919
import { generateUuid } from 'vs/base/common/uuid';
2020
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
2121
import { ICommentsConfiguration } from 'vs/workbench/parts/comments/electron-browser/comments.contribution';
22+
import { CanonicalExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
2223

2324
export class MainThreadDocumentCommentProvider implements modes.DocumentCommentProvider {
2425
private _proxy: ExtHostCommentsShape;
@@ -125,7 +126,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments
125126
}
126127
}
127128

128-
$registerWorkspaceCommentProvider(handle: number, extensionId: string): void {
129+
$registerWorkspaceCommentProvider(handle: number, extensionId: CanonicalExtensionIdentifier): void {
129130
this._workspaceProviders.set(handle, undefined);
130131

131132
const providerId = generateUuid();
@@ -165,7 +166,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments
165166
}
166167
*/
167168
this._telemetryService.publicLog('comments:registerWorkspaceCommentProvider', {
168-
extensionId: extensionId
169+
extensionId: extensionId.value
169170
});
170171
}
171172

src/vs/workbench/api/electron-browser/mainThreadExtensionService.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostC
99
import { IExtHostContext, MainContext, MainThreadExtensionServiceShape } from 'vs/workbench/api/node/extHost.protocol';
1010
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
1111
import { ExtensionService } from 'vs/workbench/services/extensions/electron-browser/extensionService';
12+
import { CanonicalExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
1213

1314
@extHostNamedCustomer(MainContext.MainThreadExtensionService)
1415
export class MainThreadExtensionService implements MainThreadExtensionServiceShape {
@@ -30,13 +31,13 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha
3031
$localShowMessage(severity: Severity, msg: string): void {
3132
this._extensionService._logOrShowMessage(severity, msg);
3233
}
33-
$onWillActivateExtension(extensionId: string): void {
34+
$onWillActivateExtension(extensionId: CanonicalExtensionIdentifier): void {
3435
this._extensionService._onWillActivateExtension(extensionId);
3536
}
36-
$onDidActivateExtension(extensionId: string, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void {
37+
$onDidActivateExtension(extensionId: CanonicalExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void {
3738
this._extensionService._onDidActivateExtension(extensionId, startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent);
3839
}
39-
$onExtensionRuntimeError(extensionId: string, data: SerializedError): void {
40+
$onExtensionRuntimeError(extensionId: CanonicalExtensionIdentifier, data: SerializedError): void {
4041
const error = new Error();
4142
error.name = data.name;
4243
error.message = data.message;
@@ -45,9 +46,9 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha
4546
console.error(`[${extensionId}]${error.message}`);
4647
console.error(error.stack);
4748
}
48-
$onExtensionActivationFailed(extensionId: string): void {
49+
$onExtensionActivationFailed(extensionId: CanonicalExtensionIdentifier): void {
4950
}
50-
$addMessage(extensionId: string, severity: Severity, message: string): void {
51+
$addMessage(extensionId: CanonicalExtensionIdentifier, severity: Severity, message: string): void {
5152
this._extensionService._addMessage(extensionId, severity, message);
5253
}
5354
}

src/vs/workbench/api/electron-browser/mainThreadMessageService.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
1414
import { Event } from 'vs/base/common/event';
1515
import { ICommandService } from 'vs/platform/commands/common/commands';
1616
import { dispose } from 'vs/base/common/lifecycle';
17+
import { CanonicalExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
1718

1819
@extHostNamedCustomer(MainContext.MainThreadMessageService)
1920
export class MainThreadMessageService implements MainThreadMessageServiceShape {
@@ -55,9 +56,9 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
5556
}
5657

5758
class ManageExtensionAction extends Action {
58-
constructor(id: string, label: string, commandService: ICommandService) {
59-
super(id, label, undefined, true, () => {
60-
return commandService.executeCommand('_extensions.manage', id);
59+
constructor(id: CanonicalExtensionIdentifier, label: string, commandService: ICommandService) {
60+
super(id.value, label, undefined, true, () => {
61+
return commandService.executeCommand('_extensions.manage', id.value);
6162
});
6263
}
6364
}
@@ -77,7 +78,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
7778

7879
const secondaryActions: IAction[] = [];
7980
if (extension && !extension.isUnderDevelopment) {
80-
secondaryActions.push(new ManageExtensionAction(extension.id, nls.localize('manageExtension', "Manage Extension"), this._commandService));
81+
secondaryActions.push(new ManageExtensionAction(extension.identifier, nls.localize('manageExtension', "Manage Extension"), this._commandService));
8182
}
8283

8384
const messageHandle = this._notificationService.notify({

src/vs/workbench/api/electron-browser/mainThreadStatusBar.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { IDisposable } from 'vs/base/common/lifecycle';
88
import { MainThreadStatusBarShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
99
import { ThemeColor } from 'vs/platform/theme/common/themeService';
1010
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
11+
import { CanonicalExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
1112

1213
@extHostNamedCustomer(MainContext.MainThreadStatusBar)
1314
export class MainThreadStatusBar implements MainThreadStatusBarShape {
@@ -27,7 +28,7 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape {
2728
}
2829
}
2930

30-
$setEntry(id: number, extensionId: string, text: string, tooltip: string, command: string, color: string | ThemeColor, alignment: MainThreadStatusBarAlignment, priority: number): void {
31+
$setEntry(id: number, extensionId: CanonicalExtensionIdentifier, text: string, tooltip: string, command: string, color: string | ThemeColor, alignment: MainThreadStatusBarAlignment, priority: number): void {
3132

3233
// Dispose any old
3334
this.$dispose(id);

src/vs/workbench/api/electron-browser/mainThreadUrls.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,18 @@ import { IURLService, IURLHandler } from 'vs/platform/url/common/url';
99
import { URI } from 'vs/base/common/uri';
1010
import { IDisposable } from 'vs/base/common/lifecycle';
1111
import { IExtensionUrlHandler } from 'vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler';
12+
import { CanonicalExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
1213

1314
class ExtensionUrlHandler implements IURLHandler {
1415

1516
constructor(
1617
private readonly proxy: ExtHostUrlsShape,
1718
private readonly handle: number,
18-
readonly extensionId: string
19+
readonly extensionId: CanonicalExtensionIdentifier
1920
) { }
2021

2122
handleURL(uri: URI): Promise<boolean> {
22-
if (uri.authority !== this.extensionId) {
23+
if (!CanonicalExtensionIdentifier.equals(this.extensionId, uri.authority)) {
2324
return Promise.resolve(false);
2425
}
2526

@@ -31,7 +32,7 @@ class ExtensionUrlHandler implements IURLHandler {
3132
export class MainThreadUrls implements MainThreadUrlsShape {
3233

3334
private readonly proxy: ExtHostUrlsShape;
34-
private handlers = new Map<number, { extensionId: string, disposable: IDisposable }>();
35+
private handlers = new Map<number, { extensionId: CanonicalExtensionIdentifier, disposable: IDisposable }>();
3536

3637
constructor(
3738
context: IExtHostContext,
@@ -41,7 +42,7 @@ export class MainThreadUrls implements MainThreadUrlsShape {
4142
this.proxy = context.getProxy(ExtHostContext.ExtHostUrls);
4243
}
4344

44-
$registerUriHandler(handle: number, extensionId: string): Promise<void> {
45+
$registerUriHandler(handle: number, extensionId: CanonicalExtensionIdentifier): Promise<void> {
4546
const handler = new ExtensionUrlHandler(this.proxy, handle, extensionId);
4647
const disposable = this.urlService.registerHandler(handler);
4748

0 commit comments

Comments
 (0)