diff --git a/.chronus/changes/show-linter-rule-url-in-codefix-2024-10-21-9-17-32.md b/.chronus/changes/show-linter-rule-url-in-codefix-2024-10-21-9-17-32.md new file mode 100644 index 0000000000..b8effbc31c --- /dev/null +++ b/.chronus/changes/show-linter-rule-url-in-codefix-2024-10-21-9-17-32.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: fix +packages: + - typespec-vscode +--- + +Support 'See Document' quick action to view the details of linter rules diff --git a/packages/typespec-vscode/src/code-action-provider.ts b/packages/typespec-vscode/src/code-action-provider.ts new file mode 100644 index 0000000000..803242b392 --- /dev/null +++ b/packages/typespec-vscode/src/code-action-provider.ts @@ -0,0 +1,71 @@ +import vscode from "vscode"; +import { OPEN_URL_COMMAND } from "./vscode-command.js"; + +export function createCodeActionProvider() { + return vscode.languages.registerCodeActionsProvider( + "typespec", + new TypeSpecCodeActionProvider(), + { + providedCodeActionKinds: TypeSpecCodeActionProvider.providedCodeActionKinds, + }, + ); +} + +/** + * Provides code actions corresponding to diagnostic problems. + */ +export class TypeSpecCodeActionProvider implements vscode.CodeActionProvider { + public static readonly providedCodeActionKinds = [vscode.CodeActionKind.QuickFix]; + + provideCodeActions( + _document: vscode.TextDocument, + _range: vscode.Range | vscode.Selection, + context: vscode.CodeActionContext, + _token: vscode.CancellationToken, + ): vscode.CodeAction[] { + // for each diagnostic entry that has the matching `code`, create a code action command + // A CodeAction will only be created if it is a TypeSpec diagnostic and code is an object and has a target attribute + // target attribute is the URL to open + + // target is a Uri type, which corresponds to diagnostic.codeDescription.href in compiler + // When target is empty, it does not exist in the code object, so the code action will not be created + const actions: vscode.CodeAction[] = []; + context.diagnostics.forEach((diagnostic) => { + if ( + diagnostic.source === "TypeSpec" && + diagnostic.code && + typeof diagnostic.code === "object" && + "target" in diagnostic.code && + "value" in diagnostic.code + ) { + actions.push( + this.createOpenUrlCodeAction( + diagnostic, + diagnostic.code.target.toString(), + diagnostic.code.value.toString(), + ), + ); + } + }); + return actions; + } + + private createOpenUrlCodeAction( + diagnostic: vscode.Diagnostic, + url: string, + codeActionTitle: string, + ): vscode.CodeAction { + // 'vscode.CodeActionKind.Empty' does not generate a Code Action menu, You must use 'vscode.CodeActionKind.QuickFix' + const action = new vscode.CodeAction( + `See documentation for "${codeActionTitle}"`, + vscode.CodeActionKind.QuickFix, + ); + action.command = { + command: OPEN_URL_COMMAND, + title: diagnostic.message, + arguments: [url], + }; + action.diagnostics = [diagnostic]; + return action; + } +} diff --git a/packages/typespec-vscode/src/extension.ts b/packages/typespec-vscode/src/extension.ts index 3647a203f6..7fe2a5f468 100644 --- a/packages/typespec-vscode/src/extension.ts +++ b/packages/typespec-vscode/src/extension.ts @@ -1,10 +1,12 @@ import vscode, { commands, ExtensionContext } from "vscode"; +import { createCodeActionProvider } from "./code-action-provider.js"; import { SettingName } from "./const.js"; import { ExtensionLogListener } from "./log/extension-log-listener.js"; import logger from "./log/logger.js"; import { TypeSpecLogOutputChannel } from "./log/typespec-log-output-channel.js"; import { createTaskProvider } from "./task-provider.js"; import { TspLanguageClient } from "./tsp-language-client.js"; +import { createCommandOpenUrl } from "./vscode-command.js"; let client: TspLanguageClient | undefined; /** @@ -17,6 +19,9 @@ logger.registerLogListener("extension-log", new ExtensionLogListener(outputChann export async function activate(context: ExtensionContext) { context.subscriptions.push(createTaskProvider()); + context.subscriptions.push(createCodeActionProvider()); + context.subscriptions.push(createCommandOpenUrl()); + context.subscriptions.push( commands.registerCommand("typespec.showOutputChannel", () => { outputChannel.show(true /*preserveFocus*/); diff --git a/packages/typespec-vscode/src/vscode-command.ts b/packages/typespec-vscode/src/vscode-command.ts new file mode 100644 index 0000000000..c9a16a0138 --- /dev/null +++ b/packages/typespec-vscode/src/vscode-command.ts @@ -0,0 +1,15 @@ +import vscode from "vscode"; +import logger from "./log/logger.js"; + +export const OPEN_URL_COMMAND = "typespec.openUrl"; + +export function createCommandOpenUrl() { + return vscode.commands.registerCommand(OPEN_URL_COMMAND, (url: string) => { + // Although vscode has already dealt with the problem of wrong URL, try catch is still added here. + try { + vscode.env.openExternal(vscode.Uri.parse(url)); + } catch (error) { + logger.error(`Failed to open URL: ${url}`, [error as any]); + } + }); +}