diff options
author | Tor Norbye <tnorbye@google.com> | 2012-02-13 10:21:50 -0800 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2012-02-13 10:27:00 -0800 |
commit | b89f2aa44747994f8a190cf0da7c466cadb9da2b (patch) | |
tree | 005f3d06715291bb8fdee59e8c11548fca09f8ba /lint | |
parent | 71008df5a5b37666ce3142c2df69fa93391260ac (diff) | |
download | sdk-b89f2aa44747994f8a190cf0da7c466cadb9da2b.zip sdk-b89f2aa44747994f8a190cf0da7c466cadb9da2b.tar.gz sdk-b89f2aa44747994f8a190cf0da7c466cadb9da2b.tar.bz2 |
Add namespace-typo detector
This changeset adds a new lint detector which looks for typos in the
namespace declaration for Android. If it sees the prefix "android" it
ensures that the URI matches exactly (and case-sensitively)
http://schemas.android.com/apk/res/android
If the prefix is "a", it also checks for the above prefix, but only
warns if the difference is "close" (edit distance <= 3) such that
it belives it's a typo, not some unrelated prefix bound to "a".
(This was requested in http://b.android.com/25449)
Change-Id: Ic6dee77a300fc048cb9bee3e455f533b858ef6bb
Diffstat (limited to 'lint')
8 files changed, 261 insertions, 1 deletions
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/XmlVisitor.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/XmlVisitor.java index ec6bd6a..c76d44d 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/XmlVisitor.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/XmlVisitor.java @@ -181,7 +181,11 @@ class XmlVisitor { NamedNodeMap attributes = element.getAttributes(); for (int i = 0, n = attributes.getLength(); i < n; i++) { Attr attribute = (Attr) attributes.item(i); - List<Detector.XmlScanner> list = mAttributeToCheck.get(attribute.getLocalName()); + String name = attribute.getLocalName(); + if (name == null) { + name = attribute.getName(); + } + List<Detector.XmlScanner> list = mAttributeToCheck.get(name); if (list != null) { for (int j = 0, max = list.size(); j < max; j++) { Detector.XmlScanner check = list.get(j); 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 ed5586d..67aabdb 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 @@ -125,6 +125,7 @@ public class BuiltinIssueRegistry extends IssueRegistry { issues.add(ViewTypeDetector.ISSUE); issues.add(WrongImportDetector.ISSUE); issues.add(ViewConstructorDetector.ISSUE); + issues.add(TypoDetector.ISSUE); issues.add(AlwaysShowActionDetector.ISSUE); addCustomIssues(issues); 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 new file mode 100644 index 0000000..3fcbcf5 --- /dev/null +++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/TypoDetector.java @@ -0,0 +1,95 @@ +/* + * 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/TypoDetectorTest.java new file mode 100644 index 0000000..b9af7a2 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/TypoDetectorTest.java @@ -0,0 +1,61 @@ +/* + * 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 com.android.tools.lint.detector.api.Detector; + +@SuppressWarnings("javadoc") +public class TypoDetectorTest extends AbstractCheckTest { + @Override + protected Detector getDetector() { + return new TypoDetector(); + } + + public void test() throws Exception { + assertEquals( + "wrong_namespace.xml:2: Warning: Unexpected namespace URI bound to the " + + "\"android\" prefix, was http://schemas.android.com/apk/res/andriod, " + + "expected http://schemas.android.com/apk/res/android", + + lintProject("res/layout/wrong_namespace.xml")); + } + + public void test2() throws Exception { + assertEquals( + "wrong_namespace2.xml:2: Warning: URI is case sensitive: was " + + "\"http://schemas.android.com/apk/res/Android\", expected " + + "\"http://schemas.android.com/apk/res/android\"", + + lintProject("res/layout/wrong_namespace2.xml")); + } + + public void test3() throws Exception { + assertEquals( + "wrong_namespace3.xml:2: Warning: Unexpected namespace URI bound to the " + + "\"android\" prefix, was http://schemas.android.com/apk/res/androi, " + + "expected http://schemas.android.com/apk/res/android", + + lintProject("res/layout/wrong_namespace3.xml")); + } + + public void testOk() throws Exception { + assertEquals( + "No warnings.", + + lintProject("res/layout/wrong_namespace4.xml")); + } +} diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace.xml new file mode 100644 index 0000000..c6e2143 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/andriod" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <include + android:layout_width="wrap_content" + android:layout_height="wrap_content" + layout="@layout/layout2" /> + + <Button + android:id="@+id/button1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + + <Button + android:id="@+id/button2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + +</LinearLayout> diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace2.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace2.xml new file mode 100644 index 0000000..49dc611 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace2.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/Android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <include + android:layout_width="wrap_content" + android:layout_height="wrap_content" + layout="@layout/layout2" /> + + <Button + android:id="@+id/button1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + + <Button + android:id="@+id/button2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + +</LinearLayout> diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace3.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace3.xml new file mode 100644 index 0000000..02252b4 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace3.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:a="http://schemas.android.com/apk/res/androi" + a:layout_width="match_parent" + a:layout_height="match_parent" + a:orientation="vertical" > + + <include + a:layout_width="wrap_content" + a:layout_height="wrap_content" + layout="@layout/layout2" /> + + <Button + a:id="@+id/button1" + a:layout_width="wrap_content" + a:layout_height="wrap_content" + a:text="Button" /> + + <Button + a:id="@+id/button2" + a:layout_width="wrap_content" + a:layout_height="wrap_content" + a:text="Button" /> + +</LinearLayout> diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace4.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace4.xml new file mode 100644 index 0000000..4142622 --- /dev/null +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/res/layout/wrong_namespace4.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- This file does *not* have a wrong namespace: it's testdata to make sure we don't complain when "a" is defined for something unrelated --> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:a="http://something/very/different" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <include + android:layout_width="wrap_content" + android:layout_height="wrap_content" + layout="@layout/layout2" /> + + <Button + android:id="@+id/button1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + + <Button + android:id="@+id/button2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" /> + +</LinearLayout> |