• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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 
17 package com.android.ide.eclipse.adt.internal.lint;
18 
19 import static com.android.SdkConstants.ATTR_IGNORE;
20 import static com.android.SdkConstants.ATTR_TARGET_API;
21 import static com.android.SdkConstants.DOT_XML;
22 
23 import com.android.annotations.NonNull;
24 import com.android.annotations.Nullable;
25 import com.android.sdklib.SdkVersionInfo;
26 import com.android.ide.eclipse.adt.AdtPlugin;
27 import com.android.ide.eclipse.adt.AdtUtils;
28 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
29 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
30 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
31 import com.android.tools.lint.checks.ApiDetector;
32 import com.google.common.collect.Lists;
33 
34 import org.eclipse.core.resources.IMarker;
35 import org.eclipse.core.runtime.CoreException;
36 import org.eclipse.jface.text.IDocument;
37 import org.eclipse.jface.text.contentassist.ICompletionProposal;
38 import org.eclipse.jface.text.contentassist.IContextInformation;
39 import org.eclipse.swt.graphics.Image;
40 import org.eclipse.swt.graphics.Point;
41 import org.w3c.dom.Document;
42 import org.w3c.dom.Element;
43 import org.w3c.dom.Node;
44 
45 import java.util.Collections;
46 import java.util.List;
47 import java.util.Locale;
48 import java.util.regex.Matcher;
49 import java.util.regex.Pattern;
50 
51 /**
52  * Fix for adding {@code tools:ignore="id"} attributes in XML files.
53  */
54 class AddSuppressAttribute implements ICompletionProposal {
55     private final AndroidXmlEditor mEditor;
56     private final String mId;
57     private final IMarker mMarker;
58     private final Element mElement;
59     private final String mDescription;
60     /**
61      * Should it create a {@code tools:targetApi} attribute instead of a
62      * {@code tools:ignore} attribute? If so pass a non null API level
63      */
64     private final String mTargetApi;
65 
66 
AddSuppressAttribute( @onNull AndroidXmlEditor editor, @NonNull String id, @NonNull IMarker marker, @NonNull Element element, @NonNull String description, @Nullable String targetApi)67     private AddSuppressAttribute(
68             @NonNull AndroidXmlEditor editor,
69             @NonNull String id,
70             @NonNull IMarker marker,
71             @NonNull Element element,
72             @NonNull String description,
73             @Nullable String targetApi) {
74         mEditor = editor;
75         mId = id;
76         mMarker = marker;
77         mElement = element;
78         mDescription = description;
79         mTargetApi = targetApi;
80     }
81 
82     @Override
getSelection(IDocument document)83     public Point getSelection(IDocument document) {
84         return null;
85     }
86 
87     @Override
getAdditionalProposalInfo()88     public String getAdditionalProposalInfo() {
89         return null;
90     }
91 
92     @Override
getDisplayString()93     public String getDisplayString() {
94         return mDescription;
95     }
96 
97     @Override
getContextInformation()98     public IContextInformation getContextInformation() {
99         return null;
100     }
101 
102     @Override
getImage()103     public Image getImage() {
104         return IconFactory.getInstance().getIcon("newannotation"); //$NON-NLS-1$
105     }
106 
107     @Override
apply(IDocument document)108     public void apply(IDocument document) {
109         String attribute;
110         String value;
111         if (mTargetApi != null) {
112             attribute = ATTR_TARGET_API;
113             value = mTargetApi;
114         } else {
115             attribute = ATTR_IGNORE;
116             value = mId;
117         }
118         AdtUtils.setToolsAttribute(mEditor, mElement, mDescription, attribute, value,
119                 true /*reveal*/, true /*append*/);
120 
121         try {
122             // Remove the marker now that the suppress attribute has been added
123             // (so the user doesn't have to re-run lint just to see it disappear)
124             mMarker.delete();
125         } catch (CoreException e) {
126             AdtPlugin.log(e, "Could not remove marker");
127         }
128     }
129 
130     /**
131      * Returns a quickfix to suppress a specific lint issue id on the node corresponding to
132      * the given marker.
133      *
134      * @param editor the associated editor containing the marker
135      * @param marker the marker to create fixes for
136      * @param id the issue id
137      * @return a list of fixes for this marker, possibly empty
138      */
139     @NonNull
createFixes( @onNull AndroidXmlEditor editor, @NonNull IMarker marker, @NonNull String id)140     public static List<AddSuppressAttribute> createFixes(
141             @NonNull AndroidXmlEditor editor,
142             @NonNull IMarker marker,
143             @NonNull String id) {
144         // This only applies to XML files:
145         String fileName = marker.getResource().getName();
146         if (!fileName.endsWith(DOT_XML)) {
147             return Collections.emptyList();
148         }
149 
150         int offset = marker.getAttribute(IMarker.CHAR_START, -1);
151         Node node;
152         if (offset == -1) {
153             node = DomUtilities.getNode(editor.getStructuredDocument(), 0);
154             if (node != null) {
155                 node = node.getOwnerDocument().getDocumentElement();
156             }
157         } else {
158             node = DomUtilities.getNode(editor.getStructuredDocument(), offset);
159         }
160         if (node == null) {
161             return Collections.emptyList();
162         }
163         Document document = node.getOwnerDocument();
164         while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
165             node = node.getParentNode();
166         }
167         if (node == null) {
168             node = document.getDocumentElement();
169             if (node == null) {
170                 return Collections.emptyList();
171             }
172         }
173 
174         String desc = String.format("Add ignore '%1$s\' to element", id);
175         Element element = (Element) node;
176         List<AddSuppressAttribute> fixes = Lists.newArrayListWithExpectedSize(2);
177         fixes.add(new AddSuppressAttribute(editor, id, marker, element, desc, null));
178 
179         int api = -1;
180         if (id.equals(ApiDetector.UNSUPPORTED.getId())
181                 || id.equals(ApiDetector.INLINED.getId())) {
182             String message = marker.getAttribute(IMarker.MESSAGE, null);
183             if (message != null) {
184                 Pattern pattern = Pattern.compile("\\s(\\d+)\\s"); //$NON-NLS-1$
185                 Matcher matcher = pattern.matcher(message);
186                 if (matcher.find()) {
187                     api = Integer.parseInt(matcher.group(1));
188                     String targetApi;
189                     String buildCode = SdkVersionInfo.getBuildCode(api);
190                     if (buildCode != null) {
191                         targetApi = buildCode.toLowerCase(Locale.US);
192                         fixes.add(new AddSuppressAttribute(editor, id, marker, element,
193                                 String.format("Add targetApi '%1$s\' to element", targetApi),
194                                 targetApi));
195                     }
196                     targetApi = Integer.toString(api);
197                     fixes.add(new AddSuppressAttribute(editor, id, marker, element,
198                             String.format("Add targetApi '%1$s\' to element", targetApi),
199                             targetApi));
200                 }
201             }
202         }
203 
204         return fixes;
205     }
206 
207     /**
208      * Returns a quickfix to suppress a given issue type on the <b>root element</b>
209      * of the given editor.
210      *
211      * @param editor the associated editor containing the marker
212      * @param marker the marker to create fixes for
213      * @param id the issue id
214      * @return a fix for this marker, or null if unable
215      */
216     @Nullable
createFixForAll( @onNull AndroidXmlEditor editor, @NonNull IMarker marker, @NonNull String id)217     public static AddSuppressAttribute createFixForAll(
218             @NonNull AndroidXmlEditor editor,
219             @NonNull IMarker marker,
220             @NonNull String id) {
221         // This only applies to XML files:
222         String fileName = marker.getResource().getName();
223         if (!fileName.endsWith(DOT_XML)) {
224             return null;
225         }
226 
227         Node node = DomUtilities.getNode(editor.getStructuredDocument(), 0);
228         if (node != null) {
229             node = node.getOwnerDocument().getDocumentElement();
230             String desc = String.format("Add ignore '%1$s\' to element", id);
231             Element element = (Element) node;
232             return new AddSuppressAttribute(editor, id, marker, element, desc, null);
233         }
234 
235         return null;
236     }
237 }
238