Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: gsoc project - add autofix feature #236

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
11 changes: 11 additions & 0 deletions .vscode-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const { defineConfig } = require('@vscode/test-cli');

module.exports = defineConfig({
files: 'src/ruleset/functions/test/*.test.ts',

mocha: {
require: ['ts-node/register'],
// timeout: 5000,
// ui: 'bdd'
}
});
Binary file removed docs/VSCode AsyncAPI Content Assistance-X4.gif
Binary file not shown.
Binary file removed docs/asyncapi-editor-title-context.png
Binary file not shown.
3,887 changes: 2,739 additions & 1,148 deletions package-lock.json

Large diffs are not rendered by default.

22 changes: 18 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,29 +98,43 @@
"watch": "webpack --watch",
"package": "webpack --mode production --devtool hidden-source-map",
"lint": "eslint src --ext ts",
"test": "",
"test": "vscode-test",
"generate:assets": "",
"bump:version": "npm --no-git-tag-version --allow-same-version version $VERSION"
"bump:version": "npm --no-git-tag-version --allow-same-version version $VERSION",
"testfixers": "mocha -r ts-mocha -r ts-node/register --require ./__mocks__/vscode.ts src/ruleset/functions/test/*.test.ts"
},
"devDependencies": {
"@asyncapi/react-component": "^2.0.0",
"@types/chai": "^4.3.16",
"@types/glob": "^7.2.0",
"@types/js-yaml": "^4.0.5",
"@types/jsonpath": "^0.2.4",
"@types/mocha": "^9.1.1",
"@types/node": "14.x",
"@types/sinon": "^17.0.3",
"@types/vscode": "^1.66.0",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.21.0",
"@vscode/test-electron": "^2.1.3",
"@vscode/test-cli": "^0.0.10",
"@vscode/test-electron": "^2.4.1",
"ajv": "^8.11.0",
"chai": "^5.1.1",
"copy-webpack-plugin": "^10.2.4",
"eslint": "^8.14.0",
"genson-js": "0.0.8",
"glob": "^8.0.1",
"mocha": "^9.2.2",
"sinon": "^18.0.0",
"ts-loader": "^9.2.8",
"typescript": "^4.6.4",
"ts-mocha": "^10.0.0",
"ts-node": "^10.9.2",
"typescript": "^4.9.5",
"webpack": "^5.70.0",
"webpack-cli": "^4.9.2"
},
"dependencies": {
"js-yaml": "^4.1.0",
"jsonpath": "^1.1.1",
"jsonpath-plus": "^8.1.0"
}
}
115 changes: 115 additions & 0 deletions src/AutoFixProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import * as vscode from 'vscode';
import * as path from 'path';
import ruleset from "./ruleset/asyncapi-rules";

let latestVersion: string | undefined;

export interface FixFunction {
(document: vscode.TextDocument, range: vscode.Range, given: string, field: string): string | undefined | Promise<string | undefined>;
}



interface FixObject {
name: string;
given: string | string[];
field: string;
function: FixFunction;
}
interface Rule {
description: string;
recommended: boolean;
given: string;
fix: FixObject | FixObject[];
}

interface RuleSet {
rules: Record<string, Rule>;
}

class DiagnosticFixProvider implements vscode.CodeActionProvider {
public provideCodeActions(
document: vscode.TextDocument,
range: vscode.Range,
context: vscode.CodeActionContext
): vscode.CodeAction[] | undefined {
const codeActions: vscode.CodeAction[] = [];
console.log("Diagnostics activated");
if (!this.isFileYAML(document.fileName)) {
return;
}
context.diagnostics.forEach(diagnostic => {
if (diagnostic.message.startsWith("The latest version is not used.")) {
const versionPattern = /"(\d+\.\d+\.\d+)"/;
const match = diagnostic.message.match(versionPattern);
if (match) {
latestVersion = match[1];
}
}
const rule = this.getRuleFromDiagnostic(diagnostic);
if (rule) {
const fixAction = this.createFixAction(document, diagnostic.range, rule);
if (fixAction) {
codeActions.push(...fixAction);
}
}
});

return codeActions;
}

private isFileYAML(fileName: string): boolean {
const extension = path.extname(fileName).toLowerCase();
return extension === '.yaml' || extension === '.yml';
}

private getRuleFromDiagnostic(diagnostic: vscode.Diagnostic): Rule | undefined {
const code = diagnostic.code ? diagnostic.code.toString() : undefined;

if (!code) {
console.error("Diagnostic code is undefined");
return undefined;
}

const rule = ruleset.rules[code as keyof typeof ruleset.rules];

if (!rule) {
console.error(`No rule found for code: ${code}`);
}

return rule;
}

private createFixAction(
document: vscode.TextDocument,
range: vscode.Range,
rule: Rule
): vscode.CodeAction[] {
const { fix } = rule;
const codeActions: vscode.CodeAction[] = [];

if (Array.isArray(fix)) {
fix.forEach(fixItem => {
const action = new vscode.CodeAction(fixItem.name, vscode.CodeActionKind.QuickFix);
action.command = {
command: 'extension.applyFix',
title: fixItem.name,
arguments: [document, range, fixItem.function, fixItem.given, fixItem.field]
};
codeActions.push(action);
});
}
else {
const action = new vscode.CodeAction(fix.name, vscode.CodeActionKind.QuickFix);
action.command = {
command: 'extension.applyFix',
title: fix.name,
arguments: [document, range, fix.function, fix.given, fix.field]
};
codeActions.push(action);
}
return codeActions;
}
}

