Home | History | Annotate | Download | only in lint
      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