Skip to content

Commit

Permalink
Add config schema version check
Browse files Browse the repository at this point in the history
  • Loading branch information
garrytrinder committed Feb 14, 2024
1 parent 108096e commit 84f6805
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 140 deletions.
30 changes: 30 additions & 0 deletions src/codeactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as vscode from 'vscode';
import { DevProxyInstall } from './types';

export const registerCodeActions = (context: vscode.ExtensionContext) => {
const devProxyInstall = context.globalState.get<DevProxyInstall>('devProxyInstall');
if (!devProxyInstall) {
return;
}
context.subscriptions.push(
vscode.languages.registerCodeActionsProvider('json', {
provideCodeActions: (document, range, context, token) => {
const diagnostic = context.diagnostics.find(diagnostic => {
return diagnostic.code === 'invalidSchema';
});
if (diagnostic) {
const fix = new vscode.CodeAction('Update schema', vscode.CodeActionKind.QuickFix);
fix.edit = new vscode.WorkspaceEdit();
fix.edit.replace(
document.uri,
new vscode.Range(
diagnostic.range.start,
diagnostic.range.end
),
`$schema": "https://raw.githubusercontent.com/microsoft/dev-proxy/main/schemas/v${devProxyInstall.version}/rc.schema.json",`
);
return [fix];
}
}
}));
};
12 changes: 11 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {PluginDocs, PluginSnippets} from './types';
import { DevProxyInstall, PluginDocs, PluginSnippets } from './types';

