From 0cdf49023d5218aae34fa5e874d729a422ec9b28 Mon Sep 17 00:00:00 2001 From: Roland Grunberg Date: Mon, 26 Apr 2021 19:57:54 -0400 Subject: [PATCH] Generate code action to ignore certain compiler issues. - Fixes redhat-developer/vscode-java#1791 - Some JDT core compiler problems can be ignored using the setting preference file, so offer a code action to do this for a given error/warning - Code action appears only when a `java.settings.url` is detected that is on the local filesystem - Add testcase Signed-off-by: Roland Grunberg --- .../jdt/ls/core/internal/ChangeUtil.java | 41 +++++++++++++-- .../ls/core/internal/ExternalFileChange.java | 33 ++++++++++++ .../internal/handlers/CodeActionHandler.java | 50 +++++++++++++++++++ .../handlers/CodeActionHandlerTest.java | 47 +++++++++++++++++ 4 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ExternalFileChange.java diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ChangeUtil.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ChangeUtil.java index 824d4d84d0..15955dd435 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ChangeUtil.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ChangeUtil.java @@ -12,7 +12,9 @@ *******************************************************************************/ package org.eclipse.jdt.ls.core.internal; +import java.net.URI; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -251,16 +253,45 @@ private static void convertCreateFileChange(WorkspaceEdit edit, CreateFileChange private static void convertTextChange(TextChange textChange, WorkspaceEdit rootEdit) { Object modifiedElement = textChange.getModifiedElement(); - if (!(modifiedElement instanceof IJavaElement)) { - return; - } TextEdit textEdits = textChange.getEdit(); if (textEdits == null) { return; } - ICompilationUnit compilationUnit = (ICompilationUnit) ((IJavaElement) modifiedElement).getAncestor(IJavaElement.COMPILATION_UNIT); - convertTextEdit(rootEdit, compilationUnit, textEdits); + + if (!(modifiedElement instanceof IJavaElement)) { + TextEdit edit = textChange.getEdit(); + if (textChange instanceof ExternalFileChange && edit instanceof InsertEdit) { + URI uri = ((ExternalFileChange) textChange).getURI(); + String fileUri = ResourceUtils.fixURI(uri); + + InsertEdit insertEdit = (InsertEdit) edit; + org.eclipse.lsp4j.TextEdit te = new org.eclipse.lsp4j.TextEdit(); + te.setNewText(insertEdit.getText()); + Position pos = new Position(0, 0); + te.setRange(new Range(pos, pos)); + + if (JavaLanguageServerPlugin.getPreferencesManager().getClientPreferences().isResourceOperationSupported()) { + List> changes = rootEdit.getDocumentChanges(); + if (changes == null) { + changes = new ArrayList<>(); + rootEdit.setDocumentChanges(changes); + } + + VersionedTextDocumentIdentifier identifier = new VersionedTextDocumentIdentifier(fileUri, null); + TextDocumentEdit documentEdit = new TextDocumentEdit(identifier, Arrays.asList(te)); + changes.add(Either.forLeft(documentEdit)); + } else { + Map> changes = rootEdit.getChanges(); + List edits = new ArrayList<>(); + edits.add(te); + changes.put(fileUri, edits); + } + } + } else { + ICompilationUnit compilationUnit = (ICompilationUnit) ((IJavaElement) modifiedElement).getAncestor(IJavaElement.COMPILATION_UNIT); + convertTextEdit(rootEdit, compilationUnit, textEdits); + } } private static void convertTextEdit(WorkspaceEdit root, ICompilationUnit unit, TextEdit edit) { diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ExternalFileChange.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ExternalFileChange.java new file mode 100644 index 0000000000..dfedc0fe53 --- /dev/null +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ExternalFileChange.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2021 Red Hat Inc. and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.ls.core.internal; + +import java.net.URI; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.ltk.core.refactoring.DocumentChange; + +public class ExternalFileChange extends DocumentChange { + + private URI file; + + public ExternalFileChange(String name, IDocument document, URI file) { + super(name, document); + this.file = file; + } + + public URI getURI() { + return file; + } + +} diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionHandler.java index f46e27be36..1bb7d99fe1 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionHandler.java @@ -12,6 +12,8 @@ *******************************************************************************/ package org.eclipse.jdt.ls.core.internal.handlers; +import java.io.File; +import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -27,23 +29,29 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.URIUtil; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaModelMarker; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants; import org.eclipse.jdt.core.manipulation.CoreASTProvider; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; import org.eclipse.jdt.internal.ui.text.correction.IProblemLocationCore; import org.eclipse.jdt.internal.ui.text.correction.ProblemLocationCore; import org.eclipse.jdt.ls.core.internal.ChangeUtil; +import org.eclipse.jdt.ls.core.internal.ExternalFileChange; import org.eclipse.jdt.ls.core.internal.JDTUtils; import org.eclipse.jdt.ls.core.internal.JavaCodeActionKind; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; +import org.eclipse.jdt.ls.core.internal.ResourceUtils; import org.eclipse.jdt.ls.core.internal.corrections.DiagnosticsHelper; import org.eclipse.jdt.ls.core.internal.corrections.InnovationContext; import org.eclipse.jdt.ls.core.internal.corrections.QuickFixProcessor; import org.eclipse.jdt.ls.core.internal.corrections.RefactorProcessor; import org.eclipse.jdt.ls.core.internal.corrections.proposals.ChangeCorrectionProposal; +import org.eclipse.jdt.ls.core.internal.corrections.proposals.IProposalRelevance; import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager; import org.eclipse.jdt.ls.core.internal.preferences.Preferences; import org.eclipse.jdt.ls.core.internal.text.correction.AssignToVariableAssistCommandProposal; @@ -52,6 +60,8 @@ import org.eclipse.jdt.ls.core.internal.text.correction.QuickAssistProcessor; import org.eclipse.jdt.ls.core.internal.text.correction.RefactoringCorrectionCommandProposal; import org.eclipse.jdt.ls.core.internal.text.correction.SourceAssistProcessor; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.IDocument; import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.CodeActionContext; import org.eclipse.lsp4j.CodeActionKind; @@ -61,6 +71,8 @@ import org.eclipse.lsp4j.DiagnosticSeverity; import org.eclipse.lsp4j.WorkspaceEdit; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.text.edits.InsertEdit; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -159,6 +171,8 @@ public List> getCodeActionCommands(CodeActionParams try { codeActions.addAll(nonProjectFixProcessor.getCorrections(params, context, locations)); List quickfixProposals = this.quickFixProcessor.getCorrections(context, locations); + List ignoreCompilerProblemProposals = getIgnoreCompilerProblemProposals(diagnostics); + quickfixProposals.addAll(ignoreCompilerProblemProposals); quickfixProposals.sort(comparator); proposals.addAll(quickfixProposals); } catch (CoreException e) { @@ -193,6 +207,7 @@ public List> getCodeActionCommands(CodeActionParams if (monitor.isCanceled()) { return Collections.emptyList(); } + try { for (ChangeCorrectionProposal proposal : proposals) { Optional> codeActionFromProposal = getCodeActionFromProposal(proposal, params.getContext()); @@ -217,6 +232,41 @@ public List> getCodeActionCommands(CodeActionParams return codeActions; } + private List getIgnoreCompilerProblemProposals(List diagnostics) throws CoreException { + List result = new ArrayList<>(); + String label = "Ignore this compiler problem"; + + URI settingsURI = JavaLanguageServerPlugin.getPreferencesManager().getPreferences().getSettingsAsURI(); + if (settingsURI != null && URIUtil.isFileURI(settingsURI)) { + File settingsFile = ResourceUtils.toFile(settingsURI); + String content = ResourceUtils.getContent(settingsURI); + IDocument doc = new Document(content); + + for (Diagnostic diag : diagnostics) { + int problemId = getProblemId(diag); + int irritant = ProblemReporter.getIrritant(problemId); + if (irritant != 0) { + String compilerOption = CompilerOptions.optionKeyFromIrritant(irritant); + if (compilerOption != null) { + String ignoreEntry = String.format("%s=%s\n", compilerOption, JavaCore.IGNORE); + + Change change; + ExternalFileChange editChange = new ExternalFileChange(label, doc, settingsURI); + InsertEdit edit = new InsertEdit(0, ignoreEntry); + editChange.setEdit(edit); + if (settingsFile.exists()) { + change = editChange; + ChangeCorrectionProposal proposal = new ChangeCorrectionProposal(label, CodeActionKind.QuickFix, change, IProposalRelevance.ADD_SUPPRESSWARNINGS); + result.add(proposal); + } + } + } + } + } + + return result; + } + private void populateDataFields(List> codeActions) { ResponseStore.ResponseItem> response = codeActionStore.createResponse(); List> proposals = new ArrayList<>(); diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionHandlerTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionHandlerTest.java index ce3f5d2c50..5bfdbbe871 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionHandlerTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionHandlerTest.java @@ -16,6 +16,8 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; +import java.io.File; +import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; @@ -48,6 +50,7 @@ import org.eclipse.jdt.ls.core.internal.corrections.CorrectionMessages; import org.eclipse.jdt.ls.core.internal.preferences.ClientPreferences; import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager; +import org.eclipse.jdt.ls.core.internal.preferences.Preferences; import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.CodeActionContext; import org.eclipse.lsp4j.CodeActionKind; @@ -57,6 +60,7 @@ import org.eclipse.lsp4j.DiagnosticSeverity; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.TextDocumentEdit; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.WorkspaceEdit; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -87,6 +91,9 @@ public void setup() throws Exception{ project = WorkspaceHelper.getProject("hello"); wcOwner = new LanguageServerWorkingCopyOwner(connection); server = new JDTLanguageServer(projectsManager, this.preferenceManager); + + File settings = new File(System.getProperty("java.io.tmpdir"), "settings.prefs"); + settings.createNewFile(); } @Override @@ -95,6 +102,12 @@ protected ClientPreferences initPreferenceManager(boolean supportClassFileConten return clientPreferences; } + @Override + protected void initPreferences(Preferences preferences) throws IOException { + super.initPreferences(preferences); + preferences.setSettingsUrl(new File("/tmp/settings.prefs").toURI().toString()); + } + @Test public void testCodeAction_removeUnusedImport() throws Exception{ ICompilationUnit unit = getWorkingCopy( @@ -605,6 +618,40 @@ public void testCodeAction_unimplementedMethodReference() throws Exception { Assert.assertNotNull(codeActions); CodeAction action = codeActions.get(1).getRight(); Assert.assertEquals("Add missing method 'action' to class 'Foo'", action.getTitle()); + } + + @Test + public void testCodeAction_ignoreCompilerIssue() throws Exception{ + when(clientPreferences.isResourceOperationSupported()).thenReturn(true); + ICompilationUnit unit = getWorkingCopy( + "src/java/Foo.java", + "package java;\n" + + "public class Foo {\n" + + " @SuppressWarnings(\"deprecation\")\n" + + " public void test () {\n" + + " }\n" + + "}"); + + CodeActionParams params = new CodeActionParams(); + params.setTextDocument(new TextDocumentIdentifier(JDTUtils.toURI(unit))); + final Range range = CodeActionUtil.getRange(unit, "deprecation"); + params.setRange(range); + params.setContext(new CodeActionContext(Arrays.asList(getDiagnostic(Integer.toString(IProblem.UnusedWarningToken), range)))); + List> codeActions = getCodeActions(params); + + Assert.assertNotNull(codeActions); + Assert.assertFalse(codeActions.isEmpty()); + Assert.assertEquals(codeActions.get(0).getRight().getKind(), CodeActionKind.QuickFix); + Command c = codeActions.get(0).getRight().getCommand(); + Assert.assertEquals(CodeActionHandler.COMMAND_ID_APPLY_EDIT, c.getCommand()); + + Assert.assertNotNull(c.getArguments()); + Assert.assertTrue(c.getArguments().get(0) instanceof WorkspaceEdit); + WorkspaceEdit we = (WorkspaceEdit) c.getArguments().get(0); + Assert.assertEquals(1, we.getDocumentChanges().size()); + + TextDocumentEdit textDocEdit = we.getDocumentChanges().get(0).getLeft(); + Assert.assertEquals("org.eclipse.jdt.core.compiler.problem.unusedWarningToken=ignore\n", textDocEdit.getEdits().get(0).getNewText()); } private List> getCodeActions(CodeActionParams params) {