Skip to content

Commit 298dadb

Browse files
authored
Add config schema version check (#48)
1 parent 108096e commit 298dadb

11 files changed

+389
-140
lines changed

src/codeactions.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import * as vscode from 'vscode';
2+
import { DevProxyInstall } from './types';
3+
4+
export const registerCodeActions = (context: vscode.ExtensionContext) => {
5+
const devProxyInstall = context.globalState.get<DevProxyInstall>('devProxyInstall');
6+
if (!devProxyInstall) {
7+
return;
8+
}
9+
context.subscriptions.push(
10+
vscode.languages.registerCodeActionsProvider('json', {
11+
provideCodeActions: (document, range, context, token) => {
12+
const diagnostic = context.diagnostics.find(diagnostic => {
13+
return diagnostic.code === 'invalidSchema';
14+
});
15+
if (diagnostic) {
16+
const fix = new vscode.CodeAction('Update schema', vscode.CodeActionKind.QuickFix);
17+
fix.edit = new vscode.WorkspaceEdit();
18+
fix.edit.replace(
19+
document.uri,
20+
new vscode.Range(
21+
diagnostic.range.start,
22+
diagnostic.range.end
23+
),
24+
`$schema": "https://raw.githubusercontent.com/microsoft/dev-proxy/main/schemas/v${devProxyInstall.version}/rc.schema.json",`
25+
);
26+
return [fix];
27+
}
28+
}
29+
}));
30+
};

src/constants.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {PluginDocs, PluginSnippets} from './types';
1+
import { DevProxyInstall, PluginDocs, PluginSnippets } from './types';
22

33
export const pluginSnippets: PluginSnippets = {
44
CachingGuidancePlugin: {
@@ -196,3 +196,13 @@ export const pluginDocs: PluginDocs = {
196196
url: 'https://learn.microsoft.com/microsoft-cloud/dev/dev-proxy/technical-reference/retryafterplugin',
197197
}
198198
};
199+
200+
export const testDevProxyInstall: DevProxyInstall = {
201+
filePath: 'somepath/devproxy',
202+
isBeta: false,
203+
isInstalled: true,
204+
isLatest: true,
205+
latestVersion: '0.14.1',
206+
platform: 'win32',
207+
version: '0.14.1'
208+
};

src/diagnostics.ts

Lines changed: 117 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -2,123 +2,143 @@ import * as vscode from 'vscode';
22
import parse from "json-to-ast";
33
import { pluginSnippets } from "./constants";
44
import { getASTNode, getRangeFromASTNode } from "./helpers";
5+
import { DevProxyInstall } from './types';
56

67
export const updateDiagnostics = (
7-
document: vscode.TextDocument,
8-
collection: vscode.DiagnosticCollection
9-
): void => {
10-
let diagnostics: vscode.Diagnostic[] = [];
11-
12-
const documentNode = parse(document.getText()) as parse.ObjectNode;
13-
14-
// check if urlsToWatch is empty
15-
const urlsToWatchNode = getASTNode(
16-
documentNode.children,
17-
'Identifier',
18-
'urlsToWatch'
8+
context: vscode.ExtensionContext,
9+
document: vscode.TextDocument,
10+
collection: vscode.DiagnosticCollection,
11+
): void => {
12+
const devProxyInstall = context.globalState.get<DevProxyInstall>('devProxyInstall');
13+
if (!devProxyInstall) {
14+
return;
15+
}
16+
const diagnostics: vscode.Diagnostic[] = [];
17+
const documentNode = parse(document.getText()) as parse.ObjectNode;
18+
19+
// check if schema version is compatible
20+
const schemaNode = getASTNode(documentNode.children, 'Identifier', '$schema');
21+
if (schemaNode) {
22+
const schemaValue = (schemaNode.value as parse.LiteralNode).value as string;
23+
if (!schemaValue.includes(`${devProxyInstall.version}`)) {
24+
const diagnostic = new vscode.Diagnostic(
25+
getRangeFromASTNode(schemaNode),
26+
`Schema version is not compatible with the installed version of Dev Proxy. Expected v${devProxyInstall.version}.`,
27+
vscode.DiagnosticSeverity.Warning
28+
);
29+
diagnostic.code = 'invalidSchema';
30+
diagnostics.push(diagnostic);
31+
}
32+
}
33+
34+
// check if urlsToWatch is empty
35+
const urlsToWatchNode = getASTNode(
36+
documentNode.children,
37+
'Identifier',
38+
'urlsToWatch'
39+
);
40+
if (
41+
urlsToWatchNode &&
42+
(urlsToWatchNode.value as parse.ArrayNode).children.length === 0
43+
) {
44+
diagnostics.push(
45+
new vscode.Diagnostic(
46+
getRangeFromASTNode(urlsToWatchNode),
47+
'Add at least one url to watch.',
48+
vscode.DiagnosticSeverity.Error
49+
)
1950
);
20-
if (
21-
urlsToWatchNode &&
22-
(urlsToWatchNode.value as parse.ArrayNode).children.length === 0
23-
) {
51+
}
52+
53+
// check validity of plugins
54+
const pluginsNode = getASTNode(
55+
documentNode.children,
56+
'Identifier',
57+
'plugins'
58+
);
59+
if (
60+
pluginsNode &&
61+
(pluginsNode.value as parse.ArrayNode).children.length !== 0
62+
) {
63+
const pluginNodes = (pluginsNode.value as parse.ArrayNode)
64+
.children as parse.ObjectNode[];
65+
66+
// check for plugins
67+
if (pluginNodes.length === 0) {
2468
diagnostics.push(
2569
new vscode.Diagnostic(
26-
getRangeFromASTNode(urlsToWatchNode),
27-
'Add at least one url to watch.',
70+
getRangeFromASTNode(pluginsNode),
71+
'Add at least one plugin',
2872
vscode.DiagnosticSeverity.Error
2973
)
3074
);
3175
}
32-
33-
// check validity of plugins
34-
const pluginsNode = getASTNode(
35-
documentNode.children,
36-
'Identifier',
37-
'plugins'
38-
);
39-
if (
40-
pluginsNode &&
41-
(pluginsNode.value as parse.ArrayNode).children.length !== 0
42-
) {
43-
const pluginNodes = (pluginsNode.value as parse.ArrayNode)
44-
.children as parse.ObjectNode[];
45-
46-
// check for plugins
47-
if (pluginNodes.length === 0) {
48-
diagnostics.push(
49-
new vscode.Diagnostic(
50-
getRangeFromASTNode(pluginsNode),
51-
'Add at least one plugin',
52-
vscode.DiagnosticSeverity.Error
53-
)
54-
);
55-
}
56-
57-
// does the plugin have a config section?
58-
pluginNodes.forEach((pluginNode: parse.ObjectNode) => {
59-
const pluginNameNode = getASTNode(
60-
pluginNode.children,
61-
'Identifier',
62-
'name'
63-
);
64-
const pluginName = (pluginNameNode?.value as parse.LiteralNode)
65-
.value as string;
66-
const enabledNode = getASTNode(
76+
77+
// does the plugin have a config section?
78+
pluginNodes.forEach((pluginNode: parse.ObjectNode) => {
79+
const pluginNameNode = getASTNode(
80+
pluginNode.children,
81+
'Identifier',
82+
'name'
83+
);
84+
const pluginName = (pluginNameNode?.value as parse.LiteralNode)
85+
.value as string;
86+
const enabledNode = getASTNode(
87+
pluginNode.children,
88+
'Identifier',
89+
'enabled'
90+
);
91+
const isEnabled = (enabledNode?.value as parse.LiteralNode)
92+
.value as boolean;
93+
const pluginSnippet = pluginSnippets[pluginName];
94+
const requiresConfig = pluginSnippet.config
95+
? pluginSnippet.config.required
96+
: false;
97+
98+
if (requiresConfig) {
99+
// check to see if the plugin has a config section
100+
const configSectionNode = getASTNode(
67101
pluginNode.children,
68102
'Identifier',
69-
'enabled'
103+
'configSection'
70104
);
71-
const isEnabled = (enabledNode?.value as parse.LiteralNode)
72-
.value as boolean;
73-
const pluginSnippet = pluginSnippets[pluginName];
74-
const requiresConfig = pluginSnippet.config
75-
? pluginSnippet.config.required
76-
: false;
77-
78-
if (requiresConfig) {
79-
// check to see if the plugin has a config section
80-
const configSectionNode = getASTNode(
81-
pluginNode.children,
105+
if (!configSectionNode) {
106+
// there is no config section defined on the plugin instance
107+
diagnostics.push(
108+
new vscode.Diagnostic(
109+
getRangeFromASTNode(pluginNode),
110+
`${pluginName} requires a config section.`,
111+
isEnabled
112+
? vscode.DiagnosticSeverity.Error
113+
: vscode.DiagnosticSeverity.Warning
114+
)
115+
);
116+
} else {
117+
// check to see if the config section is in the document
118+
const configSectionName = (
119+
configSectionNode.value as parse.LiteralNode
120+
).value as string;
121+
const configSection = getASTNode(
122+
documentNode.children,
82123
'Identifier',
83-
'configSection'
124+
configSectionName
84125
);
85-
if (!configSectionNode) {
86-
// there is no config section defined on the plugin instance
126+
127+
if (!configSection) {
87128
diagnostics.push(
88129
new vscode.Diagnostic(
89-
getRangeFromASTNode(pluginNode),
90-
`${pluginName} requires a config section.`,
130+
getRangeFromASTNode(configSectionNode.value),
131+
`${configSectionName} config section is missing. Use '${pluginSnippet.config?.name}' snippet to create one.`,
91132
isEnabled
92133
? vscode.DiagnosticSeverity.Error
93134
: vscode.DiagnosticSeverity.Warning
94135
)
95136
);
96-
} else {
97-
// check to see if the config section is in the document
98-
const configSectionName = (
99-
configSectionNode.value as parse.LiteralNode
100-
).value as string;
101-
const configSection = getASTNode(
102-
documentNode.children,
103-
'Identifier',
104-
configSectionName
105-
);
106-
107-
if (!configSection) {
108-
diagnostics.push(
109-
new vscode.Diagnostic(
110-
getRangeFromASTNode(configSectionNode.value),
111-
`${configSectionName} config section is missing. Use '${pluginSnippet.config?.name}' snippet to create one.`,
112-
isEnabled
113-
? vscode.DiagnosticSeverity.Error
114-
: vscode.DiagnosticSeverity.Warning
115-
)
116-
);
117-
}
118137
}
119138
}
120-
});
121-
}
122-
123-
collection.set(document.uri, diagnostics);
124-
};
139+
}
140+
});
141+
}
142+
143+
collection.set(document.uri, diagnostics);
144+
};

src/documents.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const registerDocumentListeners = (context: vscode.ExtensionContext, coll
88
if (!isConfigFile(document)) {
99
return;
1010
}
11-
updateDiagnostics(document, collection);
11+
updateDiagnostics(context, document, collection);
1212
})
1313
);
1414

@@ -18,7 +18,7 @@ export const registerDocumentListeners = (context: vscode.ExtensionContext, coll
1818
collection.delete(event.document.uri);
1919
return;
2020
}
21-
updateDiagnostics(event.document, collection);
21+
updateDiagnostics(context, event.document, collection);
2222
})
2323
);
2424
};

src/extension.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
import * as vscode from 'vscode';
22
import { registerCommands } from './commands';
3-
import { detectDevProxyInstall } from './detect';
43
import { handleStartNotification, processNotification } from './notifications';
54
import { registerDocumentListeners } from './documents';
65
import { registerCodeLens } from './codelens';
76
import { createStatusBar, updateStatusBar } from './statusbar';
7+
import { registerCodeActions } from './codeactions';
8+
import { updateGlobalState } from './state';
9+
10+
export const activate = async (context: vscode.ExtensionContext): Promise<vscode.ExtensionContext> => {
11+
const statusBar = createStatusBar(context);
12+
await updateGlobalState(context);
813

9-
export const activate = async (context: vscode.ExtensionContext) => {
1014
const collection = vscode.languages.createDiagnosticCollection('Dev Proxy');
11-
let statusBar = createStatusBar(context);
1215
registerDocumentListeners(context, collection);
13-
registerCommands(context);
16+
registerCodeActions(context);
1417
registerCodeLens(context);
15-
16-
const devProxyInstall = await detectDevProxyInstall();
17-
const notification = handleStartNotification(devProxyInstall);
18+
registerCommands(context);
19+
const notification = handleStartNotification(context);
1820
processNotification(notification);
19-
updateStatusBar(statusBar, devProxyInstall);
21+
22+
updateStatusBar(context, statusBar);
23+
return context;
2024
};
2125

2226
export const deactivate = () => { };

src/notifications.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
import * as vscode from 'vscode';
22
import { DevProxyInstall } from './types';
33

4-
export const handleStartNotification = (devProxyInstall: DevProxyInstall) => {
4+
export const handleStartNotification = (context: vscode.ExtensionContext) => {
5+
const devProxyInstall = context.globalState.get<DevProxyInstall>('devProxyInstall');
6+
if (!devProxyInstall) {
7+
return () => {
8+
const message = `Dev Proxy is not installed, or not in PATH.`;
9+
return {
10+
message,
11+
show: async () => {
12+
const result = await vscode.window.showInformationMessage(message, 'Install');
13+
if (result === 'Install') {
14+
await vscode.commands.executeCommand('dev-proxy-toolkit.install');
15+
};
16+
}
17+
};
18+
};
19+
};
520
if (!devProxyInstall.isInstalled) {
621
return () => {
722
const message = `Dev Proxy is not installed, or not in PATH.`;

src/state.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as vscode from 'vscode';
2+
import { detectDevProxyInstall } from './detect';
3+
import { testDevProxyInstall } from './constants';
4+
5+
export const updateGlobalState = async (context: vscode.ExtensionContext) => {
6+
context.extensionMode === vscode.ExtensionMode.Production
7+
? context.globalState.update('devProxyInstall', await detectDevProxyInstall())
8+
: context.globalState.update('devProxyInstall', testDevProxyInstall);
9+
};

0 commit comments

Comments
 (0)