• 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.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