diff --git a/generators/generators.iml b/generators/generators.iml index b6db11cc352..1406adca488 100644 --- a/generators/generators.iml +++ b/generators/generators.iml @@ -35,5 +35,6 @@ + \ No newline at end of file diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 862ddc7661e..5e0e608cf1b 100644 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -19,10 +19,7 @@ package org.jetbrains.kotlin.generators.tests import junit.framework.TestCase import org.jetbrains.kotlin.AbstractDataFlowValueRenderingTest import org.jetbrains.kotlin.addImport.AbstractAddImportTest -import org.jetbrains.kotlin.android.AbstractAndroidCompletionTest -import org.jetbrains.kotlin.android.AbstractAndroidFindUsagesTest -import org.jetbrains.kotlin.android.AbstractAndroidGotoTest -import org.jetbrains.kotlin.android.AbstractAndroidRenameTest +import org.jetbrains.kotlin.android.* import org.jetbrains.kotlin.android.configure.AbstractConfigureProjectTest import org.jetbrains.kotlin.annotation.AbstractAnnotationProcessorBoxTest import org.jetbrains.kotlin.asJava.AbstractCompilerLightClassTest @@ -136,6 +133,7 @@ import org.jetbrains.kotlin.resolve.constraintSystem.AbstractConstraintSystemTes import org.jetbrains.kotlin.serialization.AbstractLocalClassProtoTest import org.jetbrains.kotlin.shortenRefs.AbstractShortenRefsTest import org.jetbrains.kotlin.types.AbstractTypeBindingTest +import org.jetbrains.kotlin.uast.AbstractKotlinLintTest import java.io.File import java.util.* import java.util.regex.Pattern @@ -975,6 +973,12 @@ fun main(args: Array) { } } + testGroup("plugins/uast-kotlin/test", "plugins/uast-kotlin/testData") { + testClass() { + model("lint", excludeParentDirs = true) + } + } + testGroup("plugins/plugins-tests/tests", "plugins/annotation-collector/testData") { testClass() { model("collectToFile", recursive = false, extension = null) diff --git a/plugins/uast-kotlin/test/org.jetbrains.kotlin.uast/AbstractKotlinLintTest.kt b/plugins/uast-kotlin/test/org.jetbrains.kotlin.uast/AbstractKotlinLintTest.kt new file mode 100644 index 00000000000..e2dff1216b0 --- /dev/null +++ b/plugins/uast-kotlin/test/org.jetbrains.kotlin.uast/AbstractKotlinLintTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * 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 org.jetbrains.kotlin.uast + +import org.jetbrains.android.inspections.klint.AndroidLintInspectionBase +import org.jetbrains.kotlin.android.KotlinAndroidTestCase +import org.jetbrains.kotlin.test.InTextDirectivesUtils.findStringWithPrefixes +import org.jetbrains.kotlin.test.KotlinTestUtils +import java.io.File + +abstract class AbstractKotlinLintTest : KotlinAndroidTestCase() { + + override fun setUp() { + super.setUp() + AndroidLintInspectionBase.invalidateInspectionShortName2IssueMap() + } + + fun doTest(filename: String) { + val ktFile = File(filename) + val mainInspectionClassName = findStringWithPrefixes(ktFile.readText(), "// INSPECTION_CLASS: ") ?: error("Empty class name") + + val inspectionClassNames = mutableListOf(mainInspectionClassName) + for (i in 2..100) { + val className = findStringWithPrefixes(ktFile.readText(), "// INSPECTION_CLASS$i: ") ?: break + inspectionClassNames += className + } + + myFixture.enableInspections(*inspectionClassNames.map { className -> + val inspectionClass = Class.forName(className) + inspectionClass.newInstance() as AndroidLintInspectionBase + }.toTypedArray()) + + val additionalResourcesDir = File(ktFile.parentFile, getTestName(true)) + if (additionalResourcesDir.exists()) { + for (file in additionalResourcesDir.listFiles()) { + if (file.isFile) { + myFixture.copyFileToProject(file.absolutePath, file.name) + } + else if (file.isDirectory) { + myFixture.copyDirectoryToProject(file.absolutePath, file.name) + } + } + } + + val virtualFile = myFixture.copyFileToProject(ktFile.absolutePath, "src/" + getTestName(true) + ".kt"); + myFixture.configureFromExistingVirtualFile(virtualFile) + + myFixture.doHighlighting() + myFixture.checkHighlighting(true, false, false) + } + + override fun getTestDataPath() = KotlinTestUtils.getHomeDirectory() + "/plugins/uast-kotlin/testData/lint/" +} \ No newline at end of file diff --git a/plugins/uast-kotlin/test/org.jetbrains.kotlin.uast/AbstractKotlinUastStructureTest.kt b/plugins/uast-kotlin/test/org.jetbrains.kotlin.uast/AbstractKotlinUastStructureTest.kt index 68523225870..6e64a9d3b35 100644 --- a/plugins/uast-kotlin/test/org.jetbrains.kotlin.uast/AbstractKotlinUastStructureTest.kt +++ b/plugins/uast-kotlin/test/org.jetbrains.kotlin.uast/AbstractKotlinUastStructureTest.kt @@ -30,7 +30,7 @@ abstract class AbstractKotlinUastStructureTest : KotlinLightCodeInsightFixtureTe val renderFile = File(File(testDataPath, "render"), "$testName.txt") val psiFile = myFixture.file - val uElement = KotlinConverter.convertWithParent(psiFile) ?: error("UFile was not created") + val uElement = KotlinUastLanguagePlugin.converter.convertWithParent(psiFile) ?: error("UFile was not created") val logActual = uElement.logString() val renderActual = uElement.renderString() diff --git a/plugins/uast-kotlin/test/org/jetbrains/kotlin/uast/KotlinLintTestGenerated.java b/plugins/uast-kotlin/test/org/jetbrains/kotlin/uast/KotlinLintTestGenerated.java new file mode 100644 index 00000000000..b806b250fbc --- /dev/null +++ b/plugins/uast-kotlin/test/org/jetbrains/kotlin/uast/KotlinLintTestGenerated.java @@ -0,0 +1,169 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * 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 org.jetbrains.kotlin.uast; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("plugins/uast-kotlin/testData/lint") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class KotlinLintTestGenerated extends AbstractKotlinLintTest { + @TestMetadata("alarm.kt") + public void testAlarm() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/alarm.kt"); + doTest(fileName); + } + + public void testAllFilesPresentInLint() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("plugins/uast-kotlin/testData/lint"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("apiCheck.kt") + public void testApiCheck() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/apiCheck.kt"); + doTest(fileName); + } + + @TestMetadata("callSuper.kt") + public void testCallSuper() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/callSuper.kt"); + doTest(fileName); + } + + @TestMetadata("javaPerformance.kt") + public void testJavaPerformance() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/javaPerformance.kt"); + doTest(fileName); + } + + @TestMetadata("javaScriptInterface.kt") + public void testJavaScriptInterface() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/javaScriptInterface.kt"); + doTest(fileName); + } + + @TestMetadata("layoutInflation.kt") + public void testLayoutInflation() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/layoutInflation.kt"); + doTest(fileName); + } + + @TestMetadata("log.kt") + public void testLog() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/log.kt"); + doTest(fileName); + } + + @TestMetadata("noInternationalSms.kt") + public void testNoInternationalSms() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/noInternationalSms.kt"); + doTest(fileName); + } + + @TestMetadata("overrideConcrete.kt") + public void testOverrideConcrete() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/overrideConcrete.kt"); + doTest(fileName); + } + + @TestMetadata("parcel.kt") + public void testParcel() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/parcel.kt"); + doTest(fileName); + } + + @TestMetadata("sdCardTest.kt") + public void testSdCardTest() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/sdCardTest.kt"); + doTest(fileName); + } + + @TestMetadata("setJavaScriptEnabled.kt") + public void testSetJavaScriptEnabled() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/setJavaScriptEnabled.kt"); + doTest(fileName); + } + + @TestMetadata("sharedPrefs.kt") + public void testSharedPrefs() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/sharedPrefs.kt"); + doTest(fileName); + } + + @TestMetadata("sqlite.kt") + public void testSqlite() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/sqlite.kt"); + doTest(fileName); + } + + @TestMetadata("systemServices.kt") + public void testSystemServices() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/systemServices.kt"); + doTest(fileName); + } + + @TestMetadata("toast.kt") + public void testToast() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/toast.kt"); + doTest(fileName); + } + + @TestMetadata("valueOf.kt") + public void testValueOf() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/valueOf.kt"); + doTest(fileName); + } + + @TestMetadata("viewConstructor.kt") + public void testViewConstructor() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/viewConstructor.kt"); + doTest(fileName); + } + + @TestMetadata("viewHolder.kt") + public void testViewHolder() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/viewHolder.kt"); + doTest(fileName); + } + + @TestMetadata("wrongAnnotation.kt") + public void testWrongAnnotation() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/wrongAnnotation.kt"); + doTest(fileName); + } + + @TestMetadata("wrongImport.kt") + public void testWrongImport() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/wrongImport.kt"); + doTest(fileName); + } + + @TestMetadata("wrongViewCall.kt") + public void testWrongViewCall() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("plugins/uast-kotlin/testData/lint/wrongViewCall.kt"); + doTest(fileName); + } +} diff --git a/plugins/uast-kotlin/testData/lint/alarm.kt b/plugins/uast-kotlin/testData/lint/alarm.kt new file mode 100644 index 00000000000..fc067effaad --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/alarm.kt @@ -0,0 +1,24 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintShortAlarmInspection + +import android.app.AlarmManager + +@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +class TestAlarm { + fun test(alarmManager: AlarmManager) { + alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 5000, 60000, null); // OK + alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 6000, 70000, null); // OK + alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 50, 10, null); // ERROR + + alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 5000, // ERROR + OtherClass.MY_INTERVAL, null); // ERROR + + // Check value flow analysis, currently unsupported + val interval = 10; + val interval2 = 2L * interval; + alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, 5000, interval2, null); // ERROR + } + + private object OtherClass { + val MY_INTERVAL = 1000L; + } +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/lint/apiCheck.kt b/plugins/uast-kotlin/testData/lint/apiCheck.kt new file mode 100644 index 00000000000..6fa830e19b9 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/apiCheck.kt @@ -0,0 +1,270 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection +// INSPECTION_CLASS2: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInlinedApiInspection +// INSPECTION_CLASS3: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintOverrideInspection + +import android.animation.RectEvaluator +import android.annotation.TargetApi +import org.w3c.dom.DOMError +import org.w3c.dom.DOMErrorHandler +import org.w3c.dom.DOMLocator + +import android.view.ViewGroup.LayoutParams +import android.app.Activity +import android.app.ApplicationErrorReport +import android.graphics.PorterDuff +import android.graphics.Rect +import android.os.Build +import android.widget.Chronometer +import android.widget.GridLayout +import dalvik.bytecode.OpcodeInfo + +import android.os.Build.VERSION +import android.os.Build.VERSION.SDK_INT +import android.os.Build.VERSION_CODES +import android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH +import android.os.Build.VERSION_CODES.JELLY_BEAN +import android.widget.TextView + +@Suppress("SENSELESS_COMPARISON", "UNUSED_EXPRESSION", "UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +class ApiCallTest: Activity() { + + fun method(chronometer: Chronometer, locator: DOMLocator) { + + // Virtual call + getActionBar() // API 11 + actionBar // API 11 + + // Class references (no call or field access) + val error: DOMError? = null // API 8 + val clz = DOMErrorHandler::class // API 8 + + // Method call + chronometer.onChronometerTickListener // API 3 + + // Inherited method call (from TextView + chronometer.setTextIsSelectable(true) // API 11 + + GridLayout::class + + // Field access + val field = OpcodeInfo.MAXIMUM_VALUE // API 11 + + + val fillParent = LayoutParams.FILL_PARENT // API 1 + // This is a final int, which means it gets inlined + val matchParent = LayoutParams.MATCH_PARENT // API 8 + // Field access: non final + val batteryInfo = report!!.batteryInfo + + // Enum access + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) { + val mode = PorterDuff.Mode.OVERLAY // API 11 + } + } + + fun test(rect: Rect) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + RectEvaluator(rect); // OK + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (rect != null) { + RectEvaluator(rect); // OK + } + } + } + + fun test2(rect: Rect) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + RectEvaluator(rect); // OK + } + } + + fun test3(rect: Rect) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { + RectEvaluator(); // ERROR + } + } + + fun test4(rect: Rect) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + System.out.println("Something"); + RectEvaluator(rect); // OK + } else { + RectEvaluator(rect); // ERROR + } + } + + fun test5(rect: Rect) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) { + RectEvaluator(rect); // ERROR + } else { + RectEvaluator(rect); // ERROR + } + } + + fun test(priority: Boolean) { + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + GridLayout(null).getOrientation(); // Not flagged + } else { + GridLayout(null).getOrientation(); // Flagged + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + GridLayout(null).getOrientation(); // Not flagged + } else { + GridLayout(null).getOrientation(); // Flagged + } + + if (SDK_INT >= ICE_CREAM_SANDWICH) { + GridLayout(null).getOrientation(); // Not flagged + } else { + GridLayout(null).getOrientation(); // Flagged + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + GridLayout(null).getOrientation(); // Not flagged + } else { + GridLayout(null).getOrientation(); // Flagged + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + GridLayout(null).getOrientation(); // Flagged + } else { + GridLayout(null).getOrientation(); // Not flagged + } + + if (Build.VERSION.SDK_INT >= 14) { + GridLayout(null).getOrientation(); // Not flagged + } else { + GridLayout(null).getOrientation(); // Flagged + } + + if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH) { + GridLayout(null).getOrientation(); // Not flagged + } else { + GridLayout(null).getOrientation(); // Flagged + } + + // Nested conditionals + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + if (priority) { + GridLayout(null).getOrientation(); // Flagged + } else { + GridLayout(null).getOrientation(); // Flagged + } + } else { + GridLayout(null).getOrientation(); // Flagged + } + + // Nested conditionals 2 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + if (priority) { + GridLayout(null).getOrientation(); // Not flagged + } else { + GridLayout(null).getOrientation(); // Not flagged + } + } else { + GridLayout(null); // Flagged + } + } + + fun test2(priority: Boolean) { + if (android.os.Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { + GridLayout(null).getOrientation(); // Not flagged + } else { + GridLayout(null); // Flagged + } + + if (android.os.Build.VERSION.SDK_INT >= 16) { + GridLayout(null).getOrientation(); // Not flagged + } else { + GridLayout(null); // Flagged + } + + if (android.os.Build.VERSION.SDK_INT >= 13) { + GridLayout(null).getOrientation(); // Flagged + } else { + GridLayout(null); // Flagged + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + GridLayout(null).getOrientation(); // Not flagged + } else { + GridLayout(null); // Flagged + } + + if (SDK_INT >= JELLY_BEAN) { + GridLayout(null).getOrientation(); // Not flagged + } else { + GridLayout(null); // Flagged + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + GridLayout(null).getOrientation(); // Not flagged + } else { + GridLayout(null); // Flagged + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + GridLayout(null); // Flagged + } else { + GridLayout(null).getOrientation(); // Not flagged + } + + if (Build.VERSION.SDK_INT >= 16) { + GridLayout(null).getOrientation(); // Not flagged + } else { + GridLayout(null); // Flagged + } + + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { + GridLayout(null).getOrientation(); // Not flagged + } else { + GridLayout(null); // Flagged + } + } + + fun test(textView: TextView) { + if (textView.isSuggestionsEnabled()) { + //ERROR + } + if (textView.isSuggestionsEnabled) { + //ERROR + } + + if (SDK_INT >= JELLY_BEAN && textView.isSuggestionsEnabled()) { + //NO ERROR + } + + if (SDK_INT >= JELLY_BEAN && textView.isSuggestionsEnabled) { + //NO ERROR + } + + if (SDK_INT < JELLY_BEAN && textView.isSuggestionsEnabled()) { + //ERROR + } + + if (SDK_INT < JELLY_BEAN && textView.isSuggestionsEnabled) { + //ERROR + } + } + + @TargetApi(JELLY_BEAN) + fun testWithAnnotation(textView: TextView) { + if (textView.isSuggestionsEnabled()) { + //NO ERROR, annotation + } + + if (textView.isSuggestionsEnabled) { + //NO ERROR, annotation + } + } + + // Return type + internal // API 14 + val gridLayout: GridLayout? + get() = null + + private val report: ApplicationErrorReport? + get() = null +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/lint/callSuper.kt b/plugins/uast-kotlin/testData/lint/callSuper.kt new file mode 100644 index 00000000000..df30cfe7670 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/callSuper.kt @@ -0,0 +1,97 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintMissingSuperCallInspection + +package android.support.annotation + +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.FUNCTION) +annotation class CallSuper + +@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +class CallSuperTest { + private class Child : Parent() { + override fun test1() { + // ERROR + } + + override fun test2() { + // ERROR + } + + override fun test3() { + // ERROR + } + + override fun test4(arg: Int) { + // ERROR + } + + override fun test4(arg: String) { + // OK + } + + + override fun test5(arg1: Int, arg2: Boolean, arg3: Map, *>, // ERROR + arg4: Array, vararg arg5: Int) { + } + + override fun test5() { + // ERROR + super.test6() // (wrong super) + } + + override fun test6() { + // OK + val x = 5 + super.test6() + System.out.println(x) + } + } + + private open class Parent : ParentParent() { + @CallSuper + protected open fun test1() { + } + + override fun test3() { + super.test3() + } + + @CallSuper + protected open fun test4(arg: Int) { + } + + protected open fun test4(arg: String) { + } + + @CallSuper + protected open fun test5() { + } + + @CallSuper + protected open fun test5(arg1: Int, arg2: Boolean, arg3: Map, *>, + arg4: Array, vararg arg5: Int) { + } + } + + private open class ParentParent : ParentParentParent() { + @CallSuper + protected open fun test2() { + } + + @CallSuper + protected open fun test3() { + } + + @CallSuper + protected open fun test6() { + } + + @CallSuper + protected fun test7() { + } + + + } + + private open class ParentParentParent +} diff --git a/plugins/uast-kotlin/testData/lint/javaPerformance.kt b/plugins/uast-kotlin/testData/lint/javaPerformance.kt new file mode 100644 index 00000000000..2b7e32cc01c --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/javaPerformance.kt @@ -0,0 +1,193 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintDrawAllocationInspection +// INSPECTION_CLASS2: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUseSparseArraysInspection +// INSPECTION_CLASS3: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUseValueOfInspection + +import java.util.HashMap +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.* +import android.util.AttributeSet +import android.util.SparseArray +import android.widget.Button + +@SuppressWarnings("unused") +@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +class JavaPerformanceTest(context: Context, attrs: AttributeSet, defStyle: Int) : Button(context, attrs, defStyle) { + + private var cachedRect: Rect? = null + private var shader: Any? = null + private var lastHeight: Int = 0 + private var lastWidth: Int = 0 + + override fun onDraw(canvas: android.graphics.Canvas) { + super.onDraw(canvas) + + // Various allocations: + java.lang.String("foo") + val s = java.lang.String("bar") + + // This one should not be reported: + @SuppressLint("DrawAllocation") + val i = 5 + + // Cached object initialized lazily: should not complain about these + if (cachedRect == null) { + cachedRect = Rect(0, 0, 100, 100) + } + if (cachedRect == null || cachedRect!!.width() != 50) { + cachedRect = Rect(0, 0, 50, 100) + } + + val b = java.lang.Boolean.valueOf(true)!! // auto-boxing + dummy(1, 2) + + // Non-allocations + super.animate() + dummy2(1, 2) + + // This will involve allocations, but we don't track + // inter-procedural stuff here + someOtherMethod() + } + + internal fun dummy(foo: Int?, bar: Int) { + dummy2(foo!!, bar) + } + + internal fun dummy2(foo: Int, bar: Int) { + } + + internal fun someOtherMethod() { + // Allocations are okay here + java.lang.String("foo") + val s = java.lang.String("bar") + val b = java.lang.Boolean.valueOf(true)!! // auto-boxing + + + // Sparse array candidates + val myMap = HashMap() + // Should use SparseBooleanArray + val myBoolMap = HashMap() + // Should use SparseIntArray + val myIntMap = java.util.HashMap() + + // This one should not be reported: + @SuppressLint("UseSparseArrays") + val myOtherMap = HashMap() + } + + protected fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int, + x: Boolean) { + // wrong signature + java.lang.String("not an error") + } + + protected fun onMeasure(widthMeasureSpec: Int) { + // wrong signature + java.lang.String("not an error") + } + + protected fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, + bottom: Int, wrong: Int) { + // wrong signature + java.lang.String("not an error") + } + + protected fun onLayout(changed: Boolean, left: Int, top: Int, right: Int) { + // wrong signature + java.lang.String("not an error") + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, + bottom: Int) { + java.lang.String("flag me") + } + + @SuppressWarnings("null") // not real code + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + java.lang.String("flag me") + + // Forbidden factory methods: + Bitmap.createBitmap(100, 100, null) + android.graphics.Bitmap.createScaledBitmap(null, 100, 100, false) + BitmapFactory.decodeFile(null) + val canvas: Canvas? = null + canvas!!.getClipBounds() // allocates on your behalf + canvas.clipBounds // allocates on your behalf + canvas.getClipBounds(null) // NOT an error + + val layoutWidth = width + val layoutHeight = height + if (mAllowCrop && (mOverlay == null || mOverlay!!.width != layoutWidth || + mOverlay!!.height != layoutHeight)) { + mOverlay = Bitmap.createBitmap(layoutWidth, layoutHeight, Bitmap.Config.ARGB_8888) + mOverlayCanvas = Canvas(mOverlay!!) + } + + if (widthMeasureSpec == 42) { + throw IllegalStateException("Test") // NOT an allocation + } + + // More lazy init tests + var initialized = false + if (!initialized) { + java.lang.String("foo") + initialized = true + } + + // NOT lazy initialization + if (!initialized || mOverlay == null) { + java.lang.String("foo") + } + } + + internal fun factories() { + val i1 = 42 + val l1 = 42L + val b1 = true + val c1 = 'c' + val f1 = 1.0f + val d1 = 1.0 + + // The following should not generate errors: + val i3 = Integer.valueOf(42) + } + + private val mAllowCrop: Boolean = false + private var mOverlayCanvas: Canvas? = null + private var mOverlay: Bitmap? = null + + override fun layout(l: Int, t: Int, r: Int, b: Int) { + // Using "this." to reference fields + if (this.shader == null) + this.shader = LinearGradient(0f, 0f, width.toFloat(), 0f, intArrayOf(0), null, + Shader.TileMode.REPEAT) + + val width = width + val height = height + + if (shader == null || lastWidth != width || lastHeight != height) { + lastWidth = width + lastHeight = height + + shader = LinearGradient(0f, 0f, width.toFloat(), 0f, intArrayOf(0), null, Shader.TileMode.REPEAT) + } + } + + fun inefficientSparseArray() { + SparseArray() // Use SparseIntArray instead + SparseArray() // Use SparseLongArray instead + SparseArray() // Use SparseBooleanArray instead + SparseArray() // OK + } + + fun longSparseArray() { + // but only minSdkVersion >= 17 or if has v4 support lib + val myStringMap = HashMap() + } + + fun byteSparseArray() { + // bytes easily apply to ints + val myByteMap = HashMap() + } +} diff --git a/plugins/uast-kotlin/testData/lint/javaScriptInterface.kt b/plugins/uast-kotlin/testData/lint/javaScriptInterface.kt new file mode 100644 index 00000000000..3b4d09c7215 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/javaScriptInterface.kt @@ -0,0 +1,58 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintJavascriptInterfaceInspection + +import android.annotation.SuppressLint +import android.webkit.JavascriptInterface +import android.webkit.WebView + +@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "UNUSED_VALUE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +class JavaScriptTestK { + fun test(webview: WebView) { + webview.addJavascriptInterface(AnnotatedObject(), "myobj") + + webview.addJavascriptInterface(InheritsFromAnnotated(), "myobj") + webview.addJavascriptInterface(NonAnnotatedObject(), "myobj") + + var o: Any = NonAnnotatedObject() + webview.addJavascriptInterface(o, "myobj") + o = InheritsFromAnnotated() + webview.addJavascriptInterface(o, "myobj") + } + + fun test(webview: WebView, object1: AnnotatedObject, object2: NonAnnotatedObject) { + webview.addJavascriptInterface(object1, "myobj") + webview.addJavascriptInterface(object2, "myobj") + } + + @SuppressLint("JavascriptInterface") + fun testSuppressed(webview: WebView) { + webview.addJavascriptInterface(NonAnnotatedObject(), "myobj") + } + + fun testLaterReassignment(webview: WebView) { + var o: Any = NonAnnotatedObject() + val t = o + webview.addJavascriptInterface(t, "myobj") + o = AnnotatedObject() + } + + class NonAnnotatedObject() { + fun test1() {} + fun test2() {} + } + + open class AnnotatedObject { + @JavascriptInterface + open fun test1() {} + + open fun test2() {} + + @JavascriptInterface + fun test3() {} + } + + class InheritsFromAnnotated : AnnotatedObject() { + override fun test1() {} + override fun test2() {} + } + +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/lint/layoutInflation.kt b/plugins/uast-kotlin/testData/lint/layoutInflation.kt new file mode 100644 index 00000000000..65bd1e77cf5 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/layoutInflation.kt @@ -0,0 +1,50 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInflateParamsInspection + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter + +@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +abstract class LayoutInflationTest : BaseAdapter() { + lateinit var mInflater: LayoutInflater + + override fun getView(position: Int, convertView: View, parent: ViewGroup): View { + var view = convertView + view = mInflater.inflate(R.layout.your_layout, null) + view = mInflater.inflate(R.layout.your_layout, null, true) + view = mInflater.inflate(R.layout.your_layout, parent) + view = WeirdInflater.inflate(view, null) + + return view + } + + object WeirdInflater { + fun inflate(view: View, parent: View?) = view + } + + object R { + object layout { + val your_layout = 1 + } + } +} + +@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +abstract class LayoutInflationTest2 : BaseAdapter() { + lateinit var mInflater: LayoutInflater + + override fun getView(position: Int, convertView: View, parent: ViewGroup): View? { + return if (true) { + mInflater.inflate(R.layout.your_layout, parent) + } else { + null + } + } + + object R { + object layout { + val your_layout = 1 + } + } +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/lint/log.kt b/plugins/uast-kotlin/testData/lint/log.kt new file mode 100644 index 00000000000..7ce3a11e3c9 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/log.kt @@ -0,0 +1,111 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintLogConditionalInspection +// INSPECTION_CLASS2: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintLogTagMismatchInspection +// INSPECTION_CLASS3: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintLongLogTagInspection + +import android.annotation.SuppressLint +import android.util.Log +import android.util.Log.DEBUG + +@SuppressWarnings("UnusedDeclaration") +@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +class LogTest { + + fun checkConditional(m: String) { + Log.d(TAG1, "message") // ok: unconditional, but not performing computation + Log.d(TAG1, m) // ok: unconditional, but not performing computation + Log.d(TAG1, "a" + "b") // ok: unconditional, but not performing non-constant computation + Log.d(TAG1, Constants.MY_MESSAGE) // ok: unconditional, but constant string + Log.i(TAG1, "message" + m) // error: unconditional w/ computation + Log.i(TAG1, toString()) // error: unconditional w/ computation + Log.e(TAG1, toString()) // ok: only flagging debug/info messages + Log.w(TAG1, toString()) // ok: only flagging debug/info messages + Log.wtf(TAG1, toString()) // ok: only flagging debug/info messages + if (Log.isLoggable(TAG1, 0)) { + Log.d(TAG1, toString()) // ok: conditional + } + } + + fun checkWrongTag(tag: String) { + if (Log.isLoggable(TAG1, Log.DEBUG)) { + Log.d(TAG2, "message") // warn: mismatched tags! + } + if (Log.isLoggable("my_tag", Log.DEBUG)) { + Log.d("other_tag", "message") // warn: mismatched tags! + } + if (Log.isLoggable("my_tag", Log.DEBUG)) { + Log.d("my_tag", "message") // ok: strings equal + } + if (Log.isLoggable(tag, Log.DEBUG)) { + Log.d(tag, "message") // ok: same variable + } + } + + fun checkLongTag(shouldLog: Boolean) { + if (shouldLog) { + // String literal tags + Log.d("short_tag", "message") // ok: short + Log.d("really_really_really_really_really_long_tag", "message") // error: too long + + // Resolved field tags + Log.d(TAG1, "message") // ok: short + Log.d(TAG22, "message") // ok: short + Log.d(TAG23, "message") // ok: threshold + Log.d(TAG24, "message") // error: too long + Log.d(LONG_TAG, "message") // error: way too long + + // Locally defined variable tags + val LOCAL_TAG = "MyReallyReallyReallyReallyReallyLongTag" + Log.d(LOCAL_TAG, "message") // error: too long + + // Concatenated tags + Log.d(TAG22 + TAG1, "message") // error: too long + Log.d(TAG22 + "MyTag", "message") // error: too long + } + } + + fun checkWrongLevel(tag: String) { + if (Log.isLoggable(TAG1, Log.DEBUG)) { + Log.d(TAG1, "message") // ok: right level + } + if (Log.isLoggable(TAG1, Log.INFO)) { + Log.i(TAG1, "message") // ok: right level + } + if (Log.isLoggable(TAG1, Log.DEBUG)) { + Log.v(TAG1, "message") // warn: wrong level + } + if (Log.isLoggable(TAG1, DEBUG)) { + // static import of level + Log.v(TAG1, "message") // warn: wrong level + } + if (Log.isLoggable(TAG1, Log.VERBOSE)) { + Log.d(TAG1, "message") // warn? verbose is a lower logging level, which includes debug + } + if (Log.isLoggable(TAG1, Constants.MY_LEVEL)) { + Log.d(TAG1, "message") // ok: unknown level alias + } + } + + @SuppressLint("all") + fun suppressed1() { + Log.d(TAG1, "message") // ok: suppressed + } + + @SuppressLint("LogConditional") + fun suppressed2() { + Log.d(TAG1, "message") // ok: suppressed + } + + private object Constants { + val MY_MESSAGE = "My Message" + val MY_LEVEL = 5 + } + + companion object { + private val TAG1 = "MyTag1" + private val TAG2 = "MyTag2" + private val TAG22 = "1234567890123456789012" + private val TAG23 = "12345678901234567890123" + private val TAG24 = "123456789012345678901234" + private val LONG_TAG = "MyReallyReallyReallyReallyReallyLongTag" + } +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/lint/noInternationalSms.kt b/plugins/uast-kotlin/testData/lint/noInternationalSms.kt new file mode 100644 index 00000000000..31fecc25918 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/noInternationalSms.kt @@ -0,0 +1,19 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUnlocalizedSmsInspection + +import android.content.Context +import android.telephony.SmsManager + +@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +class NonInternationalizedSmsDetectorTest { + private fun sendLocalizedMessage(context: Context) { + // Don't warn here + val sms = SmsManager.getDefault() + sms.sendTextMessage("+1234567890", null, null, null, null) + } + + private fun sendAlternativeCountryPrefix(context: Context) { + // Do warn here + val sms = SmsManager.getDefault() + sms.sendMultipartTextMessage("001234567890", null, null, null, null) + } +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/lint/overrideConcrete.kt b/plugins/uast-kotlin/testData/lint/overrideConcrete.kt new file mode 100644 index 00000000000..55a5bb3c228 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/overrideConcrete.kt @@ -0,0 +1,61 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintOverrideAbstractInspection + +import android.annotation.SuppressLint +import android.annotation.TargetApi +import android.os.Build +import android.service.notification.NotificationListenerService +import android.service.notification.StatusBarNotification + +@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) +@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +class OverrideConcreteTest2 { + // OK: This one specifies both methods + private open class MyNotificationListenerService1 : NotificationListenerService() { + override fun onNotificationPosted(statusBarNotification: StatusBarNotification) { + } + + override fun onNotificationRemoved(statusBarNotification: StatusBarNotification) { + } + } + + // Error: Misses onNotificationPosted + private class MyNotificationListenerService2 : NotificationListenerService() { + override fun onNotificationRemoved(statusBarNotification: StatusBarNotification) { + } + } + + // Error: Misses onNotificationRemoved + private open class MyNotificationListenerService3 : NotificationListenerService() { + override fun onNotificationPosted(statusBarNotification: StatusBarNotification) { + } + } + + // Error: Missing both; wrong signatures (first has wrong arg count, second has wrong type) + private class MyNotificationListenerService4 : NotificationListenerService() { + fun onNotificationPosted(statusBarNotification: StatusBarNotification, flags: Int) { + } + + fun onNotificationRemoved(statusBarNotification: Int) { + } + } + + // OK: Inherits from a class which define both + private class MyNotificationListenerService5 : MyNotificationListenerService1() + + // OK: Inherits from a class which defines only one, but the other one is defined here + private class MyNotificationListenerService6 : MyNotificationListenerService3() { + override fun onNotificationRemoved(statusBarNotification: StatusBarNotification) { + } + } + + // Error: Inheriting from a class which only defines one + private class MyNotificationListenerService7 : MyNotificationListenerService3() + + // OK: Has target api setting a local version that is high enough + @TargetApi(21) + private class MyNotificationListenerService8 : NotificationListenerService() + + // OK: Suppressed + @SuppressLint("OverrideAbstract") + private class MyNotificationListenerService9 : MyNotificationListenerService1() +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/lint/parcel.kt b/plugins/uast-kotlin/testData/lint/parcel.kt new file mode 100644 index 00000000000..4434a6bf8a0 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/parcel.kt @@ -0,0 +1,37 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintParcelCreatorInspection + +@file:Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +import android.os.Parcel +import android.os.Parcelable + +class MyParcelable1 : Parcelable { + override fun describeContents() = 0 + override fun writeToParcel(arg0: Parcel, arg1: Int) {} +} + +internal class MyParcelable2 : Parcelable { + override fun describeContents() = 0 + + override fun writeToParcel(arg0: Parcel, arg1: Int) {} + + companion object { + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun newArray(size: Int) = null!! + override fun createFromParcel(source: Parcel?) = null!! + } + } +} + +internal class MyParcelable3 : Parcelable { + override fun describeContents() = 0 + override fun writeToParcel(arg0: Parcel, arg1: Int) {} + + companion object { + val CREATOR = 0 // Wrong type + } +} + +internal abstract class MyParcelable4 : Parcelable { + override fun describeContents() = 0 + override fun writeToParcel(arg0: Parcel, arg1: Int) {} +} diff --git a/plugins/uast-kotlin/testData/lint/sdCardTest.kt b/plugins/uast-kotlin/testData/lint/sdCardTest.kt new file mode 100644 index 00000000000..41c48d48afb --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/sdCardTest.kt @@ -0,0 +1,40 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSdCardPathInspection + +import java.io.File +import android.content.Intent +import android.net.Uri + +/** + * Ignore comments - create("/sdcard/foo") + */ +@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +class SdCardTest { + internal var deviceDir = File("/sdcard/vr") + + init { + if (PROFILE_STARTUP) { + android.os.Debug.startMethodTracing("/sdcard/launcher") + } + + if (File("/sdcard").exists()) { + } + val FilePath = "/sdcard/" + File("test") + System.setProperty("foo.bar", "file://sdcard") + + + val intent = Intent(Intent.ACTION_PICK) + intent.setDataAndType(Uri.parse("file://sdcard/foo.json"), "application/bar-json") + intent.putExtra("path-filter", "/sdcard(/.+)*") + intent.putExtra("start-dir", "/sdcard") + val mypath = "/data/data/foo" + val base = "/data/data/foo.bar/test-profiling" + val s = "file://sdcard/foo" +} + +companion object { + private val PROFILE_STARTUP = true + private val SDCARD_TEST_HTML = "/sdcard/test.html" + val SDCARD_ROOT = "/sdcard" + val PACKAGES_PATH = "/sdcard/o/packages/" +} +} diff --git a/plugins/uast-kotlin/testData/lint/setJavaScriptEnabled.kt b/plugins/uast-kotlin/testData/lint/setJavaScriptEnabled.kt new file mode 100644 index 00000000000..2c115572e2a --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/setJavaScriptEnabled.kt @@ -0,0 +1,24 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSetJavaScriptEnabledInspection + +import android.annotation.SuppressLint +import android.app.Activity +import android.webkit.WebView + +@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +public class HelloWebApp : Activity() { + + fun test(webView: WebView) { + webView.settings.javaScriptEnabled = true // bad + webView.getSettings().setJavaScriptEnabled(true) // bad + webView.getSettings().setJavaScriptEnabled(false) // good + webView.loadUrl("file:///android_asset/www/index.html") + } + + @SuppressLint("SetJavaScriptEnabled") + fun suppressed(webView: WebView) { + webView.getSettings().javaScriptEnabled = true; // bad + webView.getSettings().setJavaScriptEnabled(true) // bad + webView.getSettings().setJavaScriptEnabled(false); // good + webView.loadUrl("file:///android_asset/www/index.html"); + } +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/lint/sharedPrefs.kt b/plugins/uast-kotlin/testData/lint/sharedPrefs.kt new file mode 100644 index 00000000000..cfb18d9afe3 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/sharedPrefs.kt @@ -0,0 +1,69 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintCommitPrefEditsInspection + +import android.app.Activity +import android.content.Context +import android.os.Bundle +import android.preference.PreferenceManager + +@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +class SharedPrefsText(context: Context) : Activity() { + // OK 1 + fun onCreate1(savedInstanceState: Bundle) { + super.onCreate(savedInstanceState) + val preferences = PreferenceManager.getDefaultSharedPreferences(this) + val editor = preferences.edit() + editor.putString("foo", "bar") + editor.putInt("bar", 42) + editor.commit() + } + + // OK 2 + fun onCreate2(savedInstanceState: Bundle, apply: Boolean) { + super.onCreate(savedInstanceState) + val preferences = PreferenceManager.getDefaultSharedPreferences(this) + val editor = preferences.edit() + editor.putString("foo", "bar") + editor.putInt("bar", 42) + if (apply) { + editor.apply() + } + } + + // Not a bug + fun test(foo: Foo) { + val bar1 = foo.edit() + val bar3 = edit() + apply() + } + + internal fun apply() { + + } + + fun edit(): Bar { + return Bar() + } + + class Foo { + internal fun edit(): Bar { + return Bar() + } + } + + class Bar + + // Bug + fun bug1(savedInstanceState: Bundle) { + super.onCreate(savedInstanceState) + val preferences = PreferenceManager.getDefaultSharedPreferences(this) + val editor = preferences.edit() + editor.putString("foo", "bar") + editor.putInt("bar", 42) + } + + init { + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val editor = preferences.edit() + editor.putString("foo", "bar") + } +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/lint/sqlite.kt b/plugins/uast-kotlin/testData/lint/sqlite.kt new file mode 100644 index 00000000000..95da6d3f5f4 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/sqlite.kt @@ -0,0 +1,7 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSQLiteStringInspection + +import android.database.sqlite.SQLiteDatabase + +fun test(db: SQLiteDatabase) { + db.execSQL("CREATE TABLE COMPANY(NAME STRING)") +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/lint/systemServices.kt b/plugins/uast-kotlin/testData/lint/systemServices.kt new file mode 100644 index 00000000000..2e6860502b0 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/systemServices.kt @@ -0,0 +1,29 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintServiceCastInspection + +import android.content.ClipboardManager +import android.app.Activity +import android.app.WallpaperManager +import android.content.Context +import android.hardware.display.DisplayManager +import android.service.wallpaper.WallpaperService + +@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +class SystemServiceTest : Activity() { + + fun test1() { + val displayServiceOk = getSystemService(DISPLAY_SERVICE) as DisplayManager + val displayServiceWrong = getSystemService(DEVICE_POLICY_SERVICE) as DisplayManager + val wallPaperOk = getSystemService(WALLPAPER_SERVICE) as WallpaperService + val wallPaperWrong = getSystemService(WALLPAPER_SERVICE) as WallpaperManager + } + + fun test2(context: Context) { + val displayServiceOk = context.getSystemService(DISPLAY_SERVICE) as DisplayManager + val displayServiceWrong = context.getSystemService(DEVICE_POLICY_SERVICE) as DisplayManager + } + + fun clipboard(context: Context) { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clipboard2 = context.getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager + } +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/lint/toast.kt b/plugins/uast-kotlin/testData/lint/toast.kt new file mode 100644 index 00000000000..a84df0e6b32 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/toast.kt @@ -0,0 +1,55 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintShowToastInspection + +import android.app.Activity +import android.content.Context +import android.widget.Toast + +@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +class ToastTest(context: Context) : Activity() { + private fun createToast(context: Context): Toast { + // Don't warn here + return Toast.makeText(context, "foo", Toast.LENGTH_LONG) + } + + private fun showToast(context: Context) { + // Don't warn here + val toast = Toast.makeText(context, "foo", Toast.LENGTH_LONG) + System.out.println("Other intermediate code here") + val temp = 5 + 2 + toast.show() + } + + private fun showToast2(context: Context) { + // Don't warn here + val duration = Toast.LENGTH_LONG + Toast.makeText(context, "foo", Toast.LENGTH_LONG).show() + Toast.makeText(context, R.string.app_name, duration).show() + } + + private fun broken(context: Context) { + // Errors + Toast.makeText(context, "foo", Toast.LENGTH_LONG) + val toast = Toast.makeText(context, R.string.app_name, 5000) + toast.duration + } + + init { + Toast.makeText(context, "foo", Toast.LENGTH_LONG) + } + + @android.annotation.SuppressLint("ShowToast") + private fun checkSuppress1(context: Context) { + val toast = Toast.makeText(this, "MyToast", Toast.LENGTH_LONG) + } + + private fun checkSuppress2(context: Context) { + @android.annotation.SuppressLint("ShowToast") + val toast = Toast.makeText(this, "MyToast", Toast.LENGTH_LONG) + } + + class R { + object string { + val app_name = 1 + } + } +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/lint/valueOf.kt b/plugins/uast-kotlin/testData/lint/valueOf.kt new file mode 100644 index 00000000000..1702302715f --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/valueOf.kt @@ -0,0 +1,8 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintUseValueOfInspection + +@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +class Simple { + fun test() { + Integer(5) + } +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/lint/viewConstructor.kt b/plugins/uast-kotlin/testData/lint/viewConstructor.kt new file mode 100644 index 00000000000..2087cee75c4 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/viewConstructor.kt @@ -0,0 +1,32 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintViewConstructorInspection + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.TextView + +class View1(context: Context?) : View(context) +class View2(context: Context?, attrs: AttributeSet?) : View(context, attrs) +class View3(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : TextView(context, attrs, defStyleAttr) + +// Error +class View4(int: Int, context: Context?) : View(context) + +// Error +class View5(context: Context?, attrs: AttributeSet?, val name: String) : View(context, attrs) + +class View6 : View { + constructor(context: Context) : super(context) { + + } +} + +class View7 : View { + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) +} + +// Error +class View8 : View { + constructor(context: Context, a: Int) : super(context) +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/lint/viewHolder.kt b/plugins/uast-kotlin/testData/lint/viewHolder.kt new file mode 100644 index 00000000000..ad2f7fc07e9 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/viewHolder.kt @@ -0,0 +1,154 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintViewHolderInspection + +@file:Suppress("NAME_SHADOWING", "unused", "UNUSED_VALUE", "VARIABLE_WITH_REDUNDANT_INITIALIZER", "UNUSED_VARIABLE") + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.LinearLayout +import android.widget.TextView +import java.util.ArrayList + +@SuppressWarnings("ConstantConditions", "UnusedDeclaration") +abstract class ViewHolderTest : BaseAdapter() { + override fun getCount() = 0 + override fun getItem(position: Int) = null + override fun getItemId(position: Int) = 0L + + class Adapter1 : ViewHolderTest() { + override fun getView(position: Int, convertView: View, parent: ViewGroup) = null + } + + class Adapter2 : ViewHolderTest() { + lateinit var mInflater: LayoutInflater + + override fun getView(position: Int, convertView: View, parent: ViewGroup): View { + var convertView = convertView + // Should use View Holder pattern here + convertView = mInflater.inflate(R.layout.your_layout, null) + + val text = convertView.findViewById(R.id.text) as TextView + text.text = "Position " + position + + return convertView + } + } + + class Adapter3 : ViewHolderTest() { + lateinit var mInflater: LayoutInflater + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + var convertView = convertView + // Already using View Holder pattern + if (convertView == null) { + convertView = mInflater.inflate(R.layout.your_layout, null) + } + + val text = convertView!!.findViewById(R.id.text) as TextView + text.text = "Position " + position + + return convertView + } + } + + class Adapter4 : ViewHolderTest() { + lateinit var mInflater: LayoutInflater + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + var convertView = convertView + // Already using View Holder pattern + //noinspection StatementWithEmptyBody + if (convertView != null) { + } else { + convertView = mInflater.inflate(R.layout.your_layout, null) + } + + val text = convertView!!.findViewById(R.id.text) as TextView + text.text = "Position " + position + + return convertView + } + } + + class Adapter5 : ViewHolderTest() { + lateinit var mInflater: LayoutInflater + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + var convertView = convertView + // Already using View Holder pattern + convertView = if (convertView == null) mInflater.inflate(R.layout.your_layout, null) else convertView + + val text = convertView!!.findViewById(R.id.text) as TextView + text.text = "Position " + position + + return convertView + } + } + + class Adapter6 : ViewHolderTest() { + private val mContext: Context? = null + private var mLayoutInflator: LayoutInflater? = null + private lateinit var mLapTimes: ArrayList + + override fun getView(position: Int, convertView: View, parent: ViewGroup): View { + if (mLayoutInflator == null) + mLayoutInflator = mContext!!.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater + + var v: View? = convertView + if (v == null) v = mLayoutInflator!!.inflate(R.layout.your_layout, null) + + val listItemHolder = v!!.findViewById(R.id.laptimes_list_item_holder) as LinearLayout + listItemHolder.removeAllViews() + + for (i in 1..5) { + val lapItemView = mLayoutInflator!!.inflate(R.layout.laptime_item, null) + if (i == 0) { + val t = lapItemView.findViewById(R.id.laptime_text) as TextView + } + + val t2 = lapItemView.findViewById(R.id.laptime_text2) as TextView + if (i < mLapTimes.size - 1 && mLapTimes.size > 1) { + var laptime = mLapTimes[i] - mLapTimes[i + 1] + if (laptime < 0) laptime = mLapTimes[i] + } + + listItemHolder.addView(lapItemView) + + } + return v + } + } + + class Adapter7 : ViewHolderTest() { + lateinit var inflater: LayoutInflater + + override fun getView(position: Int, convertView: View, parent: ViewGroup): View { + var rootView: View? = convertView + val itemViewType = getItemViewType(position) + when (itemViewType) { + 0 -> { + if (rootView != null) + return rootView + rootView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false) + } + } + return rootView!! + } + } + + class R { + object layout { + val your_layout = 1 + val laptime_item = 2 + } + + object id { + val laptime_text = 1 + val laptime_text2 = 2 + val laptimes_list_item_holder = 3 + val text = 4 + } + } +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/lint/wrongAnnotation.kt b/plugins/uast-kotlin/testData/lint/wrongAnnotation.kt new file mode 100644 index 00000000000..327362eeff4 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/wrongAnnotation.kt @@ -0,0 +1,39 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintLocalSuppressInspection + +import android.annotation.SuppressLint +import android.view.View + +@Suppress("UsePropertyAccessSyntax", "UNUSED_VARIABLE", "unused", "UNUSED_PARAMETER", "DEPRECATION") +class WrongAnnotation2 { + @SuppressLint("NewApi") + private val field1: Int = 0 + + @SuppressLint("NewApi") + private val field2 = 5 + + companion object { + @SuppressLint("NewApi") // Valid: class-file check on method + fun foobar(view: View, @SuppressLint("NewApi") foo: Int) { + // Invalid: class-file check + @SuppressLint("NewApi") // Invalid + val a: Boolean + @SuppressLint("SdCardPath", "NewApi") // Invalid: class-file based check on local variable + val b: Boolean + @android.annotation.SuppressLint("SdCardPath", "NewApi") // Invalid (FQN) + val c: Boolean + @SuppressLint("SdCardPath") // Valid: AST-based check + val d: Boolean + } + + init { + // Local variable outside method: invalid + @SuppressLint("NewApi") + val localvar = 5 + } + + private fun test() { + @SuppressLint("NewApi") // Invalid + val a = View.MEASURED_STATE_MASK + } + } +} diff --git a/plugins/uast-kotlin/testData/lint/wrongImport.kt b/plugins/uast-kotlin/testData/lint/wrongImport.kt new file mode 100644 index 00000000000..b688362b490 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/wrongImport.kt @@ -0,0 +1,8 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintSuspiciousImportInspection + +//Warning +import android.R + +fun a() { + R.id.button1 +} \ No newline at end of file diff --git a/plugins/uast-kotlin/testData/lint/wrongViewCall.kt b/plugins/uast-kotlin/testData/lint/wrongViewCall.kt new file mode 100644 index 00000000000..0ee6e922d91 --- /dev/null +++ b/plugins/uast-kotlin/testData/lint/wrongViewCall.kt @@ -0,0 +1,23 @@ +// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintWrongCallInspection + +import android.content.Context +import android.graphics.Canvas +import android.util.AttributeSet +import android.widget.FrameLayout +import android.widget.LinearLayout + +abstract class WrongViewCall(context: Context, attrs: AttributeSet, defStyle: Int) : LinearLayout(context, attrs, defStyle) { + private val child: MyChild? = null + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + child?.onDraw(canvas) + } + + private inner class MyChild(context: Context, attrs: AttributeSet, defStyle: Int) : FrameLayout(context, attrs, defStyle) { + + public override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + } + } +} diff --git a/plugins/uast-kotlin/uast-kotlin.iml b/plugins/uast-kotlin/uast-kotlin.iml index 486fbdd1427..e86f5912532 100644 --- a/plugins/uast-kotlin/uast-kotlin.iml +++ b/plugins/uast-kotlin/uast-kotlin.iml @@ -20,5 +20,9 @@ + + + + \ No newline at end of file