Skip to content

Commit a017fde

Browse files
authored
Handle Some Disposable Registrations in TS Extension (microsoft#20775)
* Makes a few TS Extension Types Disposable **Bug** TS Extension currently registers many different calls with VSCode but does not handle the disposable result returned from these calls. **Fix** For some of these registrations, make sure we correctly dispose of them when the extension is deactivated. * Fix a few more cases * Cover a few more case
1 parent d0c1f3f commit a017fde

1 file changed

Lines changed: 63 additions & 29 deletions

File tree

extensions/typescript/src/typescriptMain.ts

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* ------------------------------------------------------------------------------------------ */
1010
'use strict';
1111

12-
import { env, languages, commands, workspace, window, ExtensionContext, Memento, IndentAction, Diagnostic, DiagnosticCollection, Range, Disposable, Uri, MessageItem, TextEditor } from 'vscode';
12+
import { env, languages, commands, workspace, window, ExtensionContext, Memento, IndentAction, Diagnostic, DiagnosticCollection, Range, Disposable, Uri, MessageItem, TextEditor, FileSystemWatcher } from 'vscode';
1313

1414
// This must be the first statement otherwise modules might got loaded with
1515
// the wrong locale.
@@ -86,6 +86,7 @@ export function activate(context: ExtensionContext): void {
8686
configFile: 'jsconfig.json'
8787
}
8888
], context.storagePath, context.globalState, context.workspaceState);
89+
context.subscriptions.push(clientHost);
8990

9091
const client = clientHost.serviceClient;
9192

@@ -139,10 +140,10 @@ const validateSetting = 'validate.enable';
139140

