├── lint-checks ├── .gitignore ├── src │ ├── main │ │ └── kotlin │ │ │ ├── KotlinVisibilityIssueRegistry.kt │ │ │ ├── PsiExtensions.kt │ │ │ └── PrivateMembersUsageDetector.kt │ └── test │ │ └── kotlin │ │ ├── FilePrivateMembersUsageDetectorTest.kt │ │ └── PackagePrivateMembersUsageDetectorTest.kt └── build.gradle.kts ├── _config.yml ├── annotations-and-lint ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ └── java │ │ └── io │ │ └── github │ │ └── esentsov │ │ ├── FilePrivate.kt │ │ └── PackagePrivate.kt ├── proguard-rules.pro └── build.gradle.kts ├── settings.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml └── vcs.xml ├── .gitignore ├── gradle.properties ├── README.md ├── gradlew.bat ├── gradlew └── LICENSE /lint-checks/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /annotations-and-lint/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | include(":lint-checks", ":annotations-and-lint") 2 | -------------------------------------------------------------------------------- /annotations-and-lint/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/esentsov/kotlin-visibility-modifiers/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /annotations-and-lint/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Kotlin visibility 3 | 4 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/* 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | 10 | # IDEA/Android Studio Ignore exceptions 11 | !/.idea/vcs.xml 12 | !/.idea/inspectionProfiles/ 13 | !/.idea/codeStyles/ 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Apr 23 08:33:11 CEST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-all.zip 7 | -------------------------------------------------------------------------------- /annotations-and-lint/src/main/java/io/github/esentsov/FilePrivate.kt: -------------------------------------------------------------------------------- 1 | package io.github.esentsov 2 | 3 | import kotlin.annotation.AnnotationTarget.* 4 | 5 | /** 6 | * Defines member visibility as a single file. 7 | */ 8 | @Retention(AnnotationRetention.SOURCE) 9 | @Target( 10 | CONSTRUCTOR, 11 | FUNCTION, 12 | PROPERTY, 13 | CLASS 14 | ) 15 | @MustBeDocumented 16 | annotation class FilePrivate -------------------------------------------------------------------------------- /annotations-and-lint/src/main/java/io/github/esentsov/PackagePrivate.kt: -------------------------------------------------------------------------------- 1 | package io.github.esentsov 2 | 3 | import kotlin.annotation.AnnotationTarget.* 4 | 5 | /** 6 | * Defines member visibility as package. 7 | */ 8 | @Retention(AnnotationRetention.SOURCE) 9 | @Target( 10 | CONSTRUCTOR, 11 | FUNCTION, 12 | PROPERTY, 13 | CLASS 14 | ) 15 | @MustBeDocumented 16 | annotation class PackagePrivate -------------------------------------------------------------------------------- /lint-checks/src/main/kotlin/KotlinVisibilityIssueRegistry.kt: -------------------------------------------------------------------------------- 1 | package io.github.esentsov.kotlinvisibility 2 | 3 | import com.android.tools.lint.client.api.IssueRegistry 4 | import com.android.tools.lint.detector.api.CURRENT_API 5 | import com.android.tools.lint.detector.api.Issue 6 | 7 | /* 8 | * Provides checks for usage of members annotated with FilePrivate or PackagePrivate. 9 | */ 10 | class KotlinVisibilityIssueRegistry : IssueRegistry() { 11 | override val api: Int = CURRENT_API 12 | override val minApi: Int = 5 13 | override val issues: List = listOf(PrivateMembersUsageDetector.FilePrivateIssue, PrivateMembersUsageDetector.PackagePrivateIssue) 14 | } 15 | 16 | -------------------------------------------------------------------------------- /annotations-and-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.kts. 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-checks/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | kotlin("jvm") 5 | } 6 | 7 | val lintVersion = "27.0.0-beta05" 8 | 9 | dependencies { 10 | compileOnly("com.android.tools.lint:lint-api:$lintVersion") 11 | compileOnly("com.android.tools.lint:lint-checks:$lintVersion") 12 | 13 | testImplementation("junit:junit:4.13") 14 | testImplementation("com.android.tools.lint:lint:$lintVersion") 15 | testImplementation("com.android.tools.lint:lint-tests:$lintVersion") 16 | testImplementation("com.android.tools:testutils:$lintVersion") 17 | } 18 | 19 | tasks.withType { 20 | manifest { 21 | attributes["Lint-Registry-v2"] = "io.github.esentsov.kotlinvisibility.KotlinVisibilityIssueRegistry" 22 | } 23 | } 24 | 25 | tasks.withType { 26 | kotlinOptions.jvmTarget = "1.8" 27 | } -------------------------------------------------------------------------------- /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 | # Kotlin code style for this project: "official" or "obsolete": 15 | kotlin.code.style=official 16 | 17 | android.useAndroidX=true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Kotlin Visibility Modifiers 2 | 3 | Are you missing package private visibility in kotlin? 4 | 5 | This repository is to provide `PackagePrivate` and `FilePrivate` annotations 6 | as well as necessary lint checks. 7 | 8 | `@PackagePrivate` annotation is a replacement for java package-private visibility. 9 | `@FilePrivate` is a new thing for java/kotlin. As the name says annotatated members 10 | can be accessed in the same file they are declared. 11 | 12 | ### Usage 13 | 14 | Add dependency to you `build.gradle`: 15 | 16 | `implementation("io.github.esentsov:kotlin-visibility:1.1.0")` 17 | 18 | Use annotations `@PackagePrivate` and `@FilePrivate` in your code. 19 | All necessary lint checks are already included, Android Studio will pick them up 20 | and show an error every time you are trying to access annotated members outside of the respective scope. 21 | 22 | ### Release notes 23 | 24 | #### 1.1.0 25 | Add support for class-level annotations. Detect usage of annotated classes, objects, annotation classes. 26 | Also detect usage of function, properties or inner classes of an annotated class. 27 | 28 | #### 1.0.0 29 | Initial release. Support detections of annotated constructors, properties and functions. 30 | -------------------------------------------------------------------------------- /lint-checks/src/main/kotlin/PsiExtensions.kt: -------------------------------------------------------------------------------- 1 | package io.github.esentsov.kotlinvisibility 2 | 3 | import org.jetbrains.kotlin.asJava.elements.KtLightDeclaration 4 | import org.jetbrains.kotlin.asJava.elements.KtLightMember 5 | import org.jetbrains.kotlin.psi.KtAnnotated 6 | import org.jetbrains.kotlin.psi.psiUtil.containingClass 7 | import org.jetbrains.uast.kotlin.KotlinUAnnotation 8 | import org.jetbrains.uast.toUElement 9 | 10 | fun KtLightDeclaration<*, *>.isAnnotatedWith(qualifiedName: String): Boolean { 11 | if (listOf(kotlinOrigin, kotlinOrigin?.containingKtFile).any { it?.isAnnotatedWith(qualifiedName) == true }) { 12 | return true 13 | } 14 | 15 | var node = kotlinOrigin?.containingClass() 16 | while (node != null) { 17 | if (node.isAnnotatedWith(qualifiedName)) return true 18 | node = node.containingClass() 19 | } 20 | 21 | if ((this as? KtLightMember<*>)?.containingClass?.isAnnotatedWith(qualifiedName) == true) return true 22 | 23 | return false 24 | } 25 | 26 | private fun KtAnnotated.isAnnotatedWith(qualifiedName: String) = 27 | annotationEntries.mapNotNull { 28 | it.toUElement() as? KotlinUAnnotation 29 | }.any { 30 | it.qualifiedName == qualifiedName 31 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /annotations-and-lint/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.gradle.jvm.tasks.Jar 2 | 3 | plugins { 4 | id("com.android.library") 5 | kotlin("android") 6 | id("org.jetbrains.dokka-android") 7 | `maven-publish` 8 | signing 9 | } 10 | 11 | object Version { 12 | const val name = "1.1.0" 13 | const val code = 2 14 | } 15 | 16 | android { 17 | compileSdkVersion(AndroidSdk.compile) 18 | 19 | defaultConfig { 20 | minSdkVersion(AndroidSdk.min) 21 | targetSdkVersion(AndroidSdk.target) 22 | 23 | versionCode = Version.code 24 | versionName = Version.name 25 | testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" 26 | } 27 | buildTypes { 28 | getByName("release") { 29 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") 30 | } 31 | } 32 | } 33 | 34 | dependencies { 35 | implementation(kotlin("stdlib-jdk8")) 36 | 37 | implementation("androidx.annotation:annotation:1.1.0") 38 | 39 | lintPublish(project(":lint-checks")) 40 | } 41 | 42 | tasks { 43 | dokka { 44 | outputFormat = "html" 45 | outputDirectory = "$buildDir/javadoc" 46 | } 47 | } 48 | 49 | tasks.register("sourcesJar", Jar::class.java) { 50 | archiveClassifier.set("sources") 51 | from(android.sourceSets["main"].java.srcDirs) 52 | } 53 | 54 | tasks.register("javadocJar", Jar::class.java) { 55 | dependsOn.add("dokka") 56 | archiveClassifier.set("javadoc") 57 | from("$buildDir/javadoc") 58 | } 59 | 60 | publishing { 61 | publications { 62 | create("maven") { 63 | groupId = "io.github.esentsov" 64 | artifactId = "kotlin-visibility" 65 | version = Version.name 66 | afterEvaluate { 67 | artifact(tasks["bundleReleaseAar"]) 68 | artifact(tasks["sourcesJar"]) 69 | artifact(tasks["javadocJar"]) 70 | } 71 | 72 | pom { 73 | name.set("Kotlin Visibility Modifiers") 74 | description.set("Provides FilePrivate and PackagePrivate annotations to use in kotlin code as well as lint checks needed to validate their usage") 75 | url.set("https://github.com/esentsov/kotlin-visibility-modifiers") 76 | 77 | licenses { 78 | license { 79 | name.set("The Apache License, Version 2.0") 80 | url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") 81 | } 82 | } 83 | 84 | developers { 85 | developer { 86 | id.set("esentsov") 87 | name.set("Evgeny Sentsov") 88 | email.set("eugeny.sentsov@gmail.com") 89 | } 90 | } 91 | 92 | scm { 93 | connection.set("scm:git:git://github.com/esentsov/kotlin-visibility-modifiers.git") 94 | developerConnection.set("scm:git:ssh://github.com/esentsov/kotlin-visibility-modifiers.git") 95 | url.set("https://github.com/esentsov/kotlin-visibility-modifiers/tree/master") 96 | } 97 | 98 | repositories { 99 | maven { 100 | setUrl("https://oss.sonatype.org/service/local/staging/deploy/maven2/") 101 | authentication { 102 | credentials { 103 | username = project.properties["ossrhUsername"] as? String 104 | password = project.properties["ossrhPassword"] as? String 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | signing { 115 | sign(publishing.publications["maven"]) 116 | } 117 | 118 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 120 | -------------------------------------------------------------------------------- /lint-checks/src/main/kotlin/PrivateMembersUsageDetector.kt: -------------------------------------------------------------------------------- 1 | package io.github.esentsov.kotlinvisibility 2 | 3 | import com.android.tools.lint.client.api.UElementHandler 4 | import com.android.tools.lint.detector.api.* 5 | import com.android.tools.lint.detector.api.Detector.UastScanner 6 | import com.intellij.psi.PsiElement 7 | import com.intellij.psi.impl.source.PsiClassReferenceType 8 | import org.jetbrains.kotlin.asJava.elements.KtLightDeclaration 9 | import org.jetbrains.uast.* 10 | 11 | private const val filePrivateAnnotationName = "io.github.esentsov.FilePrivate" 12 | private const val packagePrivateAnnotationName = "io.github.esentsov.PackagePrivate" 13 | 14 | /** 15 | * Reports usages of members marked with [filePrivateAnnotationName] annotation or [packagePrivateAnnotationName] outside of the respective scopes. 16 | */ 17 | class PrivateMembersUsageDetector : Detector(), UastScanner { 18 | 19 | /** 20 | * Currently we check the raw expressions, they cover all members usage. 21 | * The [UastScanner] api seems to not cover references to properties, declared in primary constructors. 22 | * It also resolves property assignment expressions into getter call, checking for getter annotations 23 | */ 24 | override fun getApplicableUastTypes(): List>? = 25 | listOf(UExpression::class.java, UParameter::class.java, UAnnotation::class.java) 26 | 27 | override fun createUastHandler(context: JavaContext): UElementHandler? = 28 | object : UElementHandler() { 29 | 30 | override fun visitAnnotation(node: UAnnotation) { 31 | check(node, node.resolve()) 32 | } 33 | 34 | override fun visitParameter(node: UParameter) { 35 | check(node, (node.typeReference?.type as? PsiClassReferenceType)?.resolve()) 36 | } 37 | 38 | override fun visitExpression(node: UExpression) { 39 | if (node is UQualifiedReferenceExpression) { 40 | // Avoid duplication by handling lower level part of the reference 41 | // e.g. instance.value will be handled as just value 42 | return 43 | } 44 | check(node, node.tryResolve()) 45 | } 46 | 47 | private fun check(node: UElement, resolved: PsiElement?) { 48 | (resolved as? KtLightDeclaration<*, *>)?.let { 49 | if (resolved.isAnnotatedWith(filePrivateAnnotationName) && areInDifferentFiles(node, resolved)) { 50 | context.report(FilePrivateIssue, node, context.getLocation(node), "Usage of private api") 51 | } 52 | if (resolved.isAnnotatedWith(packagePrivateAnnotationName) && areInDifferentPackages(context, node, resolved)) { 53 | context.report(PackagePrivateIssue, node, context.getLocation(node), "Usage of private api") 54 | } 55 | } 56 | } 57 | } 58 | 59 | private fun areInDifferentFiles(node: UElement, resolved: PsiElement): Boolean { 60 | val declarationFile = resolved.containingFile.virtualFile.path 61 | val referenceFile = node.getContainingUFile()?.getIoFile()?.absolutePath 62 | return referenceFile != null && declarationFile != referenceFile 63 | } 64 | 65 | private fun areInDifferentPackages(context: JavaContext, node: UElement, resolved: KtLightDeclaration<*, *>): Boolean { 66 | val declarationPackage = context.evaluator.getPackage(resolved) 67 | val referencePackage = context.evaluator.getPackage(node) 68 | return declarationPackage?.qualifiedName != referencePackage?.qualifiedName 69 | } 70 | 71 | companion object { 72 | val PackagePrivateIssue = Issue.create( 73 | "PackagePrivateId", 74 | "Kotlin package visibility", 75 | "This check highlights usage of members marked private in package they are declared in", 76 | Category.CORRECTNESS, 77 | 10, 78 | Severity.ERROR, 79 | Implementation(PrivateMembersUsageDetector::class.java, Scope.JAVA_FILE_SCOPE) 80 | ) 81 | val FilePrivateIssue = Issue.create( 82 | "FilePrivateId", 83 | "Kotlin file visibility", 84 | "This check highlights usage of members marked private for files they are declared in", 85 | Category.CORRECTNESS, 86 | 10, 87 | Severity.ERROR, 88 | Implementation(PrivateMembersUsageDetector::class.java, Scope.JAVA_FILE_SCOPE) 89 | ) 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /lint-checks/src/test/kotlin/FilePrivateMembersUsageDetectorTest.kt: -------------------------------------------------------------------------------- 1 | import com.android.tools.lint.checks.infrastructure.LintDetectorTest.kotlin 2 | import com.android.tools.lint.checks.infrastructure.TestLintTask.lint 3 | import com.android.tools.lint.checks.infrastructure.TestResultChecker 4 | import io.github.esentsov.kotlinvisibility.PrivateMembersUsageDetector 5 | import org.intellij.lang.annotations.Language 6 | import org.junit.Assert.assertTrue 7 | import org.junit.Test 8 | 9 | class FilePrivateMembersUsageDetectorTest { 10 | 11 | @Test 12 | fun `call primary constructor`() { 13 | //language=kotlin 14 | doCheck( 15 | """ 16 | class Test @FilePrivate constructor() 17 | """ 18 | , 19 | """ 20 | fun test(){ 21 | Test() 22 | } 23 | """ 24 | ) 25 | } 26 | 27 | @Test 28 | fun `call secondary constructor`() { 29 | //language=kotlin 30 | doCheck( 31 | """ 32 | class Test(val test: Int){ 33 | @FilePrivate constructor():this(1) 34 | } 35 | """ 36 | , 37 | """ 38 | fun test(){ 39 | Test() 40 | } 41 | """ 42 | ) 43 | } 44 | 45 | @Test 46 | fun `call class function on constructed object`() { 47 | //language=kotlin 48 | doCheck( 49 | """ 50 | class Test{ 51 | @FilePrivate 52 | fun test(){} 53 | } 54 | """ 55 | , 56 | """ 57 | fun test(){ 58 | Test().test() 59 | } 60 | """ 61 | ) 62 | } 63 | 64 | @Test 65 | fun `call class function reference`() { 66 | //language=kotlin 67 | doCheck( 68 | """ 69 | class Test{ 70 | @FilePrivate 71 | fun test(){} 72 | } 73 | """ 74 | , 75 | """ 76 | fun test(){ 77 | val instance = Test() 78 | instance.test() 79 | } 80 | """ 81 | ) 82 | } 83 | 84 | @Test 85 | fun `call class function apply`() { 86 | //language=kotlin 87 | doCheck( 88 | """ 89 | class Test{ 90 | @FilePrivate 91 | fun test(){} 92 | } 93 | """ 94 | , 95 | """ 96 | fun test(){ 97 | Test().apply{ 98 | test() 99 | } 100 | } 101 | """ 102 | ) 103 | } 104 | 105 | @Test 106 | fun `get property on constructed object`() { 107 | //language=kotlin 108 | doCheck( 109 | """ 110 | class Test{ 111 | @FilePrivate 112 | val test: Int = 1 113 | } 114 | """ 115 | , 116 | """ 117 | fun test(){ 118 | val value = Test().test 119 | } 120 | """ 121 | ) 122 | } 123 | 124 | @Test 125 | fun `set property on constructed object`() { 126 | //language=kotlin 127 | doCheck( 128 | """ 129 | class Test{ 130 | @FilePrivate 131 | var test: Int = 1 132 | } 133 | """ 134 | , 135 | """ 136 | fun test(){ 137 | Test().test = 2 138 | } 139 | """ 140 | ) 141 | } 142 | 143 | @Test 144 | fun `get property on reference`() { 145 | //language=kotlin 146 | doCheck( 147 | """ 148 | class Test{ 149 | @FilePrivate 150 | val test: Int = 1 151 | } 152 | """ 153 | , 154 | """ 155 | fun test(){ 156 | val instance = Test() 157 | val value = instance.test 158 | } 159 | """ 160 | ) 161 | } 162 | 163 | @Test 164 | fun `set property on reference`() { 165 | //language=kotlin 166 | doCheck( 167 | """ 168 | class Test{ 169 | @FilePrivate 170 | var test: Int = 1 171 | } 172 | """ 173 | , 174 | """ 175 | fun test(){ 176 | val instance = Test() 177 | instance.test = 2 178 | } 179 | """ 180 | ) 181 | } 182 | 183 | @Test 184 | fun `get property in apply`() { 185 | //language=kotlin 186 | doCheck( 187 | """ 188 | class Test{ 189 | @FilePrivate 190 | var test: Int = 1 191 | } 192 | """ 193 | , 194 | """ 195 | fun test(){ 196 | Test().apply{ 197 | val value = test 198 | } 199 | } 200 | """ 201 | ) 202 | } 203 | 204 | @Test 205 | fun `set property in apply`() { 206 | //language=kotlin 207 | doCheck( 208 | """ 209 | class Test{ 210 | @FilePrivate 211 | var test: Int = 1 212 | } 213 | """ 214 | , 215 | """ 216 | fun test(){ 217 | Test().apply{ 218 | test = 2 219 | } 220 | } 221 | """ 222 | ) 223 | } 224 | 225 | @Test 226 | fun `call companion object function`() { 227 | //language=kotlin 228 | doCheck( 229 | """ 230 | class Test{ 231 | companion object{ 232 | @FilePrivate 233 | fun test() = Unit 234 | } 235 | } 236 | """ 237 | , 238 | """ 239 | fun test(){ 240 | Test.test() 241 | } 242 | """ 243 | ) 244 | } 245 | 246 | @Test 247 | fun `set companion object property`() { 248 | //language=kotlin 249 | doCheck( 250 | """ 251 | class Test{ 252 | companion object{ 253 | @FilePrivate 254 | var test: Int 255 | get() = 1 256 | set(value){} 257 | } 258 | } 259 | """ 260 | , 261 | """ 262 | fun test(){ 263 | Test.test = 23 264 | } 265 | """ 266 | ) 267 | } 268 | 269 | @Test 270 | fun `access class as a function parameter`() { 271 | //language=kotlin 272 | doCheck( 273 | """ 274 | @FilePrivate 275 | class PrivateClass 276 | """ 277 | , 278 | """ 279 | fun test(instance:PrivateClass){} 280 | """ 281 | ) 282 | } 283 | 284 | @Test 285 | fun `access class as a constructor parameter`() { 286 | //language=kotlin 287 | doCheck( 288 | """ 289 | @FilePrivate 290 | class PrivateClass 291 | """ 292 | , 293 | """ 294 | class Test(property: PrivateClass) 295 | """ 296 | ) 297 | } 298 | 299 | @Test 300 | fun `access class as a constructor property`() { 301 | //language=kotlin 302 | doCheck( 303 | """ 304 | @FilePrivate 305 | class PrivateClass 306 | """ 307 | , 308 | """ 309 | class Test(val property: PrivateClass) 310 | """ 311 | ) 312 | } 313 | 314 | @Test 315 | fun `access class constructor`() { 316 | //language=kotlin 317 | doCheck( 318 | """ 319 | @FilePrivate 320 | class PrivateClass 321 | """ 322 | , 323 | """ 324 | val test = PrivateClass() 325 | """ 326 | ) 327 | } 328 | 329 | @Test 330 | fun `access class property`() { 331 | //language=kotlin 332 | doCheck( 333 | """ 334 | @FilePrivate 335 | class PrivateClass{ 336 | val p = 0 337 | } 338 | """ 339 | , 340 | """ 341 | fun test(instance:PrivateClass){ 342 | instance.p 343 | } 344 | """, 345 | 2 // class ref and property access 346 | ) 347 | } 348 | 349 | @Test 350 | fun `access class function`() { 351 | //language=kotlin 352 | doCheck( 353 | """ 354 | @FilePrivate 355 | class PrivateClass{ 356 | fun test(){} 357 | } 358 | """ 359 | , 360 | """ 361 | fun test(instance:PrivateClass){ 362 | instance.test() 363 | } 364 | """, 365 | 2 // class ref and fun call 366 | ) 367 | } 368 | 369 | @Test 370 | fun `access companion object`() { 371 | //language=kotlin 372 | doCheck( 373 | """ 374 | @FilePrivate 375 | class PrivateClass{ 376 | companion object{ 377 | const val property = 0 378 | } 379 | } 380 | """ 381 | , 382 | """ 383 | val test = PrivateClass.property 384 | """, 385 | 2 // class ref and property access 386 | ) 387 | } 388 | 389 | @Test 390 | fun `access inner class`() { 391 | //language=kotlin 392 | doCheck( 393 | """ 394 | @FilePrivate 395 | class PrivateClass{ 396 | class InnerClass() 397 | } 398 | """ 399 | , 400 | """ 401 | val test = PrivateClass.InnerClass() 402 | """, 403 | 2 // class ref, inner class constructor call 404 | ) 405 | } 406 | 407 | @Test 408 | fun `access inner class property`() { 409 | //language=kotlin 410 | doCheck( 411 | """ 412 | @FilePrivate 413 | class PrivateClass{ 414 | class InnerClass{ 415 | val property: Int = 0 416 | } 417 | } 418 | """ 419 | , 420 | """ 421 | val test = PrivateClass.InnerClass().property 422 | """, 423 | 3 // class ref, inner class constructor, inner class property 424 | ) 425 | } 426 | 427 | @Test 428 | fun `access object property`() { 429 | //language=kotlin 430 | doCheck( 431 | """ 432 | @FilePrivate 433 | object PrivateObject{ 434 | val property: Int = 0 435 | } 436 | """ 437 | , 438 | """ 439 | val test = PrivateObject.property 440 | """, 441 | 2 // object ref, property ref 442 | ) 443 | } 444 | 445 | @Test 446 | fun `access annotation on class`() { 447 | //language=kotlin 448 | doCheck( 449 | """ 450 | @FilePrivate 451 | annotation class PrivateAnnotation 452 | """ 453 | , 454 | """ 455 | @PrivateAnnotation 456 | class Test() 457 | """ 458 | ) 459 | } 460 | 461 | @Test 462 | fun `access annotation on fun and property`() { 463 | //language=kotlin 464 | doCheck( 465 | """ 466 | @FilePrivate 467 | annotation class PrivateAnnotation 468 | """ 469 | , 470 | """ 471 | class Test(){ 472 | @PrivateAnnotation 473 | val property:Int = 0 474 | 475 | @PrivateAnnotation 476 | fun test(){} 477 | } 478 | """, 479 | 2 480 | ) 481 | } 482 | 483 | @Test 484 | fun `access annotation on constructor`() { 485 | //language=kotlin 486 | doCheck( 487 | """ 488 | @FilePrivate 489 | annotation class PrivateAnnotation 490 | """ 491 | , 492 | """ 493 | @PrivateAnnotation 494 | val property:Int = 0 495 | 496 | @PrivateAnnotation 497 | fun test(){} 498 | """, 499 | 2 500 | ) 501 | } 502 | 503 | @Test 504 | fun `access annotation on top-level`() { 505 | //language=kotlin 506 | doCheck( 507 | """ 508 | @FilePrivate 509 | annotation class PrivateAnnotation 510 | """ 511 | , 512 | """ 513 | class Test @PrivateAnnotation constructor () 514 | """ 515 | ) 516 | } 517 | 518 | private fun doCheck(declaration: String, usage: String, errorCount: Int = 1) { 519 | lint() 520 | .files( 521 | kotlin(annotationDeclaration), 522 | kotlin( 523 | declarationFileName, 524 | """ 525 | $header 526 | 527 | $declaration 528 | """ 529 | ).within("src"), 530 | kotlin( 531 | usageFileName, 532 | """ 533 | $header 534 | 535 | $usage 536 | """ 537 | ).within("src") 538 | ) 539 | .allowDuplicates() 540 | .issues(PrivateMembersUsageDetector.FilePrivateIssue) 541 | .run() 542 | .expectErrorCount(errorCount) 543 | .check(TestResultChecker { 544 | assertTrue(it.contains("Usage of private api [FilePrivateId]")) 545 | }) 546 | 547 | lint() 548 | .files( 549 | kotlin(annotationDeclaration), 550 | kotlin( 551 | declarationFileName, 552 | """ 553 | $header 554 | 555 | $declaration 556 | 557 | $usage 558 | """ 559 | ) 560 | ).issues(PrivateMembersUsageDetector.FilePrivateIssue) 561 | .run() 562 | .expectClean() 563 | } 564 | } 565 | 566 | private const val declarationFileName = "Declaration.kt" 567 | private const val usageFileName = "Usage.kt" 568 | 569 | @Language("kotlin") 570 | private const val header = """ 571 | import io.github.esentsov.FilePrivate 572 | """ 573 | 574 | @Language("kotlin") 575 | private const val annotationDeclaration = """ 576 | package io.github.esentsov 577 | 578 | import kotlin.annotation.AnnotationTarget.* 579 | 580 | @Retention(AnnotationRetention.SOURCE) 581 | @Target( 582 | CONSTRUCTOR, 583 | FUNCTION, 584 | PROPERTY, 585 | CLASS 586 | ) 587 | annotation class FilePrivate 588 | """ -------------------------------------------------------------------------------- /lint-checks/src/test/kotlin/PackagePrivateMembersUsageDetectorTest.kt: -------------------------------------------------------------------------------- 1 | import com.android.tools.lint.checks.infrastructure.LintDetectorTest.kotlin 2 | import com.android.tools.lint.checks.infrastructure.TestLintTask.lint 3 | import com.android.tools.lint.checks.infrastructure.TestResultChecker 4 | import io.github.esentsov.kotlinvisibility.PrivateMembersUsageDetector 5 | import org.intellij.lang.annotations.Language 6 | import org.junit.Assert.assertTrue 7 | import org.junit.Test 8 | 9 | class PackagePrivateMembersUsageDetectorTest { 10 | @Test 11 | fun `get top level property`() { 12 | //language=kotlin 13 | doCheck( 14 | """ 15 | @PackagePrivate 16 | val variable = 1 17 | """ 18 | , 19 | """ 20 | fun test(){ 21 | val local = variable 22 | } 23 | """ 24 | ) 25 | } 26 | 27 | @Test 28 | fun `get top level property no field`() { 29 | //language=kotlin 30 | doCheck( 31 | """ 32 | @PackagePrivate 33 | val variable get() = 1 34 | """ 35 | , 36 | """ 37 | fun test(){ 38 | val local = variable 39 | } 40 | """ 41 | ) 42 | } 43 | 44 | @Test 45 | fun `set top level property`() { 46 | //language=kotlin 47 | doCheck( 48 | """ 49 | @PackagePrivate 50 | var variable = 1 51 | """ 52 | , 53 | """ 54 | fun test(){ 55 | variable = 2 56 | } 57 | """ 58 | ) 59 | } 60 | 61 | @Test 62 | fun `call top level function`() { 63 | //language=kotlin 64 | doCheck( 65 | """ 66 | @PackagePrivate 67 | fun function(){} 68 | """ 69 | , 70 | """ 71 | fun test(){ 72 | function() 73 | } 74 | """ 75 | ) 76 | } 77 | 78 | @Test 79 | fun `call primary constructor`() { 80 | //language=kotlin 81 | doCheck( 82 | """ 83 | class Test @PackagePrivate constructor() 84 | """ 85 | , 86 | """ 87 | fun test(){ 88 | Test() 89 | } 90 | """ 91 | ) 92 | } 93 | 94 | @Test 95 | fun `call secondary constructor`() { 96 | //language=kotlin 97 | doCheck( 98 | """ 99 | class Test(val test: Int){ 100 | @PackagePrivate constructor():this(1) 101 | } 102 | """ 103 | , 104 | """ 105 | fun test(){ 106 | Test() 107 | } 108 | """ 109 | ) 110 | } 111 | 112 | @Test 113 | fun `call class function on constructed object`() { 114 | //language=kotlin 115 | doCheck( 116 | """ 117 | class Test{ 118 | @PackagePrivate 119 | fun test(){} 120 | } 121 | """ 122 | , 123 | """ 124 | fun test(){ 125 | Test().test() 126 | } 127 | """ 128 | ) 129 | } 130 | 131 | @Test 132 | fun `call class function reference`() { 133 | //language=kotlin 134 | doCheck( 135 | """ 136 | class Test{ 137 | @PackagePrivate 138 | fun test(){} 139 | } 140 | """ 141 | , 142 | """ 143 | fun test(){ 144 | val instance = Test() 145 | instance.test() 146 | } 147 | """ 148 | ) 149 | } 150 | 151 | @Test 152 | fun `call class function apply`() { 153 | //language=kotlin 154 | doCheck( 155 | """ 156 | class Test{ 157 | @PackagePrivate 158 | fun test(){} 159 | } 160 | """ 161 | , 162 | """ 163 | fun test(){ 164 | Test().apply{ 165 | test() 166 | } 167 | } 168 | """ 169 | ) 170 | } 171 | 172 | @Test 173 | fun `get property on constructed object`() { 174 | //language=kotlin 175 | doCheck( 176 | """ 177 | class Test{ 178 | @PackagePrivate 179 | val test: Int = 1 180 | } 181 | """ 182 | , 183 | """ 184 | fun test(){ 185 | val value = Test().test 186 | } 187 | """ 188 | ) 189 | } 190 | 191 | @Test 192 | fun `set property on constructed object`() { 193 | //language=kotlin 194 | doCheck( 195 | """ 196 | class Test{ 197 | @PackagePrivate 198 | var test: Int = 1 199 | } 200 | """ 201 | , 202 | """ 203 | fun test(){ 204 | Test().test = 2 205 | } 206 | """ 207 | ) 208 | } 209 | 210 | @Test 211 | fun `get property on reference`() { 212 | //language=kotlin 213 | doCheck( 214 | """ 215 | class Test{ 216 | @PackagePrivate 217 | val test: Int = 1 218 | } 219 | """ 220 | , 221 | """ 222 | fun test(){ 223 | val instance = Test() 224 | val value = instance.test 225 | } 226 | """ 227 | ) 228 | } 229 | 230 | @Test 231 | fun `set property on reference`() { 232 | //language=kotlin 233 | doCheck( 234 | """ 235 | class Test{ 236 | @PackagePrivate 237 | var test: Int = 1 238 | } 239 | """ 240 | , 241 | """ 242 | fun test(){ 243 | val instance = Test() 244 | instance.test = 2 245 | } 246 | """ 247 | ) 248 | } 249 | 250 | @Test 251 | fun `get property in apply`() { 252 | //language=kotlin 253 | doCheck( 254 | """ 255 | class Test{ 256 | @PackagePrivate 257 | var test: Int = 1 258 | } 259 | """ 260 | , 261 | """ 262 | fun test(){ 263 | Test().apply{ 264 | val value = test 265 | } 266 | } 267 | """ 268 | ) 269 | } 270 | 271 | @Test 272 | fun `set property in apply`() { 273 | //language=kotlin 274 | doCheck( 275 | """ 276 | class Test{ 277 | @PackagePrivate 278 | var test: Int = 1 279 | } 280 | """ 281 | , 282 | """ 283 | fun test(){ 284 | Test().apply{ 285 | test = 2 286 | } 287 | } 288 | """ 289 | ) 290 | } 291 | 292 | @Test 293 | fun `call companion object function`() { 294 | //language=kotlin 295 | doCheck( 296 | """ 297 | class Test{ 298 | companion object{ 299 | @PackagePrivate 300 | fun test() = Unit 301 | } 302 | } 303 | """ 304 | , 305 | """ 306 | fun test(){ 307 | Test.test() 308 | } 309 | """ 310 | ) 311 | } 312 | 313 | @Test 314 | fun `set companion object property`() { 315 | //language=kotlin 316 | doCheck( 317 | """ 318 | class Test{ 319 | companion object{ 320 | @PackagePrivate 321 | var test: Int 322 | get() = 1 323 | set(value){} 324 | } 325 | } 326 | """ 327 | , 328 | """ 329 | fun test(){ 330 | Test.test = 23 331 | } 332 | """ 333 | ) 334 | } 335 | 336 | @Test 337 | fun `call extension function`() { 338 | //language=kotlin 339 | doCheck( 340 | """ 341 | class Test 342 | 343 | @PackagePrivate 344 | fun Test.test(){} 345 | """ 346 | , 347 | """ 348 | fun test(){ 349 | Test().test() 350 | } 351 | """ 352 | ) 353 | } 354 | 355 | @Test 356 | fun `get extension property`() { 357 | //language=kotlin 358 | doCheck( 359 | """ 360 | class Test 361 | 362 | @PackagePrivate 363 | val Test.test: Int get() = 1 364 | """ 365 | , 366 | """ 367 | fun test(){ 368 | val a = Test().test 369 | } 370 | """ 371 | ) 372 | } 373 | 374 | @Test 375 | fun `access class as a function parameter`() { 376 | //language=kotlin 377 | doCheck( 378 | """ 379 | @PackagePrivate 380 | class PrivateClass 381 | """ 382 | , 383 | """ 384 | fun test(instance:PrivateClass){} 385 | """ 386 | ) 387 | } 388 | 389 | @Test 390 | fun `access class as a constructor parameter`() { 391 | //language=kotlin 392 | doCheck( 393 | """ 394 | @PackagePrivate 395 | class PrivateClass 396 | """ 397 | , 398 | """ 399 | class Test(property: PrivateClass) 400 | """ 401 | ) 402 | } 403 | 404 | @Test 405 | fun `access class as a constructor property`() { 406 | //language=kotlin 407 | doCheck( 408 | """ 409 | @PackagePrivate 410 | class PrivateClass 411 | """ 412 | , 413 | """ 414 | class Test(val property: PrivateClass) 415 | """ 416 | ) 417 | } 418 | 419 | @Test 420 | fun `access class constructor`() { 421 | //language=kotlin 422 | doCheck( 423 | """ 424 | @PackagePrivate 425 | class PrivateClass 426 | """ 427 | , 428 | """ 429 | val test = PrivateClass() 430 | """ 431 | ) 432 | } 433 | 434 | @Test 435 | fun `access class property`() { 436 | //language=kotlin 437 | doCheck( 438 | """ 439 | @PackagePrivate 440 | class PrivateClass{ 441 | val p = 0 442 | } 443 | """ 444 | , 445 | """ 446 | fun test(instance:PrivateClass){ 447 | instance.p 448 | } 449 | """, 450 | 2 // class ref and property access 451 | ) 452 | } 453 | 454 | @Test 455 | fun `access class function`() { 456 | //language=kotlin 457 | doCheck( 458 | """ 459 | @PackagePrivate 460 | class PrivateClass{ 461 | fun test(){} 462 | } 463 | """ 464 | , 465 | """ 466 | fun test(instance:PrivateClass){ 467 | instance.test() 468 | } 469 | """, 470 | 2 // class ref and fun call 471 | ) 472 | } 473 | 474 | @Test 475 | fun `access companion object`() { 476 | //language=kotlin 477 | doCheck( 478 | """ 479 | @PackagePrivate 480 | class PrivateClass{ 481 | companion object{ 482 | const val property = 0 483 | } 484 | } 485 | """ 486 | , 487 | """ 488 | val test = PrivateClass.property 489 | """, 490 | 2 // class ref and property access 491 | ) 492 | } 493 | 494 | @Test 495 | fun `access inner class`() { 496 | //language=kotlin 497 | doCheck( 498 | """ 499 | @PackagePrivate 500 | class PrivateClass{ 501 | class InnerClass() 502 | } 503 | """ 504 | , 505 | """ 506 | val test = PrivateClass.InnerClass() 507 | """, 508 | 2 // class ref, inner class constructor call 509 | ) 510 | } 511 | 512 | @Test 513 | fun `access inner class property`() { 514 | //language=kotlin 515 | doCheck( 516 | """ 517 | @PackagePrivate 518 | class PrivateClass{ 519 | class InnerClass{ 520 | val property: Int = 0 521 | } 522 | } 523 | """ 524 | , 525 | """ 526 | val test = PrivateClass.InnerClass().property 527 | """, 528 | 3 // class ref, inner class constructor, inner class property 529 | ) 530 | } 531 | 532 | @Test 533 | fun `access object property`() { 534 | //language=kotlin 535 | doCheck( 536 | """ 537 | @PackagePrivate 538 | object PrivateObject{ 539 | val property: Int = 0 540 | } 541 | """ 542 | , 543 | """ 544 | val test = PrivateObject.property 545 | """, 546 | 2 // object ref, property ref 547 | ) 548 | } 549 | 550 | @Test 551 | fun `access annotation on class`() { 552 | //language=kotlin 553 | doCheck( 554 | """ 555 | @PackagePrivate 556 | annotation class PrivateAnnotation 557 | """ 558 | , 559 | """ 560 | @PrivateAnnotation 561 | class Test() 562 | """ 563 | ) 564 | } 565 | 566 | @Test 567 | fun `access annotation on fun and property`() { 568 | //language=kotlin 569 | doCheck( 570 | """ 571 | @PackagePrivate 572 | annotation class PrivateAnnotation 573 | """ 574 | , 575 | """ 576 | class Test(){ 577 | @PrivateAnnotation 578 | val property:Int = 0 579 | 580 | @PrivateAnnotation 581 | fun test(){} 582 | } 583 | """, 584 | 2 585 | ) 586 | } 587 | 588 | @Test 589 | fun `access annotation on constructor`() { 590 | //language=kotlin 591 | doCheck( 592 | """ 593 | @PackagePrivate 594 | annotation class PrivateAnnotation 595 | """ 596 | , 597 | """ 598 | @PrivateAnnotation 599 | val property:Int = 0 600 | 601 | @PrivateAnnotation 602 | fun test(){} 603 | """, 604 | 2 605 | ) 606 | } 607 | 608 | 609 | private fun doCheck(declaration: String, usage: String, errorCount: Int = 1) { 610 | lint() 611 | .files( 612 | kotlin(annotationDeclaration), 613 | kotlin( 614 | """ 615 | $anotherPackageDefinitionsHeader 616 | $declaration 617 | """ 618 | ), 619 | kotlin( 620 | """ 621 | $usageHeader 622 | $usage 623 | """ 624 | ) 625 | ) 626 | .allowDuplicates() 627 | .issues(PrivateMembersUsageDetector.PackagePrivateIssue) 628 | .run() 629 | .expectErrorCount(errorCount) 630 | .check(TestResultChecker { 631 | assertTrue(it.contains("Usage of private api [PackagePrivateId]")) 632 | }) 633 | 634 | lint() 635 | .files( 636 | kotlin(annotationDeclaration), 637 | kotlin( 638 | """ 639 | $samePackageDefinitionsHeader 640 | $declaration 641 | """ 642 | ), 643 | kotlin( 644 | """ 645 | $usageHeader 646 | $usage 647 | """ 648 | ) 649 | ).issues(PrivateMembersUsageDetector.PackagePrivateIssue) 650 | .run() 651 | .expectClean() 652 | } 653 | } 654 | 655 | @Language("kotlin") 656 | private const val anotherPackageDefinitionsHeader = """ 657 | package com.test.definitions 658 | import io.github.esentsov.PackagePrivate 659 | """ 660 | 661 | @Language("kotlin") 662 | private const val samePackageDefinitionsHeader = """ 663 | package com.test.usage 664 | import io.github.esentsov.PackagePrivate 665 | """ 666 | 667 | @Language("kotlin") 668 | private const val usageHeader = """ 669 | package com.test.usage 670 | import com.test.definitions.* 671 | """ 672 | 673 | @Language("kotlin") 674 | private const val annotationDeclaration = """ 675 | package io.github.esentsov 676 | 677 | import kotlin.annotation.AnnotationTarget.* 678 | 679 | @Retention(AnnotationRetention.SOURCE) 680 | @Target( 681 | CONSTRUCTOR, 682 | FUNCTION, 683 | PROPERTY, 684 | CLASS 685 | ) 686 | annotation class PackagePrivate 687 | """ --------------------------------------------------------------------------------