blob: 88e0880e5b107e465dc551bc22a1665d1e270e6c [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.internal.lint;
import static com.android.SdkConstants.ATTR_IGNORE;
import static com.android.SdkConstants.ATTR_TARGET_API;
import static com.android.SdkConstants.DOT_XML;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.sdklib.SdkVersionInfo;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
import com.android.tools.lint.checks.ApiDetector;
import com.google.common.collect.Lists;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Fix for adding {@code tools:ignore="id"} attributes in XML files.
*/
class AddSuppressAttribute implements ICompletionProposal {
private final AndroidXmlEditor mEditor;
private final String mId;
private final IMarker mMarker;
private final Element mElement;
private final String mDescription;
/**
* Should it create a {@code tools:targetApi} attribute instead of a
* {@code tools:ignore} attribute? If so pass a non null API level
*/
private final String mTargetApi;
private AddSuppressAttribute(
@NonNull AndroidXmlEditor editor,
@NonNull String id,
@NonNull IMarker marker,
@NonNull Element element,
@NonNull String description,
@Nullable String targetApi) {
mEditor = editor;
mId = id;
mMarker = marker;
mElement = element;
mDescription = description;
mTargetApi = targetApi;
}
@Override
public Point getSelection(IDocument document) {
return null;
}
@Override
public String getAdditionalProposalInfo() {
return null;
}
@Override
public String getDisplayString() {
return mDescription;
}
@Override
public IContextInformation getContextInformation() {
return null;
}
@Override
public Image getImage() {
return IconFactory.getInstance().getIcon("newannotation"); //$NON-NLS-1$
}
@Override
public void apply(IDocument document) {
String attribute;
String value;
if (mTargetApi != null) {
attribute = ATTR_TARGET_API;
value = mTargetApi;
} else {
attribute = ATTR_IGNORE;
value = mId;
}
AdtUtils.setToolsAttribute(mEditor, mElement, mDescription, attribute, value,
true /*reveal*/, true /*append*/);
try {
// Remove the marker now that the suppress attribute has been added
// (so the user doesn't have to re-run lint just to see it disappear)
mMarker.delete();
} catch (CoreException e) {
AdtPlugin.log(e, "Could not remove marker");
}
}
/**
* Returns a quickfix to suppress a specific lint issue id on the node corresponding to
* the given marker.
*
* @param editor the associated editor containing the marker
* @param marker the marker to create fixes for
* @param id the issue id
* @return a list of fixes for this marker, possibly empty
*/
@NonNull
public static List<AddSuppressAttribute> createFixes(
@NonNull AndroidXmlEditor editor,
@NonNull IMarker marker,
@NonNull String id) {
// This only applies to XML files:
String fileName = marker.getResource().getName();
if (!fileName.endsWith(DOT_XML)) {
return Collections.emptyList();
}
int offset = marker.getAttribute(IMarker.CHAR_START, -1);
Node node;
if (offset == -1) {
node = DomUtilities.getNode(editor.getStructuredDocument(), 0);
if (node != null) {
node = node.getOwnerDocument().getDocumentElement();
}
} else {
node = DomUtilities.getNode(editor.getStructuredDocument(), offset);
}
if (node == null) {
return Collections.emptyList();
}
Document document = node.getOwnerDocument();
while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
node = node.getParentNode();
}
if (node == null) {
node = document.getDocumentElement();
if (node == null) {
return Collections.emptyList();
}
}
String desc = String.format("Add ignore '%1$s\' to element", id);
Element element = (Element) node;
List<AddSuppressAttribute> fixes = Lists.newArrayListWithExpectedSize(2);
fixes.add(new AddSuppressAttribute(editor, id, marker, element, desc, null));
int api = -1;
if (id.equals(ApiDetector.UNSUPPORTED.getId())
|| id.equals(ApiDetector.INLINED.getId())) {
String message = marker.getAttribute(IMarker.MESSAGE, null);
if (message != null) {
Pattern pattern = Pattern.compile("\\s(\\d+)\\s"); //$NON-NLS-1$
Matcher matcher = pattern.matcher(message);
if (matcher.find()) {
api = Integer.parseInt(matcher.group(1));
String targetApi;
String buildCode = SdkVersionInfo.getBuildCode(api);
if (buildCode != null) {
targetApi = buildCode.toLowerCase(Locale.US);
fixes.add(new AddSuppressAttribute(editor, id, marker, element,
String.format("Add targetApi '%1$s\' to element", targetApi),
targetApi));
}
targetApi = Integer.toString(api);
fixes.add(new AddSuppressAttribute(editor, id, marker, element,
String.format("Add targetApi '%1$s\' to element", targetApi),
targetApi));
}
}
}
return fixes;
}
/**
* Returns a quickfix to suppress a given issue type on the <b>root element</b>
* of the given editor.
*
* @param editor the associated editor containing the marker
* @param marker the marker to create fixes for
* @param id the issue id
* @return a fix for this marker, or null if unable
*/
@Nullable
public static AddSuppressAttribute createFixForAll(
@NonNull AndroidXmlEditor editor,
@NonNull IMarker marker,
@NonNull String id) {
// This only applies to XML files:
String fileName = marker.getResource().getName();
if (!fileName.endsWith(DOT_XML)) {
return null;
}
Node node = DomUtilities.getNode(editor.getStructuredDocument(), 0);
if (node != null) {
node = node.getOwnerDocument().getDocumentElement();
String desc = String.format("Add ignore '%1$s\' to element", id);
Element element = (Element) node;
return new AddSuppressAttribute(editor, id, marker, element, desc, null);
}
return null;
}
}