├── .gitignore
├── .idea
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── misc.xml
├── runConfigurations.xml
└── vcs.xml
├── .project
├── .settings
└── org.eclipse.buildship.core.prefs
├── README.md
├── app
├── .classpath
├── .gitignore
├── .project
├── .settings
│ └── org.eclipse.buildship.core.prefs
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── rahulrav
│ │ └── app
│ │ ├── App.kt
│ │ ├── MainActivity.kt
│ │ ├── TimeTravelExperiment.kt
│ │ ├── TimeTravelProvider.kt
│ │ ├── UseTimeTravelExperimentFromJava.java
│ │ └── WhatATerribleFailure.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ └── ic_launcher_background.xml
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── .classpath
├── .gitignore
├── .project
├── .settings
│ └── org.eclipse.buildship.core.prefs
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── res
│ └── values
│ └── strings.xml
├── lint
├── .classpath
├── .gitignore
├── .project
├── .settings
│ └── org.eclipse.buildship.core.prefs
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── rahulrav
│ │ │ ├── BadConfigurationProviderDetector.kt
│ │ │ ├── ExperimentalDetector.kt
│ │ │ ├── IssueRegistry.kt
│ │ │ ├── LogWtfDetector.kt
│ │ │ ├── NoisyDetector.kt
│ │ │ └── RemoveWorkManagerInitializerDetector.kt
│ └── resources
│ │ └── META-INF
│ │ └── services
│ │ └── com.android.tools.lint.client.api.IssueRegistry
│ └── test
│ └── java
│ └── com
│ └── rahulrav
│ ├── DetectorTest.kt
│ └── Stubs.kt
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
2 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
3 |
4 | # User-specific stuff
5 | .idea/**/workspace.xml
6 | .idea/**/tasks.xml
7 | .idea/**/usage.statistics.xml
8 | .idea/**/dictionaries
9 | .idea/**/shelf
10 |
11 | # Generated files
12 | .idea/**/contentModel.xml
13 |
14 | # Sensitive or high-churn files
15 | .idea/**/dataSources/
16 | .idea/**/dataSources.ids
17 | .idea/**/dataSources.local.xml
18 | .idea/**/sqlDataSources.xml
19 | .idea/**/dynamic.xml
20 | .idea/**/uiDesigner.xml
21 | .idea/**/dbnavigator.xml
22 |
23 | # Gradle
24 | .idea/**/gradle.xml
25 | .idea/**/libraries
26 |
27 | # Gradle and Maven with auto-import
28 | # When using Gradle or Maven with auto-import, you should exclude module files,
29 | # since they will be recreated, and may cause churn. Uncomment if using
30 | # auto-import.
31 | # .idea/modules.xml
32 | # .idea/*.iml
33 | # .idea/modules
34 | # *.iml
35 | # *.ipr
36 |
37 | # CMake
38 | cmake-build-*/
39 |
40 | # Mongo Explorer plugin
41 | .idea/**/mongoSettings.xml
42 |
43 | # File-based project format
44 | *.iws
45 |
46 | # IntelliJ
47 | out/
48 |
49 | # mpeltonen/sbt-idea plugin
50 | .idea_modules/
51 |
52 | # JIRA plugin
53 | atlassian-ide-plugin.xml
54 |
55 | # Cursive Clojure plugin
56 | .idea/replstate.xml
57 |
58 | # Crashlytics plugin (for Android Studio and IntelliJ)
59 | com_crashlytics_export_strings.xml
60 | crashlytics.properties
61 | crashlytics-build.properties
62 | fabric.properties
63 |
64 | # Editor-based Rest Client
65 | .idea/httpRequests
66 |
67 | # Android studio 3.1+ serialized cache file
68 | .idea/caches/build_file_checksums.ser
69 |
70 | # Built application files
71 | *.apk
72 | *.ap_
73 | *.aab
74 |
75 | # Files for the ART/Dalvik VM
76 | *.dex
77 |
78 | # Java class files
79 | *.class
80 |
81 | # Generated files
82 | bin/
83 | gen/
84 | out/
85 | # Uncomment the following line in case you need and you don't have the release build type files in your app
86 | # release/
87 |
88 | # Gradle files
89 | .gradle/
90 | build/
91 |
92 | # Local configuration file (sdk path, etc)
93 | local.properties
94 |
95 | # Proguard folder generated by Eclipse
96 | proguard/
97 |
98 | # Log Files
99 | *.log
100 |
101 | # Android Studio Navigation editor temp files
102 | .navigation/
103 |
104 | # Android Studio captures folder
105 | captures/
106 |
107 | # IntelliJ
108 | *.iml
109 | .idea/gradle.xml
110 | .idea/assetWizardSettings.xml
111 | .idea/dictionaries
112 | .idea/libraries
113 | # Android Studio 3 in .gitignore file.
114 | .idea/caches
115 | .idea/modules.xml
116 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
117 | .idea/navEditor.xml
118 |
119 | # External native build folder generated in Android Studio 2.2 and later
120 | .externalNativeBuild
121 |
122 | # lint
123 | lint/intermediates/
124 | lint/generated/
125 | lint/outputs/
126 | lint/tmp/
127 | # lint/reports/
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | xmlns:android
14 |
15 | ^$
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | xmlns:.*
25 |
26 | ^$
27 |
28 |
29 | BY_NAME
30 |
31 |
32 |
33 |
34 |
35 |
36 | .*:id
37 |
38 | http://schemas.android.com/apk/res/android
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | .*:name
48 |
49 | http://schemas.android.com/apk/res/android
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | name
59 |
60 | ^$
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | style
70 |
71 | ^$
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | .*
81 |
82 | ^$
83 |
84 |
85 | BY_NAME
86 |
87 |
88 |
89 |
90 |
91 |
92 | .*
93 |
94 | http://schemas.android.com/apk/res/android
95 |
96 |
97 | ANDROID_ATTRIBUTE_ORDER
98 |
99 |
100 |
101 |
102 |
103 |
104 | .*
105 |
106 | .*
107 |
108 |
109 | BY_NAME
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | lint-experiments
4 | Project lint-experiments created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.buildship.core.gradleprojectbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.buildship.core.gradleprojectnature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | connection.project.dir=
2 | eclipse.preferences.version=1
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Writing Android Lint Rules
2 |
3 | This repository shows how to write Android Lint Rules and package them so they can be used for static analysis in your
4 | projects.
5 |
--------------------------------------------------------------------------------
/app/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | app
4 | Project app created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.buildship.core.gradleprojectbuilder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.eclipse.buildship.core.gradleprojectnature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | connection.project.dir=..
2 | eclipse.preferences.version=1
3 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 |
5 |
6 | ext {
7 | work_version = '2.3.0-alpha02'
8 | }
9 |
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 |
15 | android {
16 | compileSdkVersion 29
17 | buildToolsVersion "29.0.2"
18 | defaultConfig {
19 | applicationId "com.rahulrav.app"
20 | minSdkVersion 19
21 | targetSdkVersion 29
22 | versionCode 1
23 | versionName "1.0"
24 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
25 | }
26 | buildTypes {
27 | release {
28 | minifyEnabled false
29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
30 | }
31 | }
32 | compileOptions {
33 | sourceCompatibility JavaVersion.VERSION_1_8
34 | targetCompatibility JavaVersion.VERSION_1_8
35 | }
36 | }
37 |
38 | androidExtensions {
39 | experimental = true
40 | }
41 |
42 | dependencies {
43 | implementation fileTree(include: ['*.jar'], dir: 'libs')
44 | implementation project(':library')
45 | lintChecks project(':lint')
46 | implementation 'androidx.appcompat:appcompat:1.1.0'
47 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
48 | implementation "androidx.work:work-runtime-ktx:$work_version"
49 | testImplementation 'junit:junit:4.12'
50 | androidTestImplementation 'androidx.test:runner:1.3.0-alpha02'
51 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0-alpha02'
52 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
53 | }
54 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rahulrav/app/App.kt:
--------------------------------------------------------------------------------
1 | package com.rahulrav.app
2 |
3 | import android.app.Application
4 | import androidx.work.Configuration
5 |
6 | class App : Application(), Configuration.Provider {
7 | override fun getWorkManagerConfiguration() = Configuration.Builder().build()
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rahulrav/app/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.rahulrav.app
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 |
6 | class MainActivity : AppCompatActivity() {
7 | override fun onCreate(savedInstanceState: Bundle?) {
8 | super.onCreate(savedInstanceState)
9 | setContentView(R.layout.activity_main)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rahulrav/app/TimeTravelExperiment.kt:
--------------------------------------------------------------------------------
1 | package com.rahulrav.app
2 |
3 | /**
4 | * Marker for the experimental Time Travel feature set.
5 | *
6 | * Use with caution! May be removed in a future (or past) release.
7 | */
8 | @Experimental
9 | @Retention(AnnotationRetention.BINARY)
10 | @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
11 | annotation class TimeTravelExperiment
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rahulrav/app/TimeTravelProvider.kt:
--------------------------------------------------------------------------------
1 | package com.rahulrav.app
2 |
3 | @Suppress("unused")
4 | @TimeTravelExperiment
5 | class TimeTravelProvider {
6 | var timeInternal: Long = 0
7 |
8 | fun setTime(timestamp: Long) {
9 | timeInternal = timestamp
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rahulrav/app/UseTimeTravelExperimentFromJava.java:
--------------------------------------------------------------------------------
1 | package com.rahulrav.app;
2 |
3 | import kotlin.UseExperimental;
4 |
5 | @SuppressWarnings("unused")
6 | class UseTimeTravelExperimentFromJava {
7 | @TimeTravelExperiment
8 | void setTimeToNow() {
9 | new TimeTravelProvider().setTime(System.currentTimeMillis());
10 | }
11 |
12 | @UseExperimental(markerClass = TimeTravelExperiment.class)
13 | void setTimeToEpoch() {
14 | new TimeTravelProvider().setTime(0);
15 | }
16 |
17 | public void violateTimeTravelAccords() {
18 | new TimeTravelProvider().setTime(-1);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/rahulrav/app/WhatATerribleFailure.kt:
--------------------------------------------------------------------------------
1 | package com.rahulrav.app
2 |
3 | import android.util.Log
4 |
5 | @Suppress("unused")
6 | class WhatATerribleFailure {
7 | fun logAsWtf(clazz: Class, message: String) {
8 | Log.wtf(clazz.name, message)
9 |
10 | wtf(message)
11 | }
12 |
13 | fun wtf(message: String) {
14 | Log.d("TAG", message)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tikurahul/lint-experiments/843551176a34387d2d6a657fd7199a6ba9f625ae/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tikurahul/lint-experiments/843551176a34387d2d6a657fd7199a6ba9f625ae/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tikurahul/lint-experiments/843551176a34387d2d6a657fd7199a6ba9f625ae/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tikurahul/lint-experiments/843551176a34387d2d6a657fd7199a6ba9f625ae/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tikurahul/lint-experiments/843551176a34387d2d6a657fd7199a6ba9f625ae/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tikurahul/lint-experiments/843551176a34387d2d6a657fd7199a6ba9f625ae/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tikurahul/lint-experiments/843551176a34387d2d6a657fd7199a6ba9f625ae/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tikurahul/lint-experiments/843551176a34387d2d6a657fd7199a6ba9f625ae/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tikurahul/lint-experiments/843551176a34387d2d6a657fd7199a6ba9f625ae/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tikurahul/lint-experiments/843551176a34387d2d6a657fd7199a6ba9f625ae/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Lint Experiments
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.3.50'
5 | repositories {
6 | google()
7 | jcenter()
8 |
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:3.6.0-beta01'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 |
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | google()
22 | jcenter()
23 |
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 |
19 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tikurahul/lint-experiments/843551176a34387d2d6a657fd7199a6ba9f625ae/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Oct 06 16:44:41 PDT 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/library/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | library
4 | Project library created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.buildship.core.gradleprojectbuilder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.eclipse.buildship.core.gradleprojectnature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/library/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | connection.project.dir=..
2 | eclipse.preferences.version=1
3 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 |
5 | android {
6 | compileSdkVersion 29
7 | buildToolsVersion "29.0.2"
8 | defaultConfig {
9 | minSdkVersion 19
10 | targetSdkVersion 29
11 | versionCode 1
12 | versionName "1.0"
13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | }
22 |
23 | dependencies {
24 | compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
25 |
26 | // Publish custom Lint checks to clients
27 | lintPublish project(path: ':lint')
28 | }
29 |
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Library
3 |
4 |
--------------------------------------------------------------------------------
/lint/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/lint/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/lint/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | lint
4 | Project lint created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.buildship.core.gradleprojectbuilder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.eclipse.buildship.core.gradleprojectnature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/lint/.settings/org.eclipse.buildship.core.prefs:
--------------------------------------------------------------------------------
1 | connection.project.dir=..
2 | eclipse.preferences.version=1
3 |
--------------------------------------------------------------------------------
/lint/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java-library'
2 | apply plugin: 'kotlin'
3 |
4 | ext {
5 | lintVersion = "26.5.1"
6 | }
7 |
8 | repositories {
9 | google()
10 | mavenCentral()
11 | jcenter()
12 | }
13 |
14 | dependencies {
15 | compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
16 |
17 | // Lint
18 | compileOnly "com.android.tools.lint:lint-api:$lintVersion"
19 | compileOnly "com.android.tools.lint:lint-checks:$lintVersion"
20 |
21 | // Lint testing
22 | testImplementation "com.android.tools.lint:lint:$lintVersion"
23 | testImplementation "com.android.tools.lint:lint-tests:$lintVersion"
24 | testImplementation 'junit:junit:4.12'
25 | }
26 |
--------------------------------------------------------------------------------
/lint/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/lint/src/main/java/com/rahulrav/BadConfigurationProviderDetector.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | package com.rahulrav
4 |
5 | import com.android.tools.lint.detector.api.*
6 | import com.rahulrav.IssueRegistry.Companion.BadConfigurationProviderDescription
7 | import org.jetbrains.uast.UClass
8 |
9 | class BadConfigurationProviderDetector : Detector(), SourceCodeScanner {
10 | private var correct = false
11 |
12 | override fun applicableSuperClasses() = listOf("androidx.work.Configuration.Provider")
13 |
14 | override fun visitClass(context: JavaContext, declaration: UClass) {
15 | if (context.evaluator.extendsClass(
16 | declaration.javaPsi,
17 | "android.app.Application",
18 | false)) {
19 | // Application correctly extends Configuration.Provider
20 | correct = true
21 | }
22 | }
23 |
24 | override fun afterCheckRootProject(context: Context) {
25 | if (!correct) {
26 | context.report(
27 | issue = IssueRegistry.BadConfigurationProviderIssue,
28 | location = Location.create(context.file),
29 | message = BadConfigurationProviderDescription
30 | )
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lint/src/main/java/com/rahulrav/ExperimentalDetector.kt:
--------------------------------------------------------------------------------
1 | package com.rahulrav
2 |
3 | import com.android.tools.lint.detector.api.*
4 | import com.intellij.psi.PsiClassType
5 | import com.intellij.psi.PsiElement
6 | import com.intellij.psi.PsiMethod
7 | import org.jetbrains.uast.*
8 |
9 | @Suppress("UnstableApiUsage")
10 | class ExperimentalDetector : Detector(), SourceCodeScanner {
11 | override fun applicableAnnotations(): List? = listOf(
12 | "kotlin.Experimental"
13 | )
14 |
15 | override fun visitAnnotationUsage(
16 | context: JavaContext,
17 | usage: UElement,
18 | type: AnnotationUsageType,
19 | annotation: UAnnotation,
20 | qualifiedName: String,
21 | method: PsiMethod?,
22 | referenced: PsiElement?,
23 | annotations: List,
24 | allMemberAnnotations: List,
25 | allClassAnnotations: List,
26 | allPackageAnnotations: List
27 | ) {
28 | if (isKotlin(usage.sourcePsi))
29 | return
30 | if (!isValidExperimentalUsage(context, usage, annotation))
31 | reportUsage(context, usage, annotation)
32 | }
33 |
34 | private fun isValidExperimentalUsage(
35 | context: JavaContext,
36 | usage: UElement,
37 | annotation: UAnnotation
38 | ): Boolean {
39 |
40 | // Feature annotation (ex. @TimeTravel) that is @Experimental
41 | val featureName = (annotation.uastParent as? UClass)
42 | ?.qualifiedName ?: return false
43 |
44 | // Find the nearest enclosing annotated element
45 | var element: UAnnotated? = if (usage is UAnnotated) {
46 | usage
47 | } else {
48 | usage.getParentOfType(UAnnotated::class.java)
49 | }
50 |
51 | while (element != null) {
52 | val annotations = context.evaluator.getAllAnnotations(element, false)
53 |
54 | // Is the element itself part of the same experimental
55 | // feature set, e.g. annotated with @TimeTravelExperiment?
56 | if (annotations.any { it.qualifiedName == featureName })
57 | return true
58 |
59 | // Or does it explicitly opt-in as a user, e.g.
60 | // annotated with @UseExperimental(TimeTravelExperiment.class)?
61 | if (annotations
62 | .filter { annot -> annot.qualifiedName == "kotlin.UseExperimental" }
63 | .mapNotNull { annot -> annot.attributeValues.getOrNull(0) }
64 | .any { attr -> attr.getFullyQualifiedName(context) == featureName })
65 | return true
66 |
67 | // Traverse to the next enclosing annotated element
68 | element = element.getParentOfType(UAnnotated::class.java)
69 | }
70 | return false
71 | }
72 |
73 |
74 | /**
75 | * Reports an issue.
76 | */
77 | private fun reportUsage(
78 | context: JavaContext,
79 | usage: UElement,
80 | annotation: UAnnotation
81 | ) {
82 | val useAnnotation = (annotation.uastParent as? UClass)?.qualifiedName ?: return
83 | context.report(ISSUE, usage, context.getNameLocation(usage), """
84 | This declaration is experimental and its usage should be marked with
85 | '@$useAnnotation' or '@UseExperimental(markerClass = $useAnnotation.class)'
86 | """.trimIndent())
87 | }
88 |
89 | companion object {
90 | private val IMPLEMENTATION = Implementation(
91 | ExperimentalDetector::class.java,
92 | Scope.JAVA_FILE_SCOPE
93 | )
94 |
95 | @Suppress("DefaultLocale")
96 | val ISSUE: Issue = Issue.create(
97 | id = "UnsafeExperimentalUsageError",
98 | briefDescription = "Unsafe experimental usage",
99 | explanation = """
100 | This API has been flagged as experimental.
101 |
102 | Any declaration annotated with this marker is considered part of an unstable API \
103 | surface and its call sites should accept the experimental aspect of it either by \
104 | using `@UseExperimental`, or by being annotated with that marker themselves, \
105 | effectively causing further propagation of that experimental aspect.
106 | """,
107 | category = Category.CORRECTNESS,
108 | priority = 4,
109 | severity = Severity.ERROR,
110 | implementation = IMPLEMENTATION
111 | )
112 | }
113 | }
114 |
115 | /**
116 | * Returns the fully-qualified class name for a given attribute value, if any.
117 | */
118 | private fun UNamedExpression?.getFullyQualifiedName(context: JavaContext): String? {
119 | val exp = this?.expression
120 | val type = if (exp is UClassLiteralExpression) exp.type else exp?.evaluate()
121 | return (type as? PsiClassType)?.let { context.evaluator.getQualifiedName(it) }
122 | }
123 |
--------------------------------------------------------------------------------
/lint/src/main/java/com/rahulrav/IssueRegistry.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | package com.rahulrav
4 |
5 | import com.android.tools.lint.client.api.IssueRegistry
6 | import com.android.tools.lint.detector.api.*
7 | import java.util.*
8 |
9 | class IssueRegistry : IssueRegistry() {
10 | override val issues: List
11 | get() = listOf(
12 | NoisyIssue,
13 | BadConfigurationProviderIssue,
14 | RemoveWorkManagerIntializerIssue,
15 | ExperimentalDetector.ISSUE,
16 | LogWtfDetector.ISSUE
17 | )
18 |
19 | override val api: Int = CURRENT_API
20 |
21 | companion object {
22 | private const val NoisyIssueId = "NoisyIssueId"
23 | const val NoisyIssueDescription = "This is a noisy issue. Feel free to ignore for now."
24 |
25 | private const val BadConfigurationProviderId = "BadConfigurationProviderId"
26 | val BadConfigurationProviderDescription = """
27 | Only an `android.app.Application` can implement `androidx.work.Configuration.Provider`
28 | """.trimIndent()
29 |
30 | private const val RemoveWorkManagerIntializerId = "RemoveWorkManagerIntializerId"
31 | val RemoveWorkManagerIntializerDescription = """
32 | If an android.app.Application implements androidx.work.Configuration.Provider,
33 | the default androidx.work.impl.WorkManagerInitializer needs to be removed from tne
34 | AndroidManifest.xml file.
35 | """.trimIndent()
36 |
37 | val NoisyIssue = Issue.create(
38 | id = NoisyIssueId,
39 | briefDescription = NoisyIssueDescription,
40 | explanation = NoisyIssueDescription,
41 | category = Category.CORRECTNESS,
42 | priority = 4,
43 | severity = Severity.INFORMATIONAL,
44 | implementation = Implementation(NoisyDetector::class.java, Scope.MANIFEST_SCOPE)
45 | )
46 |
47 | val BadConfigurationProviderIssue = Issue.create(
48 | id = BadConfigurationProviderId,
49 | briefDescription = BadConfigurationProviderDescription,
50 | explanation = BadConfigurationProviderDescription,
51 | category = Category.CORRECTNESS,
52 | priority = 2,
53 | severity = Severity.FATAL,
54 | implementation = Implementation(
55 | BadConfigurationProviderDetector::class.java, Scope.JAVA_FILE_SCOPE)
56 | )
57 |
58 | val RemoveWorkManagerIntializerIssue = Issue.create(
59 | id = RemoveWorkManagerIntializerId,
60 | briefDescription = RemoveWorkManagerIntializerDescription,
61 | explanation = RemoveWorkManagerIntializerDescription,
62 | category = Category.CORRECTNESS,
63 | priority = 2,
64 | severity = Severity.FATAL,
65 | implementation = Implementation(
66 | RemoveWorkManagerInitializerDetector::class.java,
67 | EnumSet.of(Scope.JAVA_FILE, Scope.MANIFEST))
68 | )
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/lint/src/main/java/com/rahulrav/LogWtfDetector.kt:
--------------------------------------------------------------------------------
1 | package com.rahulrav
2 |
3 | import com.android.tools.lint.detector.api.*
4 | import com.intellij.psi.PsiMethod
5 | import org.jetbrains.uast.UCallExpression
6 |
7 | @Suppress("UnstableApiUsage")
8 | class LogWtfDetector : Detector(), SourceCodeScanner {
9 |
10 | override fun getApplicableMethodNames(): List? =
11 | listOf(
12 | "wtf"
13 | )
14 |
15 | override fun visitMethodCall(
16 | context: JavaContext,
17 | node: UCallExpression,
18 | method: PsiMethod
19 | ) {
20 | val evaluator = context.evaluator
21 | if (evaluator.isMemberInClass(method, "android.util.Log")) {
22 | reportUsage(context, node, method)
23 | }
24 | }
25 |
26 | private fun reportUsage(
27 | context: JavaContext,
28 | node: UCallExpression,
29 | method: PsiMethod
30 | ) {
31 | val quickfixData = LintFix.create()
32 | .name("Use Log.e()")
33 | .replace()
34 | .text(method.name)
35 | .with("e")
36 | .robot(true) // Can be applied automatically.
37 | .independent(true) // Does not conflict with other auto-fixes.
38 | .build()
39 |
40 | context.report(
41 | issue = ISSUE,
42 | scope = node,
43 | location = context.getCallLocation(
44 | call = node,
45 | includeReceiver = false,
46 | includeArguments = false
47 | ),
48 | message = "Usage of `Log.wtf()` is prohibited",
49 | quickfixData = quickfixData
50 | )
51 | }
52 |
53 | companion object {
54 | private val IMPLEMENTATION = Implementation(
55 | LogWtfDetector::class.java,
56 | Scope.JAVA_FILE_SCOPE
57 | )
58 |
59 | val ISSUE: Issue = Issue.create(
60 | id = "LogWtfUsageError",
61 | briefDescription = "Prohibited logging level",
62 | explanation = """
63 | This lint check prevents usage of `Log.wtf()`.
64 | """.trimIndent(),
65 | category = Category.CORRECTNESS,
66 | priority = 3,
67 | severity = Severity.ERROR,
68 | implementation = IMPLEMENTATION
69 | ).setAndroidSpecific(true)
70 | }
71 | }
72 |
73 |
--------------------------------------------------------------------------------
/lint/src/main/java/com/rahulrav/NoisyDetector.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | package com.rahulrav
4 |
5 | import com.android.tools.lint.detector.api.*
6 | import com.rahulrav.IssueRegistry.Companion.NoisyIssue
7 | import com.rahulrav.IssueRegistry.Companion.NoisyIssueDescription
8 | import org.w3c.dom.Element
9 |
10 | class NoisyDetector : Detector(), XmlScanner {
11 | override fun getApplicableElements() = listOf("manifest")
12 |
13 | override fun visitElement(context: XmlContext, element: Element) {
14 | }
15 |
16 | override fun afterCheckFile(context: Context) {
17 | context.report(NoisyIssue, Location.create(context.file), NoisyIssueDescription)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lint/src/main/java/com/rahulrav/RemoveWorkManagerInitializerDetector.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | package com.rahulrav
4 |
5 | import com.android.tools.lint.detector.api.*
6 | import org.jetbrains.uast.UClass
7 | import org.w3c.dom.Element
8 | import org.w3c.dom.Node
9 | import org.w3c.dom.NodeList
10 |
11 | class RemoveWorkManagerInitializerDetector : Detector(), XmlScanner, SourceCodeScanner {
12 |
13 | private var analyzed = false
14 | private var removedInitializer = false
15 | private var location: Location? = null
16 | private var appImplementsProvider = false
17 |
18 | override fun getApplicableElements() = listOf("manifest")
19 | override fun applicableSuperClasses() = listOf("androidx.work.Configuration.Provider")
20 |
21 | override fun visitElement(context: XmlContext, element: Element) {
22 | if (!analyzed) {
23 | analyzed = true
24 | val document = context.client.getMergedManifest(context.project)
25 | val application = document?.getElementsByTagName("application")?.item(0)
26 | val providers = document?.getElementsByTagName("provider")
27 | val provider = providers.find { node ->
28 | val name = node.attributes.getNamedItemNS(ANDROID_NS, ATTRIBUTE_NAME)?.textContent
29 | name == "androidx.work.impl.WorkManagerInitializer"
30 | }
31 | if (provider != null) {
32 | location = context.getLocation(provider)
33 | val remove = provider.attributes.getNamedItemNS(TOOLS_NS, ATTRIBUTE_NODE)
34 | if (remove?.textContent == "remove") {
35 | removedInitializer = true
36 | }
37 | } else if (application != null) {
38 | location = context.getLocation(application)
39 | } else {
40 | location = Location.create(context.file)
41 | }
42 | }
43 | }
44 |
45 | override fun visitClass(context: JavaContext, declaration: UClass) {
46 | if (context.evaluator.extendsClass(
47 | declaration.javaPsi,
48 | "android.app.Application",
49 | false)) {
50 | appImplementsProvider = true
51 | }
52 | }
53 |
54 | override fun afterCheckRootProject(context: Context) {
55 | val location = location ?: Location.create(context.file)
56 | if (appImplementsProvider) {
57 | if (!removedInitializer) {
58 | context.report(
59 | issue = IssueRegistry.RemoveWorkManagerIntializerIssue,
60 | location = location,
61 | message = IssueRegistry.RemoveWorkManagerIntializerDescription
62 | )
63 | }
64 | }
65 | }
66 |
67 | companion object {
68 | private const val ANDROID_NS = "http://schemas.android.com/apk/res/android"
69 | private const val TOOLS_NS = "http://schemas.android.com/tools"
70 |
71 | private const val ATTRIBUTE_NAME = "name"
72 | private const val ATTRIBUTE_NODE = "node"
73 |
74 | fun NodeList?.find(fn: (node: Node) -> Boolean): Node? {
75 | if (this == null) {
76 | return null
77 | } else {
78 | for (i in 0 until this.length) {
79 | val node = this.item(i)
80 | if (fn(node)) {
81 | return node
82 | }
83 | }
84 | return null
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/lint/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry:
--------------------------------------------------------------------------------
1 | com.rahulrav.IssueRegistry
2 |
--------------------------------------------------------------------------------
/lint/src/test/java/com/rahulrav/DetectorTest.kt:
--------------------------------------------------------------------------------
1 | package com.rahulrav
2 |
3 | import com.android.tools.lint.checks.infrastructure.TestLintTask.lint
4 | import com.rahulrav.IssueRegistry.Companion.BadConfigurationProviderIssue
5 | import com.rahulrav.IssueRegistry.Companion.NoisyIssue
6 | import com.rahulrav.IssueRegistry.Companion.RemoveWorkManagerIntializerIssue
7 | import com.rahulrav.Stubs.ANDROID_APP_CLASS
8 | import com.rahulrav.Stubs.ANDROID_LOG_JAVA
9 | import com.rahulrav.Stubs.APP_IMPLEMENTS_CONFIGURATION_PROVIDER
10 | import com.rahulrav.Stubs.EMPTY_MANIFEST
11 | import com.rahulrav.Stubs.EXPERIMENTAL_KT
12 | import com.rahulrav.Stubs.LOG_WTF_JAVA
13 | import com.rahulrav.Stubs.LOG_WTF_KT
14 | import com.rahulrav.Stubs.MANIFEST_WITH_INITIALIZER
15 | import com.rahulrav.Stubs.MANIFEST_WITH_NO_INITIALIZER
16 | import com.rahulrav.Stubs.OTHER_CLASS_IMPLEMENTS_CONFIGURATION_PROVIDER
17 | import com.rahulrav.Stubs.TIME_TRAVEL_EXPERIMENT_KT
18 | import com.rahulrav.Stubs.TIME_TRAVEL_PROVIDER_KT
19 | import com.rahulrav.Stubs.USE_TIME_TRAVEL_EXPERIMENT_FROM_JAVA
20 | import com.rahulrav.Stubs.WORK_MANAGER_CONFIGURATION_INTERFACE
21 | import org.junit.Test
22 |
23 | class DetectorTest {
24 | @Test
25 | fun testNoisyDetector() {
26 | lint().files(EMPTY_MANIFEST)
27 | .allowMissingSdk()
28 | .issues(NoisyIssue)
29 | .run()
30 | .expect(
31 | """
32 | AndroidManifest.xml: Information: This is a noisy issue. Feel free to ignore for now. [NoisyIssueId]
33 | 0 errors, 0 warnings
34 | """.trimIndent()
35 | )
36 | }
37 |
38 | @Test
39 | fun testBadConfigurationProviderDetector_success() {
40 | lint().files(
41 | WORK_MANAGER_CONFIGURATION_INTERFACE,
42 | ANDROID_APP_CLASS,
43 | APP_IMPLEMENTS_CONFIGURATION_PROVIDER)
44 | .allowMissingSdk()
45 | .issues(BadConfigurationProviderIssue)
46 | .run()
47 | .expect("No warnings.")
48 | }
49 |
50 | @Test
51 | fun testBadConfigurationProviderDetector_failure() {
52 | lint().files(
53 | WORK_MANAGER_CONFIGURATION_INTERFACE,
54 | ANDROID_APP_CLASS,
55 | OTHER_CLASS_IMPLEMENTS_CONFIGURATION_PROVIDER)
56 | .allowMissingSdk()
57 | .issues(BadConfigurationProviderIssue)
58 | .run()
59 | .expect(
60 | """
61 | project0: Error: Only an android.app.Application can implement androidx.work.Configuration.Provider [BadConfigurationProviderId]
62 | 1 errors, 0 warnings
63 | """.trimIndent()
64 | )
65 | }
66 |
67 | @Test
68 | fun testRemoveWorkManagerInitializerDetector_success() {
69 | lint().files(
70 | WORK_MANAGER_CONFIGURATION_INTERFACE,
71 | ANDROID_APP_CLASS,
72 | MANIFEST_WITH_NO_INITIALIZER,
73 | APP_IMPLEMENTS_CONFIGURATION_PROVIDER)
74 | .allowMissingSdk()
75 | .issues(RemoveWorkManagerIntializerIssue)
76 | .run()
77 | .expect("No warnings.")
78 | }
79 |
80 | @Test
81 | fun testRemoveWorkManagerInitializerDetector_failure_emptyManifest() {
82 | lint().files(
83 | EMPTY_MANIFEST,
84 | WORK_MANAGER_CONFIGURATION_INTERFACE,
85 | ANDROID_APP_CLASS,
86 | APP_IMPLEMENTS_CONFIGURATION_PROVIDER)
87 | .allowMissingSdk()
88 | .issues(RemoveWorkManagerIntializerIssue)
89 | .run()
90 | .expect(
91 | """
92 | AndroidManifest.xml:4: Error: If an android.app.Application implements androidx.work.Configuration.Provider,
93 | the default androidx.work.impl.WorkManagerInitializer needs to be removed from tne
94 | AndroidManifest.xml file. [RemoveWorkManagerIntializerId]
95 |
96 | ^
97 | 1 errors, 0 warnings
98 | """.trimIndent()
99 | )
100 | }
101 |
102 | @Test
103 | fun testRemoveWorkManagerInitializerDetector_failure_manifestWithInitializer() {
104 | lint().files(
105 | MANIFEST_WITH_INITIALIZER,
106 | WORK_MANAGER_CONFIGURATION_INTERFACE,
107 | ANDROID_APP_CLASS,
108 | APP_IMPLEMENTS_CONFIGURATION_PROVIDER)
109 | .allowMissingSdk()
110 | .issues(RemoveWorkManagerIntializerIssue)
111 | .run()
112 | .expect(
113 | """
114 | AndroidManifest.xml:5: Error: If an android.app.Application implements androidx.work.Configuration.Provider,
115 | the default androidx.work.impl.WorkManagerInitializer needs to be removed from tne
116 | AndroidManifest.xml file. [RemoveWorkManagerIntializerId]
117 |
14 |
15 |
16 |
17 | """.trimIndent()
18 | ).indented()
19 |
20 | val MANIFEST_WITH_NO_INITIALIZER: TestFile = manifest(
21 | """
22 |
25 |
26 |
30 |
31 |
32 |
33 | """).indented()
34 |
35 | val MANIFEST_WITH_INITIALIZER: TestFile = manifest(
36 | """
37 |
40 |
41 |
44 |
45 |
46 |
47 | """).indented()
48 |
49 | val WORK_MANAGER_CONFIGURATION_INTERFACE: TestFile = kotlin(
50 | "androidx/work/Configuration.kt",
51 | """
52 | package androidx.work
53 | interface Configuration {
54 | interface Provider {
55 | fun getWorkManagerConfiguration(): Configuration
56 | }
57 | }
58 | """)
59 | .indented().within("src")
60 |
61 | val ANDROID_APP_CLASS: TestFile = kotlin(
62 | "android/app/Application.kt",
63 | """
64 | package android.app
65 | open class Application {
66 | fun onCreate() {
67 |
68 | }
69 | }
70 | """)
71 | .indented().within("src")
72 |
73 | val APP_IMPLEMENTS_CONFIGURATION_PROVIDER: TestFile = kotlin(
74 | "com/example/App.kt",
75 | """
76 | package com.example
77 |
78 | import android.app.Application
79 | import androidx.work.Configuration
80 |
81 | class Config : Configuration
82 |
83 | class App : Configuration.Provider, Application() {
84 | override fun onCreate() {
85 | }
86 | override fun getWorkManagerConfiguration(): Configuration = Config()
87 | }
88 | """)
89 | .indented().within("src")
90 |
91 | val OTHER_CLASS_IMPLEMENTS_CONFIGURATION_PROVIDER: TestFile = kotlin(
92 | "com/example/App.kt",
93 | """
94 | package com.example
95 | import androidx.work.Configuration
96 |
97 | class Config : Configuration
98 |
99 | class App : Configuration.Provider {
100 | override fun onCreate() {
101 | }
102 | override fun getWorkManagerConfiguration(): Configuration = Config()
103 | }
104 | """)
105 | .indented().within("src")
106 |
107 | /**
108 | * [TestFile] containing Experimental.kt from the Kotlin standard library.
109 | *
110 | * This is a workaround for the Kotlin standard library used by the Lint test harness not
111 | * including the Experimental annotation by default.
112 | */
113 | val EXPERIMENTAL_KT: TestFile = kotlin(
114 | "kotlin/Experimental.kt",
115 | """
116 | package kotlin
117 |
118 | import kotlin.annotation.AnnotationRetention.BINARY
119 | import kotlin.annotation.AnnotationRetention.SOURCE
120 | import kotlin.annotation.AnnotationTarget.*
121 | import kotlin.internal.RequireKotlin
122 | import kotlin.internal.RequireKotlinVersionKind
123 | import kotlin.reflect.KClass
124 |
125 | @Target(ANNOTATION_CLASS)
126 | @Retention(BINARY)
127 | @SinceKotlin("1.2")
128 | @RequireKotlin("1.2.50", versionKind = RequireKotlinVersionKind.COMPILER_VERSION)
129 | @Suppress("ANNOTATION_CLASS_MEMBER")
130 | public annotation class Experimental(val level: Level = Level.ERROR) {
131 | public enum class Level {
132 | WARNING,
133 | ERROR,
134 | }
135 | }
136 |
137 | @Target(
138 | CLASS, PROPERTY, LOCAL_VARIABLE, VALUE_PARAMETER, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, EXPRESSION, FILE, TYPEALIAS
139 | )
140 | @Retention(SOURCE)
141 | @SinceKotlin("1.2")
142 | @RequireKotlin("1.2.50", versionKind = RequireKotlinVersionKind.COMPILER_VERSION)
143 | public annotation class UseExperimental(
144 | vararg val markerClass: KClass
145 | )
146 |
147 | @Target(CLASS, PROPERTY, CONSTRUCTOR, FUNCTION, TYPEALIAS)
148 | @Retention(BINARY)
149 | internal annotation class WasExperimental(
150 | vararg val markerClass: KClass
151 | )
152 | """).indented().within("src")
153 |
154 | val TIME_TRAVEL_EXPERIMENT_KT: TestFile = kotlin(
155 | "com/rahulrav/app/TimeTravelExperiment.kt",
156 | """
157 | package com.rahulrav.app
158 |
159 | @Experimental
160 | @Retention(AnnotationRetention.BINARY)
161 | @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
162 | annotation class TimeTravelExperiment
163 | """).indented().within("src")
164 |
165 | val TIME_TRAVEL_PROVIDER_KT = kotlin(
166 | "com/rahulrav/app/TimeTravelProvider.kt",
167 | """
168 | package com.rahulrav.app
169 |
170 | @Suppress("unused")
171 | @TimeTravelExperiment
172 | class TimeTravelProvider {
173 | var timeInternal: Long = 0
174 |
175 | fun setTime(timestamp: Long) {
176 | timeInternal = timestamp
177 | }
178 | }
179 | """).indented().within("src")
180 |
181 | val USE_TIME_TRAVEL_EXPERIMENT_FROM_JAVA = java(
182 | "com/rahulrav/app/UseTimeTravelExperimentFromJava.java",
183 | """
184 | package com.rahulrav.app;
185 |
186 | import kotlin.UseExperimental;
187 |
188 | @SuppressWarnings("unused")
189 | class UseTimeTravelExperimentFromJava {
190 | @TimeTravelExperiment
191 | void setTimeToNow() {
192 | new TimeTravelProvider().setTime(System.currentTimeMillis());
193 | }
194 |
195 | @UseExperimental(markerClass = TimeTravelExperiment.class)
196 | void setTimeToEpoch() {
197 | new TimeTravelProvider().setTime(0);
198 | }
199 |
200 | void violateTimeTravelAccords() {
201 | new TimeTravelProvider().setTime(-1);
202 | }
203 | }
204 | """).indented().within("src")
205 |
206 | /**
207 | * [TestFile] containing Log.java from the Android SDK.
208 | *
209 | * This is a hacky workaround for the Android SDK not being included on the Lint test harness
210 | * classpath. Ideally, we'd specify ANDROID_HOME as an environment variable.
211 | */
212 | val ANDROID_LOG_JAVA = java(
213 | """
214 | package android.util;
215 |
216 | public class Log {
217 | public static void wtf(String tag, String msg) {
218 | // Stub!
219 | }
220 | }
221 | """.trimIndent())
222 |
223 | val LOG_WTF_KT = kotlin(
224 | "com/rahulrav/app/WhatATerribleFailure.kt",
225 | """
226 | package com.rahulrav.app
227 |
228 | import android.util.Log
229 |
230 | class WhatATerribleFailure {
231 | fun logAsWtf(clazz: Class, message: String) {
232 | Log.wtf(clazz.name, message)
233 |
234 | wtf(message)
235 | }
236 |
237 | fun wtf(message: String) {
238 | Log.d("TAG", message)
239 | }
240 | }
241 | """).indented().within("src")
242 |
243 | val LOG_WTF_JAVA = java(
244 | "com/rahulrav/app/WhatATerribleFailureJava.java",
245 | """
246 | package com.rahulrav.app;
247 |
248 | import android.util.Log;
249 |
250 | class WhatATerribleFailureJava {
251 | void logAsWtf(Class> clazz, String message) {
252 | Log.wtf(clazz.getName(), message);
253 |
254 | wtf(message);
255 | }
256 |
257 | void wtf(String message) {
258 | Log.d("TAG", message);
259 | }
260 | }
261 | """).indented().within("src")
262 | }
263 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':lint', ':library'
2 |
--------------------------------------------------------------------------------