diff options
9 files changed, 369 insertions, 50 deletions
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java index 4edf345..27875e8 100644 --- a/lint/cli/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java +++ b/lint/cli/src/test/java/com/android/tools/lint/checks/MissingClassDetectorTest.java @@ -16,17 +16,29 @@ package com.android.tools.lint.checks; +import static com.android.tools.lint.checks.MissingClassDetector.INNERCLASS; +import static com.android.tools.lint.checks.MissingClassDetector.INSTANTIATABLE; +import static com.android.tools.lint.checks.MissingClassDetector.MISSING; + +import com.android.annotations.NonNull; +import com.android.tools.lint.client.api.LintClient; import com.android.tools.lint.detector.api.Detector; +import com.android.tools.lint.detector.api.Issue; +import com.android.tools.lint.detector.api.Project; import com.android.tools.lint.detector.api.Scope; +import com.google.common.collect.Sets; import java.io.File; import java.util.Arrays; import java.util.EnumSet; +import java.util.HashSet; import java.util.List; +import java.util.Set; @SuppressWarnings("javadoc") public class MissingClassDetectorTest extends AbstractCheckTest { private EnumSet<Scope> mScopes; + private Set<Issue> mEnabled = new HashSet<Issue>(); @Override protected Detector getDetector() { @@ -38,8 +50,19 @@ public class MissingClassDetectorTest extends AbstractCheckTest { return mScopes; } + @Override + protected TestConfiguration getConfiguration(LintClient client, Project project) { + return new TestConfiguration(client, project, null) { + @Override + public boolean isEnabled(@NonNull Issue issue) { + return super.isEnabled(issue) && mEnabled.contains(issue); + } + }; + } + public void test() throws Exception { mScopes = null; + mEnabled = Sets.newHashSet(MISSING); assertEquals( "AndroidManifest.xml:13: Error: Class referenced in the manifest, test.pkg.TestProvider, was not found in the project or the libraries [MissingRegistered]\n" + " <activity android:name=\".TestProvider\" />\n" + @@ -67,6 +90,7 @@ public class MissingClassDetectorTest extends AbstractCheckTest { public void testIncrementalInManifest() throws Exception { mScopes = Scope.MANIFEST_SCOPE; + mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS); assertEquals( "No warnings.", @@ -78,6 +102,7 @@ public class MissingClassDetectorTest extends AbstractCheckTest { public void testNoWarningBeforeBuild() throws Exception { mScopes = null; + mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS); assertEquals( "No warnings.", @@ -89,11 +114,12 @@ public class MissingClassDetectorTest extends AbstractCheckTest { public void testOkClasses() throws Exception { mScopes = null; + mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS); assertEquals( "No warnings.", lintProject( - "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml", + "bytecode/AndroidManifestRegs.xml=>AndroidManifest.xml", "bytecode/.classpath=>.classpath", "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java", "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class", @@ -110,11 +136,12 @@ public class MissingClassDetectorTest extends AbstractCheckTest { public void testOkLibraries() throws Exception { mScopes = null; + mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS); assertEquals( "No warnings.", lintProject( - "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml", + "bytecode/AndroidManifestRegs.xml=>AndroidManifest.xml", "bytecode/.classpath=>.classpath", "bytecode/classes.jar=>libs/classes.jar" )); @@ -122,10 +149,13 @@ public class MissingClassDetectorTest extends AbstractCheckTest { public void testLibraryProjects() throws Exception { mScopes = null; + mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS); File master = getProjectDir("MasterProject", // Master project - "bytecode/AndroidManifestWrongRegs.xml=>AndroidManifest.xml", + "bytecode/AndroidManifestRegs.xml=>AndroidManifest.xml", "multiproject/main.properties=>project.properties", + "bytecode/TestService.java.txt=>src/test/pkg/TestService.java", + "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class", "bytecode/.classpath=>.classpath" ); File library = getProjectDir("LibraryProject", @@ -134,25 +164,24 @@ public class MissingClassDetectorTest extends AbstractCheckTest { "multiproject/library.properties=>project.properties", "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java", "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class", - "bytecode/TestService.java.txt=>src/test/pkg/TestService.java", - "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class", "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java", "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class", "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java", "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class" // Missing TestReceiver: Test should complain about just that class ); - assertEquals( - "MasterProject/AndroidManifest.xml:17: Error: Class referenced in the manifest, test.pkg.TestReceiver, was not found in the project or the libraries [MissingRegistered]\n" + - " <service android:name=\"TestReceiver\" />\n" + - " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + - "1 errors, 0 warnings\n", + assertEquals("" + + "MasterProject/AndroidManifest.xml:32: Error: Class referenced in the manifest, test.pkg.TestReceiver, was not found in the project or the libraries [MissingRegistered]\n" + + " <receiver android:name=\"TestReceiver\" />\n" + + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + + "1 errors, 0 warnings\n", checkLint(Arrays.asList(master, library))); } public void testInnerClassStatic() throws Exception { mScopes = null; + mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS); assertEquals( "src/test/pkg/Foo.java:8: Warning: This inner class should be static (test.pkg.Foo.Baz) [Instantiatable]\n" + " public class Baz extends Activity {\n" + @@ -171,6 +200,7 @@ public class MissingClassDetectorTest extends AbstractCheckTest { public void testInnerClassPublic() throws Exception { mScopes = null; + mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS); assertEquals( "src/test/pkg/Foo/Bar.java:6: Warning: The default constructor must be public [Instantiatable]\n" + " private Bar() {\n" + @@ -187,6 +217,7 @@ public class MissingClassDetectorTest extends AbstractCheckTest { public void testInnerClass() throws Exception { mScopes = null; + mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS); assertEquals( "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.Foo.Bar, was not found in the project or the libraries [MissingRegistered]\n" + " <activity\n" + @@ -206,6 +237,7 @@ public class MissingClassDetectorTest extends AbstractCheckTest { public void testInnerClass2() throws Exception { mScopes = null; + mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS); assertEquals( "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.Foo.Bar, was not found in the project or the libraries [MissingRegistered]\n" + " <activity\n" + @@ -222,6 +254,7 @@ public class MissingClassDetectorTest extends AbstractCheckTest { public void testWrongSeparator1() throws Exception { mScopes = null; + mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS); assertEquals( "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.Foo.Bar, was not found in the project or the libraries [MissingRegistered]\n" + " <activity\n" + @@ -238,6 +271,7 @@ public class MissingClassDetectorTest extends AbstractCheckTest { public void testWrongSeparator2() throws Exception { mScopes = null; + mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS); assertEquals( "AndroidManifest.xml:14: Error: Class referenced in the manifest, test.pkg.Foo.Bar, was not found in the project or the libraries [MissingRegistered]\n" + " <activity\n" + @@ -257,6 +291,7 @@ public class MissingClassDetectorTest extends AbstractCheckTest { public void testNoClassesWithLibraries() throws Exception { mScopes = null; + mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS); assertEquals( "No warnings.", @@ -267,4 +302,75 @@ public class MissingClassDetectorTest extends AbstractCheckTest { )); } + public void testFragment() throws Exception { + mScopes = null; + mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS); + assertEquals("" + + "res/layout/fragment2.xml:7: Error: Class referenced in the layout file, my.app.Fragment, was not found in the project or the libraries [MissingRegistered]\n" + + " <fragment\n" + + " ^\n" + + "res/layout/fragment2.xml:12: Error: Class referenced in the layout file, my.app.MyView, was not found in the project or the libraries [MissingRegistered]\n" + + " <view\n" + + " ^\n" + + "res/layout/fragment2.xml:17: Error: Class referenced in the layout file, my.app.Fragment2, was not found in the project or the libraries [MissingRegistered]\n" + + " <fragment\n" + + " ^\n" + + "3 errors, 0 warnings\n", + + lintProject( + "bytecode/AndroidManifestRegs.xml=>AndroidManifest.xml", + "bytecode/.classpath=>.classpath", + "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java", + "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class", + "bytecode/TestService.java.txt=>src/test/pkg/TestService.java", + "bytecode/TestService.class.data=>bin/classes/test/pkg/TestService.class", + "bytecode/TestProvider.java.txt=>src/test/pkg/TestProvider.java", + "bytecode/TestProvider.class.data=>bin/classes/test/pkg/TestProvider.class", + "bytecode/TestProvider2.java.txt=>src/test/pkg/TestProvider2.java", + "bytecode/TestProvider2.class.data=>bin/classes/test/pkg/TestProvider2.class", + "bytecode/TestReceiver.java.txt=>src/test/pkg/TestReceiver.java", + "bytecode/TestReceiver.class.data=>bin/classes/test/pkg/TestReceiver.class", + "registration/Foo.java.txt=>src/test/pkg/Foo.java", + "registration/Foo.class.data=>bin/classes/test/pkg/Foo.class", + "registration/Bar.java.txt=>src/test/pkg/Foo/Bar.java", + "registration/Bar.class.data=>bin/classes/test/pkg/Foo/Bar.class", + + "res/layout/fragment2.xml" + )); + } + + public void testAnalytics() throws Exception { + mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS); + assertEquals("" + + "res/values/analytics.xml:13: Error: Class referenced in the analytics file, com.example.app.BaseActivity, was not found in the project or the libraries [MissingRegistered]\n" + + " <string name=\"com.example.app.BaseActivity\">Home</string>\n" + + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + + "res/values/analytics.xml:14: Error: Class referenced in the analytics file, com.example.app.PrefsActivity, was not found in the project or the libraries [MissingRegistered]\n" + + " <string name=\"com.example.app.PrefsActivity\">Preferences</string>\n" + + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + + "2 errors, 0 warnings\n", + + lintProject( + "bytecode/.classpath=>.classpath", + "res/values/analytics.xml", + "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java", + "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class" + )); + } + + public void testCustomView() throws Exception { + mEnabled = Sets.newHashSet(MISSING, INSTANTIATABLE, INNERCLASS); + assertEquals("" + + "res/layout/customview.xml:21: Error: Class referenced in the layout file, foo.bar.Baz, was not found in the project or the libraries [MissingRegistered]\n" + + " <foo.bar.Baz\n" + + " ^\n" + + "1 errors, 0 warnings\n", + + lintProject( + "bytecode/.classpath=>.classpath", + "res/layout/customview.xml", + "bytecode/OnClickActivity.java.txt=>src/test/pkg/OnClickActivity.java", + "bytecode/OnClickActivity.class.data=>bin/classes/test/pkg/OnClickActivity.class" + )); + } } diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/TypographyDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/TypographyDetectorTest.java index 16d0107..5173011 100644 --- a/lint/cli/src/test/java/com/android/tools/lint/checks/TypographyDetectorTest.java +++ b/lint/cli/src/test/java/com/android/tools/lint/checks/TypographyDetectorTest.java @@ -86,6 +86,13 @@ public class TypographyDetectorTest extends AbstractCheckTest { lintProject("res/values/typography.xml")); } + public void testAnalytics() throws Exception { + assertEquals("" + + "No warnings.", + + lintProject("res/values/analytics.xml")); + } + public void testSingleQuotesRange() { assertTrue(SINGLE_QUOTE.matcher("Foo: 'bar'").matches()); assertTrue(SINGLE_QUOTE.matcher("'Foo': bar").matches()); diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestRegs.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestRegs.xml new file mode 100644 index 0000000..e52f73c --- /dev/null +++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AndroidManifestRegs.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="test.pkg" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk android:minSdkVersion="10" /> + + <application + android:icon="@drawable/ic_launcher" + android:label="@string/app_name" > + <provider android:name=".TestProvider" /> + <provider android:name="test.pkg.TestProvider2" /> + <service android:name=".TestService" /> + <activity android:name="OnClickActivity" /> + <receiver android:name="TestReceiver" /> + + </application> + +</manifest> diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/fragment2.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/fragment2.xml new file mode 100644 index 0000000..3a672d1 --- /dev/null +++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/layout/fragment2.xml @@ -0,0 +1,53 @@ +<?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:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <fragment + class="my.app.Fragment" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <view + class="my.app.MyView" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <fragment + android:name="my.app.Fragment2" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <view + android:name="test.pkg.TestService" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <view + class="test.pkg.TestService" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <fragment + android:name="test.pkg.TestService" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <fragment + class="test.pkg.TestService" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <fragment + class="test.pkg.Foo$Bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <fragment + class="test.pkg.Nonexistent" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:ignore="MissingRegistered" /> + +</LinearLayout> diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/analytics.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/analytics.xml new file mode 100644 index 0000000..8ea40a8 --- /dev/null +++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/analytics.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8" ?> +<resources> + <!--Replace placeholder ID with your tracking ID--> + <string name="ga_trackingId">UA-12345678-1</string> + + <!--Enable Activity tracking--> + <bool name="ga_autoActivityTracking">true</bool> + + <!--Enable automatic exception tracking--> + <bool name="ga_reportUncaughtExceptions">true</bool> + + <!-- The screen names that will appear in your reporting --> + <string name="com.example.app.BaseActivity">Home</string> + <string name="com.example.app.PrefsActivity">Preferences</string> + <string name="test.pkg.OnClickActivity">Clicks</string> +</resources> diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/typography.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/typography.xml index 1dd4845..02ffb1c 100644 --- a/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/typography.xml +++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/res/values/typography.xml @@ -25,5 +25,6 @@ <item>Age 5 1/2</item> </string-array> <string name="ndash">X Y Z: 10 10 -1</string> + <string name="ga_trackingId">UA-0000-0</string> </resources> diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java index ada2fbf..d3dbe33 100644 --- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java +++ b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/MissingClassDetector.java @@ -18,6 +18,7 @@ package com.android.tools.lint.checks; import static com.android.SdkConstants.ANDROID_PKG_PREFIX; import static com.android.SdkConstants.ANDROID_URI; +import static com.android.SdkConstants.ATTR_CLASS; import static com.android.SdkConstants.ATTR_NAME; import static com.android.SdkConstants.ATTR_PACKAGE; import static com.android.SdkConstants.CONSTRUCTOR_NAME; @@ -26,8 +27,12 @@ import static com.android.SdkConstants.TAG_APPLICATION; import static com.android.SdkConstants.TAG_PROVIDER; import static com.android.SdkConstants.TAG_RECEIVER; import static com.android.SdkConstants.TAG_SERVICE; +import static com.android.SdkConstants.TAG_STRING; +import static com.android.SdkConstants.VIEW_FRAGMENT; +import static com.android.SdkConstants.VIEW_TAG; import com.android.annotations.NonNull; +import com.android.resources.ResourceFolderType; import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.ClassContext; import com.android.tools.lint.detector.api.Context; @@ -41,21 +46,25 @@ 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 com.android.utils.SdkUtils; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; import org.w3c.dom.Attr; import org.w3c.dom.Element; +import org.w3c.dom.Node; +import java.io.File; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Map; +import java.util.Set; /** * Checks to ensure that classes referenced in the manifest actually exist and are included @@ -76,8 +85,8 @@ public class MissingClassDetector extends LayoutDetector implements ClassScanner 8, Severity.ERROR, MissingClassDetector.class, - EnumSet.of(Scope.MANIFEST, Scope.CLASS_FILE, Scope.JAVA_LIBRARIES)).setMoreInfo( - "http://developer.android.com/guide/topics/manifest/manifest-intro.html"); //$NON-NLS-1$ + EnumSet.of(Scope.MANIFEST, Scope.CLASS_FILE, Scope.JAVA_LIBRARIES, Scope.RESOURCE_FILE)) + .setMoreInfo("http://developer.android.com/guide/topics/manifest/manifest-intro.html"); //$NON-NLS-1$ /** Are activity, service, receiver etc subclasses instantiatable? */ public static final Issue INSTANTIATABLE = Issue.create( @@ -114,6 +123,7 @@ public class MissingClassDetector extends LayoutDetector implements ClassScanner Scope.MANIFEST_SCOPE); private Map<String, Location.Handle> mReferencedClasses; + private Set<String> mCustomViews; private boolean mHaveClasses; /** Constructs a new {@link MissingClassDetector} */ @@ -130,40 +140,93 @@ public class MissingClassDetector extends LayoutDetector implements ClassScanner @Override public Collection<String> getApplicableElements() { - return Arrays.asList( - TAG_APPLICATION, - TAG_ACTIVITY, - TAG_SERVICE, - TAG_RECEIVER, - TAG_PROVIDER - ); + return ALL; } @Override - public void visitElement(@NonNull XmlContext context, @NonNull Element element) { + public boolean appliesTo(@NonNull ResourceFolderType folderType) { + return folderType == ResourceFolderType.VALUES || folderType == ResourceFolderType.LAYOUT; + } - Element root = element.getOwnerDocument().getDocumentElement(); - Attr classNameNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME); - if (classNameNode == null) { - return; + @Override + public void visitElement(@NonNull XmlContext context, @NonNull Element element) { + String pkg = null; + Node classNameNode; + String className; + String tag = element.getTagName(); + ResourceFolderType folderType = context.getResourceFolderType(); + if (folderType == ResourceFolderType.VALUES) { + if (!tag.equals(TAG_STRING)) { + return; + } + Attr attr = element.getAttributeNode(ATTR_NAME); + if (attr == null) { + return; + } + className = attr.getValue(); + classNameNode = attr; + } else if (folderType == ResourceFolderType.LAYOUT) { + if (tag.indexOf('.') > 0) { + className = tag; + classNameNode = element; + } else if (tag.equals(VIEW_FRAGMENT) || tag.equals(VIEW_TAG)) { + Attr attr = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME); + if (attr == null) { + attr = element.getAttributeNode(ATTR_CLASS); + } + if (attr == null) { + return; + } + className = attr.getValue(); + classNameNode = attr; + } else { + return; + } + } else { + // Manifest file + if (TAG_APPLICATION.equals(tag) + || TAG_ACTIVITY.equals(tag) + || TAG_SERVICE.equals(tag) + || TAG_RECEIVER.equals(tag) + || TAG_PROVIDER.equals(tag)) { + Element root = element.getOwnerDocument().getDocumentElement(); + pkg = root.getAttribute(ATTR_PACKAGE); + Attr attr = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME); + if (attr == null) { + return; + } + className = attr.getValue(); + classNameNode = attr; + } else { + return; + } } - String className = classNameNode.getValue(); if (className.isEmpty()) { return; } - String pkg = root.getAttribute(ATTR_PACKAGE); String fqcn; - if (className.startsWith(".")) { //$NON-NLS-1$ - fqcn = pkg + className; - } else if (className.indexOf('.') == -1) { - // According to the <activity> manifest element documentation, this is not - // valid ( http://developer.android.com/guide/topics/manifest/activity-element.html ) - // but it appears in manifest files and appears to be supported by the runtime - // so handle this in code as well: - fqcn = pkg + '.' + className; + int dotIndex = className.indexOf('.'); + if (dotIndex <= 0) { + if (pkg == null) { + return; // value file + } + if (dotIndex == 0) { + fqcn = pkg + className; + } else { + // According to the <activity> manifest element documentation, this is not + // valid ( http://developer.android.com/guide/topics/manifest/activity-element.html ) + // but it appears in manifest files and appears to be supported by the runtime + // so handle this in code as well: + fqcn = pkg + '.' + className; + } } else { // else: the class name is already a fully qualified class name fqcn = className; + // Only look for fully qualified tracker names in analytics files + if (folderType == ResourceFolderType.VALUES + && !SdkUtils.endsWith(context.file.getPath(), "analytics.xml")) { //$NON-NLS-1$ + return; + } } String signature = ClassContext.getInternalName(fqcn); @@ -176,14 +239,21 @@ public class MissingClassDetector extends LayoutDetector implements ClassScanner return; } - if (mReferencedClasses == null) { - mReferencedClasses = Maps.newHashMapWithExpectedSize(16); - } + Handle handle = null; + if (!context.getDriver().isSuppressed(MISSING, element)) { + if (mReferencedClasses == null) { + mReferencedClasses = Maps.newHashMapWithExpectedSize(16); + mCustomViews = Sets.newHashSetWithExpectedSize(8); + } - Handle handle = context.parser.createLocationHandle(context, element); - mReferencedClasses.put(signature, handle); + handle = context.parser.createLocationHandle(context, element); + mReferencedClasses.put(signature, handle); + if (folderType == ResourceFolderType.LAYOUT && !tag.equals(VIEW_FRAGMENT)) { + mCustomViews.add(className); + } + } - if (signature.indexOf('$') != -1) { + if (signature.indexOf('$') != -1 && pkg != null) { if (className.indexOf('$') == -1 && className.indexOf('.', 1) > 0) { boolean haveUpperCase = false; for (int i = 0, n = pkg.length(); i < n; i++) { @@ -209,8 +279,10 @@ public class MissingClassDetector extends LayoutDetector implements ClassScanner // class named Baz, we register *both* into the reference map. // When generating errors we'll look for these an rip them back out if // it looks like one of the two variations have been seen. - signature = signature.replace('$', '/'); - mReferencedClasses.put(signature, handle); + if (handle != null) { + signature = signature.replace('$', '/'); + mReferencedClasses.put(signature, handle); + } } } @@ -238,10 +310,30 @@ public class MissingClassDetector extends LayoutDetector implements ClassScanner } mReferencedClasses.remove(owner); + // Ignore usages of platform libraries + if (owner.startsWith("android/")) { //$NON-NLS-1$ + continue; + } + String message = String.format( "Class referenced in the manifest, %1$s, was not found in the " + - "project or the libraries", fqcn); + "project or the libraries", fqcn); Location location = handle.resolve(); + File parentFile = location.getFile().getParentFile(); + if (parentFile != null) { + String parent = parentFile.getName(); + ResourceFolderType type = ResourceFolderType.getFolderType(parent); + if (type == ResourceFolderType.LAYOUT) { + message = String.format( + "Class referenced in the layout file, %1$s, was not found in " + + "the project or the libraries", fqcn); + } else if (type == ResourceFolderType.VALUES) { + message = String.format( + "Class referenced in the analytics file, %1$s, was not " + + "found in the project or the libraries", fqcn); + } + } + context.report(MISSING, location, message, null); } } @@ -251,11 +343,13 @@ public class MissingClassDetector extends LayoutDetector implements ClassScanner @Override public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) { - if (!context.isFromClassLibrary()) { + if (!mHaveClasses && !context.isFromClassLibrary() + && context.getProject() == context.getMainProject()) { mHaveClasses = true; } String curr = classNode.name; if (mReferencedClasses != null && mReferencedClasses.containsKey(curr)) { + boolean isCustomView = mCustomViews.contains(curr); mReferencedClasses.remove(curr); // Ensure that the class is public, non static and has a null constructor! @@ -299,7 +393,7 @@ public class MissingClassDetector extends LayoutDetector implements ClassScanner } } - if (!hasDefaultConstructor) { + if (!hasDefaultConstructor && !isCustomView) { context.report(INSTANTIATABLE, context.getLocation(classNode), String.format( "This class should provide a default constructor (a public " + "constructor with no arguments) (%1$s)", diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java index 56d07e7..c4fd0a7 100644 --- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java +++ b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/RegistrationDetector.java @@ -218,7 +218,7 @@ public class RegistrationDetector extends LayoutDetector implements ClassScanner TAG_ACTIVITY, TAG_SERVICE, TAG_RECEIVER, - TAG_PROVIDER + TAG_PROVIDER, // Keep synchronized with {@link #sClasses} }; @@ -227,7 +227,7 @@ public class RegistrationDetector extends LayoutDetector implements ClassScanner ANDROID_APP_ACTIVITY, ANDROID_APP_SERVICE, ANDROID_CONTENT_BROADCAST_RECEIVER, - ANDROID_CONTENT_CONTENT_PROVIDER + ANDROID_CONTENT_CONTENT_PROVIDER, // Keep synchronized with {@link #sTags} }; @@ -242,7 +242,7 @@ public class RegistrationDetector extends LayoutDetector implements ClassScanner return null; } - /** Looks up the manifest tag a given framework class should be registered with */ + /** Looks up the tag a given framework class should be registered with */ private static String classToTag(String className) { for (int i = 0, n = sClasses.length; i < n; i++) { if (sClasses[i].equals(className)) { diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TypographyDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TypographyDetector.java index 209b3d7..8f2ed47 100644 --- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TypographyDetector.java +++ b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TypographyDetector.java @@ -16,6 +16,7 @@ package com.android.tools.lint.checks; +import static com.android.SdkConstants.ATTR_NAME; import static com.android.SdkConstants.TAG_STRING; import static com.android.SdkConstants.TAG_STRING_ARRAY; @@ -267,7 +268,7 @@ public class TypographyDetector extends ResourceXmlDetector { !Character.isWhitespace(matcher.group(2).charAt(0)) && Character.isWhitespace(matcher.group(1).charAt( matcher.group(1).length() - 1)); - if (!isNegativeNumber) { + if (!isNegativeNumber && !isAnalyticsTrackingId((Element) element)) { context.report(DASHES, element, context.getLocation(textNode), EN_DASH_MESSAGE, null); @@ -373,6 +374,11 @@ public class TypographyDetector extends ResourceXmlDetector { } } + private static boolean isAnalyticsTrackingId(Element element) { + String name = element.getAttribute(ATTR_NAME); + return "ga_trackingId".equals(name); //$NON-NLS-1$ + } + /** * An object describing a single edit to be made. The offset points to a * location to start editing; the length is the number of characters to |