• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.eclipse.org/org/documents/epl-v10.php
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.ide.eclipse.adt.internal.lint;
17 
18 import static com.android.ide.eclipse.adt.AdtConstants.DOT_JAVA;
19 import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
20 
21 import com.android.ide.eclipse.adt.AdtConstants;
22 import com.android.ide.eclipse.adt.AdtPlugin;
23 import com.android.ide.eclipse.adt.AdtUtils;
24 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
25 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
26 import com.android.tools.lint.client.api.Configuration;
27 import com.android.tools.lint.client.api.DefaultConfiguration;
28 import com.android.tools.lint.client.api.IssueRegistry;
29 import com.android.tools.lint.detector.api.Issue;
30 import com.android.tools.lint.detector.api.Project;
31 import com.android.tools.lint.detector.api.Severity;
32 
33 import org.eclipse.core.resources.IFile;
34 import org.eclipse.core.resources.IMarker;
35 import org.eclipse.core.resources.IProject;
36 import org.eclipse.core.resources.IResource;
37 import org.eclipse.core.runtime.CoreException;
38 import org.eclipse.jface.dialogs.MessageDialog;
39 import org.eclipse.jface.text.IDocument;
40 import org.eclipse.jface.text.IRegion;
41 import org.eclipse.jface.text.Region;
42 import org.eclipse.jface.text.contentassist.ICompletionProposal;
43 import org.eclipse.jface.text.contentassist.IContextInformation;
44 import org.eclipse.jface.text.quickassist.IQuickAssistInvocationContext;
45 import org.eclipse.jface.text.quickassist.IQuickAssistProcessor;
46 import org.eclipse.jface.text.source.Annotation;
47 import org.eclipse.jface.text.source.ISourceViewer;
48 import org.eclipse.swt.graphics.Image;
49 import org.eclipse.swt.graphics.Point;
50 import org.eclipse.ui.IEditorInput;
51 import org.eclipse.ui.IEditorPart;
52 import org.eclipse.ui.IMarkerResolution;
53 import org.eclipse.ui.IMarkerResolution2;
54 import org.eclipse.ui.IMarkerResolutionGenerator2;
55 import org.eclipse.ui.ISharedImages;
56 import org.eclipse.ui.PartInitException;
57 import org.eclipse.ui.PlatformUI;
58 import org.eclipse.ui.part.FileEditorInput;
59 import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
60 
61 import java.io.File;
62 import java.util.ArrayList;
63 import java.util.Collections;
64 import java.util.List;
65 
66 /**
67  * A quickfix and marker resolution for disabling lint checks, and any
68  * IDE specific implementations for fixing the warnings.
69  * <p>
70  * I would really like for this quickfix to show up as a light bulb on top of the error
71  * icon in the editor, and I've spent a whole day trying to make it work. I did not
72  * succeed, but here are the steps I tried in case I want to pick up the work again
73  * later:
74  * <ul>
75  * <li>
76  *     The WST has some support for quick fixes, and I came across some forum posts
77  *     referencing the ability to show light bulbs. However, it turns out that the
78  *     quickfix support for annotations in WST is hardcoded to source validation
79  *     errors *only*.
80  * <li>
81  *     I tried defining my own editor annotations, and customizing the icon directly
82  *     by either setting an icon or using the image provider. This works fine
83  *     if I make my marker be a new independent marker type. However, whenever I
84  *     switch the marker type back to extend the "Problem" type, then the icon reverts
85  *     back to the standard error icon and it ignores my custom settings.
86  *     And if I switch away from the Problems marker type, then the errors no longer
87  *     show up in the Problems view. (I also tried extending the JDT marker but that
88  *     still didn't work.)
89  * <li>
90  *     It looks like only JDT handles quickfix icons. It has a bunch of custom code
91  *     to handle this, along with its own Annotation subclass used by the editor.
92  *     I tried duplicating some of this by subclassing StructuredTextEditor, but
93  *     it was evident that I'd have to pull in a *huge* amount of duplicated code to
94  *     make this work, which seems risky given that all this is internal code that
95  *     can change from one Eclipse version to the next.
96  * </ul>
97  * It looks like our best bet would be to reconsider whether these should show up
98  * in the Problems view; perhaps we should use a custom view for these. That would also
99  * make marker management more obvious.
100  */
101 public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssistProcessor {
102     /** Constructs a new {@link LintFixGenerator} */
LintFixGenerator()103     public LintFixGenerator() {
104     }
105 
106     // ---- Implements IMarkerResolutionGenerator2 ----
107 
108     @Override
hasResolutions(IMarker marker)109     public boolean hasResolutions(IMarker marker) {
110         try {
111             assert marker.getType().equals(AdtConstants.MARKER_LINT);
112         } catch (CoreException e) {
113         }
114 
115         return true;
116     }
117 
118     @Override
getResolutions(IMarker marker)119     public IMarkerResolution[] getResolutions(IMarker marker) {
120         String id = marker.getAttribute(EclipseLintRunner.MARKER_CHECKID_PROPERTY,
121                 ""); //$NON-NLS-1$
122         IResource resource = marker.getResource();
123 
124         List<IMarkerResolution> resolutions = new ArrayList<IMarkerResolution>();
125 
126         if (resource.getName().endsWith(DOT_JAVA)) {
127             AddSuppressAnnotation.createFixes(marker, id, resolutions);
128         }
129 
130         resolutions.add(new MoreInfoProposal(id, marker.getAttribute(IMarker.MESSAGE, null)));
131         resolutions.add(new SuppressProposal(resource, id, false));
132         resolutions.add(new SuppressProposal(resource.getProject(), id, true /* all */));
133         resolutions.add(new SuppressProposal(resource, id, true /* all */));
134         resolutions.add(new ClearMarkersProposal(resource, true /* all */));
135 
136         if (resolutions.size() > 0) {
137             return resolutions.toArray(new IMarkerResolution[resolutions.size()]);
138         }
139 
140         return null;
141     }
142 
143     // ---- Implements IQuickAssistProcessor ----
144 
145     @Override
getErrorMessage()146     public String getErrorMessage() {
147         return "Disable Lint Error";
148     }
149 
150     @Override
canFix(Annotation annotation)151     public boolean canFix(Annotation annotation) {
152         return true;
153     }
154 
155     @Override
canAssist(IQuickAssistInvocationContext invocationContext)156     public boolean canAssist(IQuickAssistInvocationContext invocationContext) {
157         return true;
158     }
159 
160     @Override
computeQuickAssistProposals( IQuickAssistInvocationContext invocationContext)161     public ICompletionProposal[] computeQuickAssistProposals(
162             IQuickAssistInvocationContext invocationContext) {
163         ISourceViewer sourceViewer = invocationContext.getSourceViewer();
164         AndroidXmlEditor editor = AndroidXmlEditor.fromTextViewer(sourceViewer);
165         if (editor != null) {
166             IFile file = editor.getInputFile();
167             if (file == null) {
168                 return null;
169             }
170             IDocument document = sourceViewer.getDocument();
171             List<IMarker> markers = AdtUtils.findMarkersOnLine(AdtConstants.MARKER_LINT,
172                     file, document, invocationContext.getOffset());
173             List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
174             if (markers.size() > 0) {
175                 for (IMarker marker : markers) {
176                     String id = marker.getAttribute(EclipseLintRunner.MARKER_CHECKID_PROPERTY,
177                             ""); //$NON-NLS-1$
178 
179                     // TODO: Allow for more than one fix?
180                     ICompletionProposal fix = LintFix.getFix(id, marker);
181                     if (fix != null) {
182                         proposals.add(fix);
183                     }
184 
185                     String message = marker.getAttribute(IMarker.MESSAGE, null);
186                     proposals.add(new MoreInfoProposal(id, message));
187 
188                     fix = AddSuppressAttribute.createFix(editor, marker, id);
189                     if (fix != null) {
190                         proposals.add(fix);
191                     }
192 
193                     proposals.add(new SuppressProposal(file, id, false));
194                     proposals.add(new SuppressProposal(file.getProject(), id, true /* all */));
195                     proposals.add(new SuppressProposal(file, id, true /* all */));
196 
197                     proposals.add(new ClearMarkersProposal(file, true /* all */));
198                 }
199             }
200             if (proposals.size() > 0) {
201                 return proposals.toArray(new ICompletionProposal[proposals.size()]);
202             }
203         }
204 
205         return null;
206     }
207 
208     /**
209      * Suppress the given detector, and rerun the checks on the file
210      *
211      * @param id the id of the detector to be suppressed, or null
212      * @param updateMarkers if true, update all markers
213      * @param resource the resource associated with the markers
214      * @param thisFileOnly if true, only suppress this issue in this file
215      */
suppressDetector(String id, boolean updateMarkers, IResource resource, boolean thisFileOnly)216     public static void suppressDetector(String id, boolean updateMarkers, IResource resource,
217             boolean thisFileOnly) {
218         IssueRegistry registry = EclipseLintClient.getRegistry();
219         Issue issue = registry.getIssue(id);
220         if (issue != null) {
221             EclipseLintClient mClient = new EclipseLintClient(registry,
222                     Collections.singletonList(resource), null, false);
223             Project project = null;
224             IProject eclipseProject = resource.getProject();
225             if (eclipseProject != null) {
226                 File dir = AdtUtils.getAbsolutePath(eclipseProject).toFile();
227                 project = mClient.getProject(dir, dir);
228             }
229             Configuration configuration = mClient.getConfiguration(project);
230             if (thisFileOnly && configuration instanceof DefaultConfiguration) {
231                 File file = AdtUtils.getAbsolutePath(resource).toFile();
232                 ((DefaultConfiguration) configuration).ignore(issue, file);
233             } else {
234                 configuration.setSeverity(issue, Severity.IGNORE);
235             }
236         }
237 
238         if (updateMarkers) {
239             EclipseLintClient.removeMarkers(resource, id);
240         }
241     }
242 
243     /**
244      * Adds a suppress lint annotation or attribute depending on whether the
245      * error is in a Java or XML file.
246      *
247      * @param marker the marker pointing to the error to be suppressed
248      */
249     @SuppressWarnings("restriction") // XML model
addSuppressAnnotation(IMarker marker)250     public static void addSuppressAnnotation(IMarker marker) {
251         String id = EclipseLintClient.getId(marker);
252         if (id != null) {
253             IResource resource = marker.getResource();
254             if (!(resource instanceof IFile)) {
255                 return;
256             }
257             IFile file = (IFile) resource;
258             boolean isJava = file.getName().endsWith(DOT_JAVA);
259             boolean isXml = AdtUtils.endsWith(file.getName(), DOT_XML);
260             if (!isJava && !isXml) {
261                 return;
262             }
263 
264             try {
265                 IEditorPart activeEditor = AdtUtils.getActiveEditor();
266                 IEditorPart part = null;
267                 if (activeEditor != null) {
268                     IEditorInput input = activeEditor.getEditorInput();
269                     if (input instanceof FileEditorInput
270                             && ((FileEditorInput)input).getFile().equals(file)) {
271                         part = activeEditor;
272                     }
273                 }
274                 if (part == null) {
275                     IRegion region = null;
276                     int start = marker.getAttribute(IMarker.CHAR_START, -1);
277                     int end = marker.getAttribute(IMarker.CHAR_END, -1);
278                     if (start != -1 && end != -1) {
279                         region = new Region(start, end - start);
280                     }
281                     part = AdtPlugin.openFile(file, region, true /* showEditor */);
282                 }
283 
284                 if (isJava) {
285                     List<IMarkerResolution> resolutions = new ArrayList<IMarkerResolution>();
286                     AddSuppressAnnotation.createFixes(marker, id, resolutions);
287                     if (resolutions.size() > 0) {
288                         resolutions.get(0).run(marker);
289                     }
290                 } else {
291                     assert isXml;
292                     if (part instanceof AndroidXmlEditor) {
293                         AndroidXmlEditor editor = (AndroidXmlEditor) part;
294                         AddSuppressAttribute fix = AddSuppressAttribute.createFix(editor,
295                                 marker, id);
296                         if (fix != null) {
297                             IStructuredDocument document = editor.getStructuredDocument();
298                             fix.apply(document);
299                         }
300                     }
301                 }
302             } catch (PartInitException pie) {
303                 AdtPlugin.log(pie, null);
304             }
305         }
306     }
307 
308     private static class SuppressProposal implements ICompletionProposal, IMarkerResolution2 {
309         private final String mId;
310         private final boolean mGlobal;
311         private final IResource mResource;
312 
SuppressProposal(IResource resource, String check, boolean global)313         private SuppressProposal(IResource resource, String check, boolean global) {
314             mResource = resource;
315             mId = check;
316             mGlobal = global;
317         }
318 
perform()319         private void perform() {
320             suppressDetector(mId, true, mResource, !mGlobal);
321         }
322 
323         @Override
getDisplayString()324         public String getDisplayString() {
325             if (mResource instanceof IProject) {
326                 return "Disable Check in This Project";
327             } else if (mGlobal) {
328                 return "Disable Check";
329             } else {
330                 return "Disable Check in This File Only";
331             }
332         }
333 
334         // ---- Implements MarkerResolution2 ----
335 
336         @Override
getLabel()337         public String getLabel() {
338             return getDisplayString();
339         }
340 
341         @Override
run(IMarker marker)342         public void run(IMarker marker) {
343             perform();
344         }
345 
346         @Override
getDescription()347         public String getDescription() {
348             return getAdditionalProposalInfo();
349         }
350 
351         // ---- Implements ICompletionProposal ----
352 
353         @Override
apply(IDocument document)354         public void apply(IDocument document) {
355             perform();
356         }
357 
358         @Override
getSelection(IDocument document)359         public Point getSelection(IDocument document) {
360             return null;
361         }
362 
363         @Override
getAdditionalProposalInfo()364         public String getAdditionalProposalInfo() {
365             StringBuilder sb = new StringBuilder(200);
366             if (mResource instanceof IProject) {
367                 sb.append("Suppresses this type of lint warning in the current project only.");
368             } else if (mGlobal) {
369                 sb.append("Suppresses this type of lint warning in all files.");
370             } else {
371                 sb.append("Suppresses this type of lint warning in the current file only.");
372             }
373             sb.append("<br><br>"); //$NON-NLS-1$
374             sb.append("You can re-enable checks from the \"Android > Lint Error Checking\" preference page.");
375 
376             return sb.toString();
377         }
378 
379         @Override
getImage()380         public Image getImage() {
381             ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
382             return sharedImages.getImage(ISharedImages.IMG_OBJS_WARN_TSK);
383         }
384 
385         @Override
getContextInformation()386         public IContextInformation getContextInformation() {
387             return null;
388         }
389     }
390 
391     private static class ClearMarkersProposal implements ICompletionProposal, IMarkerResolution2 {
392         private final boolean mGlobal;
393         private final IResource mResource;
394 
ClearMarkersProposal(IResource resource, boolean global)395         public ClearMarkersProposal(IResource resource, boolean global) {
396             mResource = resource;
397             mGlobal = global;
398         }
399 
perform()400         private void perform() {
401             IResource resource = mGlobal ? mResource.getProject() : mResource;
402             EclipseLintClient.clearMarkers(resource);
403         }
404 
405         @Override
getDisplayString()406         public String getDisplayString() {
407             return mGlobal ? "Clear All Lint Markers" : "Clear Markers in This File Only";
408         }
409 
410         // ---- Implements MarkerResolution2 ----
411 
412         @Override
getLabel()413         public String getLabel() {
414             return getDisplayString();
415         }
416 
417         @Override
run(IMarker marker)418         public void run(IMarker marker) {
419             perform();
420         }
421 
422         @Override
getDescription()423         public String getDescription() {
424             return getAdditionalProposalInfo();
425         }
426 
427         // ---- Implements ICompletionProposal ----
428 
429         @Override
apply(IDocument document)430         public void apply(IDocument document) {
431             perform();
432         }
433 
434         @Override
getSelection(IDocument document)435         public Point getSelection(IDocument document) {
436             return null;
437         }
438 
439         @Override
getAdditionalProposalInfo()440         public String getAdditionalProposalInfo() {
441             StringBuilder sb = new StringBuilder(200);
442             if (mGlobal) {
443                 sb.append("Clears all lint warning markers from the project.");
444             } else {
445                 sb.append("Clears all lint warnings from this file.");
446             }
447             sb.append("<br><br>"); //$NON-NLS-1$
448             sb.append("This temporarily hides the problem, but does not suppress it. " +
449                     "Running Lint again can bring the error back.");
450             if (AdtPrefs.getPrefs().isLintOnSave()) {
451                 sb.append(' ');
452                 sb.append("This will happen the next time the file is saved since lint-on-save " +
453                         "is enabled. You can turn this off in the \"Lint Error Checking\" " +
454                         "preference page.");
455             }
456 
457             return sb.toString();
458         }
459 
460         @Override
getImage()461         public Image getImage() {
462             ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
463             return sharedImages.getImage(ISharedImages.IMG_ELCL_REMOVE);
464         }
465 
466         @Override
getContextInformation()467         public IContextInformation getContextInformation() {
468             return null;
469         }
470     }
471 
472     private static class MoreInfoProposal implements ICompletionProposal, IMarkerResolution2 {
473         private final String mId;
474         private final String mMessage;
475 
MoreInfoProposal(String id, String message)476         public MoreInfoProposal(String id, String message) {
477             mId = id;
478             mMessage = message;
479         }
480 
perform()481         private void perform() {
482             Issue issue = EclipseLintClient.getRegistry().getIssue(mId);
483             assert issue != null : mId;
484 
485             StringBuilder sb = new StringBuilder(300);
486             sb.append(mMessage);
487             sb.append('\n').append('\n');
488             sb.append("Issue Explanation:");
489             sb.append('\n');
490             if (issue.getExplanation() != null) {
491                 sb.append('\n');
492                 sb.append(issue.getExplanation());
493             } else {
494                 sb.append(issue.getDescription());
495             }
496 
497             if (issue.getMoreInfo() != null) {
498                 sb.append('\n').append('\n');
499                 sb.append("More Information: ");
500                 sb.append(issue.getMoreInfo());
501             }
502 
503             MessageDialog.openInformation(AdtPlugin.getDisplay().getActiveShell(), "More Info",
504                     sb.toString());
505         }
506 
507         @Override
getDisplayString()508         public String getDisplayString() {
509             return "Explain Issue";
510         }
511 
512         // ---- Implements MarkerResolution2 ----
513 
514         @Override
getLabel()515         public String getLabel() {
516             return getDisplayString();
517         }
518 
519         @Override
run(IMarker marker)520         public void run(IMarker marker) {
521             perform();
522         }
523 
524         @Override
getDescription()525         public String getDescription() {
526             return getAdditionalProposalInfo();
527         }
528 
529         // ---- Implements ICompletionProposal ----
530 
531         @Override
apply(IDocument document)532         public void apply(IDocument document) {
533             perform();
534         }
535 
536         @Override
getSelection(IDocument document)537         public Point getSelection(IDocument document) {
538             return null;
539         }
540 
541         @Override
getAdditionalProposalInfo()542         public String getAdditionalProposalInfo() {
543             return "Provides more information about this issue";
544         }
545 
546         @Override
getImage()547         public Image getImage() {
548             ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
549             return sharedImages.getImage(ISharedImages.IMG_OBJS_INFO_TSK);
550         }
551 
552         @Override
getContextInformation()553         public IContextInformation getContextInformation() {
554             return null;
555         }
556     }
557 }
558