export { DiagnosticFixProvider as autoFixProvider, latestVersion };
35 changes: 34 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as vscode from 'vscode';
import { isAsyncAPIFile, openAsyncAPI, openAsyncapiFiles, previewAsyncAPI } from './PreviewWebPanel';
import { asyncapiSmartPaste } from './SmartPasteCommand';
import { autoFixProvider, FixFunction } from './AutoFixProvider';
import performFix from './performFix';


export function activate(context: vscode.ExtensionContext) {
Expand Down Expand Up @@ -36,7 +38,38 @@ export function activate(context: vscode.ExtensionContext) {

context.subscriptions.push(vscode.commands.registerCommand('asyncapi.preview', previewAsyncAPI(context)));

// Register autofix provider and commands
console.log("AutofixProvider Initiated!");
const codeActionProvider = new autoFixProvider();
context.subscriptions.push(
vscode.languages.registerCodeActionsProvider({ scheme: 'file', language: 'yaml' }, codeActionProvider)
);

context.subscriptions.push(vscode.commands.registerCommand('extension.applyFix', async (document: vscode.TextDocument, range: vscode.Range, fixFunction: FixFunction, given: string, field: string) => {
const quickFixObj = await fixFunction(document, range, given, field);
console.log('QuickFixObj received: ', quickFixObj);
// Apply the quick fix using the performFix method
if (quickFixObj !== undefined) {
const fixAction = await performFix(document, range, fixFunction.name, quickFixObj);
if (fixAction.edit) {
const edit = new vscode.WorkspaceEdit();
fixAction.edit.entries().forEach(([uri, textEdits]) => {
edit.set(uri, textEdits);
});

const disposable = vscode.workspace.onDidChangeTextDocument(event => {
if (event.document.uri.toString() === document.uri.toString()) {
console.log('CodeAction applied:', fixAction.title);
disposable.dispose(); // Remove the event listener after the change is detected
}
});
await vscode.workspace.applyEdit(edit);
}
}
}));


context.subscriptions.push(vscode.commands.registerCommand("asyncapi.paste", asyncapiSmartPaste));
}

export function deactivate() {}
export function deactivate() { }
34 changes: 34 additions & 0 deletions src/performFix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as vscode from 'vscode';

interface CustomCodeAction extends vscode.CodeAction {
[key: string]: any;
}

export default async function performFix(document: vscode.TextDocument, range: vscode.Range, fixName: string, quickFixObj: string): Promise<vscode.CodeAction> {
const fix: CustomCodeAction = new vscode.CodeAction(
fixName,
vscode.CodeActionKind.QuickFix
);
fix.edit = new vscode.WorkspaceEdit();

try {
const fullDocRange = new vscode.Range(document.positionAt(0), document.lineAt(document.lineCount - 1).range.end);
const edit = vscode.TextEdit.replace(fullDocRange, quickFixObj);
fix.edit.set(document.uri, [edit]);
console.log("Received code action!");

// Apply the edit
const applied = await vscode.workspace.applyEdit(fix.edit);
if (applied) {
// Save the document to update diagnostics
await document.save();
console.log("Document saved after applying fix.");
} else {
console.warn("The workspace edit was not applied.");
}
} catch (error) {
console.error("Failed to parse document content as YAML", error);
}

return fix;
}
Loading
Loading