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 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 83 public Point getSelection(IDocument document) { 84 return null; 85 } 86 87 @Override 88 public String getAdditionalProposalInfo() { 89 return null; 90 } 91 92 @Override 93 public String getDisplayString() { 94 return mDescription; 95 } 96 97 @Override 98 public IContextInformation getContextInformation() { 99 return null; 100 } 101 102 @Override 103 public Image getImage() { 104 return IconFactory.getInstance().getIcon("newannotation"); //$NON-NLS-1$ 105 } 106 107 @Override 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 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 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