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.tools.lint.detector.api.LintConstants.ATTR_IGNORE; 20 import static com.android.tools.lint.detector.api.LintConstants.DOT_XML; 21 import static com.android.tools.lint.detector.api.LintConstants.TOOLS_PREFIX; 22 import static com.android.tools.lint.detector.api.LintConstants.TOOLS_URI; 23 24 import com.android.annotations.NonNull; 25 import com.android.annotations.Nullable; 26 import com.android.ide.eclipse.adt.AdtPlugin; 27 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; 28 import com.android.ide.eclipse.adt.internal.editors.IconFactory; 29 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities; 30 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; 31 32 import org.eclipse.core.resources.IMarker; 33 import org.eclipse.core.runtime.CoreException; 34 import org.eclipse.jface.text.IDocument; 35 import org.eclipse.jface.text.contentassist.ICompletionProposal; 36 import org.eclipse.jface.text.contentassist.IContextInformation; 37 import org.eclipse.swt.graphics.Image; 38 import org.eclipse.swt.graphics.Point; 39 import org.eclipse.swt.widgets.Display; 40 import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion; 41 import org.w3c.dom.Attr; 42 import org.w3c.dom.Document; 43 import org.w3c.dom.Element; 44 import org.w3c.dom.Node; 45 46 /** 47 * Fix for adding {@code tools:ignore="id"} attributes in XML files. 48 */ 49 @SuppressWarnings("restriction") // DOM model 50 class AddSuppressAttribute implements ICompletionProposal { 51 private final AndroidXmlEditor mEditor; 52 private final String mId; 53 private final IMarker mMarker; 54 private final Element mElement; 55 private final String mDescription; 56 AddSuppressAttribute(AndroidXmlEditor editor, String id, IMarker marker, Element element, String description)57 private AddSuppressAttribute(AndroidXmlEditor editor, String id, IMarker marker, 58 Element element, String description) { 59 mEditor = editor; 60 mId = id; 61 mMarker = marker; 62 mElement = element; 63 mDescription = description; 64 } 65 66 @Override getSelection(IDocument document)67 public Point getSelection(IDocument document) { 68 return null; 69 } 70 71 @Override getAdditionalProposalInfo()72 public String getAdditionalProposalInfo() { 73 return null; 74 } 75 76 @Override getDisplayString()77 public String getDisplayString() { 78 return mDescription; 79 } 80 81 @Override getContextInformation()82 public IContextInformation getContextInformation() { 83 return null; 84 } 85 86 @Override getImage()87 public Image getImage() { 88 return IconFactory.getInstance().getIcon("newannotation"); //$NON-NLS-1$ 89 } 90 91 @Override apply(IDocument document)92 public void apply(IDocument document) { 93 mEditor.wrapUndoEditXmlModel("Suppress Lint Warning", new Runnable() { 94 @Override 95 public void run() { 96 String prefix = UiElementNode.lookupNamespacePrefix(mElement, 97 TOOLS_URI, null); 98 if (prefix == null) { 99 // Add in new prefix... 100 prefix = UiElementNode.lookupNamespacePrefix(mElement, 101 TOOLS_URI, TOOLS_PREFIX); 102 // ...and ensure that the header is formatted such that 103 // the XML namespace declaration is placed in the right 104 // position and wrapping is applied etc. 105 mEditor.scheduleNodeReformat(mEditor.getUiRootNode(), 106 true /*attributesOnly*/); 107 } 108 109 String ignore = mElement.getAttributeNS(TOOLS_URI, ATTR_IGNORE); 110 if (ignore.length() > 0) { 111 ignore = ignore + ',' + mId; 112 } else { 113 ignore = mId; 114 } 115 116 // Use the non-namespace form of set attribute since we can't 117 // reference the namespace until the model has been reloaded 118 mElement.setAttribute(prefix + ':' + ATTR_IGNORE, ignore); 119 120 UiElementNode rootUiNode = mEditor.getUiRootNode(); 121 if (rootUiNode != null) { 122 final UiElementNode uiNode = rootUiNode.findXmlNode(mElement); 123 if (uiNode != null) { 124 mEditor.scheduleNodeReformat(uiNode, true /*attributesOnly*/); 125 126 // Update editor selection after format 127 Display display = AdtPlugin.getDisplay(); 128 if (display != null) { 129 display.asyncExec(new Runnable() { 130 @Override 131 public void run() { 132 Node xmlNode = uiNode.getXmlNode(); 133 Attr attribute = ((Element) xmlNode).getAttributeNodeNS( 134 TOOLS_URI, ATTR_IGNORE); 135 if (attribute instanceof IndexedRegion) { 136 IndexedRegion region = (IndexedRegion) attribute; 137 mEditor.getStructuredTextEditor().selectAndReveal( 138 region.getStartOffset(), region.getLength()); 139 } 140 } 141 }); 142 } 143 } 144 } 145 } 146 }); 147 148 try { 149 // Remove the marker now that the suppress attribute has been added 150 // (so the user doesn't have to re-run lint just to see it disappear) 151 mMarker.delete(); 152 } catch (CoreException e) { 153 AdtPlugin.log(e, "Could not add suppress annotation"); 154 } 155 } 156 157 /** 158 * Adds any applicable suppress lint fix resolutions into the given list 159 * 160 * @param editor the associated editor containing the marker 161 * @param marker the marker to create fixes for 162 * @param id the issue id 163 * @return a fix for this marker, or null if unable 164 */ 165 @Nullable createFix( @onNull AndroidXmlEditor editor, @NonNull IMarker marker, @NonNull String id)166 public static AddSuppressAttribute createFix( 167 @NonNull AndroidXmlEditor editor, 168 @NonNull IMarker marker, 169 @NonNull String id) { 170 // This only applies to XML files: 171 String fileName = marker.getResource().getName(); 172 if (!fileName.endsWith(DOT_XML)) { 173 return null; 174 } 175 176 int offset = marker.getAttribute(IMarker.CHAR_START, -1); 177 Node node; 178 if (offset == -1) { 179 node = DomUtilities.getNode(editor.getStructuredDocument(), 0); 180 if (node != null) { 181 node = node.getOwnerDocument().getDocumentElement(); 182 } 183 } else { 184 node = DomUtilities.getNode(editor.getStructuredDocument(), offset); 185 } 186 if (node == null) { 187 return null; 188 } 189 Document document = node.getOwnerDocument(); 190 while (node != null && node.getNodeType() != Node.ELEMENT_NODE) { 191 node = node.getParentNode(); 192 } 193 if (node == null) { 194 node = document.getDocumentElement(); 195 if (node == null) { 196 return null; 197 } 198 } 199 200 String desc = String.format("Add ignore '%1$s\' to element", id); 201 Element element = (Element) node; 202 return new AddSuppressAttribute(editor, id, marker, element, desc); 203 } 204 } 205