diff options
47 files changed, 608 insertions, 80 deletions
diff --git a/common/src/com/android/util/PositionXmlParser.java b/common/src/com/android/util/PositionXmlParser.java index 54146db..052348d 100644 --- a/common/src/com/android/util/PositionXmlParser.java +++ b/common/src/com/android/util/PositionXmlParser.java @@ -145,7 +145,7 @@ public class PositionXmlParser { // (see http://en.wikipedia.org/wiki/Byte_order_mark) so here we'll // just skip those up to the XML prolog beginning character, < xml = xml.replaceFirst("^([\\W]+)<","<"); //$NON-NLS-1$ //$NON-NLS-2$ - return parse(xml, null, false); + return parse(xml, new InputSource(new StringReader(xml)), false); } throw e; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java index d60d286..17a28c8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java @@ -23,7 +23,10 @@ import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH; import static com.android.ide.common.layout.LayoutConstants.ATTR_NAME; import static com.android.ide.common.layout.LayoutConstants.ATTR_STYLE; import static com.android.ide.eclipse.adt.internal.editors.color.ColorDescriptors.ATTR_COLOR; +import static com.google.common.base.Strings.nullToEmpty; +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor; @@ -196,16 +199,52 @@ public abstract class UiAttributeNode implements Comparable<UiAttributeNode> { * @return a negative number if name1 should be ordered before name2 */ public static int compareAttributes(String name1, String name2) { - int priority1 = getAttributePriority(name1); - int priority2 = getAttributePriority(name2); - if (priority1 != priority2) { - return priority1 - priority2; - } - - // Sort remaining attributes alphabetically - return name1.compareTo(name2); + int priority1 = getAttributePriority(name1); + int priority2 = getAttributePriority(name2); + if (priority1 != priority2) { + return priority1 - priority2; + } + + // Sort remaining attributes alphabetically + return name1.compareTo(name2); } + /** + * Returns {@link Comparator} values for ordering attributes in the following + * order: + * <ul> + * <li> id + * <li> style + * <li> layout_width + * <li> layout_height + * <li> other layout params, sorted alphabetically + * <li> other attributes, sorted alphabetically, first by namespace, then by name + * </ul> + * @param prefix1 the namespace prefix, if any, of {@code name1} + * @param name1 the first attribute name to compare + * @param prefix2 the namespace prefix, if any, of {@code name2} + * @param name2 the second attribute name to compare + * @return a negative number if name1 should be ordered before name2 + */ + public static int compareAttributes( + @Nullable String prefix1, @NonNull String name1, + @Nullable String prefix2, @NonNull String name2) { + int priority1 = getAttributePriority(name1); + int priority2 = getAttributePriority(name2); + if (priority1 != priority2) { + return priority1 - priority2; + } + + int namespaceDelta = nullToEmpty(prefix1).compareTo(nullToEmpty(prefix2)); + if (namespaceDelta != 0) { + return namespaceDelta; + } + + // Sort remaining attributes alphabetically + return name1.compareTo(name2); + } + + /** Returns a sorting priority for the given attribute name */ private static int getAttributePriority(String name) { if (ATTR_ID.equals(name)) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java index 40ebce5..a25d07b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java @@ -23,7 +23,9 @@ import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX; import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS; import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor.XMLNS_URI; import static com.android.sdklib.SdkConstants.NS_RESOURCES; +import static com.android.tools.lint.detector.api.LintConstants.XMLNS_PREFIX; +import com.android.annotations.Nullable; import com.android.annotations.VisibleForTesting; import com.android.ide.common.api.IAttributeInfo.Format; import com.android.ide.common.resources.platform.AttributeInfo; @@ -1638,12 +1640,19 @@ public class UiElementNode implements IPropertySource { return result; } - String firstName = dirtyAttributes.get(0).getDescriptor().getXmlLocalName(); + AttributeDescriptor descriptor = dirtyAttributes.get(0).getDescriptor(); + String firstName = descriptor.getXmlLocalName(); + String firstNamePrefix = null; + if (descriptor.getNamespaceUri() != null) { + firstNamePrefix = lookupNamespacePrefix(element, descriptor.getNamespaceUri()); + } NamedNodeMap attributes = ((Element) element).getAttributes(); List<Attr> move = new ArrayList<Attr>(); for (int i = 0, n = attributes.getLength(); i < n; i++) { Attr attribute = (Attr) attributes.item(i); - if (UiAttributeNode.compareAttributes(attribute.getLocalName(), firstName) > 0) { + if (UiAttributeNode.compareAttributes( + attribute.getPrefix(), attribute.getLocalName(), + firstNamePrefix, firstName) > 0) { move.add(attribute); } } @@ -1724,6 +1733,25 @@ public class UiElementNode implements IPropertySource { * @return The first prefix declared or the default "android" prefix. */ public static String lookupNamespacePrefix(Node node, String nsUri) { + String defaultPrefix = NS_RESOURCES.equals(nsUri) ? ANDROID_NS_NAME : "ns"; //$NON-NLS-1$ + return lookupNamespacePrefix(node, nsUri, defaultPrefix); + } + + /** + * Returns the namespace prefix matching the requested namespace URI. + * If no such declaration is found, returns the default "android" prefix. + * + * @param node The current node. Must not be null. + * @param nsUri The namespace URI of which the prefix is to be found, + * e.g. SdkConstants.NS_RESOURCES + * @param defaultPrefix The default prefix (root) to use if the namespace + * is not found. If null, do not create a new namespace + * if this URI is not defined for the document. + * @return The first prefix declared or the provided prefix (possibly with + * a number appended to avoid conflicts with existing prefixes. + */ + public static String lookupNamespacePrefix( + @Nullable Node node, @Nullable String nsUri, @Nullable String defaultPrefix) { // Note: Node.lookupPrefix is not implemented in wst/xml/core NodeImpl.java // The following code emulates this simple call: // String prefix = node.lookupPrefix(SdkConstants.NS_RESOURCES); @@ -1771,12 +1799,17 @@ public class UiElementNode implements IPropertySource { } } - // Failed the find a prefix. Generate a new sensible default prefix. + // Failed the find a prefix. Generate a new sensible default prefix, unless + // defaultPrefix was null in which case the caller does not want the document + // modified. + if (defaultPrefix == null) { + return null; + } + // // We need to make sure the prefix is not one that was declared in the scope - // visited above. Use a default namespace prefix "android" for the Android resource - // NS and use "ns" for all other custom namespaces. - String prefix = NS_RESOURCES.equals(nsUri) ? ANDROID_NS_NAME : "ns"; //$NON-NLS-1$ + // visited above. Pick a unique prefix from the provided default prefix. + String prefix = defaultPrefix; String base = prefix; for (int i = 1; visited.contains(prefix); i++) { prefix = base + Integer.toString(i); @@ -1789,9 +1822,18 @@ public class UiElementNode implements IPropertySource { node = node.getNextSibling(); } if (node != null) { - Attr attr = doc.createAttributeNS(XMLNS_URI, prefix); + // This doesn't work: + //Attr attr = doc.createAttributeNS(XMLNS_URI, prefix); + //attr.setPrefix(XMLNS); + // + // Xerces throws + //org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or + // change an object in a way which is incorrect with regard to namespaces. + // + // Instead pass in the concatenated prefix. (This is covered by + // the UiElementNodeTest#testCreateNameSpace() test.) + Attr attr = doc.createAttributeNS(XMLNS_URI, XMLNS_PREFIX + prefix); attr.setValue(nsUri); - attr.setPrefix(XMLNS); node.getAttributes().setNamedItemNS(attr); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressLintFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAnnotation.java index f23c9e5..3da617b 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressLintFix.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAnnotation.java @@ -71,7 +71,7 @@ import java.util.regex.Pattern; * Marker resolution for adding {@code @SuppressLint} annotations in Java files. * It can also add {@code @TargetApi} annotations. */ -class AddSuppressLintFix implements IMarkerResolution2 { +class AddSuppressAnnotation implements IMarkerResolution2 { private final IMarker mMarker; private final String mId; private final BodyDeclaration mNode; @@ -80,7 +80,7 @@ class AddSuppressLintFix implements IMarkerResolution2 { * If so pass a positive API number */ private final int mTargetApi; - private AddSuppressLintFix(String id, IMarker marker, BodyDeclaration node, + private AddSuppressAnnotation(String id, IMarker marker, BodyDeclaration node, String description, int targetApi) { mId = id; mMarker = marker; @@ -335,14 +335,14 @@ class AddSuppressLintFix implements IMarkerResolution2 { } String desc = String.format("Add @SuppressLint '%1$s\' to '%2$s'", id, target); - resolutions.add(new AddSuppressLintFix(id, marker, declaration, desc, -1)); + resolutions.add(new AddSuppressAnnotation(id, marker, declaration, desc, -1)); if (api != -1 // @TargetApi is only valid on methods and classes, not fields etc && (body instanceof MethodDeclaration || body instanceof TypeDeclaration)) { desc = String.format("Add @TargetApi(%1$d) to '%2$s'", api, target); - resolutions.add(new AddSuppressLintFix(id, marker, declaration, desc, api)); + resolutions.add(new AddSuppressAnnotation(id, marker, declaration, desc, api)); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java new file mode 100644 index 0000000..e3669f0 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java @@ -0,0 +1,203 @@ +/* + * 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.tools.lint.detector.api.LintConstants.ATTR_IGNORE; +import static com.android.tools.lint.detector.api.LintConstants.DOT_XML; +import static com.android.tools.lint.detector.api.LintConstants.TOOLS_PREFIX; +import static com.android.tools.lint.detector.api.LintConstants.TOOLS_URI; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.eclipse.adt.AdtPlugin; +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.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; +import com.android.tools.lint.checks.ArraySizeDetector; +import com.android.tools.lint.checks.DuplicateIdDetector; +import com.android.tools.lint.checks.MergeRootFrameLayoutDetector; +import com.android.tools.lint.checks.ObsoleteLayoutParamsDetector; +import com.android.tools.lint.checks.OverdrawDetector; +import com.android.tools.lint.checks.StringFormatDetector; +import com.android.tools.lint.checks.TranslationDetector; +import com.android.tools.lint.checks.WrongIdDetector; + +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; + +/** + * 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; + + private AddSuppressAttribute(AndroidXmlEditor editor, String id, IMarker marker, + Element element, String description) { + mEditor = editor; + mId = id; + mMarker = marker; + mElement = element; + mDescription = description; + } + + @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) { + mEditor.wrapUndoEditXmlModel("Suppress Lint Warning", new Runnable() { + @Override + public void run() { + String prefix = UiElementNode.lookupNamespacePrefix(mElement, + TOOLS_URI, null); + if (prefix == null) { + // Add in new prefix... + prefix = UiElementNode.lookupNamespacePrefix(mElement, + TOOLS_URI, TOOLS_PREFIX); + // ...and ensure that the header is formatted such that + // the XML namespace declaration is placed in the right + // position and wrapping is applied etc. + mEditor.scheduleNodeReformat(mEditor.getUiRootNode(), + true /*attributesOnly*/); + } + + String ignore = mElement.getAttributeNS(TOOLS_URI, ATTR_IGNORE); + if (ignore.length() > 0) { + ignore = ignore + ',' + mId; + } else { + ignore = mId; + } + + // Use the non-namespace form of set attribute since we can't + // reference the namespace until the model has been reloaded + mElement.setAttribute(prefix + ':' + ATTR_IGNORE, mId); + + UiElementNode rootUiNode = mEditor.getUiRootNode(); + if (rootUiNode != null) { + UiElementNode uiNode = rootUiNode.findXmlNode(mElement); + if (uiNode != null) { + mEditor.scheduleNodeReformat(uiNode, true /*attributesOnly*/); + } + } + } + }); + + 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 add suppress annotation"); + } + } + + /** + * Adds any applicable suppress lint fix resolutions into the given list + * + * @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 createFix( + @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; + } + + int offset = marker.getAttribute(IMarker.CHAR_START, -1); + Node node = DomUtilities.getNode(editor.getStructuredDocument(), offset); + if (node == null) { + return null; + } + Document document = node.getOwnerDocument(); + while (node != null && node.getNodeType() != Node.ELEMENT_NODE) { + node = node.getParentNode(); + } + + // Some issues cannot find a specific node scope associated with the error + // (for example because it involves cross-file analysis and at the end of + // the project scan when the warnings are computed the DOM model is no longer + // available). Until that's resolved, we need to filter these out such that + // we don't add misleading annotations on individual elements; the fallback + // path is the DOM document itself instead. + if (id.equals(ArraySizeDetector.INCONSISTENT.getId()) + || id.equals(DuplicateIdDetector.CROSS_LAYOUT.getId()) + || id.equals(MergeRootFrameLayoutDetector.ISSUE.getId()) + || id.equals(ObsoleteLayoutParamsDetector.ISSUE.getId()) + || id.equals(OverdrawDetector.ISSUE.getId()) + || id.equals(StringFormatDetector.ARG_TYPES.getId()) + || id.equals(StringFormatDetector.ARG_COUNT.getId()) + || id.equals(TranslationDetector.MISSING.getId()) + || id.equals(TranslationDetector.EXTRA.getId()) + || id.equals(WrongIdDetector.UNKNOWN_ID.getId()) + || id.equals(WrongIdDetector.UNKNOWN_ID_LAYOUT.getId())) { + node = document.getDocumentElement(); + } + + if (node == null) { + node = document.getDocumentElement(); + if (node == null) { + return null; + } + } + + String desc = String.format("Add ignore '%1$s\' to element", id); + Element element = (Element) node; + return new AddSuppressAttribute(editor, id, marker, element, desc); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java index 85a0a48..ef2a265 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java @@ -116,7 +116,7 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi List<IMarkerResolution> resolutions = new ArrayList<IMarkerResolution>(); if (resource.getName().endsWith(DOT_JAVA)) { - AddSuppressLintFix.createFixes(marker, id, resolutions); + AddSuppressAnnotation.createFixes(marker, id, resolutions); } resolutions.add(new MoreInfoProposal(id, marker.getAttribute(IMarker.MESSAGE, null))); @@ -164,6 +164,7 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi for (IMarker marker : markers) { String id = marker.getAttribute(EclipseLintRunner.MARKER_CHECKID_PROPERTY, ""); //$NON-NLS-1$ + // TODO: Allow for more than one fix? ICompletionProposal fix = LintFix.getFix(id, marker); if (fix != null) { @@ -173,6 +174,11 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi String message = marker.getAttribute(IMarker.MESSAGE, null); proposals.add(new MoreInfoProposal(id, message)); + fix = AddSuppressAttribute.createFix(editor, marker, id); + if (fix != null) { + proposals.add(fix); + } + proposals.add(new SuppressProposal(file, id, false)); proposals.add(new SuppressProposal(file.getProject(), id, true /* all */)); proposals.add(new SuppressProposal(file, id, true /* all */)); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AttributeSortOrder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AttributeSortOrder.java index 8ec9adc..657b83a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AttributeSortOrder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AttributeSortOrder.java @@ -37,6 +37,9 @@ public enum AttributeSortOrder { public final String key; + /** + * @return a comparator for use by this attribute sort order + */ public Comparator<Attr> getAttributeComparator() { switch (this) { case ALPHABETICAL: @@ -64,8 +67,9 @@ public enum AttributeSortOrder { } // Sort by preferred attribute order - return UiAttributeNode.compareAttributes(attr1.getLocalName(), - attr2.getLocalName()); + return UiAttributeNode.compareAttributes( + attr1.getPrefix(), attr1.getLocalName(), + attr2.getPrefix(), attr2.getLocalName()); } }; @@ -100,7 +104,9 @@ public enum AttributeSortOrder { return 1; } - return attr1.getLocalName().compareTo(attr2.getLocalName()); + // Sort by name rather than localname to ensure we sort by namespaces first, + // then by names. + return attr1.getName().compareTo(attr2.getName()); } }; }
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiElementNodeTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiElementNodeTest.java index a5149ae..43cba58 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiElementNodeTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiElementNodeTest.java @@ -17,10 +17,11 @@ package com.android.ide.eclipse.adt.internal.editors.manifest.model; import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.tools.lint.detector.api.LintConstants.TOOLS_URI; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor.Mandatory; +import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor; import com.android.ide.eclipse.adt.internal.editors.mock.MockXmlNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; @@ -36,6 +37,7 @@ import javax.xml.parsers.DocumentBuilderFactory; import junit.framework.TestCase; +@SuppressWarnings("javadoc") public class UiElementNodeTest extends TestCase { private UiElementNode ui; @@ -260,7 +262,6 @@ public class UiElementNodeTest extends TestCase { assertEquals(0, second_permission.getAllUiAttributes().size()); } - public void testlookupNamespacePrefix() throws Exception { // Setup DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); @@ -286,5 +287,31 @@ public class UiElementNodeTest extends TestCase { String prefix = UiElementNode.lookupNamespacePrefix(baz, ANDROID_URI); assertEquals("customPrefix", prefix); + + prefix = UiElementNode.lookupNamespacePrefix(baz, TOOLS_URI, "tools"); + assertEquals("tools", prefix); + } + + public void testCreateNameSpace() throws Exception { + // Setup + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setValidating(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.newDocument(); + Element rootElement = document.createElement("root"); + document.appendChild(rootElement); + Element root = document.getDocumentElement(); + root.appendChild(document.createTextNode(" ")); + Element foo = document.createElement("foo"); + root.appendChild(foo); + root.appendChild(document.createTextNode(" ")); + Element bar = document.createElement("bar"); + root.appendChild(bar); + Element baz = document.createElement("baz"); + root.appendChild(baz); + + String prefix = UiElementNode.lookupNamespacePrefix(baz, ANDROID_URI); + assertEquals("android", prefix); } } diff --git a/lint/cli/src/com/android/tools/lint/Main.java b/lint/cli/src/com/android/tools/lint/Main.java index ae1aa36..d319648 100644 --- a/lint/cli/src/com/android/tools/lint/Main.java +++ b/lint/cli/src/com/android/tools/lint/Main.java @@ -501,10 +501,11 @@ public class Main extends LintClient { "Lint errors can be suppressed in a variety of ways:\n" + "\n" + "1. With a @SuppressLint annotation in the Java code\n" + - "2. With a lint.xml configuration file in the project\n" + - "3. With a lint.xml configuration file passed to lint " + + "2. With a tools:ignore attribute in the XML file\n" + + "3. With a lint.xml configuration file in the project\n" + + "4. With a lint.xml configuration file passed to lint " + "via the " + ARG_CONFIG + " flag\n" + - "4. With the " + ARG_IGNORE + " flag passed to lint.\n" + + "5. With the " + ARG_IGNORE + " flag passed to lint.\n" + "\n" + "To suppress a lint warning with an annotation, add " + "a @SuppressLint(\"id\") annotation on the class, method " + @@ -514,6 +515,14 @@ public class Main extends LintClient { "\"UnusedIds\"}, or it can be \"all\" to suppress all lint " + "warnings in the given scope.\n" + "\n" + + "To suppress a lint warning in an XML file, add a " + + "tools:ignore=\"id\" attribute on the element containing " + + "the error, or one of its surrounding elements. You also " + + "need to define the namespace for the tools prefix on the " + + "root element in your document, next to the xmlns:android " + + "declaration:\n" + + "* xmlns:tools=\"http://schemas.android.com/tools\"\n" + + "\n" + "To suppress lint warnings with a configuration XML file, " + "create a file named lint.xml and place it at the root " + "directory of the project in which it applies. (If you " + diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java index 0a8f532..c02d735 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java @@ -17,6 +17,7 @@ package com.android.tools.lint.client.api; import static com.android.tools.lint.detector.api.LintConstants.ANDROID_MANIFEST_XML; +import static com.android.tools.lint.detector.api.LintConstants.ATTR_IGNORE; import static com.android.tools.lint.detector.api.LintConstants.DOT_CLASS; import static com.android.tools.lint.detector.api.LintConstants.DOT_JAR; import static com.android.tools.lint.detector.api.LintConstants.DOT_JAVA; @@ -24,6 +25,7 @@ import static com.android.tools.lint.detector.api.LintConstants.PROGUARD_CFG; import static com.android.tools.lint.detector.api.LintConstants.RES_FOLDER; import static com.android.tools.lint.detector.api.LintConstants.SUPPRESS_ALL; import static com.android.tools.lint.detector.api.LintConstants.SUPPRESS_LINT; +import static com.android.tools.lint.detector.api.LintConstants.TOOLS_URI; import com.android.annotations.NonNull; import com.android.annotations.Nullable; @@ -53,6 +55,8 @@ import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.MethodNode; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; import java.io.File; import java.io.FileInputStream; @@ -1359,7 +1363,7 @@ public class LintDriver { * @return true if there is a suppress annotation covering the specific * issue in this class */ - public boolean isSuppressed(Issue issue, Node scope) { + public boolean isSuppressed(@NonNull Issue issue, @Nullable Node scope) { while (scope != null) { Class<? extends Node> type = scope.getClass(); // The Lombok AST uses a flat hierarchy of node type implementation classes @@ -1459,4 +1463,43 @@ public class LintDriver { return false; } + + /** + * Returns whether the given issue is suppressed in the given XML DOM node. + * + * @param issue the issue to be checked, or null to just check for "all" + * @param node the DOM node containing the issue + * @return true if there is a suppress annotation covering the specific + * issue in this class + */ + public boolean isSuppressed(@NonNull Issue issue, @Nullable org.w3c.dom.Node node) { + if (node instanceof Attr) { + node = ((Attr) node).getOwnerElement(); + } + while (node != null) { + if (node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) { + Element element = (Element) node; + if (element.hasAttributeNS(TOOLS_URI, ATTR_IGNORE)) { + String ignore = element.getAttributeNS(TOOLS_URI, ATTR_IGNORE); + if (ignore.indexOf(',') == -1) { + if (ignore.equalsIgnoreCase(SUPPRESS_ALL) || (issue != null + && issue.getId().equalsIgnoreCase(ignore))) { + return true; + } + } else { + for (String id : ignore.split(",")) { //$NON-NLS-1$ + if (id.equalsIgnoreCase(SUPPRESS_ALL) || (issue != null + && issue.getId().equalsIgnoreCase(id))) { + return true; + } + } + } + } + } + + node = node.getParentNode(); + } + + return false; + } } diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java index db0fb9f..e1e3c54 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java @@ -30,6 +30,11 @@ public class LintConstants { /** Namespace used in XML files for Android attributes */ public static final String ANDROID_URI = "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$ + /** Namespace used in XML files for Android Tooling attributes */ + public static final String TOOLS_URI = + "http://schemas.android.com/tools"; //$NON-NLS-1$ + /** Default prefix used for tools attributes */ + public static final String TOOLS_PREFIX = "tools"; //$NON-NLS-1$ public static final String XMLNS_PREFIX = "xmlns:"; //$NON-NLS-1$ // Tags: Manifest @@ -261,6 +266,9 @@ public class LintConstants { public static final String R_DRAWABLE_PREFIX = "R.drawable."; //$NON-NLS-1$ public static final String R_ATTR_PREFIX = "R.attr."; //$NON-NLS-1$ + // Attributes related to tools + public final static String ATTR_IGNORE = "ignore"; //$NON-NLS-1$ + // SuppressLint public static final String SUPPRESS_ALL = "all"; //$NON-NLS-1$ public static final String SUPPRESS_LINT = "SuppressLint"; //$NON-NLS-1$ diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java index 8936abc..d6a24ab 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/XmlContext.java @@ -73,4 +73,48 @@ public class XmlContext extends Context { return Location.create(file); } + + /** + * Reports an issue applicable to a given DOM node. The DOM node is used as the + * scope to check for suppress lint annotations. + * + * @param issue the issue to report + * @param scope the DOM node scope the error applies to. The lint infrastructure + * will check whether there are suppress directives on this node (or its enclosing + * nodes) and if so suppress the warning without involving the client. + * @param location the location of the issue, or null if not known + * @param message the message for this warning + * @param data any associated data, or null + */ + public void report( + @NonNull Issue issue, + @Nullable Node scope, + @Nullable Location location, + @NonNull String message, + @Nullable Object data) { + if (scope != null && mDriver.isSuppressed(issue, scope)) { + return; + } + mDriver.getClient().report(this, issue, location, message, data); + } + + @Override + public void report( + @NonNull Issue issue, + @Nullable Location location, + @NonNull String message, + @Nullable Object data) { + // Warn if clients use the non-scoped form? No, there are cases where an + // XML detector's error isn't applicable to one particular location (or it's + // not feasible to compute it cheaply) + //mDriver.getClient().log(null, "Warning: Issue " + issue + // + " was reported without a scope node: Can't be suppressed."); + + // For now just check the document root itself + if (document != null && mDriver.isSuppressed(issue, document)) { + return; + } + + super.report(issue, location, message, data); + } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/AccessibilityDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/AccessibilityDetector.java index f5ef4a5..abc08b2 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/AccessibilityDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/AccessibilityDetector.java @@ -77,13 +77,13 @@ public class AccessibilityDetector extends LayoutDetector { @Override public void visitElement(XmlContext context, Element element) { if (!element.hasAttributeNS(ANDROID_URI, ATTR_CONTENT_DESCRIPTION)) { - context.report(ISSUE, context.getLocation(element), + context.report(ISSUE, element, context.getLocation(element), "[Accessibility] Missing contentDescription attribute on image", null); } else { Attr attributeNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_CONTENT_DESCRIPTION); String attribute = attributeNode.getValue(); if (attribute.length() == 0 || attribute.equals("TODO")) { //$NON-NLS-1$ - context.report(ISSUE, context.getLocation(attributeNode), + context.report(ISSUE, attributeNode, context.getLocation(attributeNode), "[Accessibility] Empty contentDescription attribute on image", null); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java index e6fd2eb..7053038 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ApiDetector.java @@ -137,7 +137,7 @@ public class ApiDetector extends LayoutDetector implements Detector.ClassScanner String message = String.format( "View requires API level %1$d (current min is %2$d): <%3$s>", api, minSdk, tag); - context.report(UNSUPPORTED, location, message, null); + context.report(UNSUPPORTED, element, location, message, null); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java index b11fdc3..cf5cd4b 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ArraySizeDetector.java @@ -140,6 +140,7 @@ public class ArraySizeDetector extends ResourceXmlDetector { String otherName = otherFile.getParentFile().getName() + File.separator + otherFile.getName(); + // TODO: Find applicable scope? context.report(INCONSISTENT, location, String.format( "Array %1$s has an inconsistent number of items (%2$d in %3$s, %4$d in %5$s)", @@ -155,7 +156,7 @@ public class ArraySizeDetector extends ResourceXmlDetector { public void visitElement(XmlContext context, Element element) { Attr attribute = element.getAttributeNode(ATTR_NAME); if (attribute == null || attribute.getValue().length() == 0) { - context.report(INCONSISTENT, context.getLocation(element), + context.report(INCONSISTENT, element, context.getLocation(element), String.format("Missing name attribute in %1$s declaration", element.getTagName()), null); } else { diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ChildCountDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ChildCountDetector.java index 2bd6a77..5a14f75 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ChildCountDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ChildCountDetector.java @@ -95,14 +95,14 @@ public class ChildCountDetector extends LayoutDetector { String tagName = element.getTagName(); if (tagName.equals(SCROLL_VIEW) || tagName.equals(HORIZONTAL_SCROLL_VIEW)) { if (childCount > 1 && getAccurateChildCount(element) > 1) { - context.report(SCROLLVIEW_ISSUE, + context.report(SCROLLVIEW_ISSUE, element, context.getLocation(element), "A scroll view can have only one child", null); } } else { // Adapter view if (childCount > 0 && getAccurateChildCount(element) > 0) { - context.report(ADAPTERVIEW_ISSUE, + context.report(ADAPTERVIEW_ISSUE, element, context.getLocation(element), "A list/grid should have no children declared in XML", null); } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DeprecationDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DeprecationDetector.java index c6cf4e8..883afc7 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DeprecationDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DeprecationDetector.java @@ -65,7 +65,7 @@ public class DeprecationDetector extends LayoutDetector { @Override public void visitElement(XmlContext context, Element element) { - context.report(ISSUE, context.getLocation(element), + context.report(ISSUE, element, context.getLocation(element), String.format("%1$s is deprecated", element.getTagName()), null); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java index 5af5606..d0cf2d4 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java @@ -99,7 +99,7 @@ public class DetectMissingPrefix extends LayoutDetector { return; } - context.report(MISSING_NAMESPACE, + context.report(MISSING_NAMESPACE, attribute, context.getLocation(attribute), "Attribute is missing the Android namespace prefix", null); diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DuplicateIdDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DuplicateIdDetector.java index 18e8b79..9c8ddea 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/DuplicateIdDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/DuplicateIdDetector.java @@ -347,6 +347,7 @@ public class DuplicateIdDetector extends LayoutDetector { secondLocation.setMessage(String.format("%1$s originally defined here", id)); location.setSecondary(secondLocation); } + // TODO: Get applicable scope? context.report(CROSS_LAYOUT, location, msg, null); } @@ -404,7 +405,7 @@ public class DuplicateIdDetector extends LayoutDetector { location.setSecondary(secondLocation); } - context.report(WITHIN_LAYOUT, location, + context.report(WITHIN_LAYOUT, attribute, location, String.format("Duplicate id %1$s, already defined earlier in this layout", id), null); } else if (id.startsWith(NEW_ID_RESOURCE_PREFIX)) { diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ExtraTextDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ExtraTextDetector.java index a14c7d0..e0665df 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ExtraTextDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ExtraTextDetector.java @@ -122,7 +122,7 @@ public class ExtraTextDetector extends ResourceXmlDetector { location = Location.create(context.file, start, location.getEnd()); } } - context.report(ISSUE, location, + context.report(ISSUE, node, location, String.format("Unexpected text found in layout file: \"%1$s\"", snippet), null); mFoundText = true; diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/GridLayoutDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/GridLayoutDetector.java index b19d537..a2788ab 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/GridLayoutDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/GridLayoutDetector.java @@ -95,7 +95,7 @@ public class GridLayoutDetector extends LayoutDetector { int column = getInt(child, ATTR_LAYOUT_COLUMN, -1); if (column >= declaredColumnCount) { Attr node = child.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_COLUMN); - context.report(ISSUE, context.getLocation(node), + context.report(ISSUE, node, context.getLocation(node), String.format("Column attribute (%1$d) exceeds declared grid column count (%2$d)", column, declaredColumnCount), null); } @@ -104,7 +104,7 @@ public class GridLayoutDetector extends LayoutDetector { int row = getInt(child, ATTR_LAYOUT_ROW, -1); if (row > declaredRowCount) { Attr node = child.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_ROW); - context.report(ISSUE, context.getLocation(node), + context.report(ISSUE, node, context.getLocation(node), String.format("Row attribute (%1$d) exceeds declared grid row count (%2$d)", row, declaredRowCount), null); } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedDebugModeDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedDebugModeDetector.java index 51a4285..306f546 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedDebugModeDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedDebugModeDetector.java @@ -86,7 +86,7 @@ public class HardcodedDebugModeDetector extends Detector implements Detector.Xml public void visitAttribute(XmlContext context, Attr attribute) { if (attribute.getNamespaceURI().equals(ANDROID_URI)) { //if (attribute.getOwnerElement().getTagName().equals(TAG_APPLICATION)) { - context.report(ISSUE, context.getLocation(attribute), + context.report(ISSUE, attribute, context.getLocation(attribute), "Avoid hardcoding the debug mode; leaving it out allows debug and " + "release builds to automatically assign one", null); } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedValuesDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedValuesDetector.java index 816ecab..09a3ffc 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedValuesDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/HardcodedValuesDetector.java @@ -90,7 +90,7 @@ public class HardcodedValuesDetector extends LayoutDetector { return; } - context.report(ISSUE, context.getLocation(attribute), + context.report(ISSUE, attribute, context.getLocation(attribute), String.format("[I18N] Hardcoded string \"%1$s\", should use @string resource", value), null); } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java index b8f2183..4dd7eb1 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/InefficientWeightDetector.java @@ -134,7 +134,7 @@ public class InefficientWeightDetector extends LayoutDetector { mInsideWeight.put(parent, Boolean.FALSE); } else if (inside) { Attr sizeNode = child.getAttributeNodeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT); - context.report(NESTED_WEIGHTS, + context.report(NESTED_WEIGHTS, sizeNode, context.getLocation(sizeNode), "Nested weights are bad for performance", null); // Don't warn again diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java index f6973e5..281dccb 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java @@ -105,7 +105,7 @@ public class ManifestOrderDetector extends Detector implements Detector.XmlScann if (tag.equals(TAG_APPLICATION)) { mSeenApplication = true; } else if (mSeenApplication) { - context.report(ISSUE, context.getLocation(element), + context.report(ISSUE, element, context.getLocation(element), String.format("<%1$s> tag appears after <application> tag", tag), null); // Don't complain for *every* element following the <application> tag diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java index 2a67a9d..3f2320a 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MergeRootFrameLayoutDetector.java @@ -122,6 +122,7 @@ public class MergeRootFrameLayoutDetector extends LayoutDetector implements Dete if (mWhitelistedLayouts.contains(layout)) { Handle handle = pair.getSecond(); Location location = handle.resolve(); + // TODO: Compute applicable scope? context.report(ISSUE, location, "This <FrameLayout> can be replaced with a <merge> tag", null); } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java index af240e2..9ca8017 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/NestedScrollingWidgetDetector.java @@ -123,7 +123,7 @@ public class NestedScrollingWidgetDetector extends LayoutDetector { "horizontally scrolling widget (%2$s)"; } String msg = String.format(format, parent.getTagName(), element.getTagName()); - context.report(ISSUE, context.getLocation(element), msg, null); + context.report(ISSUE, element, context.getLocation(element), msg, null); } } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java index 685e893..616a9ab 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ObsoleteLayoutParamsDetector.java @@ -278,7 +278,7 @@ public class ObsoleteLayoutParamsDetector extends LayoutDetector { && isValidParamForParent(context, name, TABLE_ROW, parentTag)) { return; } - context.report(ISSUE, context.getLocation(attribute), + context.report(ISSUE, attribute, context.getLocation(attribute), String.format("Invalid layout param in a %1$s: %2$s", parentTag, name), null); } @@ -369,6 +369,7 @@ public class ObsoleteLayoutParamsDetector extends LayoutDetector { } String message = String.format("Invalid layout param '%1$s' (%2$s)", name, sb.toString()); + // TODO: Compute applicable scope node context.report(ISSUE, location, message, null); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java index bad3c42..3d03e61 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/OverdrawDetector.java @@ -211,6 +211,7 @@ public class OverdrawDetector extends LayoutDetector implements Detector.JavaSca "Possible overdraw: Root element paints background %1$s with " + "a theme that also paints a background (inferred theme is %2$s)", drawable, theme); + // TODO: Compute applicable scope node context.report(ISSUE, location, message, null); } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/PrivateResourceDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/PrivateResourceDetector.java index 8b05aa3..7f9e859 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/PrivateResourceDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/PrivateResourceDetector.java @@ -65,7 +65,7 @@ public class PrivateResourceDetector extends ResourceXmlDetector { public void visitAttribute(XmlContext context, Attr attribute) { String value = attribute.getNodeValue(); if (value.startsWith("@*android:")) { //$NON-NLS-1$ - context.report(ISSUE, context.getLocation(attribute), + context.report(ISSUE, attribute, context.getLocation(attribute), "Illegal resource reference: @*android resources are private and " + "not always present", null); } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java index 2caec16..cb37d5f 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/PxUsageDetector.java @@ -76,7 +76,7 @@ public class PxUsageDetector extends LayoutDetector { // 0px is fine. 0px is 0dp regardless of density... return; } - context.report(ISSUE, context.getLocation(attribute), + context.report(ISSUE, attribute, context.getLocation(attribute), "Avoid using \"px\" as units; use \"dp\" instead", null); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ScrollViewChildDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ScrollViewChildDetector.java index 2d5e0e9..8ba2bc4 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/ScrollViewChildDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/ScrollViewChildDetector.java @@ -87,7 +87,7 @@ public class ScrollViewChildDetector extends LayoutDetector { if (VALUE_FILL_PARENT.equals(value) || VALUE_MATCH_PARENT.equals(value)) { String msg = String.format("This %1$s should use android:%2$s=\"wrap_content\"", child.getTagName(), attributeName); - context.report(ISSUE, context.getLocation(sizeNode), msg, + context.report(ISSUE, sizeNode, context.getLocation(sizeNode), msg, null); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java index 7650cfd..1493131 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/SecurityDetector.java @@ -167,7 +167,7 @@ public class SecurityDetector extends Detector implements Detector.XmlScanner, permission = application.getAttributeNS(ANDROID_URI, ATTR_PERMISSION); if (permission == null || permission.length() == 0) { // No declared permission for this exported service: complain - context.report(EXPORTED_SERVICE, + context.report(EXPORTED_SERVICE, element, context.getLocation(element), "Exported service does not require permission", null); } @@ -183,14 +183,14 @@ public class SecurityDetector extends Detector implements Detector.XmlScanner, String msg = "Content provider shares everything; this is potentially dangerous."; if (path != null && path.getValue().equals("/")) { //$NON-NLS-1$ - context.report(OPEN_PROVIDER, context.getLocation(path), msg, null); + context.report(OPEN_PROVIDER, path, context.getLocation(path), msg, null); } if (prefix != null && prefix.getValue().equals("/")) { //$NON-NLS-1$ - context.report(OPEN_PROVIDER, context.getLocation(prefix), msg, null); + context.report(OPEN_PROVIDER, prefix, context.getLocation(prefix), msg, null); } if (pattern != null && (pattern.getValue().equals("/") //$NON-NLS-1$ /* || pattern.getValue().equals(".*")*/)) { - context.report(OPEN_PROVIDER, context.getLocation(pattern), msg, null); + context.report(OPEN_PROVIDER, pattern, context.getLocation(pattern), msg, null); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StateListDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StateListDetector.java index 85d3bfd..20fcf6b 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StateListDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StateListDetector.java @@ -98,7 +98,7 @@ public class StateListDetector extends ResourceXmlDetector { } } if (!hasState) { - context.report(ISSUE, context.getLocation(child), + context.report(ISSUE, child, context.getLocation(child), String.format("No android:state_ attribute found on <item> %1$d, later states not reachable", i), null); } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java index 2ab3f57..c115fdf 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StringFormatDetector.java @@ -400,6 +400,7 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto currentFormat, format, f.getParentFile().getName() + File.separator + f.getName()); warned = true; + // TODO: Compute applicable node scope context.report(ARG_TYPES, location, message, null); break; @@ -478,6 +479,7 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto String message = String.format( "Inconsistent number of arguments in formatting string %1$s; " + "found both %2$d and %3$d", name, prevCount, count); + // TODO: Compute applicable node scope context.report(ARG_COUNT, location, message, null); diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StyleCycleDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StyleCycleDetector.java index 77de5ab..6856c14 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/StyleCycleDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/StyleCycleDetector.java @@ -80,7 +80,7 @@ public class StyleCycleDetector extends ResourceXmlDetector { String name = element.getAttribute(ATTR_NAME); if (parent.endsWith(name) && parent.equals(STYLE_RESOURCE_PREFIX + name)) { - context.report(ISSUE, context.getLocation(parentNode), + context.report(ISSUE, parentNode, context.getLocation(parentNode), String.format("Style %1$s should not extend itself", name), null); } else if (parent.startsWith(STYLE_RESOURCE_PREFIX) && parent.startsWith(name, STYLE_RESOURCE_PREFIX.length()) diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TextFieldDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TextFieldDetector.java index 0d52b83..2cc81ab 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TextFieldDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TextFieldDetector.java @@ -82,7 +82,7 @@ public class TextFieldDetector extends LayoutDetector { return; } - context.report(ISSUE, context.getLocation(element), + context.report(ISSUE, element, context.getLocation(element), "This text field does not specify an inputType or a hint", null); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TooManyViewsDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TooManyViewsDetector.java index 42e867b..257d06c 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TooManyViewsDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TooManyViewsDetector.java @@ -130,12 +130,12 @@ public class TooManyViewsDetector extends LayoutDetector { mWarnedAboutDepth = true; String msg = String.format("%1$s has more than %2$d levels, bad for performance", context.file.getName(), MAX_DEPTH); - context.report(TOO_DEEP, context.getLocation(element), msg, null); + context.report(TOO_DEEP, element, context.getLocation(element), msg, null); } if (mViewCount == MAX_VIEW_COUNT) { String msg = String.format("%1$s has more than %2$d views, bad for performance", context.file.getName(), MAX_VIEW_COUNT); - context.report(TOO_MANY, context.getLocation(element), msg, null); + context.report(TOO_MANY, element, context.getLocation(element), msg, null); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java index ff8f886..41390d7 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TranslationDetector.java @@ -315,6 +315,7 @@ public class TranslationDetector extends ResourceXmlDetector { Collections.sort(sorted); Location location = getLocation(language, parentFolderToLanguage); int maxCount = context.getDriver().isAbbreviating() ? 4 : -1; + // TODO: Compute applicable node scope context.report(MISSING, location, String.format("Locale %1$s is missing translations for: %2$s", language, LintUtils.formatList(sorted, maxCount)), null); @@ -328,6 +329,7 @@ public class TranslationDetector extends ResourceXmlDetector { Collections.sort(sorted); Location location = getLocation(language, parentFolderToLanguage); int maxCount = context.getDriver().isAbbreviating() ? 4 : -1; + // TODO: Compute applicable node scope context.report(EXTRA, location, String.format( "Locale %1$s is translating names not found in default locale: %2$s", language, LintUtils.formatList(sorted, maxCount)), null); @@ -357,7 +359,7 @@ public class TranslationDetector extends ResourceXmlDetector { Attr attribute = element.getAttributeNode(ATTR_NAME); if (attribute == null || attribute.getValue().length() == 0) { - context.report(MISSING, context.getLocation(element), + context.report(MISSING, element, context.getLocation(element), "Missing name attribute in <string> declaration", null); } else { String name = attribute.getValue(); diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypographyDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypographyDetector.java index 8f52e4c..bda828d 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypographyDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypographyDetector.java @@ -243,7 +243,7 @@ public class TypographyDetector extends ResourceXmlDetector { // Replace ... with ellipsis character? int ellipsis = text.indexOf("..."); //$NON-NLS-1$ if (ellipsis != -1 && !text.startsWith(".", ellipsis + 3)) { //$NON-NLS-1$ - context.report(ELLIPSIS, context.getLocation(element), + context.report(ELLIPSIS, element, context.getLocation(element), ELLIPSIS_MESSAGE, null); } } @@ -263,7 +263,7 @@ public class TypographyDetector extends ResourceXmlDetector { Character.isWhitespace(matcher.group(1).charAt( matcher.group(1).length() - 1)); if (!isNegativeNumber) { - context.report(DASHES, context.getLocation(element), + context.report(DASHES, element, context.getLocation(element), EN_DASH_MESSAGE, null); } @@ -274,7 +274,7 @@ public class TypographyDetector extends ResourceXmlDetector { // Don't suggest replacing -- or "--" with an m dash since these are sometimes // used as digit marker strings if (emdash > 1 && !text.startsWith("-", emdash + 2)) { //$NON-NLS-1$ - context.report(DASHES, context.getLocation(element), + context.report(DASHES, element, context.getLocation(element), EM_DASH_MESSAGE, null); } } @@ -288,7 +288,7 @@ public class TypographyDetector extends ResourceXmlDetector { if (quoteEnd != -1 && quoteEnd > quoteStart + 1 && (quoteEnd < text.length() -1 || quoteStart > 0) && SINGLE_QUOTE.matcher(text).matches()) { - context.report(QUOTES, context.getLocation(element), + context.report(QUOTES, element, context.getLocation(element), SINGLE_QUOTE_MESSAGE, null); return; } @@ -296,7 +296,7 @@ public class TypographyDetector extends ResourceXmlDetector { // Check for apostrophes that can be replaced by typographic apostrophes if (quoteEnd == -1 && quoteStart > 0 && Character.isLetterOrDigit(text.charAt(quoteStart - 1))) { - context.report(QUOTES, context.getLocation(element), + context.report(QUOTES, element, context.getLocation(element), TYPOGRAPHIC_APOSTROPHE_MESSAGE, null); return; } @@ -308,7 +308,7 @@ public class TypographyDetector extends ResourceXmlDetector { int quoteEnd = text.indexOf('"', quoteStart + 1); if (quoteEnd != -1 && quoteEnd > quoteStart + 1) { if (quoteEnd < text.length() -1 || quoteStart > 0) { - context.report(QUOTES, context.getLocation(element), + context.report(QUOTES, element, context.getLocation(element), DBL_QUOTES_MESSAGE, null); return; } @@ -318,7 +318,7 @@ public class TypographyDetector extends ResourceXmlDetector { // Check for grave accent quotations if (text.indexOf('`') != -1 && GRAVE_QUOTATION.matcher(text).matches()) { // Are we indenting ``like this'' or `this' ? If so, complain - context.report(QUOTES, context.getLocation(element), + context.report(QUOTES, element, context.getLocation(element), GRAVE_QUOTE_MESSAGE, null); return; } @@ -337,19 +337,19 @@ public class TypographyDetector extends ResourceXmlDetector { String top = matcher.group(1); // Numerator String bottom = matcher.group(2); // Denominator if (top.equals("1") && bottom.equals("2")) { //$NON-NLS-1$ //$NON-NLS-2$ - context.report(FRACTIONS, context.getLocation(element), + context.report(FRACTIONS, element, context.getLocation(element), String.format(FRACTION_MESSAGE, '\u00BD', "½", "1/2"), null); } else if (top.equals("1") && bottom.equals("4")) { //$NON-NLS-1$ //$NON-NLS-2$ - context.report(FRACTIONS, context.getLocation(element), + context.report(FRACTIONS, element, context.getLocation(element), String.format(FRACTION_MESSAGE, '\u00BC', "¼", "1/4"), null); } else if (top.equals("3") && bottom.equals("4")) { //$NON-NLS-1$ //$NON-NLS-2$ - context.report(FRACTIONS, context.getLocation(element), + context.report(FRACTIONS, element, context.getLocation(element), String.format(FRACTION_MESSAGE, '\u00BE', "¾", "3/4"), null); } else if (top.equals("1") && bottom.equals("3")) { //$NON-NLS-1$ //$NON-NLS-2$ - context.report(FRACTIONS, context.getLocation(element), + context.report(FRACTIONS, element, context.getLocation(element), String.format(FRACTION_MESSAGE, '\u2153', "⅓", "1/3"), null); } else if (top.equals("2") && bottom.equals("3")) { //$NON-NLS-1$ //$NON-NLS-2$ - context.report(FRACTIONS, context.getLocation(element), + context.report(FRACTIONS, element, context.getLocation(element), String.format(FRACTION_MESSAGE, '\u2154', "⅔", "2/3"), null); } } @@ -360,7 +360,7 @@ public class TypographyDetector extends ResourceXmlDetector { if (text.indexOf('(') != -1 && (text.contains("(c)") || text.contains("(C)"))) { //$NON-NLS-1$ //$NON-NLS-2$ // Suggest replacing with copyright symbol? - context.report(OTHER, context.getLocation(element), + context.report(OTHER, element, context.getLocation(element), COPYRIGHT_MESSAGE, null); // Replace (R) and TM as well? There are unicode characters for these but they // are probably not very common within Android app strings. diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java index a005eca..5ca97d0 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UnusedResourceDetector.java @@ -276,13 +276,18 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec } String message = String.format("The resource %1$s appears to be unused", resource); - Issue issue = resource.startsWith(R_ID_PREFIX) ? ISSUE_IDS : ISSUE; + Issue issue = getIssue(resource); + // TODO: Compute applicable node scope context.report(issue, location, message, resource); } } } } + private static Issue getIssue(String resource) { + return resource.startsWith(R_ID_PREFIX) ? ISSUE_IDS : ISSUE; + } + private void recordLocation(String resource, Location location) { Location oldLocation = mUnused.get(resource); if (oldLocation != null) { @@ -332,6 +337,10 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec } else { assert context.getPhase() == 2; if (mUnused.containsKey(resource)) { + if (context.getDriver().isSuppressed(getIssue(resource), item)) { + mUnused.remove(resource); + return; + } recordLocation(resource, context.getLocation(item)); } } @@ -385,7 +394,12 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec if (context.getPhase() == 1) { mDeclarations.add(resource); } else if (mUnused.containsKey(resource)) { + if (context.getDriver().isSuppressed(getIssue(resource), attribute)) { + mUnused.remove(resource); + return; + } recordLocation(resource, context.getLocation(attribute)); + return; } } else if (mReferences != null) { if (value.startsWith("@") //$NON-NLS-1$ diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/UseCompoundDrawableDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UseCompoundDrawableDetector.java index fb5c159..8ded0e6 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/UseCompoundDrawableDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UseCompoundDrawableDetector.java @@ -86,7 +86,7 @@ public class UseCompoundDrawableDetector extends LayoutDetector { ((second.getTagName().equals(IMAGE_VIEW) && first.getTagName().equals(TEXT_VIEW) && !second.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_WEIGHT)))) { - context.report(ISSUE, context.getLocation(element), + context.report(ISSUE, element, context.getLocation(element), "This tag and its children can be replaced by one <TextView/> and " + "a compound drawable", null); } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/UselessViewDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UselessViewDetector.java index e231320..1b4f855 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/UselessViewDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/UselessViewDetector.java @@ -204,7 +204,7 @@ public class UselessViewDetector extends LayoutDetector { format += "; transfer the background attribute to the other view"; } String message = String.format(format, tag, parentTag); - context.report(USELESS_PARENT, location, message, null); + context.report(USELESS_PARENT, element, location, message, null); } // This is the old UselessView check from layoutopt @@ -234,6 +234,6 @@ public class UselessViewDetector extends LayoutDetector { String tag = element.getTagName(); String message = String.format( "This %1$s view is useless (no children, no background, no id, no style)", tag); - context.report(USELESS_LEAF, location, message, null); + context.report(USELESS_LEAF, element, location, message, null); } } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/Utf8Detector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/Utf8Detector.java index 97c0f43..6a87e94 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/Utf8Detector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/Utf8Detector.java @@ -91,7 +91,7 @@ public class Utf8Detector extends LayoutDetector { String encoding = matcher.group(1); Location location = Location.create(context.file, xml, matcher.start(1), matcher.end(1)); - context.report(ISSUE, location, String.format( + context.report(ISSUE, null, location, String.format( "%1$s: Not using UTF-8 as the file encoding. This can lead to subtle " + "bugs with non-ascii characters", encoding), null); } diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/WrongIdDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/WrongIdDetector.java index e63b925..823eabd 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/WrongIdDetector.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/WrongIdDetector.java @@ -229,6 +229,7 @@ public class WrongIdDetector extends LayoutDetector { "The id \"%1$s\" is not defined anywhere.%2$s", id, suggestionMessage); } + // TODO: Compute applicable node scope context.report(UNKNOWN_ID, location, message, null); } else if (checkSameLayout && (!projectScope || isBound)) { // The id was defined, but in a different layout. Usually not intentional @@ -236,6 +237,7 @@ public class WrongIdDetector extends LayoutDetector { // name.) Handle handle = pair.getSecond(); Location location = handle.resolve(); + // TODO: Compute applicable node scope context.report(UNKNOWN_ID_LAYOUT, location, String.format( "The id \"%1$s\" is not referring to any views in this layout", diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java index 7005686..06ff9b6 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/HardcodedValuesDetectorTest.java @@ -33,4 +33,15 @@ public class HardcodedValuesDetectorTest extends AbstractCheckTest { "should use @string resource", lintFiles("res/layout/accessibility.xml")); } + + public void testSuppress() throws Exception { + // All but one errors in the file contain ignore attributes - direct, inherited + // and lists + assertEquals( + "ignores.xml:61: Warning: [I18N] Hardcoded string \"Hardcoded\", should use " + + "@string resource", + + lintFiles("res/layout/ignores.xml")); + } + } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/ignores.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/ignores.xml new file mode 100644 index 0000000..d4be910 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/ignores.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/newlinear" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <!-- Ignored via attribute, should be hidden --> + + <Button + android:id="@+id/button1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button1" + tools:ignore="HardcodedText" > + </Button> + + <!-- Inherited ignore from parent --> + + <LinearLayout + android:id="@+id/parent" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:ignore="HardcodedText" > + + <Button + android:id="@+id/button2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button2" > + </Button> + </LinearLayout> + + <!-- Hardcoded text warning ignored through "all" --> + + <Button + android:id="@+id/button3" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button3" + tools:ignore="all" > + </Button> + + <!-- Ignored through item in ignore list --> + + <Button + android:id="@+id/button4" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Hardcoded" + tools:ignore="NewApi,HardcodedText" > + </Button> + + <!-- Not ignored: should show up as a warning --> + + <Button + android:id="@+id/button5" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Hardcoded" + tools:ignore="Other" > + </Button> + +</LinearLayout> |