diff options
15 files changed, 182 insertions, 69 deletions
diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java index 1d8cdc8..43d3727 100644 --- a/lint/cli/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java +++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ApiDetectorTest.java @@ -60,6 +60,19 @@ public class ApiDetectorTest extends AbstractCheckTest { )); } + public void testAttrWithoutSlash() throws Exception { + assertEquals("" + + "res/layout/divider.xml:7: Error: ?android:dividerHorizontal requires API level 11 (current min is 1) [NewApi]\n" + + " android:divider=\"?android:dividerHorizontal\"\n" + + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + + "1 errors, 0 warnings\n", + + lintProject( + "apicheck/minsdk1.xml=>AndroidManifest.xml", + "apicheck/divider.xml=>res/layout/divider.xml" + )); + } + public void testXmlApi14() throws Exception { assertEquals( "No warnings.", diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java index f13cff1..ee1a2f2 100644 --- a/lint/cli/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java +++ b/lint/cli/src/test/java/com/android/tools/lint/checks/TranslationDetectorTest.java @@ -209,4 +209,15 @@ public class TranslationDetectorTest extends AbstractCheckTest { "res/values-es/strings_locale.xml=>res/values/strings.xml", "res/values-es-rUS/strings.xml")); } + + public void testAnalytics() throws Exception { + // See http://code.google.com/p/android/issues/detail?id=43070 + assertEquals( + "No warnings.", + + lintProject( + "res/values/analytics.xml", + "res/values-es/donottranslate.xml" // to make app multilingual + )); + } } diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/ViewConstructorDetectorTest.java b/lint/cli/src/test/java/com/android/tools/lint/checks/ViewConstructorDetectorTest.java index 5517646..a84a74f 100644 --- a/lint/cli/src/test/java/com/android/tools/lint/checks/ViewConstructorDetectorTest.java +++ b/lint/cli/src/test/java/com/android/tools/lint/checks/ViewConstructorDetectorTest.java @@ -61,4 +61,16 @@ public class ViewConstructorDetectorTest extends AbstractCheckTest { "bin/classes/test/pkg/Intermediate$IntermediateCustomV.class" )); } + + public void testAbstract() throws Exception { + assertEquals( + "No warnings.", + + lintProject( + "bytecode/.classpath=>.classpath", + "bytecode/AndroidManifest.xml=>AndroidManifest.xml", + "bytecode/AbstractCustomView.java.txt=>src/test/bytecode/AbstractCustomView.java", + "bytecode/AbstractCustomView.class.data=>bin/classes/test/bytecode/AbstractCustomView.class" + )); + } } diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/divider.xml b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/divider.xml new file mode 100644 index 0000000..4244319 --- /dev/null +++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/apicheck/divider.xml @@ -0,0 +1,37 @@ +<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:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:divider="?android:dividerHorizontal" + android:orientation="horizontal" + android:showDividers="middle" + tools:context=".ItemListActivity" > + + <!-- + This layout is a two-pane layout for the Items + master/detail flow. See res/values-large/refs.xml and + res/values-sw600dp/refs.xml for an example of layout aliases + that replace the single-pane version of the layout with + this two-pane version. + + For more on layout aliases, see: + http://developer.android.com/training/multiscreen/screensizes.html#TaskUseAliasFilters + --> + + <fragment + android:id="@+id/item_list" + android:name="com.example.master.ItemListFragment" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + tools:layout="@android:layout/list_content" /> + + <FrameLayout + android:id="@+id/item_detail_container" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="3" /> + +</LinearLayout> diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractCustomView.class.data b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractCustomView.class.data Binary files differnew file mode 100644 index 0000000..8dc3dc6 --- /dev/null +++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractCustomView.class.data diff --git a/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractCustomView.java.txt b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractCustomView.java.txt new file mode 100644 index 0000000..aedbafd --- /dev/null +++ b/lint/cli/src/test/java/com/android/tools/lint/checks/data/bytecode/AbstractCustomView.java.txt @@ -0,0 +1,9 @@ +package test.pkg; + +import android.view.View; + +public abstract class AbstractCustomView extends View { + public AbstractCustomView() { + super(null); + } +} diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java index f091269..eca9256 100644 --- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java +++ b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/AnnotationDetector.java @@ -191,7 +191,7 @@ public class AnnotationDetector extends Detector implements Detector.JavaScanner // This issue doesn't have AST access: annotations are not // available for local variables or parameters - mContext.report(ISSUE,mContext.getLocation(node), String.format( + mContext.report(ISSUE, node, mContext.getLocation(node), String.format( "The @SuppressLint annotation cannot be used on a local " + "variable with the lint check '%1$s': move out to the " + "surrounding method", id), diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java index 936f882..146e9e1 100644 --- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java +++ b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ApiDetector.java @@ -101,6 +101,7 @@ import lombok.ast.Expression; import lombok.ast.ForwardingAstVisitor; import lombok.ast.If; import lombok.ast.ImportDeclaration; +import lombok.ast.InlineIfExpression; import lombok.ast.IntegralLiteral; import lombok.ast.MethodDeclaration; import lombok.ast.MethodInvocation; @@ -311,6 +312,9 @@ public class ApiDetector extends ResourceXmlDetector if (name.indexOf('.') != -1) { name = name.replace('.', '_'); } + } else if (value.startsWith(ANDROID_THEME_PREFIX)) { + owner = "android/R$attr"; //$NON-NLS-1$ + name = value.substring(ANDROID_THEME_PREFIX.length()); } else { return; } @@ -446,7 +450,8 @@ public class ApiDetector extends ResourceXmlDetector return; } - boolean checkCalls = context.isEnabled(UNSUPPORTED); + boolean checkCalls = context.isEnabled(UNSUPPORTED) + || context.isEnabled(INLINED); boolean checkMethods = context.isEnabled(OVERRIDE) && context.getMainProject().getBuildSdk() >= 1; String frameworkParent = null; @@ -1040,6 +1045,82 @@ public class ApiDetector extends ResourceXmlDetector return types; } + /** + * Checks whether the given instruction is a benign usage of a constant defined in + * a later version of Android than the application's {@code minSdkVersion}. + * + * @param node the instruction to check + * @param name the name of the constant + * @param owner the field owner + * @return true if the given usage is safe on older versions than the introduction + * level of the constant + */ + public boolean isBenignConstantUsage( + @Nullable lombok.ast.Node node, + @NonNull String name, + @NonNull String owner) { + if (owner.equals("android/os/Build$VERSION_CODES")) { //$NON-NLS-1$ + // These constants are required for compilation, not execution + // and valid code checks it even on older platforms + return true; + } + if (owner.equals("android/view/ViewGroup$LayoutParams") //$NON-NLS-1$ + && name.equals("MATCH_PARENT")) { //$NON-NLS-1$ + return true; + } + if (owner.equals("android/widget/AbsListView") //$NON-NLS-1$ + && ((name.equals("CHOICE_MODE_NONE") //$NON-NLS-1$ + || name.equals("CHOICE_MODE_MULTIPLE") //$NON-NLS-1$ + || name.equals("CHOICE_MODE_SINGLE")))) { //$NON-NLS-1$ + // android.widget.ListView#CHOICE_MODE_MULTIPLE and friends have API=1, + // but in API 11 it was moved up to the parent class AbsListView. + // Referencing AbsListView#CHOICE_MODE_MULTIPLE technically requires API 11, + // but the constant is the same as the older version, so accept this without + // warning. + return true; + } + + if (node == null) { + return false; + } + + // It's okay to reference the constant as a case constant (since that + // code path won't be taken) or in a condition of an if statement + lombok.ast.Node curr = node.getParent(); + while (curr != null) { + Class<? extends lombok.ast.Node> nodeType = curr.getClass(); + if (nodeType == Case.class) { + Case caseStatement = (Case) curr; + Expression condition = caseStatement.astCondition(); + return condition != null && isAncestor(condition, node); + } else if (nodeType == If.class) { + If ifStatement = (If) curr; + Expression condition = ifStatement.astCondition(); + return condition != null && isAncestor(condition, node); + } else if (nodeType == InlineIfExpression.class) { + InlineIfExpression ifStatement = (InlineIfExpression) curr; + Expression condition = ifStatement.astCondition(); + return condition != null && isAncestor(condition, node); + } + curr = curr.getParent(); + } + + return false; + } + + private static boolean isAncestor( + @NonNull lombok.ast.Node ancestor, + @Nullable lombok.ast.Node node) { + while (node != null) { + if (node == ancestor) { + return true; + } + node = node.getParent(); + } + + return false; + } + private final class ApiVisitor extends ForwardingAstVisitor { private JavaContext mContext; private Map<String, String> mClassToImport = Maps.newHashMap(); @@ -1363,60 +1444,6 @@ public class ApiDetector extends ResourceXmlDetector } /** - * Checks whether the given instruction is a benign usage of a constant defined - * in a later version of Android than the application's {@code minSdkVersion}. - * - * @param node the instruction to check - * @param name the name of the constant - * @param owner the field owner - * @return true if the given usage is safe on older versions than the introduction - * level of the constant - */ - public boolean isBenignConstantUsage( - @NonNull lombok.ast.Node node, - @NonNull String name, - @NonNull String owner) { - if (owner.equals("android/os/Build$VERSION_CODES")) { //$NON-NLS-1$ - // These constants are required for compilation, not execution - // and valid code checks it even on older platforms - return true; - } - if (owner.equals("android/view/ViewGroup$LayoutParams") //$NON-NLS-1$ - && name.equals("MATCH_PARENT")) { //$NON-NLS-1$ - return true; - } - if (owner.equals("android/widget/AbsListView") //$NON-NLS-1$ - && ((name.equals("CHOICE_MODE_NONE") //$NON-NLS-1$ - || name.equals("CHOICE_MODE_MULTIPLE") //$NON-NLS-1$ - || name.equals("CHOICE_MODE_SINGLE")))) { //$NON-NLS-1$ - // android.widget.ListView#CHOICE_MODE_MULTIPLE and friends have API=1, - // but in API 11 it was moved up to the parent class AbsListView. - // Referencing AbsListView#CHOICE_MODE_MULTIPLE technically requires API 11, - // but the constant is the same as the older version, so accept this without - // warning. - return true; - } - - // It's okay to reference the constant as a case constant (since that - // code path won't be taken) or in a condition of an if statement - lombok.ast.Node curr = node.getParent(); - boolean usedInExpression = false; - while (curr != null) { - Class<? extends lombok.ast.Node> nodeType = curr.getClass(); - if (nodeType == Case.class) { - return true; - } else if (nodeType == BinaryExpression.class) { - usedInExpression = true; - } else if (nodeType == If.class) { - return usedInExpression; - } - curr = curr.getParent(); - } - - return false; - } - - /** * Returns the minimum SDK to use according to the given AST node, or null * if no {@code TargetApi} annotations were found * diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ColorUsageDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ColorUsageDetector.java index 622af71..e7ffcb6 100644 --- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ColorUsageDetector.java +++ b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ColorUsageDetector.java @@ -94,7 +94,7 @@ public class ColorUsageDetector extends Detector implements Detector.JavaScanner if (methodName.endsWith("Color") //$NON-NLS-1$ && methodName.startsWith("set")) { //$NON-NLS-1$ context.report( - ISSUE, context.getLocation(select), String.format( + ISSUE, select, context.getLocation(select), String.format( "Should pass resolved color instead of resource id here: " + "getResources().getColor(%1$s)", select.toString()), null); diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/NonInternationalizedSmsDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/NonInternationalizedSmsDetector.java index 3ee7e82..e970572 100644 --- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/NonInternationalizedSmsDetector.java +++ b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/NonInternationalizedSmsDetector.java @@ -91,7 +91,7 @@ public class NonInternationalizedSmsDetector extends Detector implements Detecto String number = ((StringLiteral) destinationAddress).astValue(); if (!number.startsWith("+")) { //$NON-NLS-1$ - context.report(ISSUE, context.getLocation(destinationAddress), + context.report(ISSUE, node, context.getLocation(destinationAddress), "To make sure the SMS can be sent by all users, please start the SMS number " + "with a + and a country code or restrict the code invocation to people in the country " + "you are targeting.", diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java index 7bb07f2..033996e 100644 --- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java +++ b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/StringFormatDetector.java @@ -52,7 +52,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; -import java.util.Formatter; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -322,8 +321,6 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto @Override public void afterCheckProject(@NonNull Context context) { if (mFormatStrings != null) { - Formatter formatter = new Formatter(); - boolean checkCount = context.isEnabled(ARG_COUNT); boolean checkValid = context.isEnabled(INVALID); boolean checkTypes = context.isEnabled(ARG_TYPES); @@ -342,15 +339,13 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto // Check argument types (and also make sure that the formatting strings are valid) if (checkValid || checkTypes) { - checkTypes(context, formatter, checkValid, checkTypes, name, list); + checkTypes(context, checkValid, checkTypes, name, list); } } - - formatter.close(); } } - private static void checkTypes(Context context, Formatter formatter, boolean checkValid, + private static void checkTypes(Context context, boolean checkValid, boolean checkTypes, String name, List<Pair<Handle, String>> list) { Map<Integer, String> types = new HashMap<Integer, String>(); Map<Integer, Handle> typeDefinition = new HashMap<Integer, Handle>(); @@ -927,7 +922,7 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto "Format string '%1$s' is not a valid format string so it should not be " + "passed to String.format", name); - context.report(INVALID, location, message, null); + context.report(INVALID, call, location, message, null); return; } diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ToastDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ToastDetector.java index 01804af..e245fe8 100644 --- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ToastDetector.java +++ b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ToastDetector.java @@ -94,7 +94,7 @@ public class ToastDetector extends Detector implements Detector.JavaScanner { if (args.size() == 3) { Expression duration = args.last(); if (duration instanceof IntegralLiteral) { - context.report(ISSUE, context.getLocation(duration), + context.report(ISSUE, duration, context.getLocation(duration), "Expected duration Toast.LENGTH_SHORT or Toast.LENGTH_LONG, a custom " + "duration value is not supported", null); diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java index 5ca6046..0f892b1 100644 --- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java +++ b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/TranslationDetector.java @@ -162,7 +162,8 @@ public class TranslationDetector extends ResourceXmlDetector { } // Convention seen in various projects - mIgnoreFile = context.file.getName().startsWith("donottranslate"); //$NON-NLS-1$ + mIgnoreFile = context.file.getName().startsWith("donottranslate") //$NON-NLS-1$ + || UnusedResourceDetector.isAnalyticsFile(context); if (!context.getProject().getReportIssues()) { mIgnoreFile = true; diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java index f960118..caa112a 100644 --- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java +++ b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java @@ -429,8 +429,11 @@ public class UnusedResourceDetector extends ResourceXmlDetector implements Detec * Returns true if this XML file corresponds to an Analytics configuration file; * these contain some attributes read by the library which won't be flagged as * used by the application + * + * @param context the context used for scanning + * @return true if the file represents an analytics file */ - private static boolean isAnalyticsFile(XmlContext context) { + public static boolean isAnalyticsFile(Context context) { File file = context.file; return file.getPath().endsWith(ANALYTICS_FILE) && file.getName().equals(ANALYTICS_FILE); } diff --git a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ViewConstructorDetector.java b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ViewConstructorDetector.java index 0eba024..4bffdd7 100644 --- a/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ViewConstructorDetector.java +++ b/lint/libs/lint_checks/src/main/java/com/android/tools/lint/checks/ViewConstructorDetector.java @@ -89,6 +89,11 @@ public class ViewConstructorDetector extends Detector implements Detector.ClassS return; } + // Ignore abstract classes + if ((classNode.access & Opcodes.ACC_ABSTRACT) != 0) { + return; + } + if (isViewClass(context, classNode)) { checkConstructors(context, classNode); } |