140141
class LanguageProvider {
141142

142-
private extensions: ObjectMap<boolean>;
143+
private readonly extensions: ObjectMap<boolean>;
143144
private syntaxDiagnostics: ObjectMap<Diagnostic[]>;
144-
private currentDiagnostics: DiagnosticCollection;
145-
private bufferSyncSupport: BufferSyncSupport;
145+
private readonly currentDiagnostics: DiagnosticCollection;
146+
private readonly bufferSyncSupport: BufferSyncSupport;
146147

147148
private completionItemProvider: CompletionItemProvider;
148149
private formattingProvider: FormattingProvider;
@@ -152,6 +153,8 @@ class LanguageProvider {
152153

153154
private _validate: boolean = true;
154155

156+
private readonly disposables: Disposable[] = [];
157+
155158
constructor(
156159
private readonly client: TypeScriptServiceClient,
157160
private readonly description: LanguageDescription
@@ -170,7 +173,7 @@ class LanguageProvider {
170173
this.typingsStatus = new TypingsStatus(client);
171174
new AtaProgressReporter(client);
172175

173-
workspace.onDidChangeConfiguration(this.configurationChanged, this);
176+
workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables);
174177
this.configurationChanged();
175178

176179
client.onReady().then(() => {
@@ -181,51 +184,68 @@ class LanguageProvider {
181184
});
182185
}
183186

187+
public dispose(): void {
188+
if (this.formattingProviderRegistration) {
189+
this.formattingProviderRegistration.dispose();
190+
}
191+
192+
while (this.disposables.length) {
193+
const obj = this.disposables.pop();
194+
if (obj) {
195+
obj.dispose();
196+
}
197+
}
198+
199+
this.typingsStatus.dispose();
200+
this.currentDiagnostics.dispose();
201+
this.bufferSyncSupport.dispose();
202+
}
203+
184204
private registerProviders(client: TypeScriptServiceClient): void {
185205
const selector = this.description.modeIds;
186206
const config = workspace.getConfiguration(this.id);
187207

188208
this.completionItemProvider = new CompletionItemProvider(client, this.typingsStatus);
189209
this.completionItemProvider.updateConfiguration();
190-
languages.registerCompletionItemProvider(selector, this.completionItemProvider, '.');
210+
this.disposables.push(languages.registerCompletionItemProvider(selector, this.completionItemProvider, '.'));
191211

192212
this.formattingProvider = new FormattingProvider(client);
193213
this.formattingProvider.updateConfiguration(config);
194-
languages.registerOnTypeFormattingEditProvider(selector, this.formattingProvider, ';', '}', '\n');
214+
this.disposables.push(languages.registerOnTypeFormattingEditProvider(selector, this.formattingProvider, ';', '}', '\n'));
195215
if (this.formattingProvider.isEnabled()) {
196216
this.formattingProviderRegistration = languages.registerDocumentRangeFormattingEditProvider(selector, this.formattingProvider);
197217
}
198218

199-
languages.registerHoverProvider(selector, new HoverProvider(client));
200-
languages.registerDefinitionProvider(selector, new DefinitionProvider(client));
201-
languages.registerDocumentHighlightProvider(selector, new DocumentHighlightProvider(client));
202-
languages.registerReferenceProvider(selector, new ReferenceProvider(client));
203-
languages.registerDocumentSymbolProvider(selector, new DocumentSymbolProvider(client));
204-
languages.registerSignatureHelpProvider(selector, new SignatureHelpProvider(client), '(', ',');
205-
languages.registerRenameProvider(selector, new RenameProvider(client));
219+
this.disposables.push(languages.registerHoverProvider(selector, new HoverProvider(client)));
220+
this.disposables.push(languages.registerDefinitionProvider(selector, new DefinitionProvider(client)));
221+
this.disposables.push(languages.registerDocumentHighlightProvider(selector, new DocumentHighlightProvider(client)));
222+
this.disposables.push(languages.registerReferenceProvider(selector, new ReferenceProvider(client)));
223+
this.disposables.push(languages.registerDocumentSymbolProvider(selector, new DocumentSymbolProvider(client)));
224+
this.disposables.push(languages.registerSignatureHelpProvider(selector, new SignatureHelpProvider(client), '(', ','));
225+
this.disposables.push(languages.registerRenameProvider(selector, new RenameProvider(client)));
206226

207227
if (client.apiVersion.has206Features()) {
208228
this.referenceCodeLensProvider = new ReferenceCodeLensProvider(client);
209229
this.referenceCodeLensProvider.updateConfiguration();
210-
languages.registerCodeLensProvider(selector, this.referenceCodeLensProvider);
230+
this.disposables.push(languages.registerCodeLensProvider(selector, this.referenceCodeLensProvider));
211231
}
212232

213233
if (client.apiVersion.has213Features()) {
214-
languages.registerCodeActionsProvider(selector, new CodeActionProvider(client, this.description.id));
234+
this.disposables.push(languages.registerCodeActionsProvider(selector, new CodeActionProvider(client, this.description.id)));
215235
}
216236

217237
if (client.apiVersion.has220Features()) {
218-
languages.registerImplementationProvider(selector, new ImplementationProvider(client));
238+
this.disposables.push(languages.registerImplementationProvider(selector, new ImplementationProvider(client)));
219239
}
220240

221241
if (client.apiVersion.has213Features()) {
222-
languages.registerTypeDefinitionProvider(selector, new TypeDefintionProvider(client));
242+
this.disposables.push(languages.registerTypeDefinitionProvider(selector, new TypeDefintionProvider(client)));
223243
}
224244

225245
this.description.modeIds.forEach(modeId => {
226-
languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(client, modeId));
246+
this.disposables.push(languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(client, modeId)));
227247

228-
languages.setLanguageConfiguration(modeId, {
248+
this.disposables.push(languages.setLanguageConfiguration(modeId, {
229249
indentationRules: {
230250
// ^(.*\*/)?\s*\}.*$
231251
decreaseIndentPattern: /^(.*\*\/)?\s*\}.*$/,
@@ -258,11 +278,11 @@ class LanguageProvider {
258278
action: { indentAction: IndentAction.None, removeText: 1 }
259279
}
260280
]
261-
});
281+
}));
262282

263283
const EMPTY_ELEMENTS: string[] = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'];
264284

265-
languages.setLanguageConfiguration('jsx-tags', {
285+
this.disposables.push(languages.setLanguageConfiguration('jsx-tags', {
266286
wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g,
267287
onEnterRules: [
268288
{
@@ -275,7 +295,7 @@ class LanguageProvider {
275295
action: { indentAction: IndentAction.Indent }
276296
}
277297
],
278-
});
298+
}));
279299
});
280300
}
281301

@@ -364,6 +384,8 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost {
364384
private client: TypeScriptServiceClient;
365385
private languages: LanguageProvider[];
366386
private languagePerId: ObjectMap<LanguageProvider>;
387+
private configFileWatcher: FileSystemWatcher;
388+
private readonly disposables: Disposable[] = [];
367389

368390
constructor(
369391
descriptions: LanguageDescription[],
@@ -380,19 +402,31 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost {
380402
this.triggerAllDiagnostics();
381403
}, 1500);
382404
};
383-
const watcher = workspace.createFileSystemWatcher('**/[tj]sconfig.json');
384-
watcher.onDidCreate(handleProjectCreateOrDelete);
385-
watcher.onDidDelete(handleProjectCreateOrDelete);
386-
watcher.onDidChange(handleProjectChange);
405+
const configFileWatcher = workspace.createFileSystemWatcher('**/[tj]sconfig.json');
406+
this.disposables.push(configFileWatcher);
407+
configFileWatcher.onDidCreate(handleProjectCreateOrDelete, this, this.disposables);
408+
configFileWatcher.onDidDelete(handleProjectCreateOrDelete, this, this.disposables);
409+
configFileWatcher.onDidChange(handleProjectChange, this, this.disposables);
387410

388411
this.client = new TypeScriptServiceClient(this, storagePath, globalState, workspaceState);
389412
this.languages = [];
390413
this.languagePerId = Object.create(null);
391-
descriptions.forEach(description => {
414+
for (const description of descriptions) {
392415
const manager = new LanguageProvider(this.client, description);
393416
this.languages.push(manager);
417+
this.disposables.push(manager);
394418
this.languagePerId[description.id] = manager;
395-
});
419+
}
420+
}
421+
422+
public dispose(): void {
423+
while (this.disposables.length) {
424+
const obj = this.disposables.pop();
425+
if (obj) {
426+
obj.dispose();
427+
}
428+
}
429+
this.configFileWatcher.dispose();
396430
}
397431

398432
public get serviceClient(): TypeScriptServiceClient {

0 commit comments

Comments
 (0)