diff options
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.java | 563 |
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; + } + } +} |