diff options
author | Tor Norbye <tnorbye@google.com> | 2012-02-14 13:06:43 -0800 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2012-02-14 15:30:59 -0800 |
commit | 8f0ee1ea762ccb743f6ee33550d67edd3c8bb200 (patch) | |
tree | eeb7d922ddda3f28a7b1d07f72ee5545ab935cf1 /lint | |
parent | ef6d36c7bece54393e9f1390c8162c6c3059d77e (diff) | |
download | sdk-8f0ee1ea762ccb743f6ee33550d67edd3c8bb200.tar.gz sdk-8f0ee1ea762ccb743f6ee33550d67edd3c8bb200.tar.bz2 sdk-8f0ee1ea762ccb743f6ee33550d67edd3c8bb200.zip |
Add lint check for custom attributes in library projects
Also adds a lint check for unused namespace declarations,
and migrates the TypoDetector code into this new namespace
detector.
Change-Id: I5ec2214ea4c59e14194f8eaecef422ea19baa35e
Diffstat (limited to 'lint')
-rw-r--r-- | lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintConstants.java | 1 | ||||
-rw-r--r-- | lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java | 9 | ||||
-rw-r--r-- | lint/libs/lint_checks/src/com/android/tools/lint/checks/DetectMissingPrefix.java | 3 | ||||
-rw-r--r-- | lint/libs/lint_checks/src/com/android/tools/lint/checks/ManifestOrderDetector.java | 2 | ||||
-rw-r--r-- | lint/libs/lint_checks/src/com/android/tools/lint/checks/NamespaceDetector.java | 217 | ||||
-rw-r--r-- | lint/libs/lint_checks/src/com/android/tools/lint/checks/TypoDetector.java | 95 | ||||
-rw-r--r-- | lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NamespaceDetectorTest.java (renamed from lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TypoDetectorTest.java) | 53 | ||||
-rw-r--r-- | lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/customview.xml | 30 | ||||
-rw-r--r-- | lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/unused_namespace.xml | 14 |
9 files changed, 319 insertions, 105 deletions
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 09d378bc6..9d9cf6d90 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 @@ -250,6 +250,7 @@ public class LintConstants { public static final String ANDROID_STRING_RESOURCE_PREFIX = "@android:string/"; //$NON-NLS-1$ // Packages + public static final String ANDROID_PKG_PREFIX = "android."; //$NON-NLS-1$ public static final String WIDGET_PKG_PREFIX = "android.widget."; //$NON-NLS-1$ public static final String VIEW_PKG_PREFIX = "android.view."; //$NON-NLS-1$ diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java index 4b5ebe295..e3deb3fc8 100644 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java @@ -53,7 +53,8 @@ public class BuiltinIssueRegistry extends IssueRegistry { private static final List<Issue> sIssues; static { - List<Issue> issues = new ArrayList<Issue>(75); + final int initialCapacity = 77; + List<Issue> issues = new ArrayList<Issue>(initialCapacity); issues.add(AccessibilityDetector.ISSUE); issues.add(MathDetector.ISSUE); @@ -126,11 +127,15 @@ public class BuiltinIssueRegistry extends IssueRegistry { issues.add(ViewTypeDetector.ISSUE); issues.add(WrongImportDetector.ISSUE); issues.add(ViewConstructorDetector.ISSUE); - issues.add(TypoDetector.ISSUE); + issues.add(NamespaceDetector.CUSTOMVIEW); + issues.add(NamespaceDetector.UNUSED); + issues.add(NamespaceDetector.TYPO); issues.add(AlwaysShowActionDetector.ISSUE); issues.add(JavaPerformanceDetector.PAINT_ALLOC); issues.add(JavaPerformanceDetector.USE_SPARSEARRAY); + assert initialCapacity >= issues.size() : issues.size(); + addCustomIssues(issues); sIssues = Collections.unmodifiableList(issues); 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 d0cf2d4c7..6ea789933 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 @@ -16,6 +16,7 @@ package com.android.tools.lint.checks; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_PKG_PREFIX; import static com.android.tools.lint.detector.api.LintConstants.ATTR_CLASS; import static com.android.tools.lint.detector.api.LintConstants.ATTR_LAYOUT; import static com.android.tools.lint.detector.api.LintConstants.ATTR_STYLE; @@ -114,6 +115,6 @@ public class DetectMissingPrefix extends LayoutDetector { return true; } - return tag.indexOf('.') != -1 && !tag.startsWith("android."); //$NON-NLS-1$ + return tag.indexOf('.') != -1 && !tag.startsWith(ANDROID_PKG_PREFIX); } } 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 96b677eca..97cb5f67d 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 @@ -66,7 +66,7 @@ public class ManifestOrderDetector extends Detector implements Detector.XmlScann /** Missing a {@code <uses-sdk>} element */ public static final Issue USES_SDK = Issue.create( - "UsesSdkMinTarget", //$NON-NLS-1$ + "UsesMinSdkAttributes", //$NON-NLS-1$ "Checks that the minimum SDK and target SDK attributes are defined", "The manifest should contain a <uses-sdk> element which defines the " + diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/NamespaceDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/NamespaceDetector.java new file mode 100644 index 000000000..60ae3a394 --- /dev/null +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/NamespaceDetector.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + * + * 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.tools.lint.checks; + +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_PKG_PREFIX; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI; +import static com.android.tools.lint.detector.api.LintConstants.XMLNS_PREFIX; + +import com.android.tools.lint.detector.api.Category; +import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.LayoutDetector; +import com.android.tools.lint.detector.api.LintUtils; +import com.android.tools.lint.detector.api.Scope; +import com.android.tools.lint.detector.api.Severity; +import com.android.tools.lint.detector.api.Speed; +import com.android.tools.lint.detector.api.XmlContext; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import java.util.HashMap; +import java.util.Map; + +/** + * Checks for various issues related to XML namespaces + */ +public class NamespaceDetector extends LayoutDetector { + /** Typos in the namespace */ + public static final Issue TYPO = Issue.create( + "NamespaceTypo", //$NON-NLS-1$ + "Looks for misspellings in namespace declarations", + + "Accidental misspellings in namespace declarations can lead to some very " + + "obscure error messages. This check looks for potential misspellings to " + + "help track these down.", + Category.CORRECTNESS, + 8, + Severity.WARNING, + NamespaceDetector.class, + Scope.RESOURCE_FILE_SCOPE); + + /** Unused namespace declarations */ + public static final Issue UNUSED = Issue.create( + "UnusedNamespace", //$NON-NLS-1$ + "Finds unused namespaces in XML documents", + + "Unused namespace declarations take up space and require processing that is not " + + "necessary", + + Category.CORRECTNESS, + 1, + Severity.WARNING, + NamespaceDetector.class, + Scope.RESOURCE_FILE_SCOPE); + + /** Using custom namespace attributes in a library project */ + public static final Issue CUSTOMVIEW = Issue.create( + "LibraryCustomView", //$NON-NLS-1$ + "Flags custom views in libraries, which currently do not work", + + "Using a custom view in a library project (where the custom view " + + "requires XML attributes from a custom namespace) does not yet " + + "work.", + Category.CORRECTNESS, + 6, + Severity.ERROR, + NamespaceDetector.class, + Scope.RESOURCE_FILE_SCOPE); + + /** Prefix relevant for custom namespaces */ + private static final String XMLNS_ANDROID = "xmlns:android"; //$NON-NLS-1$ + private static final String XMLNS_A = "xmlns:a"; //$NON-NLS-1$ + private static final String URI_PREFIX = "http://schemas.android.com/apk/res/"; //$NON-NLS-1$ + + private Map<String, Attr> mUnusedNamespaces; + private boolean mCheckUnused; + private boolean mCheckCustomAttrs; + + /** Constructs a new {@link NamespaceDetector} */ + public NamespaceDetector() { + } + + @Override + public Speed getSpeed() { + return Speed.FAST; + } + + @Override + public void visitDocument(XmlContext context, Document document) { + boolean haveCustomNamespace = false; + Element root = document.getDocumentElement(); + NamedNodeMap attributes = root.getAttributes(); + for (int i = 0, n = attributes.getLength(); i < n; i++) { + Node item = attributes.item(i); + if (item.getNodeName().startsWith(XMLNS_PREFIX)) { + String value = item.getNodeValue(); + + if (!value.equals(ANDROID_URI)) { + Attr attribute = (Attr) item; + + if (value.startsWith(URI_PREFIX)) { + haveCustomNamespace = true; + if (mUnusedNamespaces == null) { + mUnusedNamespaces = new HashMap<String, Attr>(); + } + mUnusedNamespaces.put(item.getNodeName().substring(XMLNS_PREFIX.length()), + attribute); + } + + String name = attribute.getName(); + if (!name.equals(XMLNS_ANDROID) && !name.equals(XMLNS_A)) { + continue; + } + + if (!context.isEnabled(TYPO)) { + continue; + } + + if (name.equals(XMLNS_A)) { + // For the "android" prefix we always assume that the namespace prefix + // should be our expected prefix, but for the "a" prefix we make sure + // that it's at least "close"; if you're bound it to something completely + // different, don't complain. + if (LintUtils.editDistance(ANDROID_URI, value) > 4) { + continue; + } + } + + if (value.equalsIgnoreCase(ANDROID_URI)) { + context.report(TYPO, attribute, context.getLocation(attribute), + String.format( + "URI is case sensitive: was \"%1$s\", expected \"%2$s\"", + value, ANDROID_URI), null); + } else { + context.report(TYPO, attribute, context.getLocation(attribute), + String.format( + "Unexpected namespace URI bound to the \"android\" " + + "prefix, was %1$s, expected %2$s", value, ANDROID_URI), + null); + } + } + } + } + + if (haveCustomNamespace) { + mCheckCustomAttrs = context.isEnabled(CUSTOMVIEW) && context.getProject().isLibrary(); + mCheckUnused = context.isEnabled(UNUSED); + checkElement(context, document.getDocumentElement()); + + if (mCheckUnused && mUnusedNamespaces.size() > 0) { + for (Map.Entry<String, Attr> entry : mUnusedNamespaces.entrySet()) { + String prefix = entry.getKey(); + Attr attribute = entry.getValue(); + context.report(UNUSED, attribute, context.getLocation(attribute), + String.format("Unused namespace %1$s", prefix), null); + } + } + } + } + + private void checkElement(XmlContext context, Node node) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + if (mCheckCustomAttrs) { + String tag = node.getNodeName(); + if (tag.indexOf('.') != -1 + // Don't consider android.support.* and android.app.FragmentBreadCrumbs etc + && !tag.startsWith(ANDROID_PKG_PREFIX)) { + NamedNodeMap attributes = ((Element) node).getAttributes(); + for (int i = 0, n = attributes.getLength(); i < n; i++) { + Attr attribute = (Attr) attributes.item(i); + String uri = attribute.getNamespaceURI(); + if (uri != null && uri.length() > 0 && uri.startsWith(URI_PREFIX) + && !uri.equals(ANDROID_URI)) { + context.report(CUSTOMVIEW, attribute, context.getLocation(attribute), + "Using a custom namespace attributes in a library project does " + + "not yet work", null); + } + } + } + } + + if (mCheckUnused) { + NamedNodeMap attributes = ((Element) node).getAttributes(); + for (int i = 0, n = attributes.getLength(); i < n; i++) { + Attr attribute = (Attr) attributes.item(i); + String prefix = attribute.getPrefix(); + if (prefix != null) { + mUnusedNamespaces.remove(prefix); + } + } + } + + NodeList childNodes = node.getChildNodes(); + for (int i = 0, n = childNodes.getLength(); i < n; i++) { + checkElement(context, childNodes.item(i)); + } + } + } +} diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypoDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypoDetector.java deleted file mode 100644 index 3fcbcf503..000000000 --- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypoDetector.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 - * - * 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.tools.lint.checks; - -import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI; - -import com.android.tools.lint.detector.api.Category; -import com.android.tools.lint.detector.api.Issue; -import com.android.tools.lint.detector.api.LintUtils; -import com.android.tools.lint.detector.api.ResourceXmlDetector; -import com.android.tools.lint.detector.api.Scope; -import com.android.tools.lint.detector.api.Severity; -import com.android.tools.lint.detector.api.Speed; -import com.android.tools.lint.detector.api.XmlContext; - -import org.w3c.dom.Attr; - -import java.util.Arrays; -import java.util.Collection; - -/** - * Check which looks for likely typos in various places. - */ -public class TypoDetector extends ResourceXmlDetector { - private static final String XMLNS_ANDROID = "xmlns:android"; //$NON-NLS-1$ - private static final String XMLNS_A = "xmlns:a"; //$NON-NLS-1$ - - /** The main issue discovered by this detector */ - public static final Issue ISSUE = Issue.create( - "NamespaceTypo", //$NON-NLS-1$ - "Looks for misspellings in namespace declarations", - - "Accidental misspellings in namespace declarations can lead to some very " + - "obscure error messages. This check looks for potential misspellings to " + - "help track these down.", - Category.CORRECTNESS, - 8, - Severity.WARNING, - TypoDetector.class, - Scope.RESOURCE_FILE_SCOPE); - - /** Constructs a new {@link TypoDetector} */ - public TypoDetector() { - } - - @Override - public Speed getSpeed() { - return Speed.FAST; - } - - @Override - public Collection<String> getApplicableAttributes() { - return Arrays.asList(XMLNS_ANDROID, XMLNS_A); - } - - @Override - public void visitAttribute(XmlContext context, Attr attribute) { - String value = attribute.getValue(); - if (!value.equals(ANDROID_URI)) { - if (attribute.getName().equals(XMLNS_A)) { - // For the "android" prefix we always assume that the namespace prefix - // should be our expected prefix, but for the "a" prefix we make sure - // that it's at least "close"; if you're bound it to something completely - // different, don't complain. - if (LintUtils.editDistance(ANDROID_URI, value) > 4) { - return; - } - } - - if (value.equalsIgnoreCase(ANDROID_URI)) { - context.report(ISSUE, attribute, context.getLocation(attribute), - String.format("URI is case sensitive: was \"%1$s\", expected \"%2$s\"", - value, ANDROID_URI), null); - } else { - context.report(ISSUE, attribute, context.getLocation(attribute), - String.format("Unexpected namespace URI bound to the \"android\" " + - "prefix, was %1$s, expected %2$s", value, ANDROID_URI), null); - } - } - } -} diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TypoDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NamespaceDetectorTest.java index b9af7a2e3..3570f5cdb 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TypoDetectorTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/NamespaceDetectorTest.java @@ -19,13 +19,39 @@ package com.android.tools.lint.checks; import com.android.tools.lint.detector.api.Detector; @SuppressWarnings("javadoc") -public class TypoDetectorTest extends AbstractCheckTest { +public class NamespaceDetectorTest extends AbstractCheckTest { @Override protected Detector getDetector() { - return new TypoDetector(); + return new NamespaceDetector(); } - public void test() throws Exception { + public void testCustom() throws Exception { + assertEquals( + "customview.xml:16: Error: Using a custom namespace attributes in a library project does not yet work", + + lintProject( + "multiproject/library-manifest.xml=>AndroidManifest.xml", + "multiproject/library.properties=>project.properties", + "res/layout/customview.xml" + )); + } + + public void testCustomOk() throws Exception { + assertEquals( + "No warnings.", + + lintProject( + "multiproject/library-manifest.xml=>AndroidManifest.xml", + + // Use a standard project properties instead: no warning since it's + // not a library project: + //"multiproject/library.properties=>project.properties", + + "res/layout/customview.xml" + )); + } + + public void testTypo() throws Exception { assertEquals( "wrong_namespace.xml:2: Warning: Unexpected namespace URI bound to the " + "\"android\" prefix, was http://schemas.android.com/apk/res/andriod, " + @@ -34,7 +60,7 @@ public class TypoDetectorTest extends AbstractCheckTest { lintProject("res/layout/wrong_namespace.xml")); } - public void test2() throws Exception { + public void testTypo2() throws Exception { assertEquals( "wrong_namespace2.xml:2: Warning: URI is case sensitive: was " + "\"http://schemas.android.com/apk/res/Android\", expected " + @@ -43,7 +69,7 @@ public class TypoDetectorTest extends AbstractCheckTest { lintProject("res/layout/wrong_namespace2.xml")); } - public void test3() throws Exception { + public void testTypo3() throws Exception { assertEquals( "wrong_namespace3.xml:2: Warning: Unexpected namespace URI bound to the " + "\"android\" prefix, was http://schemas.android.com/apk/res/androi, " + @@ -52,10 +78,25 @@ public class TypoDetectorTest extends AbstractCheckTest { lintProject("res/layout/wrong_namespace3.xml")); } - public void testOk() throws Exception { + public void testTypoOk() throws Exception { assertEquals( "No warnings.", lintProject("res/layout/wrong_namespace4.xml")); } + + public void testUnused() throws Exception { + assertEquals( + "unused_namespace.xml:3: Warning: Unused namespace unused1\n" + + "unused_namespace.xml:4: Warning: Unused namespace unused2", + + lintProject("res/layout/unused_namespace.xml")); + } + + public void testUnusedOk() throws Exception { + assertEquals( + "No warnings.", + + lintProject("res/layout/layout1.xml")); + } } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/customview.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/customview.xml new file mode 100644 index 000000000..976d636cd --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/customview.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:other="http://schemas.foo.bar.com/other" + xmlns:foo="http://schemas.android.com/apk/res/foo" + android:id="@+id/newlinear" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <foo.bar.Baz + android:id="@+id/button1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button1" + foo:misc="Custom attribute" + tools:ignore="HardcodedText" > + </foo.bar.Baz> + + <!-- Wrong namespace uri prefix: Don't warn --> + <foo.bar.Baz + android:id="@+id/button1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button1" + other:misc="Custom attribute" + tools:ignore="HardcodedText" > + </foo.bar.Baz> + +</LinearLayout> diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/unused_namespace.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/unused_namespace.xml new file mode 100644 index 000000000..f633e4b8b --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/unused_namespace.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<foo.bar.LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:unused1="http://schemas.android.com/apk/res/unused1" + xmlns:unused2="http://schemas.android.com/apk/res/unused1" + xmlns:unused3="http://foo.bar.com/foo" + xmlns:notunused="http://schemas.android.com/apk/res/notunused" + xmlns:tools="http://schemas.android.com/tools" > + + <foo.bar.Button + notunused:foo="Foo" + tools:ignore="HardcodedText" > + </foo.bar.Button> + +</foo.bar.LinearLayout> |