| /* |
| * 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; |
| } |
| } |
| } |