diff --git a/src/autoFix.ts b/src/autoFix.ts new file mode 100644 index 0000000..ef0bd47 --- /dev/null +++ b/src/autoFix.ts @@ -0,0 +1,76 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as yaml from 'js-yaml'; +import * as fs from 'fs'; +import { createFix } from './createFix'; + +interface QuickFixOption { + message: string; + errorCheck: string; + quickfix: { + text: string; + type: string; + }; +} + +interface QuickFixData { + [errorCode: string]: QuickFixOption[]; +} + +export function activate(context: vscode.ExtensionContext) { + // Register code action provider + const codeActionProvider = new DiagnosticFixProvider(); + context.subscriptions.push( + vscode.languages.registerCodeActionsProvider({ scheme: 'file', language: 'yaml' }, codeActionProvider) + ); +} + +export class DiagnosticFixProvider implements vscode.CodeActionProvider { + private quickFixes: QuickFixData = {}; + + constructor() { + // Load quick fixes from external YAML file + const yamlFilePath = path.join(__dirname, '../src/quickfixes.yaml'); + const yamlContent = fs.readFileSync(yamlFilePath, 'utf8'); + this.quickFixes = yaml.load(yamlContent) as QuickFixData; + } + + provideCodeActions( + document: vscode.TextDocument, + range: vscode.Range, + context: vscode.CodeActionContext + ): vscode.CodeAction[] | undefined { + const codeActions: vscode.CodeAction[] = []; + + // Check if the file is YAML or YML + const extension = path.extname(document.fileName); + if (extension !== '.yaml' && extension !== '.yml') { + return codeActions; + } + + for (const diagnostic of context.diagnostics) { + const errorCode = diagnostic.code?.toString(); + if (errorCode && this.quickFixes[errorCode]) { + const options = this.quickFixes[errorCode]; + for (const option of options) { + if (errorCode === 'asyncapi-schema'){ + if(diagnostic.message.includes(option.errorCheck)) { + const fix = createFix(document, range, errorCode, option, this.quickFixes); + if (fix) { + codeActions.push(fix); + } + } + }else{ + const fix = createFix(document, range, errorCode, option, this.quickFixes); + if (fix) { + codeActions.push(fix); + } + } + } + } + } + + return codeActions; + } +} + diff --git a/src/createFix.ts b/src/createFix.ts new file mode 100644 index 0000000..fd159b0 --- /dev/null +++ b/src/createFix.ts @@ -0,0 +1,98 @@ +import * as vscode from 'vscode'; + +interface QuickFixOption { + message: string; + quickfix: { + text: string; + type: string; + }; +} + +interface CustomCodeAction extends vscode.CodeAction { + [key: string]: any; +} + +export function createFix( + document: vscode.TextDocument, + range: vscode.Range, + errorCode: string, + option: QuickFixOption, + quickFixes: { [errorCode: string]: QuickFixOption[] } +): vscode.CodeAction | undefined { + const { message, quickfix } = option; + + const fix: CustomCodeAction = new vscode.CodeAction( + quickfix.text, + vscode.CodeActionKind.QuickFix + ); + fix.edit = new vscode.WorkspaceEdit(); + + if (quickfix.type === 'update') { + // Replace the version within the range + fix.edit.replace(document.uri, range, quickfix.text); + } else if (quickfix.type === 'delete') { + // Delete the text within the range + const lineToDelete = range.start.line; + if (lineToDelete >= 0) { + const line = document.lineAt(lineToDelete); + const deleteRange = new vscode.Range(line.range.start, line.range.end); + fix.edit.delete(document.uri, deleteRange); + } + }else if (quickfix.type === 'append') { + // Append '-1' to the duplicate name value + const line = document.lineAt(range.start.line); + const lineText = line.text; + + // Find the position of the duplicate name value + const nameStartIndex = lineText.indexOf(': "') + 3; + const nameEndIndex = lineText.lastIndexOf('"'); + + if (nameStartIndex !== -1 && nameEndIndex !== -1) { + const existingNameValue = lineText.substring(nameStartIndex, nameEndIndex); + const newNameValue = `${existingNameValue}-1`; + //vscode.window.showInformationMessage(`Existing Name: ${existingNameValue}, New Name: ${newNameValue}`); + // Replace the existing name value with the appended one + const nameValueRange = new vscode.Range( + range.start.line, + nameStartIndex, + range.start.line, + nameEndIndex + ); + fix.edit.replace(document.uri, nameValueRange, newNameValue); + } + } else if (quickfix.type === 'add') { + const line = document.lineAt(range.start.line); + const lineText = line.text; + + // Calculate the indentation based on the line before the error + const previousLine = document.lineAt(range.start.line - 1); + const previousIndentation = previousLine.text.match(/^\s*/)?.[0] || ''; + + // Check if the current line has an error, and then add the indentation + if (errorCode === 'asyncapi-schema') { + const insertionPosition = line.range.end; + + // Insert the quick fix text after the ':' + const insertionText = ` ${quickfix.text}`; + fix.edit.insert(document.uri, insertionPosition, insertionText); + } else { + // Insert the quick fix text with the calculated indentation + const shouldAddIndentation = lineText.trim().length > 0; + const indentation = shouldAddIndentation ? previousIndentation : ''; + + const insertionPosition = line.range.end; + const insertionText = `\n${indentation}${quickfix.text}`; + fix.edit.insert(document.uri, insertionPosition, insertionText); + } + + + } + + // Set the error message as the code action's title + fix.title = message; + + // Add type field to code action metadata + fix.type = quickfix.type; + + return fix; +} diff --git a/src/extension.ts b/src/extension.ts index 55cfbfb..a991045 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,7 +1,7 @@ import * as vscode from 'vscode'; import { isAsyncAPIFile, openAsyncAPI, openAsyncapiFiles, previewAsyncAPI } from './PreviewWebPanel'; import { asyncapiSmartPaste } from './SmartPasteCommand'; - +import { MyCodeActionProvider } from './autoFix'; export function activate(context: vscode.ExtensionContext) { console.log('Congratulations, your extension "asyncapi-preview" is now active!'); @@ -37,6 +37,11 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand('asyncapi.preview', previewAsyncAPI(context))); context.subscriptions.push(vscode.commands.registerCommand("asyncapi.paste", asyncapiSmartPaste)); + + const codeActionProvider = new MyCodeActionProvider(); + context.subscriptions.push( + vscode.languages.registerCodeActionsProvider({ scheme: 'file', language: 'yaml' }, codeActionProvider) + ); } export function deactivate() {} diff --git a/src/quickfixes.yaml b/src/quickfixes.yaml new file mode 100644 index 0000000..825bbfb --- /dev/null +++ b/src/quickfixes.yaml @@ -0,0 +1,62 @@ +asyncapi-latest-version: + - message: "The latest version is not used. You should update to the '2.4.0' version." + errorCheck: "version" + quickfix: + text: "2.4.0" + type: "update" + - message: "The latest version is not used. You should update to the '2.5.0' version." + errorCheck: "version" + quickfix: + text: "2.5.0" + type: "update" + +asyncapi-schema: + - message: "'title' property type must be string" + errorCheck: "title" + quickfix: + text: "Example API" + type: "add" + - message: "'version' property type must be string" + errorCheck: "version" + quickfix: + text: "1.0.0" + type: "add" + - message: "'name' property type must be string" + errorCheck: "name" + quickfix: + text: "Dummy Name" + type: "add" + - message: "'url' property type must be string" + errorCheck: "url" + quickfix: + text: "https://example.com" + type: "add" + - message: "'description' property type must be string" + errorCheck: "description" + quickfix: + text: "Dummy description" + type: "add" + + +asyncapi-info-contact-properties: + - message: "Add a 'name'" + quickfix: + text: "name: Dummy Name" + type: "add" + - message: "Add a 'email'" + quickfix: + text: "email: mail@gmail.com" + type: "add" + - message: "Add a 'url'" + quickfix: + text: "url: https://example.com" + type: "add" + +asyncapi-tags-uniqueness: + - message: "Delete the same value object" + quickfix: + type: "delete" + - message: "Update the same value object" + quickfix: + type: "append" + text: "-1" \ No newline at end of file