export const pluginSnippets: PluginSnippets = {
CachingGuidancePlugin: {
Expand Down Expand Up @@ -196,3 +196,13 @@ export const pluginDocs: PluginDocs = {
url: 'https://learn.microsoft.com/microsoft-cloud/dev/dev-proxy/technical-reference/retryafterplugin',
}
};

export const testDevProxyInstall: DevProxyInstall = {
filePath: 'somepath/devproxy',
isBeta: false,
isInstalled: true,
isLatest: true,
latestVersion: '0.14.1',
platform: 'win32',
version: '0.14.1'
};
214 changes: 117 additions & 97 deletions src/diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,123 +2,143 @@ import * as vscode from 'vscode';
import parse from "json-to-ast";
import { pluginSnippets } from "./constants";
import { getASTNode, getRangeFromASTNode } from "./helpers";
import { DevProxyInstall } from './types';

export const updateDiagnostics = (
document: vscode.TextDocument,
collection: vscode.DiagnosticCollection
): void => {
let diagnostics: vscode.Diagnostic[] = [];

const documentNode = parse(document.getText()) as parse.ObjectNode;

// check if urlsToWatch is empty
const urlsToWatchNode = getASTNode(
documentNode.children,
'Identifier',
'urlsToWatch'
context: vscode.ExtensionContext,
document: vscode.TextDocument,
collection: vscode.DiagnosticCollection,
): void => {
const devProxyInstall = context.globalState.get<DevProxyInstall>('devProxyInstall');
if (!devProxyInstall) {
return;
}
const diagnostics: vscode.Diagnostic[] = [];
const documentNode = parse(document.getText()) as parse.ObjectNode;

// check if schema version is compatible
const schemaNode = getASTNode(documentNode.children, 'Identifier', '$schema');
if (schemaNode) {
const schemaValue = (schemaNode.value as parse.LiteralNode).value as string;
if (!schemaValue.includes(`${devProxyInstall.version}`)) {
const diagnostic = new vscode.Diagnostic(
getRangeFromASTNode(schemaNode),
`Schema version is not compatible with the installed version of Dev Proxy. Expected v${devProxyInstall.version}.`,
vscode.DiagnosticSeverity.Warning
);
diagnostic.code = 'invalidSchema';
diagnostics.push(diagnostic);
}
}

// check if urlsToWatch is empty
const urlsToWatchNode = getASTNode(
documentNode.children,
'Identifier',
'urlsToWatch'
);
if (
urlsToWatchNode &&
(urlsToWatchNode.value as parse.ArrayNode).children.length === 0
) {
diagnostics.push(
new vscode.Diagnostic(
getRangeFromASTNode(urlsToWatchNode),
'Add at least one url to watch.',
vscode.DiagnosticSeverity.Error
)
);
if (
urlsToWatchNode &&
(urlsToWatchNode.value as parse.ArrayNode).children.length === 0
) {
}

// check validity of plugins
const pluginsNode = getASTNode(
documentNode.children,
'Identifier',
'plugins'
);
if (
pluginsNode &&
(pluginsNode.value as parse.ArrayNode).children.length !== 0
) {
const pluginNodes = (pluginsNode.value as parse.ArrayNode)
.children as parse.ObjectNode[];

// check for plugins
if (pluginNodes.length === 0) {
diagnostics.push(
new vscode.Diagnostic(
getRangeFromASTNode(urlsToWatchNode),
'Add at least one url to watch.',
getRangeFromASTNode(pluginsNode),
'Add at least one plugin',
vscode.DiagnosticSeverity.Error
)
);
}

// check validity of plugins
const pluginsNode = getASTNode(
documentNode.children,
'Identifier',
'plugins'
);
if (
pluginsNode &&
(pluginsNode.value as parse.ArrayNode).children.length !== 0
) {
const pluginNodes = (pluginsNode.value as parse.ArrayNode)
.children as parse.ObjectNode[];

// check for plugins
if (pluginNodes.length === 0) {
diagnostics.push(
new vscode.Diagnostic(
getRangeFromASTNode(pluginsNode),
'Add at least one plugin',
vscode.DiagnosticSeverity.Error
)
);
}

// does the plugin have a config section?
pluginNodes.forEach((pluginNode: parse.ObjectNode) => {
const pluginNameNode = getASTNode(
pluginNode.children,
'Identifier',
'name'
);
const pluginName = (pluginNameNode?.value as parse.LiteralNode)
.value as string;
const enabledNode = getASTNode(

// does the plugin have a config section?
pluginNodes.forEach((pluginNode: parse.ObjectNode) => {
const pluginNameNode = getASTNode(
pluginNode.children,
'Identifier',
'name'
);
const pluginName = (pluginNameNode?.value as parse.LiteralNode)
.value as string;
const enabledNode = getASTNode(
pluginNode.children,
'Identifier',
'enabled'
);
const isEnabled = (enabledNode?.value as parse.LiteralNode)
.value as boolean;
const pluginSnippet = pluginSnippets[pluginName];
const requiresConfig = pluginSnippet.config
? pluginSnippet.config.required
: false;

if (requiresConfig) {
// check to see if the plugin has a config section
const configSectionNode = getASTNode(
pluginNode.children,
'Identifier',
'enabled'
'configSection'
);
const isEnabled = (enabledNode?.value as parse.LiteralNode)
.value as boolean;
const pluginSnippet = pluginSnippets[pluginName];
const requiresConfig = pluginSnippet.config
? pluginSnippet.config.required
: false;

if (requiresConfig) {
// check to see if the plugin has a config section
const configSectionNode = getASTNode(
pluginNode.children,
if (!configSectionNode) {
// there is no config section defined on the plugin instance
diagnostics.push(
new vscode.Diagnostic(
getRangeFromASTNode(pluginNode),
`${pluginName} requires a config section.`,
isEnabled
? vscode.DiagnosticSeverity.Error
: vscode.DiagnosticSeverity.Warning
)
);
} else {
// check to see if the config section is in the document
const configSectionName = (
configSectionNode.value as parse.LiteralNode
).value as string;
const configSection = getASTNode(
documentNode.children,
'Identifier',
'configSection'
configSectionName
);
if (!configSectionNode) {
// there is no config section defined on the plugin instance

if (!configSection) {
diagnostics.push(
new vscode.Diagnostic(
getRangeFromASTNode(pluginNode),
`${pluginName} requires a config section.`,
getRangeFromASTNode(configSectionNode.value),
`${configSectionName} config section is missing. Use '${pluginSnippet.config?.name}' snippet to create one.`,
isEnabled
? vscode.DiagnosticSeverity.Error
: vscode.DiagnosticSeverity.Warning
)
);
} else {
// check to see if the config section is in the document
const configSectionName = (
configSectionNode.value as parse.LiteralNode
).value as string;
const configSection = getASTNode(
documentNode.children,
'Identifier',
configSectionName
);

if (!configSection) {
diagnostics.push(
new vscode.Diagnostic(
getRangeFromASTNode(configSectionNode.value),
`${configSectionName} config section is missing. Use '${pluginSnippet.config?.name}' snippet to create one.`,
isEnabled
? vscode.DiagnosticSeverity.Error
: vscode.DiagnosticSeverity.Warning
)
);
}
}
}
});
}

collection.set(document.uri, diagnostics);
};
}
});
}

collection.set(document.uri, diagnostics);
};
4 changes: 2 additions & 2 deletions src/documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const registerDocumentListeners = (context: vscode.ExtensionContext, coll
if (!isConfigFile(document)) {
return;
}
updateDiagnostics(document, collection);
updateDiagnostics(context, document, collection);
})
);

Expand All @@ -18,7 +18,7 @@ export const registerDocumentListeners = (context: vscode.ExtensionContext, coll
collection.delete(event.document.uri);
return;
}
updateDiagnostics(event.document, collection);
updateDiagnostics(context, event.document, collection);
})
);
};
20 changes: 12 additions & 8 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import * as vscode from 'vscode';
import { registerCommands } from './commands';
import { detectDevProxyInstall } from './detect';
import { handleStartNotification, processNotification } from './notifications';
import { registerDocumentListeners } from './documents';
import { registerCodeLens } from './codelens';
import { createStatusBar, updateStatusBar } from './statusbar';
import { registerCodeActions } from './codeactions';
import { updateGlobalState } from './state';

