Skip to content

Commit 8661ba7

Browse files
author
Kartik Raj
authored
Add command to clear extension related storage (#15883)
* Add command to clear extension related storage * News entry * Clean up * Fix linting tests * Fix linting * Add tests * Capitalize
1 parent a5c8a36 commit 8661ba7

File tree

8 files changed

+163
-2
lines changed

8 files changed

+163
-2
lines changed

news/1 Enhancements/15883.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added command `Python: Clear internal extension cache` to clear extension related cache.

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@
103103
}
104104
],
105105
"commands": [
106+
{
107+
"command": "python.clearPersistentStorage",
108+
"title": "%python.command.python.clearPersistentStorage.title%",
109+
"category": "Python"
110+
},
106111
{
107112
"command": "python.enableSourceMapSupport",
108113
"title": "%python.command.python.enableSourceMapSupport.title%",

package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"python.command.python.enableLinting.title": "Enable/Disable Linting",
3535
"python.command.python.runLinting.title": "Run Linting",
3636
"python.command.python.enableSourceMapSupport.title": "Enable Source Map Support For Extension Debugging",
37+
"python.command.python.clearPersistentStorage.title": "Clear Internal Extension Cache",
3738
"python.command.python.startPage.open.title": "Open Start Page",
3839
"python.command.python.analysis.clearCache.title": "Clear Module Analysis Cache",
3940
"python.command.python.analysis.restartLanguageServer.title": "Restart Language Server",

src/client/common/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export namespace Commands {
6363
export const SwitchToInsidersWeekly = 'python.switchToWeeklyChannel';
6464
export const PickLocalProcess = 'python.pickLocalProcess';
6565
export const GetSelectedInterpreterPath = 'python.interpreterPath';
66+
export const ClearStorage = 'python.clearPersistentStorage';
6667
export const ClearWorkspaceInterpreter = 'python.clearWorkspaceInterpreter';
6768
export const ResetInterpreterSecurityStorage = 'python.resetInterpreterSecurityStorage';
6869
export const OpenStartPage = 'python.startPage.open';

src/client/common/persistentState.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
import { inject, injectable, named } from 'inversify';
77
import { Memento } from 'vscode';
8+
import { IExtensionSingleActivationService } from '../activation/types';
9+
import { ICommandManager } from './application/types';
10+
import { Commands } from './constants';
811
import {
912
GLOBAL_MEMENTO,
1013
IExtensionContext,
@@ -44,26 +47,72 @@ export class PersistentState<T> implements IPersistentState<T> {
4447
}
4548
}
4649

50+
const GLOBAL_PERSISTENT_KEYS = 'PYTHON_EXTENSION_GLOBAL_STORAGE_KEYS';
51+
const WORKSPACE_PERSISTENT_KEYS = 'PYTHON_EXTENSION_WORKSPACE_STORAGE_KEYS';
52+
type keysStorage = { key: string; defaultValue: unknown };
53+
4754
@injectable()
48-
export class PersistentStateFactory implements IPersistentStateFactory {
55+
export class PersistentStateFactory implements IPersistentStateFactory, IExtensionSingleActivationService {
56+
private readonly globalKeysStorage = new PersistentState<keysStorage[]>(
57+
this.globalState,
58+
GLOBAL_PERSISTENT_KEYS,
59+
[],
60+
);
61+
private readonly workspaceKeysStorage = new PersistentState<keysStorage[]>(
62+
this.workspaceState,
63+
WORKSPACE_PERSISTENT_KEYS,
64+
[],
65+
);
4966
constructor(
5067
@inject(IMemento) @named(GLOBAL_MEMENTO) private globalState: Memento,
5168
@inject(IMemento) @named(WORKSPACE_MEMENTO) private workspaceState: Memento,
69+
@inject(ICommandManager) private cmdManager: ICommandManager,
5270
) {}
71+
72+
public async activate(): Promise<void> {
73+
this.cmdManager.registerCommand(Commands.ClearStorage, this.cleanAllPersistentStates.bind(this));
74+
}
75+
5376
public createGlobalPersistentState<T>(
5477
key: string,
5578
defaultValue?: T,
5679
expiryDurationMs?: number,
5780
): IPersistentState<T> {
81+
if (!this.globalKeysStorage.value.includes({ key, defaultValue })) {
82+
this.globalKeysStorage.updateValue([{ key, defaultValue }, ...this.globalKeysStorage.value]).ignoreErrors();
83+
}
5884
return new PersistentState<T>(this.globalState, key, defaultValue, expiryDurationMs);
5985
}
86+
6087
public createWorkspacePersistentState<T>(
6188
key: string,
6289
defaultValue?: T,
6390
expiryDurationMs?: number,
6491
): IPersistentState<T> {
92+
if (!this.workspaceKeysStorage.value.includes({ key, defaultValue })) {
93+
this.workspaceKeysStorage
94+
.updateValue([{ key, defaultValue }, ...this.workspaceKeysStorage.value])
95+
.ignoreErrors();
96+
}
6597
return new PersistentState<T>(this.workspaceState, key, defaultValue, expiryDurationMs);
6698
}
99+
100+
private async cleanAllPersistentStates(): Promise<void> {
101+
await Promise.all(
102+
this.globalKeysStorage.value.map(async (keyContent) => {
103+
const storage = this.createGlobalPersistentState(keyContent.key);
104+
await storage.updateValue(keyContent.defaultValue);
105+
}),
106+
);
107+
await Promise.all(
108+
this.workspaceKeysStorage.value.map(async (keyContent) => {
109+
const storage = this.createWorkspacePersistentState(keyContent.key);
110+
await storage.updateValue(keyContent.defaultValue);
111+
}),
112+
);
113+
await this.globalKeysStorage.updateValue([]);
114+
await this.workspaceKeysStorage.updateValue([]);
115+
}
67116
}
68117

69118
/////////////////////////////
@@ -79,6 +128,10 @@ interface IPersistentStorage<T> {
79128
* Build a global storage object for the given key.
80129
*/
81130
export function getGlobalStorage<T>(context: IExtensionContext, key: string): IPersistentStorage<T> {
131+
const globalKeysStorage = new PersistentState<keysStorage[]>(context.globalState, GLOBAL_PERSISTENT_KEYS, []);
132+
if (!globalKeysStorage.value.includes({ key, defaultValue: undefined })) {
133+
globalKeysStorage.updateValue([{ key, defaultValue: undefined }, ...globalKeysStorage.value]).ignoreErrors();
134+
}
82135
const raw = new PersistentState<T>(context.globalState, key);
83136
return {
84137
// We adapt between PersistentState and IPersistentStorage.

src/client/common/serviceRegistry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ export function registerTypes(serviceManager: IServiceManager) {
131131
serviceManager.addSingleton<IExtensions>(IExtensions, Extensions);
132132
serviceManager.addSingleton<IRandom>(IRandom, Random);
133133
serviceManager.addSingleton<IPersistentStateFactory>(IPersistentStateFactory, PersistentStateFactory);
134+
serviceManager.addBinding(IPersistentStateFactory, IExtensionSingleActivationService);
134135
serviceManager.addSingleton<ITerminalServiceFactory>(ITerminalServiceFactory, TerminalServiceFactory);
135136
serviceManager.addSingleton<IPathUtils>(IPathUtils, PathUtils);
136137
serviceManager.addSingleton<IApplicationShell>(IApplicationShell, ApplicationShell);
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { expect } from 'chai';
7+
import * as TypeMoq from 'typemoq';
8+
import { Memento } from 'vscode';
9+
import { IExtensionSingleActivationService } from '../../client/activation/types';
10+
import { ICommandManager } from '../../client/common/application/types';
11+
import { Commands } from '../../client/common/constants';
12+
import { PersistentStateFactory } from '../../client/common/persistentState';
13+
import { IDisposable, IPersistentStateFactory } from '../../client/common/types';
14+
import { MockMemento } from '../mocks/mementos';
15+
16+
suite('Persistent State', () => {
17+
let cmdManager: TypeMoq.IMock<ICommandManager>;
18+
let persistentStateFactory: IPersistentStateFactory & IExtensionSingleActivationService;
19+
let workspaceMemento: Memento;
20+
let globalMemento: Memento;
21+
setup(() => {
22+
cmdManager = TypeMoq.Mock.ofType<ICommandManager>();
23+
workspaceMemento = new MockMemento();
24+
globalMemento = new MockMemento();
25+
persistentStateFactory = new PersistentStateFactory(globalMemento, workspaceMemento, cmdManager.object);
26+
});
27+
28+
test('Global states created are restored on invoking clean storage command', async () => {
29+
let clearStorageCommand: (() => Promise<void>) | undefined;
30+
cmdManager
31+
.setup((c) => c.registerCommand(Commands.ClearStorage, TypeMoq.It.isAny()))
32+
.callback((_, c) => {
33+
clearStorageCommand = c;
34+
})
35+
.returns(() => TypeMoq.Mock.ofType<IDisposable>().object);
36+
37+
// Register command to clean storage
38+
await persistentStateFactory.activate();
39+
40+
expect(clearStorageCommand).to.not.equal(undefined, 'Callback not registered');
41+
42+
const globalKey1State = persistentStateFactory.createGlobalPersistentState('key1', 'defaultKey1Value');
43+
await globalKey1State.updateValue('key1Value');
44+
const globalKey2State = persistentStateFactory.createGlobalPersistentState<string | undefined>(
45+
'key2',
46+
undefined,
47+
);
48+
await globalKey2State.updateValue('key2Value');
49+
50+
// Verify states are updated correctly
51+
expect(globalKey1State.value).to.equal('key1Value');
52+
expect(globalKey2State.value).to.equal('key2Value');
53+
54+
await clearStorageCommand!(); // Invoke command
55+
56+
// Verify states are now reset to their default value.
57+
expect(globalKey1State.value).to.equal('defaultKey1Value');
58+
expect(globalKey2State.value).to.equal(undefined);
59+
});
60+
61+
test('Workspace states created are restored on invoking clean storage command', async () => {
62+
let clearStorageCommand: (() => Promise<void>) | undefined;
63+
cmdManager
64+
.setup((c) => c.registerCommand(Commands.ClearStorage, TypeMoq.It.isAny()))
65+
.callback((_, c) => {
66+
clearStorageCommand = c;
67+
})
68+
.returns(() => TypeMoq.Mock.ofType<IDisposable>().object);
69+
70+
// Register command to clean storage
71+
await persistentStateFactory.activate();
72+
73+
expect(clearStorageCommand).to.not.equal(undefined, 'Callback not registered');
74+
75+
const workspaceKey1State = persistentStateFactory.createWorkspacePersistentState('key1');
76+
await workspaceKey1State.updateValue('key1Value');
77+
const workspaceKey2State = persistentStateFactory.createWorkspacePersistentState('key2', 'defaultKey2Value');
78+
await workspaceKey2State.updateValue('key2Value');
79+
80+
// Verify states are updated correctly
81+
expect(workspaceKey1State.value).to.equal('key1Value');
82+
expect(workspaceKey2State.value).to.equal('key2Value');
83+
84+
await clearStorageCommand!(); // Invoke command
85+
86+
// Verify states are now reset to their default value.
87+
expect(workspaceKey1State.value).to.equal(undefined);
88+
expect(workspaceKey2State.value).to.equal('defaultKey2Value');
89+
});
90+
});

src/test/linters/lint.provider.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import { Container } from 'inversify';
77
import * as TypeMoq from 'typemoq';
88
import * as vscode from 'vscode';
99
import { LanguageServerType } from '../../client/activation/types';
10-
import { IApplicationShell, IDocumentManager, IWorkspaceService } from '../../client/common/application/types';
10+
import {
11+
IApplicationShell,
12+
ICommandManager,
13+
IDocumentManager,
14+
IWorkspaceService,
15+
} from '../../client/common/application/types';
1116
import { PersistentStateFactory } from '../../client/common/persistentState';
1217
import { IFileSystem } from '../../client/common/platform/types';
1318
import {
@@ -109,6 +114,10 @@ suite('Linting - Provider', () => {
109114
serviceManager.addSingleton<IPersistentStateFactory>(IPersistentStateFactory, PersistentStateFactory);
110115
serviceManager.addSingleton<vscode.Memento>(IMemento, MockMemento, GLOBAL_MEMENTO);
111116
serviceManager.addSingleton<vscode.Memento>(IMemento, MockMemento, WORKSPACE_MEMENTO);
117+
serviceManager.addSingletonInstance<ICommandManager>(
118+
ICommandManager,
119+
TypeMoq.Mock.ofType<ICommandManager>().object,
120+
);
112121
lm = new LinterManager(serviceContainer, workspaceService.object);
113122
serviceManager.addSingletonInstance<ILinterManager>(ILinterManager, lm);
114123
emitter = new vscode.EventEmitter<vscode.TextDocument>();

0 commit comments

Comments
 (0)