aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java563
1 files changed, 563 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
new file mode 100644
index 000000000..da100850a
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.lint;
+
+import static com.android.SdkConstants.DOT_JAVA;
+import static com.android.SdkConstants.DOT_XML;
+
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.tools.lint.client.api.Configuration;
+import com.android.tools.lint.client.api.DefaultConfiguration;
+import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.TextFormat;
+import com.android.tools.lint.detector.api.Project;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.utils.SdkUtils;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.contentassist.IContextInformation;
+import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
+import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IMarkerResolution;
+import org.eclipse.ui.IMarkerResolution2;
+import org.eclipse.ui.IMarkerResolutionGenerator2;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.part.FileEditorInput;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A quickfix and marker resolution for disabling lint checks, and any
+ * IDE specific implementations for fixing the warnings.
+ * <p>
+ * I would really like for this quickfix to show up as a light bulb on top of the error
+ * icon in the editor, and I've spent a whole day trying to make it work. I did not
+ * succeed, but here are the steps I tried in case I want to pick up the work again
+ * later:
+ * <ul>
+ * <li>
+ * The WST has some support for quick fixes, and I came across some forum posts
+ * referencing the ability to show light bulbs. However, it turns out that the
+ * quickfix support for annotations in WST is hardcoded to source validation
+ * errors *only*.
+ * <li>
+ * I tried defining my own editor annotations, and customizing the icon directly
+ * by either setting an icon or using the image provider. This works fine
+ * if I make my marker be a new independent marker type. However, whenever I
+ * switch the marker type back to extend the "Problem" type, then the icon reverts
+ * back to the standard error icon and it ignores my custom settings.
+ * And if I switch away from the Problems marker type, then the errors no longer
+ * show up in the Problems view. (I also tried extending the JDT marker but that
+ * still didn't work.)
+ * <li>
+ * It looks like only JDT handles quickfix icons. It has a bunch of custom code
+ * to handle this, along with its own Annotation subclass used by the editor.
+ * I tried duplicating some of this by subclassing StructuredTextEditor, but
+ * it was evident that I'd have to pull in a *huge* amount of duplicated code to
+ * make this work, which seems risky given that all this is internal code that
+ * can change from one Eclipse version to the next.
+ * </ul>
+ * It looks like our best bet would be to reconsider whether these should show up
+ * in the Problems view; perhaps we should use a custom view for these. That would also
+ * make marker management more obvious.
+ */
+@SuppressWarnings("restriction") // DOM model
+public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssistProcessor {
+ /** Constructs a new {@link LintFixGenerator} */
+ public LintFixGenerator() {
+ }
+
+ // ---- Implements IMarkerResolutionGenerator2 ----
+
+ @Override
+ public boolean hasResolutions(IMarker marker) {
+ try {
+ assert marker.getType().equals(AdtConstants.MARKER_LINT);
+ } catch (CoreException e) {
+ }
+
+ return true;
+ }
+
+ @Override
+ public IMarkerResolution[] getResolutions(IMarker marker) {
+ String id = marker.getAttribute(EclipseLintRunner.MARKER_CHECKID_PROPERTY,
+ ""); //$NON-NLS-1$
+ IResource resource = marker.getResource();
+
+ List<IMarkerResolution> resolutions = new ArrayList<IMarkerResolution>();
+
+ if (resource.getName().endsWith(DOT_JAVA)) {
+ AddSuppressAnnotation.createFixes(marker, id, resolutions);
+ }
+
+ resolutions.add(new MoreInfoProposal(id, marker.getAttribute(IMarker.MESSAGE, null)));
+ resolutions.add(new SuppressProposal(resource, id, false));
+ resolutions.add(new SuppressProposal(resource.getProject(), id, true /* all */));
+ resolutions.add(new SuppressProposal(resource, id, true /* all */));
+ resolutions.add(new ClearMarkersProposal(resource, true /* all */));
+
+ if (resolutions.size() > 0) {
+ return resolutions.toArray(new IMarkerResolution[resolutions.size()]);
+ }
+
+ return null;
+ }
+
+ // ---- Implements IQuickAssistProcessor ----
+
+ @Override
+ public String getErrorMessage() {
+ return "Disable Lint Error";
+ }
+
+ @Override
+ public boolean canFix(Annotation annotation) {
+ return true;
+ }
+
+ @Override
+ public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
+ return true;
+ }
+
+ @Override
+ public ICompletionProposal[] computeQuickAssistProposals(
+ IQuickAssistInvocationContext invocationContext) {
+ ISourceViewer sourceViewer = invocationContext.getSourceViewer();
+ AndroidXmlEditor editor = AndroidXmlEditor.fromTextViewer(sourceViewer);
+ if (editor != null) {
+ IFile file = editor.getInputFile();
+ if (file == null) {
+ return null;
+ }
+ IDocument document = sourceViewer.getDocument();
+ List<IMarker> markers = AdtUtils.findMarkersOnLine(AdtConstants.MARKER_LINT,
+ file, document, invocationContext.getOffset());
+ List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
+ if (markers.size() > 0) {
+ for (IMarker marker : markers) {
+ String id = marker.getAttribute(EclipseLintRunner.MARKER_CHECKID_PROPERTY,
+ ""); //$NON-NLS-1$
+
+ // TODO: Allow for more than one fix?
+ List<LintFix> fixes = LintFix.getFixes(id, marker);
+ if (fixes != null) {
+ for (LintFix fix : fixes) {
+ proposals.add(fix);
+ }
+ }
+
+ String message = marker.getAttribute(IMarker.MESSAGE, null);
+ proposals.add(new MoreInfoProposal(id, message));
+
+ proposals.addAll(AddSuppressAttribute.createFixes(editor, marker, id));
+ proposals.add(new SuppressProposal(file, id, false));
+ proposals.add(new SuppressProposal(file.getProject(), id, true /* all */));
+ proposals.add(new SuppressProposal(file, id, true /* all */));
+
+ proposals.add(new ClearMarkersProposal(file, true /* all */));
+ }
+ }
+ if (proposals.size() > 0) {
+ return proposals.toArray(new ICompletionProposal[proposals.size()]);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Suppress the given detector, and rerun the checks on the file
+ *
+ * @param id the id of the detector to be suppressed, or null
+ * @param updateMarkers if true, update all markers
+ * @param resource the resource associated with the markers
+ * @param thisFileOnly if true, only suppress this issue in this file
+ */
+ public static void suppressDetector(String id, boolean updateMarkers, IResource resource,
+ boolean thisFileOnly) {
+ IssueRegistry registry = EclipseLintClient.getRegistry();
+ Issue issue = registry.getIssue(id);
+ if (issue != null) {
+ EclipseLintClient mClient = new EclipseLintClient(registry,
+ Collections.singletonList(resource), null, false);
+ Project project = null;
+ IProject eclipseProject = resource.getProject();
+ if (eclipseProject != null) {
+ File dir = AdtUtils.getAbsolutePath(eclipseProject).toFile();
+ project = mClient.getProject(dir, dir);
+ }
+ Configuration configuration = mClient.getConfigurationFor(project);
+ if (thisFileOnly && configuration instanceof DefaultConfiguration) {
+ File file = AdtUtils.getAbsolutePath(resource).toFile();
+ ((DefaultConfiguration) configuration).ignore(issue, file);
+ } else {
+ configuration.setSeverity(issue, Severity.IGNORE);
+ }
+ }
+
+ if (updateMarkers) {
+ EclipseLintClient.removeMarkers(resource, id);
+ }
+ }
+
+ /**
+ * Adds a suppress lint annotation or attribute depending on whether the
+ * error is in a Java or XML file.
+ *
+ * @param marker the marker pointing to the error to be suppressed
+ */
+ public static void addSuppressAnnotation(IMarker marker) {
+ String id = EclipseLintClient.getId(marker);
+ if (id != null) {
+ IResource resource = marker.getResource();
+ if (!(resource instanceof IFile)) {
+ return;
+ }
+ IFile file = (IFile) resource;
+ boolean isJava = file.getName().endsWith(DOT_JAVA);
+ boolean isXml = SdkUtils.endsWith(file.getName(), DOT_XML);
+ if (!isJava && !isXml) {
+ return;
+ }
+
+ try {
+ // See if the current active file is the one containing this marker;
+ // if so we can take some shortcuts
+ IEditorPart activeEditor = AdtUtils.getActiveEditor();
+ IEditorPart part = null;
+ if (activeEditor != null) {
+ IEditorInput input = activeEditor.getEditorInput();
+ if (input instanceof FileEditorInput
+ && ((FileEditorInput)input).getFile().equals(file)) {
+ part = activeEditor;
+ }
+ }
+ if (part == null) {
+ IRegion region = null;
+ int start = marker.getAttribute(IMarker.CHAR_START, -1);
+ int end = marker.getAttribute(IMarker.CHAR_END, -1);
+ if (start != -1 && end != -1) {
+ region = new Region(start, end - start);
+ }
+ part = AdtPlugin.openFile(file, region, true /* showEditor */);
+ }
+
+ if (isJava) {
+ List<IMarkerResolution> resolutions = new ArrayList<IMarkerResolution>();
+ AddSuppressAnnotation.createFixes(marker, id, resolutions);
+ if (resolutions.size() > 0) {
+ resolutions.get(0).run(marker);
+ }
+ } else {
+ assert isXml;
+ if (part instanceof AndroidXmlEditor) {
+ AndroidXmlEditor editor = (AndroidXmlEditor) part;
+ List<AddSuppressAttribute> fixes = AddSuppressAttribute.createFixes(editor,
+ marker, id);
+ if (fixes.size() > 0) {
+ IStructuredDocument document = editor.getStructuredDocument();
+ fixes.get(0).apply(document);
+ }
+ }
+ }
+ } catch (PartInitException pie) {
+ AdtPlugin.log(pie, null);
+ }
+ }
+ }
+
+ private static class SuppressProposal implements ICompletionProposal, IMarkerResolution2 {
+ private final String mId;
+ private final boolean mGlobal;
+ private final IResource mResource;
+
+ private SuppressProposal(IResource resource, String check, boolean global) {
+ mResource = resource;
+ mId = check;
+ mGlobal = global;
+ }
+
+ private void perform() {
+ suppressDetector(mId, true, mResource, !mGlobal);
+ }
+
+ @Override
+ public String getDisplayString() {
+ if (mResource instanceof IProject) {
+ return "Disable Check in This Project";
+ } else if (mGlobal) {
+ return "Disable Check";
+ } else {
+ return "Disable Check in This File Only";
+ }
+ }
+
+ // ---- Implements MarkerResolution2 ----
+
+ @Override
+ public String getLabel() {
+ return getDisplayString();
+ }
+
+ @Override
+ public void run(IMarker marker) {
+ perform();
+ }
+
+ @Override
+ public String getDescription() {
+ return getAdditionalProposalInfo();
+ }
+
+ // ---- Implements ICompletionProposal ----
+
+ @Override
+ public void apply(IDocument document) {
+ perform();
+ }
+
+ @Override
+ public Point getSelection(IDocument document) {
+ return null;
+ }
+
+ @Override
+ public String getAdditionalProposalInfo() {
+ StringBuilder sb = new StringBuilder(200);
+ if (mResource instanceof IProject) {
+ sb.append("Suppresses this type of lint warning in the current project only.");
+ } else if (mGlobal) {
+ sb.append("Suppresses this type of lint warning in all files.");
+ } else {
+ sb.append("Suppresses this type of lint warning in the current file only.");
+ }
+ sb.append("<br><br>"); //$NON-NLS-1$
+ sb.append("You can re-enable checks from the \"Android > Lint Error Checking\" preference page.");
+
+ return sb.toString();
+ }
+
+ @Override
+ public Image getImage() {
+ ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+ return sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK);
+ }
+
+ @Override
+ public IContextInformation getContextInformation() {
+ return null;
+ }
+ }
+
+ private static class ClearMarkersProposal implements ICompletionProposal, IMarkerResolution2 {
+ private final boolean mGlobal;
+ private final IResource mResource;
+
+ public ClearMarkersProposal(IResource resource, boolean global) {
+ mResource = resource;
+ mGlobal = global;
+ }
+
+ private void perform() {
+ IResource resource = mGlobal ? mResource.getProject() : mResource;
+ EclipseLintClient.clearMarkers(resource);
+ }
+
+ @Override
+ public String getDisplayString() {
+ return mGlobal ? "Clear All Lint Markers" : "Clear Markers in This File Only";
+ }
+
+ // ---- Implements MarkerResolution2 ----
+
+ @Override
+ public String getLabel() {
+ return getDisplayString();
+ }
+
+ @Override
+ public void run(IMarker marker) {
+ perform();
+ }
+
+ @Override
+ public String getDescription() {
+ return getAdditionalProposalInfo();
+ }
+
+ // ---- Implements ICompletionProposal ----
+
+ @Override
+ public void apply(IDocument document) {
+ perform();
+ }
+
+ @Override
+ public Point getSelection(IDocument document) {
+ return null;
+ }
+
+ @Override
+ public String getAdditionalProposalInfo() {
+ StringBuilder sb = new StringBuilder(200);
+ if (mGlobal) {
+ sb.append("Clears all lint warning markers from the project.");
+ } else {
+ sb.append("Clears all lint warnings from this file.");
+ }
+ sb.append("<br><br>"); //$NON-NLS-1$
+ sb.append("This temporarily hides the problem, but does not suppress it. " +
+ "Running Lint again can bring the error back.");
+ if (AdtPrefs.getPrefs().isLintOnSave()) {
+ sb.append(' ');
+ sb.append("This will happen the next time the file is saved since lint-on-save " +
+ "is enabled. You can turn this off in the \"Lint Error Checking\" " +
+ "preference page.");
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public Image getImage() {
+ ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+ return sharedImages.getImage(ISharedImages.IMG_ELCL_REMOVE);
+ }
+
+ @Override
+ public IContextInformation getContextInformation() {
+ return null;
+ }
+ }
+
+ private static class MoreInfoProposal implements ICompletionProposal, IMarkerResolution2 {
+ private final String mId;
+ private final String mMessage;
+
+ public MoreInfoProposal(String id, String message) {
+ mId = id;
+ mMessage = message;
+ }
+
+ private void perform() {
+ Issue issue = EclipseLintClient.getRegistry().getIssue(mId);
+ assert issue != null : mId;
+
+ StringBuilder sb = new StringBuilder(300);
+ sb.append(mMessage);
+ sb.append('\n').append('\n');
+ sb.append("Issue Explanation:");
+ sb.append('\n');
+ String explanation = issue.getExplanation(TextFormat.TEXT);
+ if (explanation != null && !explanation.isEmpty()) {
+ sb.append('\n');
+ sb.append(explanation);
+ } else {
+ sb.append(issue.getBriefDescription(TextFormat.TEXT));
+ }
+
+ if (issue.getMoreInfo() != null) {
+ sb.append('\n').append('\n');
+ sb.append("More Information: ");
+ sb.append(issue.getMoreInfo());
+ }
+
+ MessageDialog.openInformation(AdtPlugin.getShell(), "More Info",
+ sb.toString());
+ }
+
+ @Override
+ public String getDisplayString() {
+ return String.format("Explain Issue (%1$s)", mId);
+ }
+
+ // ---- Implements MarkerResolution2 ----
+
+ @Override
+ public String getLabel() {
+ return getDisplayString();
+ }
+
+ @Override
+ public void run(IMarker marker) {
+ perform();
+ }
+
+ @Override
+ public String getDescription() {
+ return getAdditionalProposalInfo();
+ }
+
+ // ---- Implements ICompletionProposal ----
+
+ @Override
+ public void apply(IDocument document) {
+ perform();
+ }
+
+ @Override
+ public Point getSelection(IDocument document) {
+ return null;
+ }
+
+ @Override
+ public String getAdditionalProposalInfo() {
+ return "Provides more information about this issue."
+ + "<br><br>" //$NON-NLS-1$
+ + EclipseLintClient.getRegistry().getIssue(mId).getExplanation(
+ TextFormat.HTML);
+ }
+
+ @Override
+ public Image getImage() {
+ ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+ return sharedImages.getImage(ISharedImages.IMG_OBJS_INFO_TSK);
+ }
+
+ @Override
+ public IContextInformation getContextInformation() {
+ return null;
+ }
+ }
+}