export const activate = async (context: vscode.ExtensionContext): Promise<vscode.ExtensionContext> => {
const statusBar = createStatusBar(context);
await updateGlobalState(context);

export const activate = async (context: vscode.ExtensionContext) => {
const collection = vscode.languages.createDiagnosticCollection('Dev Proxy');
let statusBar = createStatusBar(context);
registerDocumentListeners(context, collection);
registerCommands(context);
registerCodeActions(context);
registerCodeLens(context);

const devProxyInstall = await detectDevProxyInstall();
const notification = handleStartNotification(devProxyInstall);
registerCommands(context);
const notification = handleStartNotification(context);
processNotification(notification);
updateStatusBar(statusBar, devProxyInstall);

updateStatusBar(context, statusBar);
return context;
};

export const deactivate = () => { };
17 changes: 16 additions & 1 deletion src/notifications.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
import * as vscode from 'vscode';
import { DevProxyInstall } from './types';

export const handleStartNotification = (devProxyInstall: DevProxyInstall) => {
export const handleStartNotification = (context: vscode.ExtensionContext) => {
const devProxyInstall = context.globalState.get<DevProxyInstall>('devProxyInstall');
if (!devProxyInstall) {
return () => {
const message = `Dev Proxy is not installed, or not in PATH.`;
return {
message,
show: async () => {
const result = await vscode.window.showInformationMessage(message, 'Install');
if (result === 'Install') {
await vscode.commands.executeCommand('dev-proxy-toolkit.install');
};
}
};
};
};
if (!devProxyInstall.isInstalled) {
return () => {
const message = `Dev Proxy is not installed, or not in PATH.`;
Expand Down
9 changes: 9 additions & 0 deletions src/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as vscode from 'vscode';
import { detectDevProxyInstall } from './detect';
import { testDevProxyInstall } from './constants';

export const updateGlobalState = async (context: vscode.ExtensionContext) => {
context.extensionMode === vscode.ExtensionMode.Test
? context.globalState.update('devProxyInstall', testDevProxyInstall)
: context.globalState.update('devProxyInstall', await detectDevProxyInstall());
};
Loading

0 comments on commit 84f6805

Please sign in to comment.