diff --git a/app/build.gradle b/app/build.gradle
index 19ae875..f660671 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -4,12 +4,12 @@ plugins {
}
android {
- compileSdk 30
+ compileSdk 32
defaultConfig {
applicationId "ru.otus.homework.androidlint"
minSdk 21
- targetSdk 30
+ targetSdk 32
versionCode 1
versionName "1.0"
@@ -32,6 +32,7 @@ android {
}
dependencies {
+ implementation project(':library')
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation 'androidx.core:core-ktx:1.6.0'
diff --git a/build.gradle b/build.gradle
index 611ef82..5c5fb84 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,17 +1,38 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
+ ext{
+
+ detekt_version = '1.20.0'
+ kotlinVersion = '1.6.21'
+ // Current release: Chipmunk / AGP 7.2
+ gradlePluginVersion = '7.0.2'
+ lintVersion = '30.2.0'
+ // lintVersion = '30.2.0-rc02'
+ // Upcoming lint target: Dolphin / AGP 7.3
+ // gradlePluginVersion = '7.3.0-beta01'
+ // lintVersion = '30.3.0-beta01'
+ // Upcoming lint target: Electric Eel / AGP 7.4
+ // gradlePluginVersion = '7.4.0-alpha02'
+ // lintVersion = '30.4.0-alpha02'
+ }
repositories {
google()
mavenCentral()
+ maven { url 'https://jitpack.io' }
}
dependencies {
- classpath "com.android.tools.build:gradle:7.0.2"
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.30"
+ classpath "com.android.tools.build:gradle:$gradlePluginVersion"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
+ classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$detekt_version"
+
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
+allprojects {
+ apply from: "$rootDir/detekt.gradle"
+}
task clean(type: Delete) {
delete rootProject.buildDir
diff --git a/detekt.gradle b/detekt.gradle
new file mode 100644
index 0000000..f1548d3
--- /dev/null
+++ b/detekt.gradle
@@ -0,0 +1,26 @@
+apply plugin: "io.gitlab.arturbosch.detekt"
+
+detekt {
+ toolVersion = detekt_version
+ input = files(
+ "src/main/java",
+ "src/main/kotlin"
+ )
+ failFast = false
+ parallel = true
+ buildUponDefaultConfig = false
+ ignoreFailures = false
+ reports {
+ xml {
+ enabled = true
+ destination = file("build/reports/detekt.xml")
+ }
+ html {
+ enabled = true
+ destination = file("build/reports/detekt.html")
+ }
+ }
+ dependencies {
+ detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:$detekt_version"
+ }
+}
\ No newline at end of file
diff --git a/library/.gitignore b/library/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/library/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/library/build.gradle b/library/build.gradle
new file mode 100644
index 0000000..9816565
--- /dev/null
+++ b/library/build.gradle
@@ -0,0 +1,22 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 32
+ defaultConfig {
+ minSdkVersion 19
+ targetSdkVersion 32
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ lintOptions {
+ checkDependencies true
+ }
+}
+
+/** Package the given lint checks library into this AAR */
+dependencies {
+ implementation project(':lintRules')
+ lintPublish project(':lintRules')
+}
diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..843049f
--- /dev/null
+++ b/library/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/lintRules/.gitignore b/lintRules/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/lintRules/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/lintRules/build.gradle b/lintRules/build.gradle
new file mode 100644
index 0000000..3876f1b
--- /dev/null
+++ b/lintRules/build.gradle
@@ -0,0 +1,28 @@
+plugins {
+ id 'java-library'
+ id 'org.jetbrains.kotlin.jvm'
+ id 'kotlin'
+ id 'com.android.lint'
+}
+
+lintOptions {
+ htmlReport true
+ htmlOutput file("lint-report.html")
+ textReport true
+ absolutePaths false
+ ignoreTestSources true
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+dependencies {
+ compileOnly "com.android.tools.lint:lint-api:$lintVersion"
+ compileOnly "com.android.tools.lint:lint-checks:$lintVersion"
+
+ testImplementation "junit:junit:4.13.2"
+ testImplementation "com.android.tools.lint:lint:$lintVersion"
+ testImplementation "com.android.tools.lint:lint-tests:$lintVersion"
+}
\ No newline at end of file
diff --git a/lintRules/src/main/java/ru/otus/lintrules/GlobalScopeDetector.kt b/lintRules/src/main/java/ru/otus/lintrules/GlobalScopeDetector.kt
new file mode 100644
index 0000000..43ba100
--- /dev/null
+++ b/lintRules/src/main/java/ru/otus/lintrules/GlobalScopeDetector.kt
@@ -0,0 +1,51 @@
+package ru.otus.lintrules
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Detector.UastScanner
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.*
+
+@Suppress("UnstableApiUsage")
+class GlobalScopeDetector : Detector(), UastScanner {
+ override fun getApplicableUastTypes(): List> {
+ return listOf(USimpleNameReferenceExpression::class.java)
+ }
+
+ override fun createUastHandler(context: JavaContext): UElementHandler {
+ return object : UElementHandler() {
+
+ override fun visitSimpleNameReferenceExpression(node: USimpleNameReferenceExpression){
+ if (node.identifier.contains("GlobalScope")) {
+ context.report(
+ ISSUE, node, context.getLocation(node),
+ "Don't use GlobalScope!!!"
+ )
+ }
+ }
+ }
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue = Issue.create(
+ id = "GlobalScopeWarningId",
+ briefDescription = "GlobalScope usage warning",
+ explanation = """
+ You shouldn't use GlobalScope! Instead of use a custom scope.
+ """,
+ category = Category.CORRECTNESS,
+ priority = 6,
+ severity = Severity.WARNING,
+ implementation = Implementation(
+ GlobalScopeDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/lintRules/src/main/java/ru/otus/lintrules/SampleCodeDetector.kt b/lintRules/src/main/java/ru/otus/lintrules/SampleCodeDetector.kt
new file mode 100644
index 0000000..63ea14a
--- /dev/null
+++ b/lintRules/src/main/java/ru/otus/lintrules/SampleCodeDetector.kt
@@ -0,0 +1,64 @@
+package ru.otus.lintrules
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Detector.UastScanner
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.ULiteralExpression
+import org.jetbrains.uast.evaluateString
+
+@Suppress("UnstableApiUsage")
+class SampleCodeDetector : Detector(), UastScanner {
+ override fun getApplicableUastTypes(): List> {
+ return listOf(ULiteralExpression::class.java)
+ }
+
+ override fun createUastHandler(context: JavaContext): UElementHandler {
+ return object : UElementHandler() {
+ override fun visitLiteralExpression(node: ULiteralExpression) {
+ val string = node.evaluateString() ?: return
+ if (string.contains("lint") && string.matches(Regex(".*\\blint\\b.*"))) {
+ context.report(
+ ISSUE, node, context.getLocation(node),
+ "This code mentions `lint`: **Congratulations**"
+ )
+ }
+ }
+ }
+ }
+
+ companion object {
+ /**
+ * Issue describing the problem and pointing to the detector
+ * implementation.
+ */
+ @JvmField
+ val ISSUE: Issue = Issue.create(
+ // ID: used in @SuppressLint warnings etc
+ id = "SampleId",
+ // Title -- shown in the IDE's preference dialog, as category headers in the
+ // Analysis results window, etc
+ briefDescription = "Lint Mentions",
+ // Full explanation of the issue; you can use some markdown markup such as
+ // `monospace`, *italic*, and **bold**.
+ explanation = """
+ This check highlights string literals in code which mentions the word `lint`. \
+ Blah blah blah.
+ Another paragraph here.
+ """, // no need to .trimIndent(), lint does that automatically
+ category = Category.CORRECTNESS,
+ priority = 6,
+ severity = Severity.WARNING,
+ implementation = Implementation(
+ SampleCodeDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/lintRules/src/main/java/ru/otus/lintrules/SampleIssueRegistry.kt b/lintRules/src/main/java/ru/otus/lintrules/SampleIssueRegistry.kt
new file mode 100644
index 0000000..65da816
--- /dev/null
+++ b/lintRules/src/main/java/ru/otus/lintrules/SampleIssueRegistry.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ru.otus.lintrules
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+
+/*
+ * The list of issues that will be checked when running lint.
+ */
+@Suppress("UnstableApiUsage")
+class SampleIssueRegistry : IssueRegistry() {
+ override val issues = listOf(SampleCodeDetector.ISSUE)
+
+ override val api: Int
+ get() = CURRENT_API
+
+ override val minApi: Int
+ get() = 8 // works with Studio 4.1 or later; see com.android.tools.lint.detector.api.Api / ApiKt
+}
diff --git a/lintRules/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry b/lintRules/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
new file mode 100644
index 0000000..34b2e04
--- /dev/null
+++ b/lintRules/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
@@ -0,0 +1 @@
+ru.otus.lintrules.SampleIssueRegistry
diff --git a/lintRules/src/test/kotlin/GlobalScopeDetectorTest.kt b/lintRules/src/test/kotlin/GlobalScopeDetectorTest.kt
new file mode 100644
index 0000000..48a22e0
--- /dev/null
+++ b/lintRules/src/test/kotlin/GlobalScopeDetectorTest.kt
@@ -0,0 +1,46 @@
+package ru.otus.lint.checks
+
+import com.android.tools.lint.checks.infrastructure.TestFiles.kotlin
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
+import org.junit.Before
+import org.junit.Test
+import ru.otus.lintrules.GlobalScopeDetector
+
+@Suppress("UnstableApiUsage")
+class GlobalScopeDetectorTest {
+
+ private lateinit var task: TestLintTask
+
+ @Before
+ fun prepare() {
+ task = lint().allowMissingSdk(true)
+ }
+
+ @Test
+ fun checkFoundGlobalScope() {
+ task.files(
+ kotlin(
+ """
+ package test.pkg
+ class TestClass1 {
+
+ init {
+ GlobalScope.launch { }
+ }
+ }
+ """
+ ).indented()
+ )
+ .issues(GlobalScopeDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass1.kt:5: Warning: Don't use GlobalScope!!! [GlobalScopeWarningId]
+ GlobalScope.launch { }
+ ~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ }
+}
\ No newline at end of file
diff --git a/lintRules/src/test/kotlin/SampleCodeDetectorTest.kt b/lintRules/src/test/kotlin/SampleCodeDetectorTest.kt
new file mode 100644
index 0000000..0b6dc5a
--- /dev/null
+++ b/lintRules/src/test/kotlin/SampleCodeDetectorTest.kt
@@ -0,0 +1,45 @@
+package ru.otus.lint.checks
+
+import com.android.tools.lint.checks.infrastructure.TestFiles.java
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
+import org.junit.Before
+import org.junit.Test
+import ru.otus.lintrules.SampleCodeDetector
+
+@Suppress("UnstableApiUsage")
+class SampleCodeDetectorTest {
+
+ private lateinit var task: TestLintTask
+
+ @Before
+ fun prepare() {
+ task = lint().allowMissingSdk(true)
+ }
+
+ @Test
+ fun testBasic() {
+ task.files(
+ java(
+ """
+ package test.pkg;
+ public class TestClass1 {
+ // In a comment, mentioning "lint" has no effect
+ private static String s1 = "Ignore non-word usages: linting";
+ private static String s2 = "Let's say it: lint";
+ }
+ """
+ ).indented()
+ )
+ .issues(SampleCodeDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass1.java:5: Warning: This code mentions lint: Congratulations [SampleId]
+ private static String s2 = "Let's say it: lint";
+ ~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 4eccc00..7fb01bd 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -8,3 +8,5 @@ dependencyResolutionManagement {
}
rootProject.name = "Android Lint"
include ':app'
+include ':lintRules'
+include ':library'