diff --git a/server/src/diagnostics/ignore-violation.ts b/server/src/diagnostics/ignore-violation.ts index 1e0a7c1..5ada50b 100644 --- a/server/src/diagnostics/ignore-violation.ts +++ b/server/src/diagnostics/ignore-violation.ts @@ -18,14 +18,15 @@ import { CodeActionParams } from 'vscode-languageserver'; export const provideIgnoreFixCodeActions = ( document: TextDocument, range: Range, - context: CodeActionParams + context: CodeActionParams, + shouldComputeEdit: boolean | undefined = false ): CodeAction[] => { const diagnostics = context.context.diagnostics .filter(diagnostic => diagnostic.source?.toLocaleString().indexOf(DIAGNOSTIC_SOURCE) != -1); const ignoreFixes: CodeAction[] = []; for (const diagnostic of diagnostics) { - ignoreFixes.push(createIgnoreFix(diagnostic, document)); + ignoreFixes.push(createIgnoreFix(diagnostic, document, shouldComputeEdit)); } return ignoreFixes; @@ -39,14 +40,15 @@ export const provideIgnoreFixCodeActions = ( */ export const createIgnoreFix = ( diagnostic: Diagnostic, - document: TextDocument + document: TextDocument, + shouldComputeEdit: boolean | undefined = false ): CodeAction => { const ruleIdentifier = diagnostic.code; const title = ruleIdentifier ? `Ignore rule ${ruleIdentifier}` : "Ignore rule"; - return { + const ignoreFix: CodeAction = { title: title, kind: CodeActionKind.QuickFix, /** @@ -73,6 +75,11 @@ export const createIgnoreFix = ( documentUri: document.uri } }; + if (shouldComputeEdit) { + // @ts-ignore + ignoreFix.edit = createIgnoreWorkspaceEdit(document, ignoreFix.diagnostics[0]?.range); + } + return ignoreFix; }; /** diff --git a/server/src/rosie/rosiefix.ts b/server/src/rosie/rosiefix.ts index 6c84394..7d1b2d8 100644 --- a/server/src/rosie/rosiefix.ts +++ b/server/src/rosie/rosiefix.ts @@ -121,11 +121,12 @@ const validateOffsetsAndCreateTextEdit = ( */ export const provideApplyFixCodeActions = ( document: TextDocument, - range: Range + range: Range, + shouldComputeEdit: boolean | undefined = false ): CodeAction[] => { const fixes = getFixesForDocument(document.uri, range); return fixes - ? fixes?.map(rosieFix => createRuleFix(document, rosieFix)) + ? fixes?.map(rosieFix => createRuleFix(document, rosieFix, shouldComputeEdit)) : []; }; @@ -139,13 +140,14 @@ export const provideApplyFixCodeActions = ( */ export const createRuleFix = ( document: TextDocument, - rosieFix: RosieFix + rosieFix: RosieFix, + shouldComputeEdit: boolean | undefined = false ): CodeAction => { /* From CodeAction's documentation: If a code action provides an edit and a command, first the edit is executed and then the command. */ - return { + const ruleFix: CodeAction = { title: `Fix: ${rosieFix.description}`, kind: CodeActionKind.QuickFix, //Registers the 'codiga.applyFix' command for this CodeAction, so that we can execute further @@ -165,4 +167,9 @@ export const createRuleFix = ( rosieFixEdits: rosieFix.edits } }; + if (shouldComputeEdit) { + const rosieFixEdits = ruleFix.data.rosieFixEdits as RosieFixEdit[]; + createAndSetRuleFixCodeActionEdit(ruleFix, document, rosieFixEdits); + } + return ruleFix; }; diff --git a/server/src/server.ts b/server/src/server.ts index 7672d11..63bb947 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -50,7 +50,6 @@ const documents: TextDocuments = new TextDocuments(TextDocument); let hasConfigurationCapability: boolean; let hasWorkspaceCapability: boolean; let hasWorkspaceFoldersCapability: boolean; -let hasDiagnosticCapability: boolean; let hasApplyEditCapability: boolean; let hasCodeActionLiteralSupport: boolean; let hasCodeActionResolveSupport: boolean; @@ -58,12 +57,20 @@ let hasCodeActionDataSupport: boolean; let clientName: string | undefined; let clientVersion: string | undefined; +/** + * This is set to true for clients that don't support codeAction/resolve, + * or they support it, but they announce their support incorrectly, e.g. due to a bug. + */ +let isShouldComputeEditInCodeAction: boolean | undefined = false; /** * Starts to initialize the language server. * * In case of VS Code, upon opening a different folder in the same window, the server is shut down, * and a new language client is initialized. + * + * The language server presumes that diagnostics are supported by the client application, otherwise the integration + * of the server would not make much sense, thus there is no check for the textDocument/publishDiagnostics capability. */ connection.onInitialize((_params: InitializeParams) => { //https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_didChangeConfiguration @@ -82,12 +89,6 @@ connection.onInitialize((_params: InitializeParams) => { cacheWorkspaceFolders(_params.rootUri ? [_params.rootUri] : []); } - //https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics - hasDiagnosticCapability = !!( - _params.capabilities.textDocument && - _params.capabilities.textDocument.publishDiagnostics - ); - //https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_executeCommand //https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_applyEdit hasApplyEditCapability = !!(hasWorkspaceCapability && _params.capabilities.workspace?.applyEdit); @@ -102,20 +103,23 @@ connection.onInitialize((_params: InitializeParams) => { hasCodeActionResolveSupport = !!(_params.capabilities.textDocument?.codeAction?.resolveSupport); hasCodeActionDataSupport = !!(_params.capabilities.textDocument?.codeAction?.dataSupport); - //If there is no support for diagnostics, which is the core functionality and purpose of the Rosie platform, - // return with no capability, and don't register any further event handler. - if (!hasDiagnosticCapability) - return { capabilities: {} }; - //Retrieves client information, so that we can use it in the User-Agent header of GraphQL requests clientName = _params.clientInfo?.name; clientVersion = _params.clientInfo?.version; + //The condition for Eclipse can be removed, when + // https://github.com/eclipse/lsp4e/commit/2cf0a803936635a62d7fad2d05fde78bc7ce6a17 is released. + if (clientName?.startsWith("Eclipse IDE")) { + isShouldComputeEditInCodeAction = true; + } /** * Runs when the configuration, e.g. the Codiga API Token changes. */ connection.onDidChangeConfiguration(async _change => { - cacheCodigaApiToken(_change.settings?.codiga?.api?.token); + if (_change.settings?.codiga?.api?.token) + cacheCodigaApiToken(_change.settings?.codiga?.api?.token); + else if (_change.settings?.codigaApiToken) + cacheCodigaApiToken(_change.settings?.codigaApiToken); documents.all().forEach(validateTextDocument); }); @@ -134,8 +138,8 @@ connection.onInitialize((_params: InitializeParams) => { if (hasApplyEditCapability && hasCodeActionLiteralSupport && params.context.diagnostics.length > 0) { const document = documents.get(params.textDocument.uri); if (document) { - codeActions.push(...provideApplyFixCodeActions(document, params.range)); - const ignoreFixes = provideIgnoreFixCodeActions(document, params.range, params); + codeActions.push(...provideApplyFixCodeActions(document, params.range, isShouldComputeEditInCodeAction)); + const ignoreFixes = provideIgnoreFixCodeActions(document, params.range, params, isShouldComputeEditInCodeAction); codeActions.push(...ignoreFixes); } } @@ -150,7 +154,7 @@ connection.onInitialize((_params: InitializeParams) => { * only when we actually need that information, kind of lazy evaluation. */ connection.onCodeActionResolve(codeAction => { - if (codeAction.data) { + if (!isShouldComputeEditInCodeAction && codeAction.data) { if (codeAction.data.fixKind === "rosie.rule.fix") { const document = documents.get(codeAction.data.documentUri); if (document) { @@ -275,16 +279,17 @@ connection.onInitialized(async () => { - if `onDidChangeConfiguration()` cached the value in the meantime, we don't update the cache (e.g. Jupyter Lab) - if `onDidChangeConfiguration()` didn't cache the value, we use the returned value (e.g. VS Code) */ - const apiToken = await connection.workspace.getConfiguration("codiga.api.token"); + let apiToken = await connection.workspace.getConfiguration("codiga.api.token"); + if (!apiToken) { + apiToken = await connection.workspace.getConfiguration("codigaApiToken"); + } + if (!getApiToken()) { cacheCodigaApiToken(apiToken); } - //Start the rules cache updater only if the client supports diagnostics - if (hasDiagnosticCapability) { - setAllTextDocumentsValidator(() => documents.all().forEach(validateTextDocument)); - refreshCachePeriodic(); - } + setAllTextDocumentsValidator(() => documents.all().forEach(validateTextDocument)); + refreshCachePeriodic(); }); /**