diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 5d8b9918..3463ba78 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -49,15 +49,6 @@
-
diff --git a/.travis.yml b/.travis.yml
index 33211a14..21c4aff4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,29 +1,52 @@
language: android
-
-android:
- components:
- - tools
- - platform-tools
- - build-tools-28.0.3
- - build-tools-29.0.2
- - android-28
- - extra-google-google_play_services
- - extra-android-m2repository
- - extra-google-m2repository
-
-jdk:
-- oraclejdk8
+jdk: oraclejdk11
branches:
except:
- - gh-pages
+ - gh-pages
notifications:
email: false
-script: "./gradlew check"
+env:
+ global:
+ - ADB_INSTALL_TIMEOUT=8
+
+android:
+ licenses:
+ - 'android-sdk-preview-license-.+'
+ - 'android-sdk-license-.+'
+ - 'google-gdk-license-.+'
+ components:
+ - tools
+ - platform-tools
+ - build-tools-30.0.2
+ - android-21
+ - android-28
+ - android-29
+ - android-30
+ - extra-google-google_play_services
+ - extra-android-m2repository
+ - extra-google-m2repository
+ - sys-img-armeabi-v7a-android-21
+
+before_install:
+ - mkdir "$ANDROID_HOME/licenses" || true
+ - echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" > "$ANDROID_HOME/licenses/android-sdk-license"
+
+before_script:
+ - android list targets
+ - echo no | android create avd --force -n test -t android-21 --abi armeabi-v7a
+ - emulator -avd test -no-skin -no-window &
+ - android-wait-for-emulator
+ - adb shell input keyevent 82 &
+ - adb shell settings put secure long_press_timeout 1500
+
+script:
+ - ./gradlew check --stacktrace
+ - ./gradlew connectedDebugAndroidTest --stacktrace
cache:
directories:
- - $HOME/.m2
- - $HOME/.gradle
\ No newline at end of file
+ - $HOME/.m2
+ - $HOME/.gradle
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9495bd7f..3d513bcb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,67 @@
+# 2.0.0
+Paris now supports Kotlin Symbol Processing for faster builds! https://github.com/google/ksp
+
+Breaking changes needed to support KSP:
+- ParisConfig annotation can no longer be used on package elements, only on class or interfaces
+- R class references used in annotations cannot be star imported
+- Added `aggregateStyleablesOnClassPath` parameter to @ParisConfig. This must now be set to true to have the module generate a Paris class using only classpath dependencies if there are no Styleables in the module sources.
+
+Note: Due to a bug in KSP (https://github.com/google/ksp/issues/630) it is recommended to disable KSP incremental compilation until the bug is fixed in KSP, otherwise you may encounter spurious build failures.
+
+Additionally, if you are using R2 generation via the butterknife gradle plugin you must configure KSP to be aware of those generated sources.
+This can be done either via the experiment KSP setting `allowSourcesFromOtherPlugins`
+```
+// build.gradle.kts
+ksp {
+ allowSourceFromOtherPlugins=true
+}
+```
+
+Or by manually registering R2 sources as an input to KSP
+```kotlin
+// In a gradle plugin or build.gradle.kts
+project.afterEvaluate {
+ setUpR2TaskDependency()
+}
+
+fun Project.setUpR2TaskDependency() {
+ requireAndroidVariants().forEach { variant ->
+ val r2Task = runCatching { project.tasks.named("generate${variant.name.capitalize()}R2") }.getOrNull()
+ if (r2Task != null) {
+ project.tasks.named("ksp${variant.name.capitalize()}Kotlin").dependsOn(r2Task)
+
+ project.extensions.configure(KotlinAndroidProjectExtension::class.java) {
+ sourceSets.getByName(variant.name)
+ .kotlin
+ .srcDir("build/generated/source/r2/${variant.name}")
+ }
+ }
+ }
+}
+
+/**
+ * Return the Android variants for this module, or error if this is not a module with a known Android plugin.
+ */
+fun Project.requireAndroidVariants(): DomainObjectSet {
+ return androidVariants() ?: error("no known android extension found for ${project.name}")
+}
+
+/**
+ * Return the Android variants for this module, or null if this is not a module with a known Android plugin.
+ */
+fun Project.androidVariants(): DomainObjectSet? {
+ return when (val androidExtension = this.extensions.findByName("android")) {
+ is LibraryExtension -> {
+ androidExtension.libraryVariants
+ }
+ is AppExtension -> {
+ androidExtension.applicationVariants
+ }
+ else -> null
+ }
+}
+```
+
# 1.7.3 (April 13, 2021)
- Fix generated code consistency in module class (#154)
diff --git a/README.md b/README.md
index cedc8325..2197a95d 100644
--- a/README.md
+++ b/README.md
@@ -13,9 +13,11 @@ Paris lets you define and apply styles programmatically to Android views, includ
In your project's `build.gradle`:
```gradle
dependencies {
- implementation 'com.airbnb.android:paris:1.7.3'
- // If you're using Paris annotations.
- kapt 'com.airbnb.android:paris-processor:1.7.3'
+ implementation 'com.airbnb.android:paris:2.0.0'
+ // Apply the Paris processor if you're using Paris annotations for code gen.
+ kapt 'com.airbnb.android:paris-processor:2.0.0'
+ // or if you are using Kotlin Symbol Processing
+ ksp 'com.airbnb.android:paris-processor:2.0.0'
}
```
diff --git a/blessedDeps.gradle b/blessedDeps.gradle
index 21dd7f49..82ad0105 100644
--- a/blessedDeps.gradle
+++ b/blessedDeps.gradle
@@ -12,6 +12,7 @@ rootProject.ext {
ANDROIDX_APPCOMPAT_VERSION = '1.3.1'
ANDROIDX_CONSTRAINTLAYOUT_VERSION = '2.1.0'
ANDROIDX_ESPRESSO_VERSION = '3.4.0'
+ COROUTINES_VERSION = '1.5.2'
INCAP_VERSION = "0.3"
JAVAPOET_VERSION = '1.13.0'
JUNIT_VERSION = '4.13.2'
@@ -20,24 +21,26 @@ rootProject.ext {
MOCKITO_VERSION = '3.11.2'
ROBOLECTRIC_VERSION = '4.6.1'
TESTING_COMPILE_VERSION = '0.19'
+ KOTLIN_TESTING_COMPILE_VERSION = '1.4.4'
deps = [
// Keep these alphabetized
- androidAnnotations: "androidx.annotation:annotation:$ANDROIDX_ANNOTATIONS_VERSION",
- appcompat : "androidx.appcompat:appcompat:$ANDROIDX_APPCOMPAT_VERSION",
- constraintLayout : "androidx.constraintlayout:constraintlayout:$ANDROIDX_CONSTRAINTLAYOUT_VERSION",
- javaPoet : "com.squareup:javapoet:$JAVAPOET_VERSION",
- espresso : "androidx.test.espresso:espresso-core:$ANDROIDX_ESPRESSO_VERSION",
- incapRuntime : "net.ltgt.gradle.incap:incap:$INCAP_VERSION",
- incapProcessor : "net.ltgt.gradle.incap:incap-processor:$INCAP_VERSION",
- junit : "junit:junit:$JUNIT_VERSION",
- kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$KOTLIN_VERSION",
- kotlinReflect : "org.jetbrains.kotlin:kotlin-reflect:$KOTLIN_VERSION",
- kotlinPoet : "com.squareup:kotlinpoet:$KOTLINPOET_VERSION",
- kotlinTest : "io.kotlintest:kotlintest:$KOTLIN_TEST_VERSION",
- mockitoCore : "org.mockito:mockito-core:$MOCKITO_VERSION",
- mockitoAndroid : "org.mockito:mockito-android:$MOCKITO_VERSION",
- robolectric : "org.robolectric:robolectric:$ROBOLECTRIC_VERSION",
- testingCompile : "com.google.testing.compile:compile-testing:$TESTING_COMPILE_VERSION",
+ androidAnnotations : "androidx.annotation:annotation:$ANDROIDX_ANNOTATIONS_VERSION",
+ appcompat : "androidx.appcompat:appcompat:$ANDROIDX_APPCOMPAT_VERSION",
+ constraintLayout : "androidx.constraintlayout:constraintlayout:$ANDROIDX_CONSTRAINTLAYOUT_VERSION",
+ coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$COROUTINES_VERSION",
+ javaPoet : "com.squareup:javapoet:$JAVAPOET_VERSION",
+ espresso : "androidx.test.espresso:espresso-core:$ANDROIDX_ESPRESSO_VERSION",
+ incapRuntime : "net.ltgt.gradle.incap:incap:$INCAP_VERSION",
+ incapProcessor : "net.ltgt.gradle.incap:incap-processor:$INCAP_VERSION",
+ junit : "junit:junit:$JUNIT_VERSION",
+ kotlinCompileTesting: "com.github.tschuchortdev:kotlin-compile-testing-ksp:$KOTLIN_TESTING_COMPILE_VERSION",
+ kotlinReflect : "org.jetbrains.kotlin:kotlin-reflect:$KOTLIN_VERSION",
+ kotlinPoet : "com.squareup:kotlinpoet:$KOTLINPOET_VERSION",
+ kotlinTest : "io.kotlintest:kotlintest:$KOTLIN_TEST_VERSION",
+ mockitoCore : "org.mockito:mockito-core:$MOCKITO_VERSION",
+ mockitoAndroid : "org.mockito:mockito-android:$MOCKITO_VERSION",
+ robolectric : "org.robolectric:robolectric:$ROBOLECTRIC_VERSION",
+ testingCompile : "com.google.testing.compile:compile-testing:$TESTING_COMPILE_VERSION",
]
}
diff --git a/build.gradle b/build.gradle
index ff6e7183..31a21f52 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,11 +1,10 @@
-apply plugin: "com.github.ben-manes.versions"
-
buildscript {
ext {
- ANDROID_PLUGIN_VERSION = '4.1.3'
- BUTTERKNIFE_VERSION = '10.2.1'
- KOTLIN_VERSION = '1.5.21'
+ ANDROID_PLUGIN_VERSION = '7.0.1'
+ BUTTERKNIFE_VERSION = '10.2.3'
+ KOTLIN_VERSION = '1.5.30'
VERSIONS_VERSION = '0.39.0'
+ KSP_VERSION = '1.5.30-1.0.0'
}
repositories {
@@ -26,6 +25,12 @@ buildscript {
}
}
+plugins {
+ id "com.google.devtools.ksp" version "$KSP_VERSION"
+}
+
+apply plugin: "com.github.ben-manes.versions"
+
allprojects {
repositories {
google()
@@ -39,7 +44,6 @@ allprojects {
subprojects { project ->
apply from: "$rootDir/blessedDeps.gradle"
- // TODO Same for maven upload script?
}
task clean(type: Delete) {
diff --git a/gradle.properties b/gradle.properties
index fcd82de0..2a683448 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,4 @@
-VERSION_NAME=1.7.3
+VERSION_NAME=2.0.0
GROUP=com.airbnb.android
POM_DESCRIPTION=Paris is a system for creating and applying styles to views in Android.
POM_URL=https://github.com/airbnb/paris
@@ -17,9 +17,10 @@ android.useAndroidX=true
org.gradle.parallel=true
org.gradle.incremental=true
org.gradle.configureondemand=true
-org.gradle.daemon=true
kotlin.incremental=true
kapt.includeCompileClasspath=false
# Dokka fails without a larger metaspace https://github.com/Kotlin/dokka/issues/1405
org.gradle.jvmargs=-XX:MaxMetaspaceSize=1g
+org.gradle.daemon=true
+#org.gradle.debug=true
diff --git a/libs/rt.jar b/libs/rt.jar
new file mode 100644
index 00000000..064d462b
Binary files /dev/null and b/libs/rt.jar differ
diff --git a/libs/tools.jar b/libs/tools.jar
new file mode 100644
index 00000000..fe2069d5
Binary files /dev/null and b/libs/tools.jar differ
diff --git a/paris-annotations/build.gradle b/paris-annotations/build.gradle
index 7dcdedb0..6fff7659 100644
--- a/paris-annotations/build.gradle
+++ b/paris-annotations/build.gradle
@@ -6,5 +6,4 @@ sourceCompatibility = rootProject.JAVA_SOURCE_VERSION
targetCompatibility = rootProject.JAVA_TARGET_VERSION
dependencies {
- implementation deps.kotlin
}
diff --git a/paris-annotations/src/main/java/com/airbnb/paris/annotations/ParisConfig.java b/paris-annotations/src/main/java/com/airbnb/paris/annotations/ParisConfig.java
index efd78eb8..d342158a 100644
--- a/paris-annotations/src/main/java/com/airbnb/paris/annotations/ParisConfig.java
+++ b/paris-annotations/src/main/java/com/airbnb/paris/annotations/ParisConfig.java
@@ -6,11 +6,10 @@
import java.lang.annotation.Target;
/**
- * Note that when using KAPT with incremental annotation processing it is recommended to only use this annotation on class or interface elements,
- * not on package elements in package-info.java. This is because there is a bug where package-info is not properly reprocessed in incremental builds.
+ * Place this annotation on a single class or interface within your module to specify configuration options for that module.
*/
@Retention(RetentionPolicy.CLASS)
-@Target({ElementType.PACKAGE, ElementType.TYPE})
+@Target(ElementType.TYPE)
public @interface ParisConfig {
String defaultStyleNameFormat() default "";
@@ -18,8 +17,15 @@
Class> rClass() default Void.class;
/**
- * This is an experimental gradle flag (android.namespacedRClass=true). Setting to true allows Paris to generate code compatible
- * with R files that only have resources from the module the resource was declared in.
+ * This is an experimental gradle flag (android.namespacedRClass=true). Setting to true allows Paris to generate code compatible with R files that
+ * only have resources from the module the resource was declared in.
*/
boolean namespacedResourcesEnabled() default false;
+
+ /**
+ * By default no Paris class is generated if a module contains no @Styleable classes.
+ * However, if this is set to true a Paris class will still be generated in that case, using only
+ * the @Styleables that are discovered on the class path.
+ */
+ boolean aggregateStyleablesOnClassPath() default false;
}
diff --git a/paris-processor/build.gradle b/paris-processor/build.gradle
index a93c3294..baad2eef 100644
--- a/paris-processor/build.gradle
+++ b/paris-processor/build.gradle
@@ -10,21 +10,31 @@ targetCompatibility = rootProject.JAVA_TARGET_VERSION
dependencies {
implementation project(':paris-annotations')
+ implementation "com.google.devtools.ksp:symbol-processing-api:$KSP_VERSION"
+ implementation "com.google.devtools.ksp:symbol-processing:$KSP_VERSION"
+ implementation "androidx.room:room-compiler-processing:2.4.0-alpha04"
+ // Compiler needed to resolve resource references in annotations
+ implementation "org.jetbrains.kotlin:kotlin-compiler-embeddable:$KOTLIN_VERSION"
implementation deps.androidAnnotations
- implementation deps.kotlin
- implementation deps.javaPoet
- implementation deps.kotlinPoet
compileOnly deps.incapRuntime
kapt deps.incapProcessor
- compileOnly files(Jvm.current().getToolsJar())
+ compileOnly files(rootProject.file("libs/tools.jar"))
+ testImplementation "androidx.room:room-compiler-processing-testing:2.4.0-alpha04"
testImplementation deps.junit
+ testImplementation "io.strikt:strikt-core:0.31.0"
testImplementation deps.kotlinTest
}
-compileKotlin {
- kotlinOptions.jvmTarget = "1.8"
+tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
+ kotlinOptions {
+ jvmTarget = "1.8"
+ freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
+ freeCompilerArgs += "-Xopt-in=kotlin.contracts.ExperimentalContracts"
+ freeCompilerArgs += "-Xopt-in=androidx.room.compiler.processing.ExperimentalProcessingApi"
+ freeCompilerArgs += "-Xopt-in=com.google.devtools.ksp.KspExperimental"
+ }
}
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/BaseProcessor.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/BaseProcessor.kt
new file mode 100644
index 00000000..e2a6e345
--- /dev/null
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/BaseProcessor.kt
@@ -0,0 +1,78 @@
+package com.airbnb.paris.processor
+
+import androidx.room.compiler.processing.XFiler
+import androidx.room.compiler.processing.XMessager
+import androidx.room.compiler.processing.XProcessingEnv
+import androidx.room.compiler.processing.XRoundEnv
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
+import com.google.devtools.ksp.symbol.KSAnnotated
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.ProcessingEnvironment
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.TypeElement
+import javax.tools.Diagnostic
+
+/**
+ * Creates a unified abstraction for processors of both KSP and java annotation processing.
+ */
+abstract class BaseProcessor(var kspEnvironment: SymbolProcessorEnvironment? = null) : AbstractProcessor(), SymbolProcessor {
+
+ lateinit var environment: XProcessingEnv
+ private set
+
+ val messager: XMessager
+ get() = environment.messager
+
+ val filer: XFiler
+ get() = environment.filer
+
+ val isKsp: Boolean get() = kspEnvironment != null
+
+ override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.RELEASE_8
+
+ override fun init(processingEnv: ProcessingEnvironment) {
+ super.init(processingEnv)
+ environment = XProcessingEnv.create(processingEnv)
+ }
+
+ final override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean {
+
+ // The expectation is that all files will be generated during the first round where we get
+ // all the annotated elements. Then a second empty round will happen to give us a chance to
+ // do more but we ignore it
+ if (annotations.isNotEmpty()) {
+ try {
+ process(environment, XRoundEnv.create(environment, roundEnv))
+ } catch (e: Throwable) {
+ val rootCause = generateSequence(e) { it.cause }.last()
+ messager.printMessage(Diagnostic.Kind.ERROR, rootCause.stackTraceToString())
+ }
+ }
+
+ if (roundEnv.errorRaised()) {
+ onError()
+ }
+
+
+ if (roundEnv.processingOver()) {
+ finish()
+ }
+
+ return false
+ }
+
+ final override fun process(resolver: Resolver): List {
+ val kspEnvironment = requireNotNull(kspEnvironment)
+ environment = XProcessingEnv.create(kspEnvironment.options, resolver, kspEnvironment.codeGenerator, kspEnvironment.logger)
+ process(environment, XRoundEnv.create(environment))
+ return emptyList()
+ }
+
+ abstract fun process(
+ environment: XProcessingEnv,
+ round: XRoundEnv
+ )
+}
\ No newline at end of file
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/Format.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/Format.kt
index 029dec8c..84abb618 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/Format.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/Format.kt
@@ -1,14 +1,24 @@
package com.airbnb.paris.processor
+import androidx.annotation.ColorInt
+import androidx.annotation.Px
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XFieldElement
+import androidx.room.compiler.processing.XMethodElement
+import androidx.room.compiler.processing.XVariableElement
+import androidx.room.compiler.processing.isArray
+import androidx.room.compiler.processing.isInt
+import androidx.room.compiler.processing.isMethod
import com.airbnb.paris.annotations.Fraction
+import com.airbnb.paris.annotations.LayoutDimension
import com.airbnb.paris.processor.framework.AndroidClassNames
-import com.airbnb.paris.processor.framework.hasAnnotation
-import com.airbnb.paris.processor.framework.hasAnyAnnotation
+import com.airbnb.paris.processor.framework.Memoizer
+import com.airbnb.paris.processor.utils.hasAnyAnnotationBySimpleName
+import com.airbnb.paris.processor.utils.isBoolean
+import com.airbnb.paris.processor.utils.isFieldElement
+import com.airbnb.paris.processor.utils.isFloat
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.CodeBlock
-import javax.lang.model.element.Element
-import javax.lang.model.element.ElementKind
-import javax.lang.model.element.ExecutableElement
internal class Format private constructor(
private val type: Type,
@@ -69,17 +79,22 @@ internal class Format private constructor(
"XmlRes"
)
- fun forElement(processor: ParisProcessor, element: Element): Format {
- return if (element.kind == ElementKind.FIELD) {
- forField(processor, element)
- } else {
- forMethod(element)
+ fun forElement(memoizer: Memoizer, element: XElement): Format {
+ return when {
+ element.isFieldElement() -> {
+ forField(memoizer, element)
+ }
+ element.isMethod() -> {
+ forMethod(element)
+ }
+ else -> {
+ error("unsupported $element")
+ }
}
}
- private fun forField(processor: ParisProcessor, element: Element): Format {
- val type = element.asType()
- if (processor.isView(type)) {
+ private fun forField(memoizer: Memoizer, element: XFieldElement): Format {
+ if (memoizer.androidViewClassTypeX.rawType.isAssignableFrom(element.type)) {
// If the field is a View then the attribute must be a style or style resource id
return Format(Type.STYLE)
}
@@ -87,43 +102,45 @@ internal class Format private constructor(
return forEitherFieldOrMethodParameter(element)
}
- private fun forMethod(element: Element): Format {
- return forEitherFieldOrMethodParameter((element as ExecutableElement).parameters[0])
+ private fun forMethod(element: XMethodElement): Format {
+ val param = element.parameters.firstOrNull() ?: error("No parameter for $element")
+ return forEitherFieldOrMethodParameter(param)
}
- private fun forEitherFieldOrMethodParameter(element: Element): Format {
+ private fun forEitherFieldOrMethodParameter(element: XVariableElement): Format {
// TODO Use qualified name of annotations
// TODO Check that the type of the parameters corresponds to the annotation
- if (element.hasAnnotation("ColorInt")) {
+ if (element.hasAnnotation(ColorInt::class)) {
return Format(Type.COLOR)
}
- if (element.hasAnnotation("Fraction")) {
- val fraction = element.getAnnotation(Fraction::class.java)
+ element.getAnnotation(Fraction::class)?.value?.let { fraction ->
return Format(Type.FRACTION, fraction.base, fraction.pbase)
}
- if (element.hasAnnotation("LayoutDimension")) {
+ if (element.hasAnnotation(LayoutDimension::class)) {
return Format(Type.LAYOUT_DIMENSION)
}
// TODO What about Sp?
- if (element.hasAnnotation("Px")) {
+ if (element.hasAnnotation(Px::class)) {
return Format(Type.DIMENSION_PIXEL_SIZE)
}
- if (element.hasAnyAnnotation(RES_ANNOTATIONS)) {
+ if (element.hasAnyAnnotationBySimpleName(RES_ANNOTATIONS)) {
return Format.RESOURCE_ID
}
- val formatType = when (val typeString = element.asType().toString()) {
- "java.lang.Boolean", "boolean" -> Type.BOOLEAN
- "java.lang.CharSequence" -> Type.CHARSEQUENCE
- "java.lang.CharSequence[]" -> Type.CHARSEQUENCE_ARRAY
- "android.content.res.ColorStateList" -> Type.COLOR_STATE_LIST
- "android.graphics.Typeface" -> Type.FONT
- "android.graphics.drawable.Drawable" -> Type.DRAWABLE
- "java.lang.Float", "float" -> Type.FLOAT
- "java.lang.Integer", "int" -> Type.INT
- "java.lang.String" -> Type.STRING
- else -> throw IllegalArgumentException("Invalid type: $typeString")
+ val type = element.type.makeNonNullable()
+ val typeString by lazy { type.typeName.toString() }
+ val formatType = when {
+ type.isBoolean() -> Type.BOOLEAN
+ type.isFloat() -> Type.FLOAT
+ type.isInt() -> Type.INT
+ type.isTypeOf(CharSequence::class) -> Type.CHARSEQUENCE
+ type.isTypeOf(String::class) -> Type.STRING
+ typeString == "android.content.res.ColorStateList" -> Type.COLOR_STATE_LIST
+ typeString == "android.graphics.Typeface" -> Type.FONT
+ typeString == "android.graphics.drawable.Drawable" -> Type.DRAWABLE
+ type.isArray() && type.componentType.isTypeOf(CharSequence::class) -> Type.CHARSEQUENCE_ARRAY
+ else -> error("Invalid type: $type $typeString")
}
return Format(formatType)
}
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/KspProcessorProvider.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/KspProcessorProvider.kt
new file mode 100644
index 00000000..7c770407
--- /dev/null
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/KspProcessorProvider.kt
@@ -0,0 +1,11 @@
+package com.airbnb.paris.processor
+
+import com.google.devtools.ksp.processing.SymbolProcessor
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
+import com.google.devtools.ksp.processing.SymbolProcessorProvider
+
+class ParisProcessorProvider : SymbolProcessorProvider {
+ override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
+ return ParisProcessor(environment)
+ }
+}
\ No newline at end of file
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/ParisProcessor.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/ParisProcessor.kt
index 4529de63..6694fb1c 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/ParisProcessor.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/ParisProcessor.kt
@@ -1,46 +1,57 @@
package com.airbnb.paris.processor
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XProcessingEnv
+import androidx.room.compiler.processing.XRoundEnv
+import androidx.room.compiler.processing.XTypeElement
import com.airbnb.paris.annotations.Attr
import com.airbnb.paris.annotations.ParisConfig
import com.airbnb.paris.annotations.Styleable
-import com.airbnb.paris.processor.android_resource_scanner.AndroidResourceScanner
+import com.airbnb.paris.processor.android_resource_scanner.AndroidResourceId
+import com.airbnb.paris.processor.android_resource_scanner.JavacResourceScanner
+import com.airbnb.paris.processor.android_resource_scanner.KspResourceScanner
+import com.airbnb.paris.processor.android_resource_scanner.ResourceScanner
import com.airbnb.paris.processor.framework.Memoizer
-import com.airbnb.paris.processor.framework.SkyProcessor
-import com.airbnb.paris.processor.framework.packageName
+import com.airbnb.paris.processor.framework.Message
import com.airbnb.paris.processor.models.AfterStyleInfoExtractor
import com.airbnb.paris.processor.models.AttrInfoExtractor
-import com.airbnb.paris.processor.models.BaseStyleableInfo
import com.airbnb.paris.processor.models.BaseStyleableInfoExtractor
import com.airbnb.paris.processor.models.BeforeStyleInfoExtractor
import com.airbnb.paris.processor.models.StyleInfoExtractor
import com.airbnb.paris.processor.models.StyleableChildInfoExtractor
import com.airbnb.paris.processor.models.StyleableInfo
import com.airbnb.paris.processor.models.StyleableInfoExtractor
+import com.airbnb.paris.processor.utils.enclosingElementIfApplicable
import com.airbnb.paris.processor.writers.ModuleJavaClass
import com.airbnb.paris.processor.writers.ParisJavaClass
import com.airbnb.paris.processor.writers.StyleApplierJavaClass
import com.airbnb.paris.processor.writers.StyleExtensionsKotlinFile
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType
import javax.annotation.processing.ProcessingEnvironment
-import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
-import javax.lang.model.element.TypeElement
+import javax.tools.Diagnostic
+import kotlin.reflect.KClass
@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.AGGREGATING)
-class ParisProcessor : SkyProcessor(), WithParisProcessor {
+class ParisProcessor(
+ kspEnvironment: SymbolProcessorEnvironment? = null
+) : BaseProcessor(kspEnvironment) {
- override val processor = this
+ val loggedMessages: MutableList = mutableListOf()
- internal val resourceScanner = AndroidResourceScanner()
+ lateinit var resourceScanner: ResourceScanner
internal val rFinder = RFinder(this)
+ val RElement: XTypeElement? get() = rFinder.element
- override var defaultStyleNameFormat: String = ""
+ var defaultStyleNameFormat: String = ""
- override var namespacedResourcesEnabled: Boolean = false
+ var namespacedResourcesEnabled: Boolean = false
+ var aggregateStyleablesOnClassPath: Boolean = false
- override val memoizer = Memoizer(this)
+ val memoizer = Memoizer(this)
private val beforeStyleInfoExtractor = BeforeStyleInfoExtractor(this)
@@ -54,12 +65,16 @@ class ParisProcessor : SkyProcessor(), WithParisProcessor {
private val styleableInfoExtractor = StyleableInfoExtractor(this)
- private lateinit var externalStyleablesInfo: List
+ init {
+ if (kspEnvironment != null) {
+ resourceScanner = KspResourceScanner()
+ }
+ }
@Synchronized
override fun init(processingEnv: ProcessingEnvironment) {
super.init(processingEnv)
- resourceScanner.init(processingEnv)
+ resourceScanner = JavacResourceScanner(processingEnv)
}
override fun getSupportedAnnotationTypes(): Set {
@@ -72,72 +87,94 @@ class ParisProcessor : SkyProcessor(), WithParisProcessor {
override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latestSupported()
- override fun processRound(annotations: Set, roundEnv: RoundEnvironment) {
- // The expectation is that all files will be generated during the first round where we get
- // all the annotated elements. Then a second empty round will happen to give us a chance to
- // do more but we ignore it
- if (annotations.isEmpty()) {
- return
- }
- roundEnv.getElementsAnnotatedWith(ParisConfig::class.java)
+ var roundCount = 0
+ override fun process(environment: XProcessingEnv, round: XRoundEnv) {
+ // Writing to the paris and module class file on every round causes an infinite loop, because it triggers another round.
+ // We could write to that file only in finish once we collect all styleables, or just force a single round here (which
+ // assumes that no other code generates styleables, which we've never supported anyway).
+ if (roundCount > 0) return
+ roundCount++
+ val timer = Timer("Paris Processor")
+ timer.start()
+
+ round.getElementsAnnotatedWith(ParisConfig::class)
.firstOrNull()
- ?.getAnnotation(ParisConfig::class.java)
+ ?.getAnnotation(ParisConfig::class)
?.let {
- defaultStyleNameFormat = it.defaultStyleNameFormat
- namespacedResourcesEnabled = it.namespacedResourcesEnabled
+ defaultStyleNameFormat = it.value.defaultStyleNameFormat
+ namespacedResourcesEnabled = it.value.namespacedResourcesEnabled
+ aggregateStyleablesOnClassPath = it.value.aggregateStyleablesOnClassPath
rFinder.processConfig(it)
}
+ timer.markStepCompleted("Paris Config lookup")
- beforeStyleInfoExtractor.process(roundEnv)
+ beforeStyleInfoExtractor.process(round)
val classesToBeforeStyleInfo =
beforeStyleInfoExtractor.latest.groupBy { it.enclosingElement }
+ timer.markStepCompleted("Process before styles")
- afterStyleInfoExtractor.process(roundEnv)
+ afterStyleInfoExtractor.process(round)
val classesToAfterStyleInfo = afterStyleInfoExtractor.latest.groupBy { it.enclosingElement }
+ timer.markStepCompleted("Process after styles")
- styleableChildInfoExtractor.process(roundEnv)
+ styleableChildInfoExtractor.process(round)
val styleableChildrenInfo = styleableChildInfoExtractor.latest
val classesToStyleableChildrenInfo = styleableChildrenInfo.groupBy { it.enclosingElement }
+ timer.markStepCompleted("Process styleable children")
- attrInfoExtractor.process(roundEnv)
+ attrInfoExtractor.process(round)
val attrsInfo = attrInfoExtractor.latest
val classesToAttrsInfo = attrsInfo.groupBy { it.enclosingElement }
+ timer.markStepCompleted("Process attrs")
rFinder.processResourceAnnotations(styleableChildrenInfo, attrsInfo)
+ timer.markStepCompleted("Process resources")
- styleInfoExtractor.process(roundEnv)
+ styleInfoExtractor.process(round)
val classesToStylesInfo = styleInfoExtractor.latest.groupBy { it.enclosingElement }
+ timer.markStepCompleted("Process styles")
val styleablesInfo: List = styleableInfoExtractor.process(
- roundEnv,
- classesToStyleableChildrenInfo,
- classesToBeforeStyleInfo,
- classesToAfterStyleInfo,
- classesToAttrsInfo,
- classesToStylesInfo
+ roundEnv = round,
+ classesToStyleableChildInfo = classesToStyleableChildrenInfo,
+ classesToBeforeStyleInfo = classesToBeforeStyleInfo,
+ classesToAfterStyleInfo = classesToAfterStyleInfo,
+ classesToAttrsInfo = classesToAttrsInfo,
+ classesToStylesInfo = classesToStylesInfo
)
+ timer.markStepCompleted("Process styleables")
rFinder.processStyleables(styleablesInfo)
+ timer.markStepCompleted("Process styleables resources")
+ if (styleablesInfo.isEmpty() && !aggregateStyleablesOnClassPath) {
+ // No styleables to process, so we have no files to write and can stop here
+ return
+ }
/** Make sure to get these before writing the [ModuleJavaClass] for this module */
- externalStyleablesInfo = BaseStyleableInfoExtractor(this).fromEnvironment()
+ val externalStyleablesInfo = BaseStyleableInfoExtractor(this).fromEnvironment()
+ timer.markStepCompleted("Extract styleables from classpath")
val allStyleables = styleablesInfo + externalStyleablesInfo
+
if (allStyleables.isNotEmpty() && rFinder.element == null) {
logError {
"Unable to locate R class. Please annotate an arbitrary package with @ParisConfig and set the rClass parameter to the R class."
}
return
}
+
val styleablesTree = StyleablesTree(this, allStyleables)
for (styleableInfo in styleablesInfo) {
StyleApplierJavaClass(this, styleablesTree, styleableInfo).write()
StyleExtensionsKotlinFile(this, styleableInfo).write()
}
+ timer.markStepCompleted("Write all styleables files")
if (styleablesInfo.isNotEmpty()) {
ModuleJavaClass(this, styleablesInfo).write()
+ timer.markStepCompleted("Write module class file")
}
if (allStyleables.isNotEmpty()) {
@@ -148,20 +185,77 @@ class ParisProcessor : SkyProcessor(), WithParisProcessor {
styleablesInfo,
externalStyleablesInfo
).write()
+
+ timer.markStepCompleted("Write Paris class")
}
+
+ timer.finishAndPrint(messager)
}
- override fun claimAnnotations(
- annotations: Set,
- roundEnv: RoundEnvironment
- ): Boolean {
- // Let other annotation processors use them if they want
- return false
+ override fun onError() {
+ printLogsIfAny()
}
- override fun processingOver() {
+ override fun finish() {
// Errors and warnings are only printed at the end to generate as many classes as possible
// and avoid "could not find" errors which make debugging harder
- printLogsIfAny(messager)
+ printLogsIfAny()
+ }
+
+ fun printLogsIfAny() {
+ loggedMessages.forEach { message ->
+ val kind = when (message.severity) {
+ Message.Severity.Warning -> Diagnostic.Kind.WARNING
+ Message.Severity.Error -> Diagnostic.Kind.ERROR
+ Message.Severity.Note -> Diagnostic.Kind.NOTE
+ }
+ val element = message.element
+
+ val details = if (element != null) {
+
+ buildString {
+ append(" [element=$element ${element.javaClass.simpleName}")
+
+ element.enclosingElementIfApplicable?.className?.let {
+ append(" in $it")
+ }
+
+ append("]")
+ }
+ } else {
+ ""
+ }
+
+ environment.messager.printMessage(kind, message.message + details)
+ }
+ loggedMessages.clear()
+ }
+
+ fun getResourceId(annotation: KClass, element: XElement, value: Int): AndroidResourceId? {
+ val resourceId = resourceScanner.getId(annotation, element, value)
+ if (resourceId == null) {
+ logError(element) {
+ "Could not retrieve Android resource ID from annotation."
+ }
+ }
+ return resourceId
+ }
+
+ fun logError(element: XElement? = null, lazyMessage: () -> String) {
+ log(Message.Severity.Error, element, lazyMessage)
+ }
+
+ fun logWarning(element: XElement? = null, lazyMessage: () -> String) {
+ if (isKsp) {
+ // Ksp warnings cause kotlin compile errors when warnings as errors is turned on.
+ // To prevent build failures from warnings, use note mode in KSP
+ log(Message.Severity.Note, element, lazyMessage)
+ } else {
+ log(Message.Severity.Warning, element, lazyMessage)
+ }
+ }
+
+ fun log(severity: Message.Severity, element: XElement? = null, lazyMessage: () -> String) {
+ loggedMessages.add(Message(severity, lazyMessage(), element))
}
}
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/RFinder.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/RFinder.kt
index dfd484ac..b52fab92 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/RFinder.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/RFinder.kt
@@ -1,26 +1,25 @@
package com.airbnb.paris.processor
+import androidx.room.compiler.processing.XAnnotationBox
+import androidx.room.compiler.processing.XTypeElement
+import androidx.room.compiler.processing.isVoid
+import androidx.room.compiler.processing.isVoidObject
import com.airbnb.paris.annotations.ParisConfig
import com.airbnb.paris.processor.models.AttrInfo
import com.airbnb.paris.processor.models.StyleableChildInfo
import com.airbnb.paris.processor.models.StyleableInfo
-import javax.lang.model.element.TypeElement
-import javax.lang.model.type.MirroredTypeException
-import javax.lang.model.type.TypeMirror
-internal class RFinder(override val processor: ParisProcessor) : WithParisProcessor {
+internal class RFinder(val processor: ParisProcessor) {
- var element: TypeElement? = null
+ var element: XTypeElement? = null
private set
- fun processConfig(config: ParisConfig) {
+ fun processConfig(config: XAnnotationBox) {
if (element != null) {
return
}
- getRTypeFromConfig(config)?.let {
- element = it.asTypeElement()
- }
+ element = getRTypeFromConfig(config)
}
fun processResourceAnnotations(
@@ -39,7 +38,7 @@ internal class RFinder(override val processor: ParisProcessor) : WithParisProces
else -> null
}
arbitraryResId?.let {
- element = elements.getTypeElement(it.className.enclosingClassName().reflectionName())
+ element = processor.environment.findTypeElement(it.className.enclosingClassName().reflectionName())
}
}
@@ -49,7 +48,7 @@ internal class RFinder(override val processor: ParisProcessor) : WithParisProces
styleablesInfo[0].let { styleableInfo ->
var packageName = styleableInfo.elementPackageName
while (packageName.isNotBlank()) {
- elements.getTypeElement("$packageName.R")?.let {
+ processor.environment.findTypeElement("$packageName.R")?.let {
element = it
return
}
@@ -63,26 +62,21 @@ internal class RFinder(override val processor: ParisProcessor) : WithParisProces
}
}
- private fun getRTypeFromConfig(config: ParisConfig): TypeMirror? {
- var rType: TypeMirror? = null
- try {
- config.rClass
- } catch (mte: MirroredTypeException) {
- rType = mte.typeMirror
- }
+ private fun getRTypeFromConfig(config: XAnnotationBox): XTypeElement? {
+ val rType = config.getAsType("rClass")
// Void is the default so check against that
- val voidType = elements.getTypeElement(Void::class.java.canonicalName).asType()
- return if (types.isSameType(voidType, rType)) {
+ return if (rType == null || rType.isVoidObject() || rType.isVoid()) {
null
} else {
- if (rType != null && rType.asTypeElement().simpleName.toString() != "R") {
- logError {
+ val rTypeElement = rType.typeElement ?: return null
+ if (rTypeElement.name != "R") {
+ processor.logError(rTypeElement) {
"@ParisConfig's rClass parameter is pointing to a non-R class"
}
null
} else {
- rType
+ rTypeElement
}
}
}
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/StyleablesTree.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/StyleablesTree.kt
index 4dff7691..e3e48718 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/StyleablesTree.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/StyleablesTree.kt
@@ -1,43 +1,50 @@
package com.airbnb.paris.processor
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XTypeElement
import com.airbnb.paris.processor.models.BaseStyleableInfo
import com.squareup.javapoet.ClassName
-import javax.lang.model.element.Element
-import javax.lang.model.element.Name
-import javax.lang.model.element.TypeElement
internal class StyleablesTree(
- override val processor: ParisProcessor,
+ val processor: ParisProcessor,
private val styleablesInfo: List
-) : WithParisProcessor {
+) {
// This is a map of the View class qualified name to the StyleApplier class details
// eg. "android.view.View" -> "com.airbnb.paris.ViewStyleApplier".className()
- private val viewQualifiedNameToStyleApplierClassName = mutableMapOf()
+ private val viewQualifiedNameToStyleApplierClassName = mutableMapOf()
/**
* Traverses the class hierarchy of the given View type to find and return the first
* corresponding style applier
*/
- internal fun findStyleApplier(viewTypeElement: TypeElement): StyleApplierDetails {
- return viewQualifiedNameToStyleApplierClassName.getOrPut(viewTypeElement.qualifiedName) {
+ internal fun findStyleApplier(viewTypeElement: XTypeElement, errorContext: (() -> String)? = null): StyleApplierDetails {
+ return findStyleApplierRecursive(viewTypeElement)
+ ?: error("Could not find style applier for ${viewTypeElement.qualifiedName} ${viewTypeElement.type}. " +
+ errorContext?.invoke()?.let { "$it. " }.orEmpty() +
+ "Available types are ${styleablesInfo.map { it.viewElementType }}")
+ }
+
+ private fun findStyleApplierRecursive(viewTypeElement: XTypeElement): StyleApplierDetails? {
+ return viewQualifiedNameToStyleApplierClassName.getOrPut(viewTypeElement) {
- val type = viewTypeElement.asType()
+ val type = viewTypeElement.type
// Check to see if the view type is handled by a styleable class
- val styleableInfo = styleablesInfo.find { isSameType(type, it.viewElementType) }
+ val styleableInfo = styleablesInfo.find { type.isSameType(it.viewElementType) }
if (styleableInfo != null) {
StyleApplierDetails(
annotatedElement = styleableInfo.annotatedElement,
className = styleableInfo.styleApplierClassName
)
} else {
- findStyleApplier(viewTypeElement.superclass.asTypeElement())
+ val superType = viewTypeElement.superType?.typeElement ?: return@getOrPut null
+ findStyleApplier(superType)
}
}
}
}
data class StyleApplierDetails(
- val annotatedElement: Element,
+ val annotatedElement: XElement,
val className: ClassName
)
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/Timer.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/Timer.kt
new file mode 100644
index 00000000..f55deb9f
--- /dev/null
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/Timer.kt
@@ -0,0 +1,51 @@
+package com.airbnb.paris.processor
+
+import androidx.room.compiler.processing.XMessager
+import javax.tools.Diagnostic
+import kotlin.math.pow
+import kotlin.math.roundToInt
+
+class Timer(val name: String) {
+ private val timingSteps = mutableListOf()
+ private var startNanos: Long? = null
+ private var lastTimingNanos: Long? = null
+
+
+ fun start() {
+ timingSteps.clear()
+ startNanos = System.nanoTime()
+ lastTimingNanos = startNanos
+ }
+
+ fun markStepCompleted(stepDescription: String) {
+ val nowNanos = System.nanoTime()
+ val lastNanos = lastTimingNanos ?: error("Timer was not started")
+ lastTimingNanos = nowNanos
+
+ timingSteps.add(TimingStep(nowNanos - lastNanos, stepDescription))
+ }
+
+ fun finishAndPrint(messager: XMessager) {
+ val start = startNanos ?: error("Timer was not started")
+ val message = buildString {
+ appendLine("$name finished in ${formatNanos(System.nanoTime() - start)}")
+ timingSteps.forEach { step ->
+ appendLine(" - ${step.description} (${formatNanos(step.durationNanos)})")
+ }
+ }
+
+ messager.printMessage(Diagnostic.Kind.NOTE, message)
+ }
+
+ private class TimingStep(val durationNanos: Long, val description: String)
+
+ private fun formatNanos(nanos: Long): String {
+ val diffMs = nanos.div(1_000_000.0).roundTo(3)
+ return "$diffMs ms"
+ }
+
+ private fun Double.roundTo(numFractionDigits: Int): Double {
+ val factor = 10.0.pow(numFractionDigits.toDouble())
+ return (this * factor).roundToInt() / factor
+ }
+}
\ No newline at end of file
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/WithParisProcessor.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/WithParisProcessor.kt
deleted file mode 100644
index be505a21..00000000
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/WithParisProcessor.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.airbnb.paris.processor
-
-import com.airbnb.paris.processor.android_resource_scanner.AndroidResourceId
-import com.airbnb.paris.processor.framework.WithSkyProcessor
-import javax.lang.model.element.Element
-
-internal interface WithParisProcessor : WithSkyProcessor {
-
- override val processor: ParisProcessor
-
- val RElement get() = processor.rFinder.element
-
- val defaultStyleNameFormat get() = processor.defaultStyleNameFormat
-
- val namespacedResourcesEnabled get() = processor.namespacedResourcesEnabled
-
-
- fun getResourceId(annotation: Class, element: Element, value: Int): AndroidResourceId? {
- val resourceId = processor.resourceScanner.getId(annotation, element, value)
- if (resourceId == null) {
- logError(element) {
- "Could not retrieve Android resource ID from annotation."
- }
- }
- return resourceId
- }
-}
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/AndroidResourceId.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/AndroidResourceId.kt
index 2f594b27..f916e003 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/AndroidResourceId.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/AndroidResourceId.kt
@@ -6,6 +6,10 @@ import com.airbnb.paris.processor.framework.KotlinCodeBlock
import com.airbnb.paris.processor.framework.toKPoet
import com.squareup.javapoet.ClassName
+/**
+ * @param className Like com.example.R.styleable
+ * @param resourceName Like title_view
+ */
class AndroidResourceId(val value: Int, val className: ClassName, val resourceName: String) {
val rClassName: ClassName = className.topLevelClassName()
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/AndroidResourceScanner.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/JavacResourceScanner.kt
similarity index 88%
rename from paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/AndroidResourceScanner.kt
rename to paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/JavacResourceScanner.kt
index 04d1c33a..c401274f 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/AndroidResourceScanner.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/JavacResourceScanner.kt
@@ -1,5 +1,7 @@
package com.airbnb.paris.processor.android_resource_scanner
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.compat.XConverters.toJavac
import com.squareup.javapoet.ClassName
import com.sun.source.util.Trees
import com.sun.tools.javac.code.Symbol.VarSymbol
@@ -13,16 +15,16 @@ import javax.lang.model.element.Element
import javax.lang.model.type.MirroredTypeException
import javax.lang.model.util.Elements
import javax.lang.model.util.Types
+import kotlin.reflect.KClass
-class AndroidResourceScanner {
- private lateinit var typeUtils: Types
- private lateinit var elementUtils: Elements
+class JavacResourceScanner(
+ processingEnv: ProcessingEnvironment
+) : ResourceScanner {
+ private val typeUtils: Types = processingEnv.typeUtils
+ private val elementUtils: Elements = processingEnv.elementUtils
private var trees: Trees? = null
- fun init(processingEnv: ProcessingEnvironment) {
- typeUtils = processingEnv.typeUtils
- elementUtils = processingEnv.elementUtils
-
+ init {
trees = try {
Trees.instance(processingEnv)
} catch (ignored: IllegalArgumentException) {
@@ -45,14 +47,14 @@ class AndroidResourceScanner {
}
/**
- * Returns the [AndroidResourceId] that is used as an annotation value of the given [Element]
+ * Returns the [AndroidResourceId] that is used as an annotation value of the given [XElement]
*/
- fun getId(
- annotation: Class,
- element: Element,
+ override fun getId(
+ annotation: KClass,
+ element: XElement,
value: Int
): AndroidResourceId? {
- val results = getResults(annotation, element)
+ val results = getResults(annotation.java, element.toJavac())
return if (results.containsKey(value)) {
results[value]
} else {
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/KspResourceScanner.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/KspResourceScanner.kt
new file mode 100644
index 00000000..9ed16b8d
--- /dev/null
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/KspResourceScanner.kt
@@ -0,0 +1,323 @@
+package com.airbnb.paris.processor.android_resource_scanner
+
+import androidx.room.compiler.processing.XElement
+import com.airbnb.paris.processor.android_resource_scanner.KspResourceScanner.ImportMatch.*
+import com.airbnb.paris.processor.utils.containingPackage
+import com.google.devtools.ksp.symbol.KSAnnotation
+import com.google.devtools.ksp.symbol.impl.java.KSAnnotationJavaImpl
+import com.google.devtools.ksp.symbol.impl.kotlin.KSAnnotationImpl
+import com.squareup.javapoet.ClassName
+import org.jetbrains.kotlin.com.intellij.psi.PsiAnnotation
+import org.jetbrains.kotlin.com.intellij.psi.PsiJavaFile
+import org.jetbrains.kotlin.com.intellij.psi.PsiNameValuePair
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.psi.KtAnnotationEntry
+import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
+import org.jetbrains.kotlin.psi.KtExpression
+import org.jetbrains.kotlin.psi.KtSimpleNameExpression
+import org.jetbrains.kotlin.psi.ValueArgument
+import kotlin.reflect.KClass
+
+class KspResourceScanner : ResourceScanner {
+ private val cache = mutableMapOf, XElement>, List>()
+
+ override fun getId(annotation: KClass, element: XElement, value: Int): AndroidResourceId? {
+ val annotationArgs = cache.getOrPut(annotation to element) {
+ val annotationBox = element.getAnnotation(annotation) ?: return@getOrPut emptyList()
+ val ksAnnotation = annotationBox.getFieldWithReflection("annotation")
+ processAnnotationWithResource(ksAnnotation)
+ }
+
+ val matchingArg = annotationArgs.firstOrNull { it.value == value } ?: return null
+
+ return matchingArg.toAndroidResourceId()
+ }
+
+ private fun processAnnotationWithResource(annotation: KSAnnotation): List {
+ val packageName = annotation.containingPackage.orEmpty()
+ return when (annotation) {
+ is KSAnnotationImpl -> processKtAnnotation(annotation.ktAnnotationEntry, annotation, packageName)
+ is KSAnnotationJavaImpl -> processJavaAnnotation(annotation.psi, annotation, packageName)
+ else -> emptyList()
+ }
+ }
+
+ private fun processJavaAnnotation(
+ psi: PsiAnnotation,
+ annotation: KSAnnotationJavaImpl,
+ packageName: String
+ ): List {
+ return psi.parameterList
+ .attributes
+ .zip(annotation.arguments)
+ .map { (psiNameValue, ksValueArgument) ->
+ AnnotationWithReferenceValue(
+ name = ksValueArgument.name?.asString(),
+ value = ksValueArgument.value,
+ reference = extractJavaReferenceAnnotationArgument(psiNameValue, annotation, packageName)
+ )
+ }
+ }
+
+ private fun extractJavaReferenceAnnotationArgument(
+ psiNameValue: PsiNameValuePair,
+ annotation: KSAnnotationJavaImpl,
+ packageName: String
+ ): String? {
+ // eg: R.layout.foo, com.example.R.layout.foo, layout.foo, etc
+ return psiNameValue.value?.text?.let { annotationReference ->
+ extractReferenceAnnotationArgument(annotationReference) { annotationReferencePrefix ->
+ findMatchingImportPackageJava(annotation.psi, annotationReference, annotationReferencePrefix, packageName)
+ }
+ }
+ }
+
+ private fun extractReferenceAnnotationArgument(
+ // eg: R.layout.foo, com.example.R.layout.foo, layout.foo, etc
+ annotationReference: String,
+ /**
+ * Given the name referenced in source code, return the matching import for that name.
+ */
+ importLookup: (annotationReferencePrefix: String) -> ImportMatch,
+ ): String? {
+ // First part of dot reference, eg: "R"
+ // If no dots, then it could be fully statically imported, so we take the full string.
+ val annotationReferencePrefix = annotationReference.substringBefore(".").ifEmpty { return null }
+
+ val importMatch = importLookup(annotationReferencePrefix)
+
+ return importMatch.fullyQualifiedReference
+ }
+
+ private fun processKtAnnotation(
+ annotationEntry: KtAnnotationEntry,
+ annotation: KSAnnotation,
+ packageName: String
+ ): List {
+ return annotationEntry.valueArguments
+ .zip(annotation.arguments)
+ .map { (valueArgument, ksValueArgument) ->
+
+ AnnotationWithReferenceValue(
+ name = ksValueArgument.name?.asString(),
+ value = ksValueArgument.value,
+ reference = extractKotlinReferenceAnnotationArgument(valueArgument, annotationEntry, packageName)
+ )
+ }
+ }
+
+ private fun extractKotlinReferenceAnnotationArgument(
+ valueArgument: ValueArgument,
+ annotationEntry: KtAnnotationEntry,
+ packageName: String
+ ): String? {
+ return valueArgument.getArgumentExpression()?.let { ex ->
+
+ // eg: R.layout.foo, com.example.R.layout.foo, layout.foo, etc
+ val annotationReference: String = fqNameFromExpression(ex)?.asString() ?: return@let null
+
+ extractReferenceAnnotationArgument(annotationReference) { annotationReferencePrefix ->
+ findMatchingImportPackageKt(annotationEntry, annotationReference, annotationReferencePrefix, packageName)
+ }
+ }
+ }
+
+ private fun findMatchingImportPackageJava(
+ annotationEntry: PsiAnnotation,
+ annotationReference: String,
+ annotationReferencePrefix: String,
+ packageName: String
+ ): ImportMatch {
+ // Note: Star imports are not included in this, and there doesn't seem to be a way to resolve them, so
+ // they are not included or supported.
+ val importedNames = (annotationEntry.containingFile as? PsiJavaFile)
+ ?.importList
+ ?.importStatements
+ ?.mapNotNull { it.qualifiedName }
+ ?: emptyList()
+
+ return findMatchingImportPackage(importedNames, annotationReference, annotationReferencePrefix, packageName)
+ }
+
+ private fun findMatchingImportPackageKt(
+ annotationEntry: KtAnnotationEntry,
+ annotationReference: String,
+ annotationReferencePrefix: String,
+ packageName: String
+ ): ImportMatch {
+ val importedNames = annotationEntry
+ .containingKtFile
+ .importDirectives
+ .mapNotNull { it.importPath?.toString() }
+
+ return findMatchingImportPackage(importedNames, annotationReference, annotationReferencePrefix, packageName)
+ }
+
+
+ sealed class ImportMatch {
+ abstract val fullyQualifiedReference: String
+
+ class TypeAlias(val import: String, alias: String, annotationReference: String) : ImportMatch() {
+ // Example: Type alias "com.airbnb.paris.test.R2 as typeAliasedR"
+ // import - com.airbnb.paris.test.R2
+ // alias - typeAliasedR
+ // annotationReference - typeAliasedR.layout.my_layout
+ // actual fqn - com.airbnb.paris.test.R2.layout.my_layout
+ override val fullyQualifiedReference: String = import.trim() + annotationReference.substringAfter(alias)
+ }
+
+ class Normal(val referenceImportPrefix: String, val annotationReference: String) : ImportMatch() {
+ override val fullyQualifiedReference: String =
+ referenceImportPrefix + (if (referenceImportPrefix.isNotEmpty()) "." else "") + annotationReference
+ }
+ }
+
+ data class AnnotationWithReferenceValue(
+ val name: String?,
+ val value: Any?,
+ val reference: String?
+ ) {
+ fun toAndroidResourceId(): AndroidResourceId? {
+ if (value !is Int || reference == null) return null
+
+ val resourceInfo = when {
+ ".R2." in reference || reference.startsWith("R2.") -> {
+ extractResourceInfo(reference, "R2")
+ }
+ ".R." in reference || reference.startsWith("R.") -> {
+ extractResourceInfo(reference, "R")
+ }
+ else -> {
+ error("Unsupported resource reference $reference")
+ }
+ }
+
+ return AndroidResourceId(
+ value,
+ // Regardless of if the input is R or R2, we always need the generated code to reference R
+ ClassName.get(resourceInfo.packageName, "R", resourceInfo.rSubclassName),
+ resourceName = resourceInfo.resourceName
+ )
+ }
+
+ /**
+ * @param reference fully qualified resource reference. eg com.example.R.layout.my_view
+ * @param rClassSimpleName ie R or R2
+ */
+ private fun extractResourceInfo(reference: String, rClassSimpleName: String): ResourceReferenceInfo {
+ // get package before R and resource details after R
+ val packageAndResourceType = reference.split(".$rClassSimpleName.").also {
+ check(it.size == 2) { "Unexpected annotation value reference pattern $reference" }
+ }
+
+ val packageName = packageAndResourceType[0]
+
+ val (rSubclass, resourceName) = packageAndResourceType[1].split(".").also {
+ check(it.size == 2) { "Unexpected annotation value reference pattern $reference" }
+ }
+
+ return ResourceReferenceInfo(
+ packageName = packageName,
+ rSimpleName = rClassSimpleName,
+ rSubclassName = rSubclass,
+ resourceName = resourceName
+ )
+ }
+ }
+
+ private data class ResourceReferenceInfo(
+ val packageName: String,
+ val rSimpleName: String,
+ val rSubclassName: String,
+ val resourceName: String
+ )
+
+ // From https://github.com/JetBrains/kotlin/blob/92d200e093c693b3c06e53a39e0b0973b84c7ec5/compiler/psi/src/org/jetbrains/kotlin/psi/KtImportDirective.java
+ private fun fqNameFromExpression(expression: KtExpression): FqName? {
+ return when (expression) {
+ is KtDotQualifiedExpression -> {
+ val parentFqn: FqName? = fqNameFromExpression(expression.receiverExpression)
+ val child: Name = expression.selectorExpression?.let { nameFromExpression(it) } ?: return parentFqn
+ parentFqn?.child(child)
+ }
+ is KtSimpleNameExpression -> {
+ FqName.topLevel(expression.getReferencedNameAsName())
+ }
+ else -> {
+ null
+ }
+ }
+ }
+
+ private fun nameFromExpression(expression: KtExpression): Name? {
+ return if (expression is KtSimpleNameExpression) {
+ expression.getReferencedNameAsName()
+ } else {
+ null
+ }
+ }
+
+ companion object {
+ internal fun findMatchingImportPackage(
+ importedNames: List,
+ annotationReference: String,
+ annotationReferencePrefix: String,
+ packageName: String
+ ): ImportMatch {
+ // Match something like "com.airbnb.paris.test.R2 as typeAliasedR"
+ val typeAliasRegex = Regex("(.*)\\s+as\\s+$annotationReferencePrefix\$")
+ return importedNames.firstNotNullOfOrNull { importedName ->
+
+ when {
+ importedName.endsWith(".$annotationReferencePrefix") -> {
+ // import com.example.R
+ // R.layout.my_layout -> R
+ Normal(
+ referenceImportPrefix = importedName.substringBeforeLast(".$annotationReferencePrefix"),
+ annotationReference = annotationReference
+ )
+ }
+ importedName.contains(typeAliasRegex) -> {
+ typeAliasRegex.find(importedName)?.groupValues?.getOrNull(1)?.let { import ->
+ TypeAlias(import, annotationReferencePrefix, annotationReference)
+ }
+ }
+ (!importedName.contains(".") && importedName == annotationReferencePrefix) -> {
+ // import foo
+ // foo.R.layout.my_layout -> foo
+ Normal("", annotationReference)
+ }
+ else -> null
+ }
+ } ?: run {
+ // If first character in the reference is upper case, and we didn't find a matching import,
+ // assume that it is a class reference in the same package (ie R class is in the same package, so we use the same package name)
+ if (annotationReferencePrefix.firstOrNull()?.isUpperCase() == true) {
+ Normal(packageName, annotationReference)
+ } else {
+ // Reference is already fully qualified so we don't need to prepend package info to the reference
+ Normal("", annotationReference)
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Easy way to retrieve the value of a field via reflection.
+ *
+ * @param fieldName Name of the field on this class
+ * @param U The type of the field..
+ */
+inline fun Any.getFieldWithReflection(fieldName: String): U {
+ return this.javaClass.getDeclaredField(fieldName).let {
+ it.isAccessible = true
+ val value = it.get(this)
+ check(value is U) {
+ "Expected field '$fieldName' to be ${U::class.java.simpleName} but got a ${value.javaClass.simpleName}"
+ }
+ @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
+ value
+ }
+}
\ No newline at end of file
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/ResourceScanner.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/ResourceScanner.kt
new file mode 100644
index 00000000..d7d71dd0
--- /dev/null
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/ResourceScanner.kt
@@ -0,0 +1,15 @@
+package com.airbnb.paris.processor.android_resource_scanner
+
+import androidx.room.compiler.processing.XElement
+import kotlin.reflect.KClass
+
+interface ResourceScanner {
+ /**
+ * Returns the [AndroidResourceId] that is used as an annotation value of the given [XElement]
+ */
+ fun getId(
+ annotation: KClass,
+ element: XElement,
+ value: Int
+ ): AndroidResourceId?
+}
\ No newline at end of file
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/JavaPoetExtensions.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/JavaPoetExtensions.kt
index f249e9c0..287f5a47 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/JavaPoetExtensions.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/JavaPoetExtensions.kt
@@ -90,4 +90,3 @@ internal fun TypeSpec.Builder.public() {
internal fun TypeSpec.Builder.static() {
addModifiers(Modifier.STATIC)
}
-
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/JavaSkyMemoizer.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/JavaSkyMemoizer.kt
new file mode 100644
index 00000000..17395b87
--- /dev/null
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/JavaSkyMemoizer.kt
@@ -0,0 +1,11 @@
+package com.airbnb.paris.processor.framework
+
+import androidx.room.compiler.processing.XType
+import com.airbnb.paris.processor.BaseProcessor
+
+open class JavaSkyMemoizer(val processor: BaseProcessor) {
+
+ val androidViewClassTypeX: XType by lazy {
+ processor.environment.requireType(AndroidClassNames.VIEW)
+ }
+}
\ No newline at end of file
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/KotlinPoetExtensions.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/KotlinPoetExtensions.kt
index 238c109d..ca075286 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/KotlinPoetExtensions.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/KotlinPoetExtensions.kt
@@ -1,5 +1,6 @@
package com.airbnb.paris.processor.framework
+import androidx.room.compiler.processing.XType
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.ParameterSpec
@@ -73,6 +74,15 @@ internal fun FunSpec.Builder.parameter(
}
}
+internal fun FunSpec.Builder.receiver(
+ type: XType,
+): FunSpec.Builder {
+ return receiver(type.typeNameKotlin())
+}
+
internal fun ParameterSpec.Builder.addAnnotation(type: JavaClassName) {
addAnnotation(type.toKPoet())
-}
\ No newline at end of file
+}
+
+fun XType.typeNameKotlin(): KotlinTypeName = typeName.toKPoet()
+
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/Log.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/Log.kt
index 947f89b5..99d9b5bd 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/Log.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/Log.kt
@@ -1,7 +1,14 @@
package com.airbnb.paris.processor.framework
-import javax.tools.Diagnostic
+import androidx.room.compiler.processing.XElement
+
+
+class Message(val severity: Severity, val message: String, val element: XElement?) {
+
+ enum class Severity {
+ Note, Warning, Error
+ }
+}
-class Message(val kind: Diagnostic.Kind, val message: CharSequence)
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/Memoizer.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/Memoizer.kt
index 84580e66..11de468a 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/Memoizer.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/Memoizer.kt
@@ -1,18 +1,20 @@
package com.airbnb.paris.processor.framework
+import androidx.room.compiler.processing.XRawType
+import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.XTypeElement
import com.airbnb.paris.processor.PROXY_CLASS_NAME
import com.airbnb.paris.processor.ParisProcessor
import com.airbnb.paris.processor.STYLE_CLASS_NAME
-import javax.lang.model.element.TypeElement
-import javax.lang.model.type.TypeMirror
-class Memoizer(override val processor: ParisProcessor) : SkyMemoizer(processor) {
+class Memoizer(processor: ParisProcessor) : JavaSkyMemoizer(processor) {
- val proxyClassTypeErased: TypeMirror by lazy { erasure(PROXY_CLASS_NAME.toTypeMirror()) }
+ val proxyClassType: XType by lazy { processor.environment.requireType(PROXY_CLASS_NAME) }
- val styleClassType: TypeMirror by lazy { STYLE_CLASS_NAME.toTypeMirror() }
+ val styleClassTypeX: XType by lazy { processor.environment.requireType(STYLE_CLASS_NAME) }
- val rStyleTypeElement: TypeElement? by lazy {
+ val rStyleTypeElementX: XTypeElement? by lazy {
val rElement = processor.RElement ?: error("R Class not found")
- elements.getTypeElement("${rElement.qualifiedName}.style") }
+ processor.environment.findType("${rElement.qualifiedName}.style")?.typeElement
+ }
}
\ No newline at end of file
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyExtensions.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyExtensions.kt
index f4990a11..4007e487 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyExtensions.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyExtensions.kt
@@ -81,6 +81,12 @@ internal fun Element.siblings(): List = when (this) {
else -> TODO()
}
+//internal fun XElement.siblings(): List = when (this) {
+// is XExecutableElement -> enclosingTypeElement.en
+// is XFieldElement -> enclosingElement.enclosedElements.filterNot { it === this }
+// else -> TODO()
+//}
+
// String
internal fun String.className(): ClassName =
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyJavaClass.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyJavaClass.kt
index dafbc353..7996f4d0 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyJavaClass.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyJavaClass.kt
@@ -1,26 +1,31 @@
package com.airbnb.paris.processor.framework
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XFiler
+import androidx.room.compiler.processing.addOriginatingElement
+import com.airbnb.paris.processor.BaseProcessor
+import com.airbnb.paris.processor.utils.addOriginatingElementFixed
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.TypeSpec
-import javax.lang.model.element.Element
-internal abstract class SkyJavaClass(override val processor: SkyProcessor) : WithSkyProcessor {
+internal abstract class SkyJavaClass(val processor: BaseProcessor) {
protected abstract val packageName: String
protected abstract val name: String
protected abstract val block: TypeSpec.Builder.() -> Unit
- protected abstract val originatingElements: List
+ protected abstract val originatingElements: List
fun build(): TypeSpec {
val builder = TypeSpec.classBuilder(name)
originatingElements.forEach {
- builder.addOriginatingElement(it)
+ builder.addOriginatingElementFixed(it)
}
builder.block()
return builder.build()
}
- fun write() {
- JavaFile.builder(packageName, build()).build().writeTo(filer)
+ fun write(mode: XFiler.Mode = XFiler.Mode.Aggregating) {
+ val javaFile = JavaFile.builder(packageName, build()).build()
+ processor.filer.write(javaFile, mode)
}
}
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyKotlinFile.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyKotlinFile.kt
index 788ddce8..d582427b 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyKotlinFile.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyKotlinFile.kt
@@ -1,9 +1,11 @@
package com.airbnb.paris.processor.framework
+import androidx.room.compiler.processing.XFiler
+import com.airbnb.paris.processor.BaseProcessor
import com.squareup.kotlinpoet.FileSpec
-internal abstract class SkyKotlinFile(override val processor: SkyProcessor) : WithSkyProcessor {
+internal abstract class SkyKotlinFile( val processor: BaseProcessor) {
protected abstract val packageName: String
protected abstract val name: String
@@ -19,7 +21,7 @@ internal abstract class SkyKotlinFile(override val processor: SkyProcessor) : Wi
/**
* If this module is being processed with kapt then the file is written, otherwise this is a no-op.
*/
- fun write() {
- build().writeTo(processor.filer)
+ fun write(mode: XFiler.Mode = XFiler.Mode.Aggregating) {
+ processor.filer.write(build(), mode)
}
}
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyMemoizer.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyMemoizer.kt
deleted file mode 100644
index 01867e73..00000000
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyMemoizer.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package com.airbnb.paris.processor.framework
-
-import javax.lang.model.type.TypeMirror
-
-open class SkyMemoizer(withSkyProcessor: WithSkyProcessor) : WithSkyProcessor by withSkyProcessor {
-
- val androidViewClassType: TypeMirror by lazy { AndroidClassNames.VIEW.toTypeMirror() }
-
-}
\ No newline at end of file
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyProcessor.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyProcessor.kt
deleted file mode 100644
index 0af147e9..00000000
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyProcessor.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.airbnb.paris.processor.framework
-
-import javax.annotation.processing.AbstractProcessor
-import javax.annotation.processing.Filer
-import javax.annotation.processing.Messager
-import javax.annotation.processing.RoundEnvironment
-import javax.lang.model.element.TypeElement
-import javax.lang.model.util.Elements
-import javax.lang.model.util.Types
-
-abstract class SkyProcessor : AbstractProcessor(), WithSkyProcessor {
-
- override val filer: Filer by lazy { processingEnv.filer }
- override val messager: Messager by lazy { processingEnv.messager }
- override val elements: Elements by lazy { processingEnv.elementUtils }
- override val types: Types by lazy { processingEnv.typeUtils }
-
- override val memoizer: SkyMemoizer by lazy { SkyMemoizer(this) }
- override val loggedMessages = mutableListOf()
-
- override fun process(annotations: Set, roundEnv: RoundEnvironment): Boolean {
- processRound(annotations, roundEnv)
-
- if (roundEnv.processingOver()) {
- processingOver()
- }
-
- return claimAnnotations(annotations, roundEnv)
- }
-
- abstract fun processRound(annotations: Set, roundEnv: RoundEnvironment)
-
- abstract fun claimAnnotations(
- annotations: Set,
- roundEnv: RoundEnvironment
- ): Boolean
-
- abstract fun processingOver()
-}
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/WithSkyProcessor.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/WithSkyProcessor.kt
deleted file mode 100644
index 4a5f28d8..00000000
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/WithSkyProcessor.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.airbnb.paris.processor.framework
-
-import javax.annotation.processing.Filer
-import javax.annotation.processing.Messager
-import javax.lang.model.element.Element
-import javax.lang.model.element.PackageElement
-import javax.lang.model.element.TypeElement
-import javax.lang.model.type.TypeMirror
-import javax.lang.model.util.Elements
-import javax.lang.model.util.Types
-import javax.tools.Diagnostic
-
-/**
- * Most annotation processor classes will need access to [Filer], [Messager], [Elements] and [Types], among other things.
- */
-interface WithSkyProcessor {
-
- val processor: SkyProcessor
-
- val filer get() = processor.filer
- val messager get() = processor.messager
- val elements get() = processor.elements
- val types get() = processor.types
- val loggedMessages get() = processor.loggedMessages
- val memoizer get() = processor.memoizer
-
- fun erasure(type: TypeMirror): TypeMirror = types.erasure(type)
-
- fun isSameType(type1: TypeMirror, type2: TypeMirror) = types.isSameType(type1, type2)
-
- fun isSubtype(type1: TypeMirror, type2: TypeMirror) = types.isSubtype(type1, type2)
-
- // ClassName
-
- fun JavaClassName.toTypeElement(): TypeElement = elements.getTypeElement(reflectionName())
-
- fun JavaClassName.toTypeMirror(): TypeMirror = toTypeElement().asType()
-
- // Element
-
- fun Element.getPackageElement(): PackageElement = elements.getPackageOf(this)
-
- // TypeMirror
-
- fun TypeMirror.asTypeElement(): TypeElement = types.asElement(this) as TypeElement
-
- /**
- * Kapt replaces unknown types by "NonExistentClass". This can happen when code refers to generated classes. For example:
- *
- *
- * ```
- * @Style val myStyle = myViewStyle { }
- * ```
- *
- * myViewStyle is a generated function so the type of the field will be "NonExistentClass" when processed with kapt.
- *
- * This behavior can be altered by using `kapt { correctErrorTypes = true }` in the Gradle config.
- */
- fun TypeMirror.isNonExistent() = this.toString() == "error.NonExistentClass"
-
- // Android specific
-
- fun isView(type: TypeMirror): Boolean = isSubtype(type, processor.memoizer.androidViewClassType)
-
- // Error handling
-
- fun logError(element: Element, lazyMessage: () -> String) {
- logError { "${element.toStringId()}: ${lazyMessage()}" }
- }
-
- fun logError(lazyMessage: () -> String) {
- loggedMessages.add(Message(Diagnostic.Kind.ERROR, lazyMessage()))
- }
-
- fun logWarning(element: Element, lazyMessage: () -> String) {
- logWarning { "${element.toStringId()}: ${lazyMessage()}" }
- }
-
- fun logWarning(lazyMessage: () -> String) {
- loggedMessages.add(Message(Diagnostic.Kind.WARNING, lazyMessage()))
- }
-
- fun printLogsIfAny(messager: Messager) {
- loggedMessages.forEach {
- messager.printMessage(it.kind, it.message)
- }
- }
-}
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyCompanionPropertyModel.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyCompanionPropertyModel.kt
deleted file mode 100644
index c4470d71..00000000
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyCompanionPropertyModel.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-package com.airbnb.paris.processor.framework.models
-
-import com.airbnb.paris.processor.framework.JavaCodeBlock
-import com.airbnb.paris.processor.framework.KotlinCodeBlock
-import com.airbnb.paris.processor.framework.SkyProcessor
-import com.airbnb.paris.processor.framework.isJava
-import com.airbnb.paris.processor.framework.siblings
-import com.airbnb.paris.processor.framework.toStringId
-import javax.lang.model.element.Element
-import javax.lang.model.element.ElementKind
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.TypeElement
-import javax.lang.model.element.VariableElement
-import javax.lang.model.type.TypeMirror
-
-/**
- * Applies to Java fields and Kotlin properties
- */
-abstract class SkyCompanionPropertyModel(val element: VariableElement) : SkyModel {
-
- val enclosingElement: TypeElement = element.enclosingElement as TypeElement
- val type: TypeMirror = element.asType()
- val name: String = element.simpleName.toString()
- val getterElement: Element
- val javaGetter: JavaCodeBlock
- val kotlinGetter: KotlinCodeBlock
-
- init {
- if (element.isJava()) {
- getterElement = element
- javaGetter = JavaCodeBlock.of("\$N", element.simpleName)
- } else {
- // In Kotlin the annotated element is a private static field which is accompanied by a Companion method
-
- val getterName = "get${name.capitalize()}"
- val companionFunctions = element.siblings().asSequence()
- .single {
- it is TypeElement && it.simpleName.toString() == "Companion"
- }
- .enclosedElements
- .filterIsInstance()
-
- // If the property is public the name of the getter function will be prepended with "get". If it's internal, it will also
- // be appended with "$" and an arbitrary string for obfuscation purposes.
- // Kotlin 1.4.x contains BOTH at once, but only the none synthetic one can be used, so we check for the real one first.
- getterElement = companionFunctions.firstOrNull {
- val elementSimpleName = it.simpleName.toString()
- elementSimpleName == getterName
- } ?: companionFunctions.firstOrNull {
- val elementSimpleName = it.simpleName.toString()
- elementSimpleName.startsWith("$getterName$")
- } ?: error("${element.toStringId()} - could not get companion property")
-
- javaGetter = JavaCodeBlock.of("Companion.\$N()", getterElement.simpleName)
- }
-
- kotlinGetter = KotlinCodeBlock.of("%N()", getterElement.simpleName)
- }
-}
-
-abstract class SkyCompanionPropertyModelFactory(
- override val processor: SkyProcessor,
- annotationClass: Class
-) : SkyModelFactory(processor, annotationClass) {
-
- override fun filter(element: Element): Boolean = element.kind == ElementKind.FIELD
-}
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyMethodModel.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyMethodModel.kt
index 7cb5b3ac..9ab8ae66 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyMethodModel.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyMethodModel.kt
@@ -1,32 +1,31 @@
package com.airbnb.paris.processor.framework.models
-import com.airbnb.paris.processor.framework.SkyProcessor
-import javax.lang.model.element.Element
-import javax.lang.model.element.ElementKind
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.TypeElement
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XMethodElement
+import androidx.room.compiler.processing.XTypeElement
+import androidx.room.compiler.processing.isMethod
+import com.airbnb.paris.processor.BaseProcessor
abstract class SkyMethodModel private constructor(
- val enclosingElement: TypeElement,
- val element: ExecutableElement,
- val name: String
+ val enclosingElement: XTypeElement,
+ val element: XMethodElement,
) : SkyModel {
+ val name: String get() = element.name
- protected constructor(element: ExecutableElement) : this(
- element.enclosingElement as TypeElement,
- element,
- element.simpleName.toString()
+ protected constructor(element: XMethodElement) : this(
+ element.enclosingElement as XTypeElement,
+ element
)
}
typealias SkyStaticMethodModel = SkyMethodModel
abstract class SkyMethodModelFactory(
- processor: SkyProcessor,
+ processor: BaseProcessor,
annotationClass: Class
-) : SkyModelFactory(processor, annotationClass) {
+) : JavaSkyModelFactory(processor, annotationClass) {
- override fun filter(element: Element): Boolean = element.kind == ElementKind.METHOD
+ override fun filter(element: XElement): Boolean = element.isMethod()
}
typealias SkyStaticMethodModelFactory = SkyMethodModelFactory
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyModel.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyModel.kt
index 6efc24ca..f84f65b2 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyModel.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyModel.kt
@@ -1,16 +1,15 @@
package com.airbnb.paris.processor.framework.models
-import com.airbnb.paris.processor.framework.SkyProcessor
-import com.airbnb.paris.processor.framework.WithSkyProcessor
-import javax.annotation.processing.RoundEnvironment
-import javax.lang.model.element.Element
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XRoundEnv
+import com.airbnb.paris.processor.BaseProcessor
interface SkyModel
-abstract class SkyModelFactory(
- override val processor: SkyProcessor,
+abstract class JavaSkyModelFactory(
+ val processor: BaseProcessor,
private val annotationClass: Class
-) : WithSkyProcessor {
+) {
var models = emptyList()
private set
@@ -18,11 +17,12 @@ abstract class SkyModelFactory(
var latest = emptyList()
private set
- fun process(roundEnv: RoundEnvironment) {
- roundEnv.getElementsAnnotatedWith(annotationClass)
+ fun process(roundEnv: XRoundEnv) {
+ roundEnv.getElementsAnnotatedWith(annotationClass.canonicalName)
+ .filter(::filter)
.mapNotNull {
@Suppress("UNCHECKED_CAST")
- if (filter(it)) elementToModel(it as E) else null
+ elementToModel(it as E)
}
.let {
models += it
@@ -30,7 +30,7 @@ abstract class SkyModelFactory(
}
}
- open fun filter(element: Element): Boolean = true
+ open fun filter(element: XElement): Boolean = true
abstract fun elementToModel(element: E): T?
}
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyPropertyModel.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyPropertyModel.kt
index 5410b89f..d773c77e 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyPropertyModel.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyPropertyModel.kt
@@ -1,28 +1,33 @@
package com.airbnb.paris.processor.framework.models
-import com.airbnb.paris.processor.framework.SkyProcessor
-import com.airbnb.paris.processor.framework.isJava
-import com.airbnb.paris.processor.framework.isKotlin
-import com.airbnb.paris.processor.framework.siblings
-import com.airbnb.paris.processor.framework.toStringId
-import javax.lang.model.element.Element
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.TypeElement
-import javax.lang.model.type.TypeMirror
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XFieldElement
+import androidx.room.compiler.processing.XMethodElement
+import androidx.room.compiler.processing.XProcessingEnv
+import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.XTypeElement
+import com.airbnb.paris.processor.BaseProcessor
+import com.airbnb.paris.processor.utils.isJavac
+import com.airbnb.paris.processor.utils.javaGetterSyntax
/**
* Applies to Java fields and Kotlin properties
*/
-abstract class SkyPropertyModel(val element: Element) : SkyModel {
+abstract class SkyPropertyModel(val element: XElement, val env: XProcessingEnv) : SkyModel {
- val enclosingElement: TypeElement = element.enclosingElement as TypeElement
- val type: TypeMirror
+ val enclosingElement: XTypeElement = when (element) {
+ is XMethodElement -> element.enclosingElement as XTypeElement
+ is XFieldElement -> element.enclosingElement as XTypeElement
+ else -> error("Unsupported type $element")
+ }
+
+ val type: XType
val name: String
/**
* The getter could be a field or a method depending on if the annotated class is in Java or in Kotlin
*/
- val getterElement: Element
+ val getterElement: XElement
/**
* What you'd call to get the property value
@@ -30,61 +35,92 @@ abstract class SkyPropertyModel(val element: Element) : SkyModel {
val getter: String
init {
- if (element.isJava()) {
- type = element.asType()
- name = element.simpleName.toString()
- getterElement = element
- getter = name
- } else {
- // In Kotlin it's a synthetic empty static method whose name is $annotations that ends
- // up being annotated.
- // In kotlin 1.4.0+ the method is changed to start with "get", so we need to handle both cases
- name = element.simpleName.toString()
- .substringBefore("\$annotations")
- // get prefix will only exist for kotlin 1.4
- .removePrefix("get")
- .decapitalize()
-
- val getterName = "get${name.capitalize()}"
- val getters = element.siblings().asSequence()
- .filterIsInstance()
- .filter { it.parameters.isEmpty() }
-
- // If the property is public the name of the getter function will be prepended with "get". If it's internal, it will also
- // be appended with "$" and an arbitrary string for obfuscation purposes.
- // In kotlin 1.4.0 both versions will be present, so we check for the real getter first.
- val kotlinGetterElement = getters.firstOrNull {
- val elementSimpleName = it.simpleName.toString()
- elementSimpleName == getterName
- } ?: getters.firstOrNull {
- val elementSimpleName = it.simpleName.toString()
- elementSimpleName.startsWith("$getterName$")
- } ?: error(
- "${element.toStringId()}: Could not find getter ($getterName) for property annotated with @StyleableChild. " +
- "This probably means the property is private or protected."
- )
-
- getterElement = kotlinGetterElement
- getter = "${kotlinGetterElement.simpleName}()"
-
- type = kotlinGetterElement.returnType
+ when (element) {
+ is XMethodElement -> {
+ val (propertyName, getterFunction) = findGetterPropertyFromSyntheticFunction(element)
+ ?: error(
+ "${element}: Could not find getter for property annotated with @StyleableChild. " +
+ "This probably means the property is private or protected."
+ )
+
+ name = propertyName
+ getterElement = getterFunction
+ getter = "${getterFunction.name}()"
+ type = getterFunction.returnType
+ }
+ is XFieldElement -> {
+ name = element.name
+ type = element.type
+ getterElement = element
+
+ if (element.isJavac) {
+ getter = name
+ } else {
+ // KSP case
+ getter = element.javaGetterSyntax(env)
+ }
+ }
+ else -> error("Unsupported type $element")
+ }
+
+ if (type.toString() == "void") {
+ error("$element has void type")
}
}
+}
- /**
- * True is [isJava] is false and vice-versa
- */
- fun isKotlin(): Boolean = element.isKotlin()
+// In Kotlin it's a synthetic empty static method whose name is $annotations that ends
+// up being annotated.
+// In kotlin 1.4.0+ the method is changed to start with "get", so we need to handle both cases
+internal fun findGetterPropertyFromSyntheticFunction(syntheticMethod: XMethodElement): GetterResult? {
+ val name = syntheticMethod.name
+ .substringBefore("\$annotations", missingDelimiterValue = "")
+ // get prefix will only exist for kotlin 1.4
+ .removePrefix("get")
+ .decapitalize()
+ .ifBlank { return null }
- /**
- * True is [isKotlin] is false and vice-versa
- */
- fun isJava(): Boolean = element.isJava()
+ val enclosing = syntheticMethod.enclosingElement as? XTypeElement ?: return null
+
+ val getters = enclosing.getDeclaredMethods().filter { it.parameters.isEmpty() }
+
+ val getterName = "get${name.capitalize()}"
+
+ // If the property is public the name of the getter function will be prepended with "get". If it's internal, it will also
+ // be appended with "$" and an arbitrary string for obfuscation purposes.
+ // In kotlin 1.4.0 both versions will be present, so we check for the real getter first.
+ val kotlinGetterElement = getters.firstOrNull {
+ val elementSimpleName = it.name
+ elementSimpleName == getterName
+ } ?: getters.firstOrNull {
+ val elementSimpleName = it.name
+ elementSimpleName.startsWith("$getterName$")
+ } ?: return null
+
+ return GetterResult(name, kotlinGetterElement)
+
+ // For example, in Kotlin 1.4.30 this property is turned into java code like:
+ // @StyleableChild(R2.styleable.Test_WithStyleableChildKotlinView_test_arbitraryStyle)
+ // val arbitrarySubView = View(context)
+ // ->
+ // @NotNull
+ // private final View arbitrarySubView;
+ //
+ // /** @deprecated */
+ // // $FF: synthetic method
+ // @StyleableChild(1725)
+ // public static void getArbitrarySubView$annotations() {
+ // }
+ //
+ // @NotNull
+ // public final View getArbitrarySubView() {
+ // return this.arbitrarySubView;
+ // }
}
-typealias SkyFieldModel = SkyPropertyModel
+internal data class GetterResult(val propertyName: String, val getterFunction: XMethodElement)
abstract class SkyFieldModelFactory(
- processor: SkyProcessor,
+ processor: BaseProcessor,
annotationClass: Class
-) : SkyModelFactory(processor, annotationClass)
+) : JavaSkyModelFactory(processor, annotationClass)
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyStaticPropertyModel.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyStaticPropertyModel.kt
new file mode 100644
index 00000000..aef32e69
--- /dev/null
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyStaticPropertyModel.kt
@@ -0,0 +1,123 @@
+package com.airbnb.paris.processor.framework.models
+
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XFieldElement
+import androidx.room.compiler.processing.XMethodElement
+import androidx.room.compiler.processing.XProcessingEnv
+import androidx.room.compiler.processing.XTypeElement
+import androidx.room.compiler.processing.compat.XConverters.toJavac
+import androidx.room.compiler.processing.compat.XConverters.toXProcessing
+import androidx.room.compiler.processing.isMethod
+import com.airbnb.paris.processor.BaseProcessor
+import com.airbnb.paris.processor.framework.JavaCodeBlock
+import com.airbnb.paris.processor.framework.siblings
+import com.airbnb.paris.processor.utils.enclosingElementIfCompanion
+import com.airbnb.paris.processor.utils.isFieldElement
+import com.airbnb.paris.processor.utils.isJavac
+import com.airbnb.paris.processor.utils.javaGetterSyntax
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.TypeElement
+
+/**
+ * Applies to Java static fields and Kotlin companion properties.
+ * Element will be a method element in javac as a getter function, and a field property in KSP.
+ */
+abstract class SkyStaticPropertyModel(val element: XElement, env: XProcessingEnv) : SkyModel {
+
+ private val directEnclosingElement: XTypeElement = when (element) {
+ is XMethodElement -> element.enclosingElement as XTypeElement
+ is XFieldElement -> element.enclosingElement as XTypeElement
+ else -> error("Unsupported type $element of type ${element.javaClass}")
+ }
+
+ val enclosingElement: XTypeElement get() = directEnclosingElement.enclosingElementIfCompanion
+
+ // Code for use in java source to access the property via a getter function.
+ val javaGetter: JavaCodeBlock
+ val getterElement: XElement
+
+ init {
+ when (element) {
+ is XMethodElement -> {
+ val (_, getter) = findGetterPropertyFromSyntheticFunction(element)
+ ?: error(
+ "${element}: Could not find getter for property annotated with @StyleableChild. " +
+ "This probably means the property is private or protected."
+ )
+ getterElement = getter
+
+ // Method case for kotlin companion property in javac/kapt
+ // Original source is kotlin, so java interop will use a getter function
+ javaGetter = JavaCodeBlock.of("Companion.\$N()", getter.name)
+ }
+ is XFieldElement -> {
+
+ if (element.isJavac) {
+ val javacElement = element.toJavac()
+ if (directEnclosingElement.isCompanionObject() || directEnclosingElement.hasAnnotation(Metadata::class)) {
+ // Kotlin source viewed in javac/kapt.
+ // Java representation is a field when the annotation target is "Field"
+ val companionFunctions = javacElement.siblings()
+ .single {
+ it is TypeElement && it.simpleName.toString() == "Companion"
+ }
+ .enclosedElements
+ .filterIsInstance()
+ .ifEmpty {
+ error("$element ${element.enclosingElement} - could not get companion object")
+ }
+
+ // If the property is public the name of the getter function will be prepended with "get". If it's internal, it will also
+ // be appended with "$" and an arbitrary string for obfuscation purposes.
+ // Kotlin 1.4.x contains BOTH at once, but only the none synthetic one can be used, so we check for the real one first.
+ val getterName = "get${element.name.capitalize()}"
+ val companionGetter = companionFunctions.firstOrNull {
+ val elementSimpleName = it.simpleName.toString()
+ elementSimpleName == getterName
+ } ?: companionFunctions.firstOrNull {
+ val elementSimpleName = it.simpleName.toString()
+ elementSimpleName.startsWith("$getterName$")
+ } ?: error("$element ${element.enclosingElement} - could not find companion property $getterName")
+
+ getterElement = companionGetter.toXProcessing(env)
+ javaGetter = JavaCodeBlock.of("Companion.\$N()", companionGetter.simpleName)
+ } else {
+ // java source accessing java static property uses field reference directly.
+ getterElement = element
+ javaGetter = JavaCodeBlock.of("\$N", element.name)
+ }
+ } else {
+ // KSP case
+ val enclosingType = element.enclosingElement as? XTypeElement
+ if (enclosingType?.isCompanionObject() == true) {
+ // kotlin source case. ksp represents elements as a private field in the companion object.
+ // we need to expose its public getter.
+ val getterSyntax = element.javaGetterSyntax(env)
+
+ // We don't have a getter in ksp - would have to create a synthetic one.
+ getterElement = element
+ javaGetter = JavaCodeBlock.of("Companion.\$N", getterSyntax)
+ } else {
+ // Java source case, can access java field directly
+ javaGetter = JavaCodeBlock.of("\$N", element.name)
+ getterElement = element
+ }
+ }
+ }
+ else -> error("Unsupported type $element of type ${element.javaClass}")
+ }
+ }
+}
+
+abstract class SkyStaticPropertyModelFactory(
+ processor: BaseProcessor,
+ annotationClass: Class
+) : JavaSkyModelFactory(processor, annotationClass) {
+
+ override fun filter(element: XElement): Boolean {
+ // Will be a field in the kotlin/ksp case or the java source case
+ return element.isFieldElement() && element.isStatic()
+ // Kapt/javac sees kotlin companion properties as static getter function
+ || (element.isMethod() && element.isStatic() && (element.enclosingElement as? XTypeElement)?.isCompanionObject() == true)
+ }
+}
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/models/AfterStyleInfo.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/models/AfterStyleInfo.kt
index 64db985d..8343e1e5 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/models/AfterStyleInfo.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/models/AfterStyleInfo.kt
@@ -1,28 +1,28 @@
package com.airbnb.paris.processor.models
+import androidx.room.compiler.processing.XMethodElement
import com.airbnb.paris.annotations.AfterStyle
import com.airbnb.paris.processor.ParisProcessor
import com.airbnb.paris.processor.STYLE_CLASS_NAME
-import com.airbnb.paris.processor.framework.isPrivate
-import com.airbnb.paris.processor.framework.isProtected
import com.airbnb.paris.processor.framework.models.SkyMethodModel
import com.airbnb.paris.processor.framework.models.SkyMethodModelFactory
-import javax.lang.model.element.ExecutableElement
+import com.airbnb.paris.processor.utils.isSameTypeName
-internal class AfterStyleInfoExtractor(override val processor: ParisProcessor) : SkyMethodModelFactory(processor, AfterStyle::class.java) {
+internal class AfterStyleInfoExtractor(val parisProcessor: ParisProcessor) : SkyMethodModelFactory(parisProcessor, AfterStyle::class.java) {
+
+ override fun elementToModel(element: XMethodElement): AfterStyleInfo? {
- override fun elementToModel(element: ExecutableElement): AfterStyleInfo? {
if (element.isPrivate() || element.isProtected()) {
- logError(element) {
+ parisProcessor.logError(element) {
"Methods annotated with @AfterStyle can't be private or protected."
}
return null
}
- val parameterType = element.parameters.firstOrNull()?.asType()
+ val parameterType = element.parameters.firstOrNull()?.type
- if (parameterType == null || !isSameType(processor.memoizer.styleClassType, parameterType)) {
- logError(element) {
+ if (parameterType == null || !parameterType.isSameTypeName(STYLE_CLASS_NAME)) {
+ parisProcessor.logError(element) {
"Methods annotated with @AfterStyle must have a single Style parameter."
}
return null
@@ -32,4 +32,4 @@ internal class AfterStyleInfoExtractor(override val processor: ParisProcessor) :
}
}
-internal class AfterStyleInfo(element: ExecutableElement) : SkyMethodModel(element)
+internal class AfterStyleInfo(element: XMethodElement) : SkyMethodModel(element)
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/models/AttrInfo.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/models/AttrInfo.kt
index 9a211330..db2370ac 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/models/AttrInfo.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/models/AttrInfo.kt
@@ -1,51 +1,47 @@
package com.airbnb.paris.processor.models
import androidx.annotation.RequiresApi
+import androidx.room.compiler.processing.XMethodElement
+import androidx.room.compiler.processing.XType
import com.airbnb.paris.annotations.Attr
import com.airbnb.paris.processor.Format
import com.airbnb.paris.processor.ParisProcessor
-import com.airbnb.paris.processor.WithParisProcessor
import com.airbnb.paris.processor.android_resource_scanner.AndroidResourceId
import com.airbnb.paris.processor.framework.JavaCodeBlock
import com.airbnb.paris.processor.framework.KotlinCodeBlock
-import com.airbnb.paris.processor.framework.isPrivate
-import com.airbnb.paris.processor.framework.isProtected
import com.airbnb.paris.processor.framework.models.SkyMethodModel
import com.airbnb.paris.processor.framework.models.SkyMethodModelFactory
-import java.lang.annotation.AnnotationTypeMismatchException
-import javax.lang.model.element.ExecutableElement
-import javax.lang.model.element.TypeElement
-import javax.lang.model.type.TypeMirror
+import com.airbnb.paris.processor.framework.toKPoet
internal class AttrInfoExtractor(
- override val processor: ParisProcessor
-) : SkyMethodModelFactory(processor, Attr::class.java), WithParisProcessor {
+ val parisProcessor: ParisProcessor
+) : SkyMethodModelFactory(parisProcessor, Attr::class.java) {
- override fun elementToModel(element: ExecutableElement): AttrInfo? {
+ override fun elementToModel(element: XMethodElement): AttrInfo? {
if (element.isPrivate() || element.isProtected()) {
- logError(element) {
+ parisProcessor.logError(element) {
"Methods annotated with @Attr can't be private or protected."
}
return null
}
- val attr = element.getAnnotation(Attr::class.java)
+ val attr: Attr = element.getAnnotation(Attr::class)?.value ?: error("@Attr annotation not found on $element")
- val targetType = element.parameters.firstOrNull()?.asType() ?: run {
- logError(element) {
+ val targetType = element.parameters.firstOrNull()?.type ?: run {
+ parisProcessor.logError(element) {
"Method with @Attr must provide a single parameter"
}
return null
}
- val targetFormat = Format.forElement(processor, element)
+ val targetFormat = Format.forElement(parisProcessor.memoizer, element)
val styleableResId: AndroidResourceId
try {
- styleableResId = getResourceId(Attr::class.java, element, attr.value) ?: return null
- } catch (e: AnnotationTypeMismatchException) {
- logError(element) {
- "Incorrectly typed @Attr value parameter. (This usually happens when an R value doesn't exist.)"
+ styleableResId = parisProcessor.getResourceId(Attr::class, element, attr.value) ?: return null
+ } catch (e: Throwable) {
+ parisProcessor.logError(element) {
+ "Incorrectly typed @Attr value parameter. (This usually happens when an R value doesn't exist.) $e"
}
return null
}
@@ -53,26 +49,26 @@ internal class AttrInfoExtractor(
var defaultValueResId: AndroidResourceId? = null
try {
if (attr.defaultValue != -1) {
- defaultValueResId = getResourceId(Attr::class.java, element, attr.defaultValue) ?: return null
+ defaultValueResId = parisProcessor.getResourceId(Attr::class, element, attr.defaultValue) ?: return null
}
- } catch (e: AnnotationTypeMismatchException) {
- logError(element) {
+ } catch (e: Throwable) {
+ parisProcessor.logError(element) {
"Incorrectly typed @Attr defaultValue parameter. (This usually happens when an R value doesn't exist.)"
}
return null
}
- val enclosingElement = element.enclosingElement as TypeElement
- val name = element.simpleName.toString()
- val javadoc = JavaCodeBlock.of("@see \$T#\$N(\$T)\n", enclosingElement, name, targetType)
+ val enclosingElement = element.enclosingElement
+ val name = element.name
+ val javadoc = JavaCodeBlock.of("@see \$T#\$N(\$T)\n", enclosingElement.className, name, targetType.typeName)
// internal functions have a '$' in their name which creates a kdoc error. We could escape it but the part after the '$' is meant for
// obfuscation anyway so not using it should result in clearer documentation.
val kdocName = name.substringBefore('$')
- val kdoc = KotlinCodeBlock.of("@see %T.%N\n", enclosingElement, kdocName)
+ val kdoc = KotlinCodeBlock.of("@see %T.%N\n", enclosingElement.className.toKPoet(), kdocName)
// We rely on the `RequiresApi` Android annotation to disable certain attributes based on the Android SDK version.
// 1 is the default since that's the minimum version.
- val requiresApi = element.getAnnotation(RequiresApi::class.java)?.let {
+ val requiresApi = element.getAnnotation(RequiresApi::class)?.value?.let {
// value is an alias of api, so we give precedence to api.
if (it.api > 1) it.api else it.value
} ?: 1
@@ -95,8 +91,8 @@ internal class AttrInfoExtractor(
* Target The method parameter
*/
internal class AttrInfo(
- element: ExecutableElement,
- val targetType: TypeMirror,
+ element: XMethodElement,
+ val targetType: XType,
val targetFormat: Format,
val styleableResId: AndroidResourceId,
val defaultValueResId: AndroidResourceId?,
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/models/BaseStyleableInfo.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/models/BaseStyleableInfo.kt
index 7de52c14..1670c045 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/models/BaseStyleableInfo.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/models/BaseStyleableInfo.kt
@@ -1,70 +1,50 @@
package com.airbnb.paris.processor.models
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.XTypeElement
+import com.airbnb.paris.annotations.GeneratedStyleableClass
import com.airbnb.paris.annotations.GeneratedStyleableModule
import com.airbnb.paris.annotations.Styleable
import com.airbnb.paris.processor.PARIS_MODULES_PACKAGE_NAME
-import com.airbnb.paris.processor.PROXY_CLASS_NAME
import com.airbnb.paris.processor.ParisProcessor
import com.airbnb.paris.processor.STYLE_APPLIER_SIMPLE_CLASS_NAME_FORMAT
-import com.airbnb.paris.processor.framework.WithSkyProcessor
-import com.airbnb.paris.processor.framework.packageName
+import com.airbnb.paris.processor.utils.getTypeElementsFromPackageSafe
import com.squareup.javapoet.ClassName
-import javax.lang.model.element.Element
-import javax.lang.model.element.TypeElement
-import javax.lang.model.type.DeclaredType
-import javax.lang.model.type.MirroredTypeException
-import javax.lang.model.type.TypeMirror
/**
* It's important that base styleables be extracted before new ones are written for the current module, otherwise the latter will be included in the
* results
*/
-internal class BaseStyleableInfoExtractor(override val processor: ParisProcessor) : WithSkyProcessor {
+internal class BaseStyleableInfoExtractor( val processor: ParisProcessor) {
fun fromEnvironment(): List {
- val baseStyleablesInfo = mutableListOf()
- elements.getPackageElement(PARIS_MODULES_PACKAGE_NAME)?.let { packageElement ->
- packageElement.enclosedElements
- .map { it.getAnnotation(GeneratedStyleableModule::class.java) }
- .forEach { styleableModule ->
- baseStyleablesInfo.addAll(
- styleableModule.value
- .mapNotNull { generatedStyleableClass ->
- var typeElement: TypeElement? = null
- try {
- generatedStyleableClass.value
- } catch (e: MirroredTypeException) {
- typeElement = e.typeMirror.asTypeElement()
- }
- typeElement
- }
- .map { typeElement ->
- BaseStyleableInfoExtractor(processor).fromElement(typeElement)
- }
- )
- }
- }
- return baseStyleablesInfo
+ return processor.environment.getTypeElementsFromPackageSafe(PARIS_MODULES_PACKAGE_NAME)
+ .mapNotNull { it.getAnnotation(GeneratedStyleableModule::class) }
+ .flatMap { styleableModule ->
+ styleableModule.getAsAnnotationBoxArray("value")
+ .mapNotNull { it.getAsType("value")?.typeElement }
+ .map { BaseStyleableInfoExtractor(processor).fromElement(it) }
+ }
}
- fun fromElement(element: TypeElement): BaseStyleableInfo {
+ fun fromElement(element: XTypeElement): BaseStyleableInfo {
val elementPackageName = element.packageName
- val elementName = element.simpleName.toString()
- val elementType = element.asType()
+ val elementName = element.name
+ val elementType = element.type
- val viewElementType: TypeMirror
- viewElementType = if (isSubtype(elementType, processor.memoizer.proxyClassTypeErased)) {
+ val viewElementType: XType = if (processor.memoizer.proxyClassType.rawType.isAssignableFrom(elementType)) {
// Get the parameterized type, which should be the view type
- (element.superclass as DeclaredType).typeArguments[1]
+ element.superType?.typeArguments?.getOrNull(1) ?: error("No type for $elementType")
} else {
elementType
}
- val viewElement = viewElementType.asTypeElement()
+ val viewElement = viewElementType.typeElement!!
val viewElementPackageName = viewElement.packageName
- val viewElementName = viewElement.simpleName.toString()
+ val viewElementName = viewElement.name
- val styleable = element.getAnnotation(Styleable::class.java)
+ val styleable = element.getAnnotation(Styleable::class)?.value!!
val styleableResourceName = styleable.value
return BaseStyleableInfo(
@@ -86,23 +66,23 @@ internal open class BaseStyleableInfo(
* The element that is annotated with @Styleable.
* This is used to determine the originating element of generated files.
*/
- val annotatedElement: Element,
+ val annotatedElement: XElement,
val elementPackageName: String,
val elementName: String,
/**
* If the styleable class is not a proxy, will be equal to [viewElementType]. Otherwise,
* will refer to the proxy class
*/
- val elementType: TypeMirror,
+ val elementType: XType,
private val viewElementPackageName: String,
- val viewElement: TypeElement,
+ val viewElement: XTypeElement,
/** The simple name of the view eg. "AirImageView" */
val viewElementName: String,
/**
* If the styleable class is not a proxy, will be equal to [elementType]. Refers to the view
* class
*/
- val viewElementType: TypeMirror,
+ val viewElementType: XType,
val styleableResourceName: String
) {
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/models/BeforeStyleInfo.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/models/BeforeStyleInfo.kt
index bb991545..a5395aa1 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/models/BeforeStyleInfo.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/models/BeforeStyleInfo.kt
@@ -1,28 +1,26 @@
package com.airbnb.paris.processor.models
+import androidx.room.compiler.processing.XMethodElement
import com.airbnb.paris.annotations.BeforeStyle
import com.airbnb.paris.processor.ParisProcessor
-import com.airbnb.paris.processor.STYLE_CLASS_NAME
-import com.airbnb.paris.processor.framework.isPrivate
-import com.airbnb.paris.processor.framework.isProtected
import com.airbnb.paris.processor.framework.models.SkyMethodModel
import com.airbnb.paris.processor.framework.models.SkyMethodModelFactory
-import javax.lang.model.element.ExecutableElement
-internal class BeforeStyleInfoExtractor(override val processor: ParisProcessor) : SkyMethodModelFactory(processor, BeforeStyle::class.java) {
+internal class BeforeStyleInfoExtractor(val parisProcessor: ParisProcessor) : SkyMethodModelFactory(parisProcessor, BeforeStyle::class.java) {
- override fun elementToModel(element: ExecutableElement): BeforeStyleInfo? {
+ override fun elementToModel(element: XMethodElement): BeforeStyleInfo? {
if (element.isPrivate() || element.isProtected()) {
- logError(element) {
+ parisProcessor.logError(element) {
"Methods annotated with @BeforeStyle can't be private or protected."
}
return null
}
- val parameterType = element.parameters.firstOrNull()?.asType()
- if (parameterType == null || !isSameType(processor.memoizer.styleClassType, parameterType)) {
- logError(element) {
+ val parameterType = element.parameters.firstOrNull()?.type
+ // TODO: 2/21/21 BeforeStyle doesn't seem tested in the project?!
+ if (parameterType == null || parisProcessor.memoizer.styleClassTypeX.isAssignableFrom(parameterType)) {
+ parisProcessor.logError(element) {
"Methods annotated with @BeforeStyle must have a single Style parameter."
}
return null
@@ -32,5 +30,5 @@ internal class BeforeStyleInfoExtractor(override val processor: ParisProcessor)
}
}
-internal class BeforeStyleInfo(element: ExecutableElement) : SkyMethodModel(element)
+internal class BeforeStyleInfo(element: XMethodElement) : SkyMethodModel(element)
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleCompanionPropertyInfo.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleCompanionPropertyInfo.kt
deleted file mode 100644
index 2a65f02e..00000000
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleCompanionPropertyInfo.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.airbnb.paris.processor.models
-
-import com.airbnb.paris.annotations.Style
-import com.airbnb.paris.processor.ParisProcessor
-import com.airbnb.paris.processor.STYLE_CLASS_NAME
-import com.airbnb.paris.processor.framework.JavaCodeBlock
-import com.airbnb.paris.processor.framework.KotlinCodeBlock
-import com.airbnb.paris.processor.framework.isNotFinal
-import com.airbnb.paris.processor.framework.isNotStatic
-import com.airbnb.paris.processor.framework.isPrivate
-import com.airbnb.paris.processor.framework.isProtected
-import com.airbnb.paris.processor.framework.models.SkyCompanionPropertyModel
-import com.airbnb.paris.processor.framework.models.SkyCompanionPropertyModelFactory
-import com.airbnb.paris.processor.utils.ParisProcessorUtils
-import javax.lang.model.element.VariableElement
-import javax.lang.model.type.TypeKind
-
-internal class StyleCompanionPropertyInfoExtractor(override val processor: ParisProcessor) :
- SkyCompanionPropertyModelFactory(processor, Style::class.java) {
-
- override fun elementToModel(element: VariableElement): StyleCompanionPropertyInfo? {
- // TODO Get Javadoc from field/method and add it to the generated methods
-
- if (element.isNotStatic()) {
- logError(element) {
- "Fields annotated with @Style must be static."
- }
- return null
- }
-
- if (element.isNotFinal()) {
- logError(element) {
- "Fields annotated with @Style must be final."
- }
- return null
- }
-
- val type = element.asType()
- if (type.kind != TypeKind.INT && !isSubtype(type, processor.memoizer.styleClassType) && !type.isNonExistent()) {
- // Note: if the type is non existent we ignore this error check so that users don't need to change their kapt configuration, they'll still
- // get a build error though not as explicit.
- logError(element) {
- "Fields annotated with @Style must implement com.airbnb.paris.styles.Style or be of type int (and refer to a style resource)."
- }
- return null
- }
-
- val style = element.getAnnotation(Style::class.java)
- val isDefault = style.isDefault
-
- val enclosingElement = element.enclosingElement
-
- val elementName = element.simpleName.toString()
-
- val formattedName = ParisProcessorUtils.reformatStyleFieldOrMethodName(elementName)
-
- val javadoc = JavaCodeBlock.of("@see \$T#\$N\n", enclosingElement, elementName)
- val kdoc = KotlinCodeBlock.of("@see %T.%N\n", enclosingElement, elementName)
-
- val styleInfo = StyleCompanionPropertyInfo(
- element,
- elementName,
- formattedName,
- javadoc,
- kdoc,
- isDefault
- )
-
- if (styleInfo.getterElement.isPrivate() || styleInfo.getterElement.isProtected()) {
- logError(element) {
- "Fields annotated with @Style can't be private or protected."
- }
- return null
- }
-
- return styleInfo
- }
-}
-
-internal class StyleCompanionPropertyInfo(
- element: VariableElement,
- override val elementName: String,
- override val formattedName: String,
- override val javadoc: JavaCodeBlock,
- override val kdoc: KotlinCodeBlock,
- override val isDefault: Boolean = false
-) : SkyCompanionPropertyModel(element), StyleInfo
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleInfo.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleInfo.kt
index 45b2d757..6730666f 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleInfo.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleInfo.kt
@@ -1,17 +1,16 @@
package com.airbnb.paris.processor.models
+import androidx.room.compiler.processing.XRoundEnv
+import androidx.room.compiler.processing.XTypeElement
import com.airbnb.paris.annotations.Styleable
import com.airbnb.paris.processor.ParisProcessor
-import com.airbnb.paris.processor.WithParisProcessor
import com.airbnb.paris.processor.framework.JavaCodeBlock
import com.airbnb.paris.processor.framework.KotlinCodeBlock
import java.util.Locale
-import javax.annotation.processing.RoundEnvironment
-import javax.lang.model.element.Element
private const val DEFAULT_STYLE_FORMATTED_NAME = "Default"
-internal class StyleInfoExtractor(override val processor: ParisProcessor) : WithParisProcessor {
+internal class StyleInfoExtractor(val processor: ParisProcessor) {
var models = emptyList()
private set
@@ -19,13 +18,13 @@ internal class StyleInfoExtractor(override val processor: ParisProcessor) : With
var latest = emptyList()
private set
- private val styleCompanionPropertyInfoExtractor = StyleCompanionPropertyInfoExtractor(processor)
+ private val styleCompanionPropertyInfoExtractor = StyleStaticPropertyInfoExtractor(processor)
private val styleStaticMethodInfoExtractor = StyleStaticMethodInfoExtractor(processor)
- fun process(roundEnv: RoundEnvironment) {
+ fun process(roundEnv: XRoundEnv) {
// TODO Check that no style was left behind?
- val styleableElements = roundEnv.getElementsAnnotatedWith(Styleable::class.java)
+ val styleableElements = roundEnv.getElementsAnnotatedWith(Styleable::class).filterIsInstance()
// TODO Make sure there aren't conflicting names?
styleCompanionPropertyInfoExtractor.process(roundEnv)
@@ -37,12 +36,12 @@ internal class StyleInfoExtractor(override val processor: ParisProcessor) : With
styleableElements
.map { it to (stylesFromStyleAnnotation[it] ?: emptyList()) }
- .flatMap>, StyleInfo> { (styleableElement, styles) ->
+ .flatMap>, StyleInfo> { (styleableElement, styles) ->
val styleWithNameDefault = styles.find { it.formattedName == DEFAULT_STYLE_FORMATTED_NAME }
val styleMarkedAsDefault = styles.find { it.isDefault }
if (styleWithNameDefault != styleMarkedAsDefault && styleWithNameDefault != null && styleMarkedAsDefault != null) {
- logError(styleableElement) {
+ processor.logError(styleableElement) {
"Naming a linked style \"default\" and annotating another with @Style(isDefault = true) is invalid."
}
}
@@ -56,12 +55,13 @@ internal class StyleInfoExtractor(override val processor: ParisProcessor) : With
// We suppress this warning because it is wrong, not casting results in an error
@Suppress("USELESS_CAST")
styles + when (styleMarkedAsDefault) {
- is StyleCompanionPropertyInfo -> StyleCompanionPropertyInfo(
- styleMarkedAsDefault.element,
- styleMarkedAsDefault.elementName,
- DEFAULT_STYLE_FORMATTED_NAME,
- styleMarkedAsDefault.javadoc,
- styleMarkedAsDefault.kdoc,
+ is StyleStaticPropertyInfo -> StyleStaticPropertyInfo(
+ env = processor.environment,
+ element = styleMarkedAsDefault.element,
+ elementName = styleMarkedAsDefault.elementName,
+ formattedName = DEFAULT_STYLE_FORMATTED_NAME,
+ javadoc = styleMarkedAsDefault.javadoc,
+ kdoc = styleMarkedAsDefault.kdoc,
isDefault = true
) as StyleInfo
is StyleStaticMethodInfo -> StyleStaticMethodInfo(
@@ -72,7 +72,7 @@ internal class StyleInfoExtractor(override val processor: ParisProcessor) : With
styleMarkedAsDefault.kdoc,
isDefault = true
) as StyleInfo
- else -> throw IllegalStateException()
+ else -> error("Unsupported $styleMarkedAsDefault")
}
} else {
// Next we check to see if a style exists that matches the default name
@@ -81,9 +81,9 @@ internal class StyleInfoExtractor(override val processor: ParisProcessor) : With
if (defaultNameFormatStyle != null) {
styles + defaultNameFormatStyle
} else {
- if (processor.namespacedResourcesEnabled && !styleableElement.getAnnotation(Styleable::class.java).emptyDefaultStyle) {
- logError {
- "No default style found for ${styleableElement.simpleName}. Link an appropriate default style, " +
+ if (processor.namespacedResourcesEnabled && !styleableElement.getAnnotation(Styleable::class)!!.value.emptyDefaultStyle) {
+ processor.logError(styleableElement) {
+ "No default style found for ${styleableElement.name}. Link an appropriate default style, " +
"or set @Styleable(emptyDefaultStyle = true) for this element if none exist."
}
}
@@ -97,21 +97,21 @@ internal class StyleInfoExtractor(override val processor: ParisProcessor) : With
}
}
- private fun fromDefaultNameFormat(styleableElement: Element): StyleInfo? {
- if (defaultStyleNameFormat.isBlank()) {
+ private fun fromDefaultNameFormat(styleableElement: XTypeElement): StyleInfo? {
+ if (processor.defaultStyleNameFormat.isBlank()) {
return null
}
- val elementName = styleableElement.simpleName.toString()
- val defaultStyleName = String.format(Locale.US, defaultStyleNameFormat, elementName)
+ val elementName = styleableElement.name
+ val defaultStyleName = String.format(Locale.US, processor.defaultStyleNameFormat, elementName)
- val rStyleTypeElement = processor.memoizer.rStyleTypeElement
- val defaultStyleExists = rStyleTypeElement != null && elements.getAllMembers(rStyleTypeElement).any {
- it.simpleName.toString() == defaultStyleName
+ val rStyleTypeElement = processor.memoizer.rStyleTypeElementX
+ val defaultStyleExists = rStyleTypeElement != null && rStyleTypeElement.getDeclaredFields().any {
+ it.name == defaultStyleName
}
if (defaultStyleExists) {
- val styleResourceCode = JavaCodeBlock.of("\$T.\$L", rStyleTypeElement, defaultStyleName)
+ val styleResourceCode = JavaCodeBlock.of("\$T.\$L", rStyleTypeElement?.className, defaultStyleName)
val javadoc = JavaCodeBlock.of("See $defaultStyleName style (defined as an XML resource).")
val kdoc = KotlinCodeBlock.of(javadoc.toString())
@@ -132,7 +132,7 @@ internal class StyleInfoExtractor(override val processor: ParisProcessor) : With
}
internal interface StyleInfo {
- val enclosingElement: Element
+ val enclosingElement: XTypeElement
val elementName: String
val formattedName: String
@@ -142,7 +142,7 @@ internal interface StyleInfo {
val isDefault: Boolean
}
-class EmptyStyleInfo(override val enclosingElement: Element, override val isDefault: Boolean) : StyleInfo {
+class EmptyStyleInfo(override val enclosingElement: XTypeElement, override val isDefault: Boolean) : StyleInfo {
override val elementName = "empty_default"
override val formattedName = DEFAULT_STYLE_FORMATTED_NAME
@@ -151,7 +151,7 @@ class EmptyStyleInfo(override val enclosingElement: Element, override val isDefa
}
class StyleResInfo(
- override val enclosingElement: Element,
+ override val enclosingElement: XTypeElement,
override val elementName: String,
override val formattedName: String,
override val javadoc: JavaCodeBlock,
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleStaticMethodInfo.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleStaticMethodInfo.kt
index 3ed90645..6b3b6182 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleStaticMethodInfo.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleStaticMethodInfo.kt
@@ -1,44 +1,42 @@
package com.airbnb.paris.processor.models
+import androidx.room.compiler.processing.XMethodElement
import com.airbnb.paris.annotations.Style
import com.airbnb.paris.processor.ParisProcessor
import com.airbnb.paris.processor.framework.JavaCodeBlock
import com.airbnb.paris.processor.framework.KotlinCodeBlock
-import com.airbnb.paris.processor.framework.isNotStatic
-import com.airbnb.paris.processor.framework.isPrivate
-import com.airbnb.paris.processor.framework.isProtected
import com.airbnb.paris.processor.framework.models.SkyStaticMethodModel
import com.airbnb.paris.processor.framework.models.SkyStaticMethodModelFactory
+import com.airbnb.paris.processor.framework.toKPoet
import com.airbnb.paris.processor.utils.ParisProcessorUtils
-import javax.lang.model.element.ExecutableElement
-internal class StyleStaticMethodInfoExtractor(processor: ParisProcessor) :
- SkyStaticMethodModelFactory(processor, Style::class.java) {
+internal class StyleStaticMethodInfoExtractor(val parisProcessor: ParisProcessor) :
+ SkyStaticMethodModelFactory(parisProcessor, Style::class.java) {
- override fun elementToModel(element: ExecutableElement): StyleStaticMethodInfo? {
+ override fun elementToModel(element: XMethodElement): StyleStaticMethodInfo? {
// TODO Get Javadoc from field/method and add it to the generated methods
- if (element.isNotStatic() || element.isPrivate() || element.isProtected()) {
- logError(element) {
+ if (!element.isStatic() || element.isPrivate() || element.isProtected()) {
+ parisProcessor.logError(element) {
"Methods annotated with @Style must be static and can't be private or protected."
}
return null
}
- val style = element.getAnnotation(Style::class.java)
- val isDefault = style.isDefault
+ val style = element.getAnnotation(Style::class)
+ val isDefault = style!!.value.isDefault
val enclosingElement = element.enclosingElement
- val elementName = element.simpleName.toString()
+ val elementName = element.name
val formattedName = ParisProcessorUtils.reformatStyleFieldOrMethodName(elementName)
// TODO Check that the target type is a builder
- val targetType = element.parameters[0].asType()
+ val targetType = element.parameters[0].type.typeName
- val javadoc = JavaCodeBlock.of("@see \$T#\$N(\$T)\n", enclosingElement, elementName, targetType)
- val kdoc = KotlinCodeBlock.of("@see %T.%N\n", enclosingElement, elementName)
+ val javadoc = JavaCodeBlock.of("@see \$T#\$N(\$T)\n", enclosingElement.className, elementName, targetType)
+ val kdoc = KotlinCodeBlock.of("@see %T.%N\n", enclosingElement.className.toKPoet(), elementName)
return StyleStaticMethodInfo(
element,
@@ -52,7 +50,7 @@ internal class StyleStaticMethodInfoExtractor(processor: ParisProcessor) :
}
internal class StyleStaticMethodInfo(
- element: ExecutableElement,
+ element: XMethodElement,
override val elementName: String,
override val formattedName: String,
override val javadoc: JavaCodeBlock,
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleStaticPropertyInfo.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleStaticPropertyInfo.kt
new file mode 100644
index 00000000..282132d8
--- /dev/null
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleStaticPropertyInfo.kt
@@ -0,0 +1,114 @@
+package com.airbnb.paris.processor.models
+
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XFieldElement
+import androidx.room.compiler.processing.XMethodElement
+import androidx.room.compiler.processing.XProcessingEnv
+import androidx.room.compiler.processing.XTypeElement
+import androidx.room.compiler.processing.compat.XConverters.toJavac
+import androidx.room.compiler.processing.isInt
+import com.airbnb.paris.annotations.Style
+import com.airbnb.paris.processor.ParisProcessor
+import com.airbnb.paris.processor.framework.JavaCodeBlock
+import com.airbnb.paris.processor.framework.KotlinCodeBlock
+import com.airbnb.paris.processor.framework.models.SkyStaticPropertyModel
+import com.airbnb.paris.processor.framework.models.SkyStaticPropertyModelFactory
+import com.airbnb.paris.processor.framework.toKPoet
+import com.airbnb.paris.processor.utils.ParisProcessorUtils
+import com.airbnb.paris.processor.utils.enclosingElementIfCompanion
+import com.airbnb.paris.processor.utils.isErrorFixed
+
+internal class StyleStaticPropertyInfoExtractor(val parisProcessor: ParisProcessor) :
+ SkyStaticPropertyModelFactory(parisProcessor, Style::class.java) {
+
+ override fun filter(element: XElement): Boolean {
+ if ((element as? XFieldElement)?.isStatic() == false) {
+ // Style annotations must be only on static properties, but they are filtered out before they are processed,
+ // so in order to warn on this error we must do it here.
+ parisProcessor.logError(element) {
+ "Fields annotated with @Style must be static."
+ }
+ }
+ return super.filter(element)
+ }
+
+ override fun elementToModel(element: XElement): StyleStaticPropertyInfo? {
+ // TODO Get Javadoc from field/method and add it to the generated methods
+
+ val (type, elementName, enclosingElement) = when (element) {
+ is XFieldElement -> {
+ Triple(element.type, element.name, element.enclosingElement as XTypeElement)
+ }
+ is XMethodElement -> {
+ Triple(element.returnType, element.name, element.enclosingElement as XTypeElement)
+ }
+ else -> {
+ parisProcessor.logError(element) {
+ "Unsupported companion property element type $element is ${element.javaClass}"
+ }
+ return null
+ }
+ }
+
+ if (!type.isInt() && !parisProcessor.memoizer.styleClassTypeX.isAssignableFrom(type) && !type.isErrorFixed) {
+ // Note: if the type is non existent we ignore this error check so that users don't need to change their kapt configuration, they'll still
+ // get a build error though not as explicit.
+ parisProcessor.logError(element) {
+ "Fields annotated with @Style must implement com.airbnb.paris.styles.Style or be of type int (and refer to a style resource). Found type $type}"
+ }
+ return null
+ }
+
+ val style = element.getAnnotation(Style::class)!!.value
+ val isDefault = style.isDefault
+
+ val formattedName = ParisProcessorUtils.reformatStyleFieldOrMethodName(elementName)
+
+ val javadoc = JavaCodeBlock.of("@see \$T#\$N\n", enclosingElement.className, elementName)
+ val kdoc = KotlinCodeBlock.of("@see %T.%N\n", enclosingElement.enclosingElementIfCompanion.className.toKPoet(), elementName)
+
+ val propertyInfo = StyleStaticPropertyInfo(
+ env = parisProcessor.environment,
+ element = element,
+ elementName = elementName,
+ formattedName = formattedName,
+ javadoc = javadoc,
+ kdoc = kdoc,
+ isDefault = isDefault
+ )
+
+ when (val getterElement = propertyInfo.getterElement) {
+ is XFieldElement -> {
+ if (!getterElement.isFinal()) {
+ parisProcessor.logError(getterElement) {
+ "Fields annotated with @Style must be final."
+ }
+ return null
+ }
+
+ // skipping the private/protected check, as JvmStatic companion properties are private without an easy way to easy the original
+ // kotlin property visibility.
+ }
+ is XMethodElement -> {
+ if (getterElement.isPrivate() || getterElement.isProtected()) {
+ parisProcessor.logError(getterElement) {
+ "Fields annotated with @Style can't be private or protected."
+ }
+ return null
+ }
+ }
+ }
+
+ return propertyInfo
+ }
+}
+
+internal class StyleStaticPropertyInfo(
+ env: XProcessingEnv,
+ element: XElement,
+ override val elementName: String,
+ override val formattedName: String,
+ override val javadoc: JavaCodeBlock,
+ override val kdoc: KotlinCodeBlock,
+ override val isDefault: Boolean = false
+) : SkyStaticPropertyModel(element, env), StyleInfo
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleableChildInfo.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleableChildInfo.kt
index 1da98f80..6d1f5698 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleableChildInfo.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleableChildInfo.kt
@@ -1,33 +1,33 @@
package com.airbnb.paris.processor.models
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XProcessingEnv
+import androidx.room.compiler.processing.isMethod
import com.airbnb.paris.annotations.StyleableChild
import com.airbnb.paris.processor.ParisProcessor
-import com.airbnb.paris.processor.WithParisProcessor
import com.airbnb.paris.processor.android_resource_scanner.AndroidResourceId
-import com.airbnb.paris.processor.framework.isPrivate
-import com.airbnb.paris.processor.framework.isProtected
import com.airbnb.paris.processor.framework.models.SkyFieldModelFactory
import com.airbnb.paris.processor.framework.models.SkyPropertyModel
-import java.lang.annotation.AnnotationTypeMismatchException
-import javax.lang.model.element.Element
+import com.airbnb.paris.processor.utils.isFieldElement
// TODO Forward Javadoc to the generated functions/methods
internal class StyleableChildInfoExtractor(
- override val processor: ParisProcessor
-) : SkyFieldModelFactory(processor, StyleableChild::class.java), WithParisProcessor {
+ val parisProcessor: ParisProcessor
+) : SkyFieldModelFactory(parisProcessor, StyleableChild::class.java) {
/**
* @param element Represents a field annotated with @StyleableChild
*/
- override fun elementToModel(element: Element): StyleableChildInfo? {
- val attr = element.getAnnotation(StyleableChild::class.java)
+ override fun elementToModel(element: XElement): StyleableChildInfo? {
+
+ val attr = element.getAnnotation(StyleableChild::class)?.value ?: error("@StyleableChild not found on $element")
val styleableResId: AndroidResourceId
try {
- styleableResId = getResourceId(StyleableChild::class.java, element, attr.value) ?: return null
- } catch (e: AnnotationTypeMismatchException) {
- logError(element) {
- "Incorrectly typed @StyleableChild value parameter. (This usually happens when an R value doesn't exist.)"
+ styleableResId = parisProcessor.getResourceId(StyleableChild::class, element, attr.value) ?: return null
+ } catch (e: Throwable) {
+ parisProcessor.logError(element) {
+ "Incorrectly typed @StyleableChild value parameter. (This usually happens when an R value doesn't exist.) $e ${e.message}"
}
return null
}
@@ -35,9 +35,9 @@ internal class StyleableChildInfoExtractor(
var defaultValueResId: AndroidResourceId? = null
if (attr.defaultValue != -1) {
try {
- defaultValueResId = getResourceId(StyleableChild::class.java, element, attr.defaultValue) ?: return null
- } catch (e: AnnotationTypeMismatchException) {
- logError(element) {
+ defaultValueResId = parisProcessor.getResourceId(StyleableChild::class, element, attr.defaultValue) ?: return null
+ } catch (e: Throwable) {
+ parisProcessor.logError(element) {
"Incorrectly typed @StyleableChild defaultValue parameter. (This usually happens when an R value doesn't exist.)"
}
return null
@@ -47,11 +47,13 @@ internal class StyleableChildInfoExtractor(
val model = StyleableChildInfo(
element,
styleableResId,
- defaultValueResId
+ defaultValueResId,
+ parisProcessor.environment
)
- if (model.getterElement.isPrivate() || model.getterElement.isProtected()) {
- logError(element) {
+ val getter = model.getterElement
+ if (getter.isMethod() && getter.isPrivate() || getter.isFieldElement() && getter.isProtected()) {
+ parisProcessor.logError(element) {
"Fields and properties annotated with @StyleableChild can't be private or protected."
}
return null
@@ -62,7 +64,8 @@ internal class StyleableChildInfoExtractor(
}
internal class StyleableChildInfo(
- element: Element,
+ element: XElement,
val styleableResId: AndroidResourceId,
- val defaultValueResId: AndroidResourceId?
-) : SkyPropertyModel(element)
+ val defaultValueResId: AndroidResourceId?,
+ env: XProcessingEnv
+) : SkyPropertyModel(element, env)
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleableInfo.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleableInfo.kt
index 67208596..783e33d6 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleableInfo.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleableInfo.kt
@@ -1,54 +1,55 @@
package com.airbnb.paris.processor.models
+import androidx.room.compiler.processing.XRoundEnv
+import androidx.room.compiler.processing.XTypeElement
import com.airbnb.paris.annotations.Styleable
import com.airbnb.paris.processor.ParisProcessor
-import com.airbnb.paris.processor.framework.WithSkyProcessor
-import javax.annotation.processing.RoundEnvironment
-import javax.lang.model.element.Element
-import javax.lang.model.element.TypeElement
+import com.squareup.javapoet.ClassName
-internal class StyleableInfoExtractor(override val processor: ParisProcessor) : WithSkyProcessor {
+internal class StyleableInfoExtractor(val processor: ParisProcessor) {
private val mutableModels = mutableListOf()
val models get() = mutableModels.toList()
fun process(
- roundEnv: RoundEnvironment,
- classesToStyleableChildInfo: Map>,
- classesToBeforeStyleInfo: Map>,
- classesToAfterStyleInfo: Map>,
- classesToAttrsInfo: Map>,
- classesToStylesInfo: Map>
+ roundEnv: XRoundEnv,
+ classesToStyleableChildInfo: Map>,
+ classesToBeforeStyleInfo: Map>,
+ classesToAfterStyleInfo: Map>,
+ classesToAttrsInfo: Map>,
+ classesToStylesInfo: Map>
): List {
- val styleableElements = roundEnv.getElementsAnnotatedWith(Styleable::class.java)
+
+ val styleableElements = roundEnv.getElementsAnnotatedWith(Styleable::class).filterIsInstance()
val classesMissingStyleableAnnotation =
(classesToStyleableChildInfo + classesToAttrsInfo + classesToStylesInfo)
- .filter { (`class`, _) -> `class` !in styleableElements }
+ .filter { (clazz, _) -> clazz !in styleableElements }
.keys
- if (classesMissingStyleableAnnotation.isNotEmpty()) {
- logError(classesMissingStyleableAnnotation.first()) {
+
+ classesMissingStyleableAnnotation.forEach {
+ processor.logError(it) {
"Uses @Attr, @StyleableChild and/or @Style but is not annotated with @Styleable."
}
}
return styleableElements.mapNotNull {
fromElement(
- it as TypeElement,
- classesToStyleableChildInfo[it] ?: emptyList(),
- classesToBeforeStyleInfo[it] ?: emptyList(),
- classesToAfterStyleInfo[it] ?: emptyList(),
- classesToAttrsInfo[it] ?: emptyList(),
- classesToStylesInfo[it] ?: emptyList()
+ element = it,
+ styleableChildren = classesToStyleableChildInfo[it] ?: emptyList(),
+ beforeStyles = classesToBeforeStyleInfo[it] ?: emptyList(),
+ afterStyles = classesToAfterStyleInfo[it] ?: emptyList(),
+ attrs = classesToAttrsInfo[it] ?: emptyList(),
+ styles = classesToStylesInfo[it] ?: emptyList()
)
}.also {
- mutableModels.addAll(it)
- }
+ mutableModels.addAll(it)
+ }
}
private fun fromElement(
- element: TypeElement,
+ element: XTypeElement,
styleableChildren: List,
beforeStyles: List,
afterStyles: List,
@@ -59,27 +60,27 @@ internal class StyleableInfoExtractor(override val processor: ParisProcessor) :
val baseStyleableInfo = BaseStyleableInfoExtractor(processor).fromElement(element)
if (baseStyleableInfo.styleableResourceName.isEmpty() && (attrs.isNotEmpty() || styleableChildren.isNotEmpty())) {
- logError(element) {
+ processor.logError(element) {
"@Styleable is missing its value parameter (@Attr or @StyleableChild won't work otherwise)."
}
return null
}
if (baseStyleableInfo.styleableResourceName.isNotEmpty() && styleableChildren.isEmpty() && attrs.isEmpty()) {
- logWarning(element) {
+ processor.logWarning(element) {
"No need to specify the @Styleable value parameter if no class members are annotated with @Attr."
}
}
return StyleableInfo(
- processor,
- element,
- styleableChildren,
- beforeStyles,
- afterStyles,
- attrs,
- styles,
- baseStyleableInfo
+ processor = processor,
+ element = element,
+ styleableChildren = styleableChildren,
+ beforeStyles = beforeStyles,
+ afterStyles = afterStyles,
+ attrs = attrs,
+ styles = styles,
+ baseStyleableInfo = baseStyleableInfo
)
}
}
@@ -89,21 +90,21 @@ internal class StyleableInfoExtractor(override val processor: ParisProcessor) :
* empty either
*/
internal class StyleableInfo(
- override val processor: ParisProcessor,
- val element: TypeElement,
+ val processor: ParisProcessor,
+ val element: XTypeElement,
val styleableChildren: List,
val beforeStyles: List,
val afterStyles: List,
val attrs: List,
val styles: List,
baseStyleableInfo: BaseStyleableInfo
-) : BaseStyleableInfo(baseStyleableInfo), WithSkyProcessor {
+) : BaseStyleableInfo(baseStyleableInfo) {
/**
* A styleable declaration is guaranteed to be in the same R file as any attribute or styleable child.
* `min` is used to ensure in the case there are multiple R files, a consistent one is chosen.
*/
- val styleableRClassName = (attrs.map { it.styleableResId.rClassName } + styleableChildren.map { it.styleableResId.rClassName }).minOrNull()
+ val styleableRClassName: ClassName? = (attrs.map { it.styleableResId.rClassName } + styleableChildren.map { it.styleableResId.rClassName }).minOrNull()
/**
* Applies lower camel case formatting
@@ -111,7 +112,7 @@ internal class StyleableInfo(
fun attrResourceNameToCamelCase(name: String): String {
val prefix = "${styleableResourceName}_"
if (!name.startsWith(prefix)) {
- logError(element) {
+ processor.logError(element) {
"Attribute \"$name\" does not belong to styleable declaration \"$styleableResourceName\"."
}
}
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/utils/ParisProcessorUtils.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/utils/ParisProcessorUtils.kt
index 4c307d1c..56162213 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/utils/ParisProcessorUtils.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/utils/ParisProcessorUtils.kt
@@ -28,15 +28,15 @@ class ParisProcessorUtils {
acc
} else {
if (index == 0) {
- c.toUpperCase() + acc
+ c.uppercaseChar() + acc
} else if (name[index - 1] != '_') {
if (isNameAllCaps) {
- c.toLowerCase() + acc
+ c.lowercaseChar() + acc
} else {
c + acc
}
} else {
- c.toUpperCase() + acc
+ c.uppercaseChar() + acc
}
}
}
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/utils/XProcessingUtils.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/utils/XProcessingUtils.kt
new file mode 100644
index 00000000..b8d75211
--- /dev/null
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/utils/XProcessingUtils.kt
@@ -0,0 +1,185 @@
+package com.airbnb.paris.processor.utils
+
+import androidx.room.compiler.processing.XAnnotated
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XExecutableElement
+import androidx.room.compiler.processing.XFieldElement
+import androidx.room.compiler.processing.XMemberContainer
+import androidx.room.compiler.processing.XProcessingEnv
+import androidx.room.compiler.processing.XType
+import androidx.room.compiler.processing.XTypeElement
+import androidx.room.compiler.processing.addOriginatingElement
+import androidx.room.compiler.processing.compat.XConverters.toJavac
+import com.airbnb.paris.processor.android_resource_scanner.getFieldWithReflection
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.symbol.KSAnnotation
+import com.google.devtools.ksp.symbol.KSDeclaration
+import com.google.devtools.ksp.symbol.KSFile
+import com.google.devtools.ksp.symbol.KSNode
+import com.google.devtools.ksp.symbol.KSPropertyAccessor
+import com.google.devtools.ksp.symbol.KSPropertyDeclaration
+import com.google.devtools.ksp.symbol.Origin
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import com.squareup.kotlinpoet.OriginatingElementsHolder
+import javax.lang.model.element.Element
+import kotlin.contracts.contract
+
+fun XElement.isFieldElement(): Boolean {
+ contract {
+ returns(true) implies (this@isFieldElement is XFieldElement)
+ }
+ return this is XFieldElement
+}
+
+fun XElement.isExecutableElement(): Boolean {
+ contract {
+ returns(true) implies (this@isExecutableElement is XExecutableElement)
+ }
+ return this is XExecutableElement
+}
+
+fun XAnnotated.hasAnyAnnotationBySimpleName(annotationSimpleNames: Iterable): Boolean {
+ return getAllAnnotations().any { annotation -> annotationSimpleNames.any { it == annotation.name } }
+}
+
+private object BoxedTypeNames {
+ val BOXED_FLOAT = TypeName.FLOAT.box()
+ val BOXED_BOOLEAN = TypeName.BOOLEAN.box()
+}
+
+fun XType.isFloat(): Boolean = typeName == TypeName.FLOAT || typeName == BoxedTypeNames.BOXED_FLOAT
+
+fun XType.isBoolean(): Boolean = typeName == TypeName.BOOLEAN || typeName == BoxedTypeNames.BOXED_BOOLEAN
+
+fun XType.isSameTypeName(other: TypeName, useRawType: Boolean = false): Boolean {
+ return if (useRawType) {
+ typeName.rawTypeName() == other.rawTypeName()
+ } else {
+ typeName == other
+ }
+}
+
+internal fun TypeName.rawTypeName(): TypeName {
+ return if (this is ParameterizedTypeName) {
+ this.rawType
+ } else {
+ this
+ }
+}
+
+/**
+ * A bug in XProcessing throws an NPE if the package is not found. This is a workaround until the library is fixed.
+ * Fix merged in https://github.com/androidx/androidx/pull/222 but waiting on the next release.
+ */
+fun XProcessingEnv.getTypeElementsFromPackageSafe(packageName: String): List {
+ return try {
+ getTypeElementsFromPackage(packageName)
+ } catch (e: NullPointerException) {
+ emptyList()
+ }
+}
+
+val XElement.enclosingElementIfApplicable: XMemberContainer?
+ get() {
+ return when (this) {
+ is XExecutableElement -> enclosingElement
+ is XFieldElement -> enclosingElement
+ else -> null
+ }
+ }
+
+// The isError doesn't seem to appropriately detect a java type mirror of error.NonExistentClass for some reason.
+val XType.isErrorFixed: Boolean get() = isError() || toString() == "error.NonExistentClass"
+
+val XElement.isJavac: Boolean
+ get() = try {
+ this.toJavac()
+ true
+ } catch (e: Throwable) {
+ false
+ }
+
+val XProcessingEnv.resolver: Resolver
+ get() = getFieldWithReflection("resolver")
+
+val KSAnnotation.containingPackage: String?
+ get() = parent?.containingPackage
+
+val KSNode.containingPackage: String?
+ get() {
+ return when (this) {
+ is KSFile -> packageName.asString()
+ is KSDeclaration -> packageName.asString()
+ else -> parent?.containingPackage
+ }
+ }
+
+fun XFieldElement.javaGetterSyntax(env: XProcessingEnv): String {
+ val ksDeclaration = getFieldWithReflection("declaration")
+
+ return when (ksDeclaration.origin) {
+ // java to java interop references the field directly.
+ Origin.JAVA, Origin.JAVA_LIB -> name
+ Origin.KOTLIN, Origin.KOTLIN_LIB -> {
+ val accessor = ksDeclaration.getter ?: error("No getter found for $this $enclosingElement")
+ // Getter is a function call from java to kotlin
+ return env.resolver.getJvmName(accessor)?.plus("()") ?: error("Getter name not found for $this $enclosingElement")
+ }
+ Origin.SYNTHETIC -> error("Don't know how to get jvm name for element of synthetic origin $this $enclosingElement")
+ }
+}
+
+val XTypeElement.enclosingElementIfCompanion: XTypeElement
+ get() = if (isCompanionObject()) enclosingTypeElement!! else this
+
+// TODO: update xprocessing library to support KspSyntheticPropertyMethodElement, then delete this workaround.
+// fix will be in next version of xprocessing after alpha4
+fun > T.addOriginatingElementFixed(
+ element: XElement
+): T {
+ if (element.isJavac) {
+ addOriginatingElement(element)
+ return this
+ }
+
+ try {
+ element.getFieldWithReflection("accessor")
+ .receiver
+ .containingFile?.let { containingFile ->
+ val wrapperElement = Class.forName("androidx.room.compiler.processing.ksp.KSFileAsOriginatingElement")
+ .getConstructor(KSFile::class.java)
+ .newInstance(containingFile)
+
+ addOriginatingElement(wrapperElement as Element)
+ }
+ } catch (e: Throwable) {
+ addOriginatingElement(element)
+ }
+ return this
+}
+
+// TODO: update xprocessing library to support KspSyntheticPropertyMethodElement, then delete this workaround.
+// fix will be in next version of xprocessing after alpha4
+fun TypeSpec.Builder.addOriginatingElementFixed(element: XElement): TypeSpec.Builder {
+ if (element.isJavac) {
+ addOriginatingElement(element)
+ return this
+ }
+
+ try {
+ element.getFieldWithReflection("accessor")
+ .receiver
+ .containingFile?.let { containingFile ->
+ val wrapperElement = Class.forName("androidx.room.compiler.processing.ksp.KSFileAsOriginatingElement")
+ .getConstructor(KSFile::class.java)
+ .newInstance(containingFile)
+
+ addOriginatingElement(wrapperElement as Element)
+ }
+ } catch (e: Throwable) {
+ addOriginatingElement(element)
+ }
+ return this
+}
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/writers/BaseStyleBuilderJavaClass.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/writers/BaseStyleBuilderJavaClass.kt
index 7b18c528..b56360e4 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/writers/BaseStyleBuilderJavaClass.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/writers/BaseStyleBuilderJavaClass.kt
@@ -1,6 +1,8 @@
package com.airbnb.paris.processor.writers
import androidx.annotation.RequiresApi
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.addOriginatingElement
import com.airbnb.paris.processor.Format
import com.airbnb.paris.processor.ParisProcessor
import com.airbnb.paris.processor.STYLE_APPLIER_CLASS_NAME
@@ -10,7 +12,6 @@ import com.airbnb.paris.processor.STYLE_CLASS_NAME
import com.airbnb.paris.processor.StyleablesTree
import com.airbnb.paris.processor.framework.AndroidClassNames
import com.airbnb.paris.processor.framework.SkyJavaClass
-import com.airbnb.paris.processor.framework.WithSkyProcessor
import com.airbnb.paris.processor.framework.abstract
import com.airbnb.paris.processor.framework.constructor
import com.airbnb.paris.processor.framework.method
@@ -18,29 +19,28 @@ import com.airbnb.paris.processor.framework.public
import com.airbnb.paris.processor.framework.static
import com.airbnb.paris.processor.models.AttrInfo
import com.airbnb.paris.processor.models.StyleableInfo
+import com.airbnb.paris.processor.utils.addOriginatingElementFixed
import com.squareup.javapoet.AnnotationSpec
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.ParameterSpec
import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeSpec
import com.squareup.javapoet.TypeVariableName
import com.squareup.javapoet.WildcardTypeName
-import javax.lang.model.element.Element
internal class BaseStyleBuilderJavaClass(
- override val processor: ParisProcessor,
+ val parisProcessor: ParisProcessor,
parentStyleApplierClassName: ClassName?,
styleablesTree: StyleablesTree,
styleableInfo: StyleableInfo
-) : SkyJavaClass(processor), WithSkyProcessor {
+) : SkyJavaClass(parisProcessor) {
override val packageName: String
override val name: String
- override val originatingElements: List = listOfNotNull(
+ override val originatingElements: List = listOfNotNull(
styleableInfo.annotatedElement,
- processor.memoizer.rStyleTypeElement
+ parisProcessor.memoizer.rStyleTypeElementX
)
init {
@@ -143,9 +143,9 @@ internal class BaseStyleBuilderJavaClass(
}
val (subStyleApplierAnnotatedElement, subStyleApplierClassName) = styleablesTree.findStyleApplier(
- styleableChildInfo.type.asTypeElement()
+ styleableChildInfo.type.typeElement ?: error("${styleableChildInfo.type} does not have type element")
)
- addOriginatingElement(subStyleApplierAnnotatedElement)
+ addOriginatingElementFixed(subStyleApplierAnnotatedElement)
val subStyleBuilderClassName = subStyleApplierClassName.nestedClass("StyleBuilder")
method(methodName) {
@@ -181,7 +181,7 @@ internal class BaseStyleBuilderJavaClass(
val nonResTargetAttrs = groupedAttrs.filter { it.targetFormat != Format.RESOURCE_ID }
if (nonResTargetAttrs.isNotEmpty() && nonResTargetAttrs.distinctBy { it.targetType }.size > 1) {
- logError {
+ parisProcessor.logError {
"The same @Attr value can't be used on methods with different parameter types (excluding resource id types)"
}
}
@@ -200,7 +200,7 @@ internal class BaseStyleBuilderJavaClass(
addJavadoc(attr.javadoc)
val valueParameterBuilder =
- ParameterSpec.builder(TypeName.get(attr.targetType), "value")
+ ParameterSpec.builder(attr.targetType.typeName, "value")
attr.targetFormat.valueAnnotation?.let {
valueParameterBuilder.addAnnotation(it)
}
@@ -292,7 +292,7 @@ internal class BaseStyleBuilderJavaClass(
public()
addParameter(
ParameterSpec.builder(
- TypeName.get(styleableInfo.viewElementType),
+ styleableInfo.viewElementType.typeName,
"view"
).build()
)
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/writers/ModuleJavaClass.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/writers/ModuleJavaClass.kt
index 452da7f5..dc13c0cb 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/writers/ModuleJavaClass.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/writers/ModuleJavaClass.kt
@@ -1,5 +1,6 @@
package com.airbnb.paris.processor.writers
+import androidx.room.compiler.processing.XElement
import com.airbnb.paris.annotations.GeneratedStyleableClass
import com.airbnb.paris.annotations.GeneratedStyleableModule
import com.airbnb.paris.processor.MODULE_SIMPLE_CLASS_NAME_FORMAT
@@ -15,7 +16,6 @@ import com.squareup.javapoet.AnnotationSpec
import com.squareup.javapoet.TypeSpec
import java.math.BigInteger
import java.security.MessageDigest
-import javax.lang.model.element.Element
/**
* Module classes index the styleable views available in their module. Since they are all put in the
@@ -23,7 +23,7 @@ import javax.lang.model.element.Element
* dependencies through these classes
*/
internal class ModuleJavaClass(
- override val processor: ParisProcessor,
+ processor: ParisProcessor,
styleablesInfo: List
) : SkyJavaClass(processor) {
@@ -33,7 +33,7 @@ internal class ModuleJavaClass(
override val packageName = PARIS_MODULES_PACKAGE_NAME
override val name: String
- override val originatingElements: List = styleablesInfo.map { it.annotatedElement }
+ override val originatingElements: List = styleablesInfo.map { it.annotatedElement }
init {
// The class name is a hash of all the styleable views' canonical names so the likelihood of
@@ -56,7 +56,7 @@ internal class ModuleJavaClass(
add("{")
for (styleableInfo in sortedStyleablesInfo) {
add("\$L,", AnnotationSpec.builder(GeneratedStyleableClass::class.java).apply {
- value("\$T.class", styleableInfo.elementType)
+ value("\$T.class", styleableInfo.elementType.typeName)
}.build())
}
add("}")
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/writers/ParisJavaClass.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/writers/ParisJavaClass.kt
index 0b64269f..9abfa249 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/writers/ParisJavaClass.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/writers/ParisJavaClass.kt
@@ -1,5 +1,6 @@
package com.airbnb.paris.processor.writers
+import androidx.room.compiler.processing.XElement
import com.airbnb.paris.processor.PARIS_SIMPLE_CLASS_NAME
import com.airbnb.paris.processor.ParisProcessor
import com.airbnb.paris.processor.SPANNABLE_BUILDER_CLASS_NAME
@@ -11,12 +12,10 @@ import com.airbnb.paris.processor.framework.public
import com.airbnb.paris.processor.framework.static
import com.airbnb.paris.processor.models.BaseStyleableInfo
import com.airbnb.paris.processor.models.StyleableInfo
-import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeSpec
-import javax.lang.model.element.Element
internal class ParisJavaClass(
- override val processor: ParisProcessor,
+ processor: ParisProcessor,
parisClassPackageName: String,
styleableClassesInfo: List,
externalStyleableClassesInfo: List
@@ -28,7 +27,7 @@ internal class ParisJavaClass(
override val packageName: String = parisClassPackageName
override val name: String = PARIS_SIMPLE_CLASS_NAME
- override val originatingElements: List =
+ override val originatingElements: List =
sortedStyleableClassesInfo.map { it.annotatedElement }
override val block: TypeSpec.Builder.() -> Unit = {
@@ -37,7 +36,7 @@ internal class ParisJavaClass(
for (styleableClassInfo in sortedStyleableClassesInfo) {
val styleApplierClassName = styleableClassInfo.styleApplierClassName
- val viewParameterTypeName = TypeName.get(styleableClassInfo.viewElementType)
+ val viewParameterTypeName = styleableClassInfo.viewElementType.typeName
method("style") {
public()
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/writers/StyleApplierJavaClass.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/writers/StyleApplierJavaClass.kt
index 23db2f4d..7da4be26 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/writers/StyleApplierJavaClass.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/writers/StyleApplierJavaClass.kt
@@ -1,5 +1,7 @@
package com.airbnb.paris.processor.writers
+import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.addOriginatingElement
import com.airbnb.paris.processor.Format
import com.airbnb.paris.processor.ParisProcessor
import com.airbnb.paris.processor.STYLE_APPLIER_CLASS_NAME
@@ -7,7 +9,6 @@ import com.airbnb.paris.processor.STYLE_APPLIER_UTILS_CLASS_NAME
import com.airbnb.paris.processor.STYLE_CLASS_NAME
import com.airbnb.paris.processor.StyleablesTree
import com.airbnb.paris.processor.TYPED_ARRAY_WRAPPER_CLASS_NAME
-import com.airbnb.paris.processor.WithParisProcessor
import com.airbnb.paris.processor.framework.AndroidClassNames
import com.airbnb.paris.processor.framework.SkyJavaClass
import com.airbnb.paris.processor.framework.codeBlock
@@ -20,32 +21,31 @@ import com.airbnb.paris.processor.framework.protected
import com.airbnb.paris.processor.framework.public
import com.airbnb.paris.processor.framework.static
import com.airbnb.paris.processor.models.EmptyStyleInfo
-import com.airbnb.paris.processor.models.StyleCompanionPropertyInfo
import com.airbnb.paris.processor.models.StyleResInfo
import com.airbnb.paris.processor.models.StyleStaticMethodInfo
+import com.airbnb.paris.processor.models.StyleStaticPropertyInfo
import com.airbnb.paris.processor.models.StyleableInfo
+import com.airbnb.paris.processor.utils.addOriginatingElementFixed
import com.squareup.javapoet.ArrayTypeName
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.CodeBlock
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.ParameterizedTypeName
-import com.squareup.javapoet.TypeName
import com.squareup.javapoet.TypeSpec
-import javax.lang.model.element.Element
internal class StyleApplierJavaClass(
- override val processor: ParisProcessor,
+ val parisProcessor: ParisProcessor,
styleablesTree: StyleablesTree,
styleableInfo: StyleableInfo
-) : SkyJavaClass(processor), WithParisProcessor {
+) : SkyJavaClass(parisProcessor) {
override val packageName = styleableInfo.styleApplierClassName.packageName()!!
override val name = styleableInfo.styleApplierClassName.simpleName()!!
- override val originatingElements: List = listOfNotNull(
+ override val originatingElements: List = listOfNotNull(
styleableInfo.annotatedElement,
// The R.style class is used to look up matching default style names, so if
// the R class changes it can affect the code we generate.
- processor.memoizer.rStyleTypeElement
+ parisProcessor.memoizer.rStyleTypeElementX
)
override val block: TypeSpec.Builder.() -> Unit = {
@@ -55,31 +55,32 @@ internal class StyleApplierJavaClass(
superclass(
ParameterizedTypeName.get(
STYLE_APPLIER_CLASS_NAME,
- TypeName.get(styleableInfo.elementType),
- TypeName.get(styleableInfo.viewElementType)
+ styleableInfo.elementType.typeName,
+ styleableInfo.viewElementType.typeName
)
)
constructor {
public()
- addParameter(TypeName.get(styleableInfo.viewElementType), "view")
+ addParameter(styleableInfo.viewElementType.typeName, "view")
if (styleableInfo.elementType == styleableInfo.viewElementType) {
addStatement("super(view)")
} else {
// Different types means this style applier uses a proxy
- addStatement("super(new \$T(view))", styleableInfo.elementType)
+ addStatement("super(new \$T(view))", styleableInfo.elementType.typeName)
}
}
// If the view type is "View" then there is no parent
var parentStyleApplierClassName: ClassName? = null
- if (!isSameType(processor.memoizer.androidViewClassType, styleableInfo.viewElementType)) {
+ if (!parisProcessor.memoizer.androidViewClassTypeX.isSameType(styleableInfo.viewElementType)) {
val parentStyleApplierDetails = styleablesTree.findStyleApplier(
- styleableInfo.viewElementType.asTypeElement().superclass.asTypeElement()
+ styleableInfo.viewElementType.typeElement?.superType?.typeElement!!,
+ errorContext = {"Parent view: ${styleableInfo.viewElementType.typeElement?.qualifiedName}"}
)
parentStyleApplierClassName = parentStyleApplierDetails.className
- addOriginatingElement(parentStyleApplierDetails.annotatedElement)
+ addOriginatingElementFixed(parentStyleApplierDetails.annotatedElement)
method("applyParent") {
override()
@@ -100,7 +101,7 @@ internal class StyleApplierJavaClass(
override()
protected()
returns(ArrayTypeName.of(Integer.TYPE))
- addStatement("return \$T.styleable.\$L", styleableInfo.styleableRClassName ?: RElement, styleableInfo.styleableResourceName)
+ addStatement("return \$T.styleable.\$L", styleableInfo.styleableRClassName ?: parisProcessor.RElement?.className, styleableInfo.styleableResourceName)
}
val attrsWithDefaultValue = styleableInfo.attrs
@@ -225,14 +226,14 @@ internal class StyleApplierJavaClass(
addType(
BaseStyleBuilderJavaClass(
- processor,
+ parisProcessor,
parentStyleApplierClassName,
styleablesTree,
styleableInfo
).build()
)
val styleBuilderClassName = styleApplierClassName.nestedClass("StyleBuilder")
- addType(StyleBuilderJavaClass(processor, styleableInfo).build())
+ addType(StyleBuilderJavaClass(parisProcessor, styleableInfo).build())
// builder() method
method("builder") {
@@ -243,11 +244,11 @@ internal class StyleApplierJavaClass(
for (styleableChildInfo in styleableInfo.styleableChildren) {
val (subStyleApplierAnnotatedElement, subStyleApplierClassName) = styleablesTree.findStyleApplier(
- styleableChildInfo.type.asTypeElement()
+ styleableChildInfo.type.typeElement!!
)
// If the name of the proxy or subStyle type changes then our generated code needs to update as well,
// therefore we must depend on it as an originating element.
- addOriginatingElement(subStyleApplierAnnotatedElement)
+ addOriginatingElementFixed(subStyleApplierAnnotatedElement)
method(styleableChildInfo.name) {
public()
@@ -269,7 +270,7 @@ internal class StyleApplierJavaClass(
public()
when (styleInfo) {
- is StyleCompanionPropertyInfo -> addStatement("apply(\$T.\$L)", styleInfo.enclosingElement, styleInfo.javaGetter)
+ is StyleStaticPropertyInfo -> addStatement("apply(\$T.\$L)", styleInfo.enclosingElement.className, styleInfo.javaGetter)
is StyleStaticMethodInfo -> {
addStatement(
"\$T builder = new \$T()",
@@ -278,7 +279,7 @@ internal class StyleApplierJavaClass(
)
.addStatement(
"\$T.\$L(builder)",
- styleInfo.enclosingElement,
+ styleInfo.enclosingElement.className,
styleInfo.elementName
)
.addStatement("apply(builder.build())")
@@ -300,9 +301,9 @@ internal class StyleApplierJavaClass(
if (styleableInfo.styles.size > 1) {
addStatement(
"\$T \$T = new \$T(context)",
- styleableInfo.viewElementType,
- styleableInfo.viewElementType,
- styleableInfo.viewElementType
+ styleableInfo.viewElementType.typeName,
+ styleableInfo.viewElementType.typeName,
+ styleableInfo.viewElementType.typeName
)
val styleVarargCode = codeBlock {
@@ -321,7 +322,7 @@ internal class StyleApplierJavaClass(
"\$T.Companion.assertSameAttributes(new \$T(\$T), \$L);\n",
STYLE_APPLIER_UTILS_CLASS_NAME,
styleApplierClassName,
- styleableInfo.viewElementType,
+ styleableInfo.viewElementType.typeName,
styleVarargCode
)
addCode(assertEqualAttributesCode)
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/writers/StyleBuilderJavaClass.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/writers/StyleBuilderJavaClass.kt
index dfd9d34c..a2e6b571 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/writers/StyleBuilderJavaClass.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/writers/StyleBuilderJavaClass.kt
@@ -1,5 +1,6 @@
package com.airbnb.paris.processor.writers
+import androidx.room.compiler.processing.XElement
import com.airbnb.paris.processor.ParisProcessor
import com.airbnb.paris.processor.framework.AndroidClassNames
import com.airbnb.paris.processor.framework.SkyJavaClass
@@ -9,26 +10,25 @@ import com.airbnb.paris.processor.framework.method
import com.airbnb.paris.processor.framework.public
import com.airbnb.paris.processor.framework.static
import com.airbnb.paris.processor.models.EmptyStyleInfo
-import com.airbnb.paris.processor.models.StyleCompanionPropertyInfo
import com.airbnb.paris.processor.models.StyleResInfo
import com.airbnb.paris.processor.models.StyleStaticMethodInfo
+import com.airbnb.paris.processor.models.StyleStaticPropertyInfo
import com.airbnb.paris.processor.models.StyleableInfo
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeSpec
-import javax.lang.model.element.Element
internal fun getStyleBuilderClassName(styleApplierClassName: ClassName) =
styleApplierClassName.nestedClass("StyleBuilder")
internal class StyleBuilderJavaClass(
- override val processor: ParisProcessor,
+ processor: ParisProcessor,
styleableInfo: StyleableInfo
) : SkyJavaClass(processor) {
override val packageName: String
override val name: String
- override val originatingElements: List = listOf(styleableInfo.annotatedElement)
+ override val originatingElements: List = listOf(styleableInfo.annotatedElement)
init {
val className = getStyleBuilderClassName(styleableInfo.styleApplierClassName)
@@ -65,11 +65,11 @@ internal class StyleBuilderJavaClass(
returns(styleBuilderClassName)
when (it) {
- is StyleCompanionPropertyInfo -> addStatement("add(\$T.\$L)", it.enclosingElement, it.javaGetter)
+ is StyleStaticPropertyInfo -> addStatement("add(\$T.\$L)", it.enclosingElement.className, it.javaGetter)
is StyleStaticMethodInfo -> {
addStatement("consumeProgrammaticStyleBuilder()")
addStatement("debugName(\$S)", it.formattedName)
- addStatement("\$T.\$L(this)", it.enclosingElement, it.elementName)
+ addStatement("\$T.\$L(this)", it.enclosingElement.className, it.elementName)
addStatement("consumeProgrammaticStyleBuilder()")
}
is StyleResInfo -> addStatement("add(\$L)", it.styleResourceCode)
diff --git a/paris-processor/src/main/java/com/airbnb/paris/processor/writers/StyleExtensionsKotlinFile.kt b/paris-processor/src/main/java/com/airbnb/paris/processor/writers/StyleExtensionsKotlinFile.kt
index 60f75d85..d2b49e1c 100644
--- a/paris-processor/src/main/java/com/airbnb/paris/processor/writers/StyleExtensionsKotlinFile.kt
+++ b/paris-processor/src/main/java/com/airbnb/paris/processor/writers/StyleExtensionsKotlinFile.kt
@@ -1,6 +1,7 @@
package com.airbnb.paris.processor.writers
import androidx.annotation.RequiresApi
+import androidx.room.compiler.processing.addOriginatingElement
import com.airbnb.paris.processor.EXTENDABLE_STYLE_BUILDER_CLASS_NAME
import com.airbnb.paris.processor.EXTENSIONS_FILE_NAME_FORMAT
import com.airbnb.paris.processor.Format
@@ -13,6 +14,7 @@ import com.airbnb.paris.processor.framework.AndroidClassNames.COLOR_INT
import com.airbnb.paris.processor.framework.AndroidClassNames.STYLE_RES
import com.airbnb.paris.processor.models.AttrInfo
import com.airbnb.paris.processor.models.StyleableInfo
+import com.airbnb.paris.processor.utils.addOriginatingElementFixed
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
@@ -23,8 +25,6 @@ import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.UNIT
import com.squareup.kotlinpoet.WildcardTypeName
-import com.squareup.kotlinpoet.asClassName
-import com.squareup.kotlinpoet.asTypeName
/**
* This generates a kt file with extension functions to style a specific view.
@@ -41,7 +41,7 @@ import com.squareup.kotlinpoet.asTypeName
* different packages)
*/
internal class StyleExtensionsKotlinFile(
- override val processor: ParisProcessor,
+ processor: ParisProcessor,
styleable: StyleableInfo
) : SkyKotlinFile(processor) {
@@ -70,7 +70,7 @@ internal class StyleExtensionsKotlinFile(
receiver(styleable.viewElementType)
addParameter("style", STYLE_CLASS_NAME.toKPoet())
addStatement("%T(this).apply(style)", styleable.styleApplierClassName.toKPoet())
- addOriginatingElement(styleable.annotatedElement)
+ addOriginatingElementFixed(styleable.annotatedElement)
}
/*
@@ -84,7 +84,7 @@ internal class StyleExtensionsKotlinFile(
addAnnotation(STYLE_RES)
}
addStatement("%T(this).apply(styleRes)", styleable.styleApplierClassName.toKPoet())
- addOriginatingElement(styleable.annotatedElement)
+ addOriginatingElementFixed(styleable.annotatedElement)
}
/*
@@ -96,7 +96,7 @@ internal class StyleExtensionsKotlinFile(
receiver(styleable.viewElementType)
addParameter("attrs", ATTRIBUTE_SET.toKPoet().copy(nullable = true))
addStatement("%T(this).apply(attrs)", styleable.styleApplierClassName.toKPoet())
- addOriginatingElement(styleable.annotatedElement)
+ addOriginatingElementFixed(styleable.annotatedElement)
}
/*
@@ -109,10 +109,10 @@ internal class StyleExtensionsKotlinFile(
val viewTypeName: KotlinTypeName
if (styleable.viewElement.isFinal()) {
- viewTypeName = styleable.viewElement.asClassName()
+ viewTypeName = styleable.viewElement.type.typeNameKotlin()
} else {
// If the styleable class isn't final we use generics so that subclasses are able to override this extension function
- viewTypeName = KotlinTypeVariableName("V", styleable.viewElementType.asTypeName())
+ viewTypeName = KotlinTypeVariableName("V", styleable.viewElementType.typeNameKotlin())
addTypeVariable(viewTypeName)
}
@@ -136,7 +136,7 @@ internal class StyleExtensionsKotlinFile(
builderParameter
)
- addOriginatingElement(styleable.annotatedElement)
+ addOriginatingElementFixed(styleable.annotatedElement)
}
/*
@@ -151,7 +151,7 @@ internal class StyleExtensionsKotlinFile(
addKdoc(it.kdoc)
val extendableStyleBuilderTypeName = EXTENDABLE_STYLE_BUILDER_CLASS_NAME.toKPoet().parameterizedBy(
- styleable.viewElementType.asTypeName()
+ styleable.viewElementType.typeNameKotlin()
)
receiver(extendableStyleBuilderTypeName)
@@ -160,12 +160,12 @@ internal class StyleExtensionsKotlinFile(
styleable.styleBuilderClassName.toKPoet()
)
- addOriginatingElement(styleable.annotatedElement)
+ addOriginatingElementFixed(styleable.annotatedElement)
}
}
val extendableStyleBuilderTypeName = EXTENDABLE_STYLE_BUILDER_CLASS_NAME.toKPoet().parameterizedBy(
- WildcardTypeName.producerOf(styleable.viewElementType.asTypeName())
+ WildcardTypeName.producerOf(styleable.viewElementType.typeNameKotlin())
)
/*
@@ -193,8 +193,8 @@ internal class StyleExtensionsKotlinFile(
styleableChildInfo.styleableResId.kotlinCode
)
- addOriginatingElement(styleable.annotatedElement)
- addOriginatingElement(styleableChildInfo.element)
+ addOriginatingElementFixed(styleable.annotatedElement)
+ addOriginatingElementFixed(styleableChildInfo.element)
}
// Sub-styles can be style objects: "view.style { titleStyle(styleObject) }"
@@ -207,8 +207,8 @@ internal class StyleExtensionsKotlinFile(
styleable.styleableResourceName,
styleableChildInfo.styleableResId.kotlinCode
)
- addOriginatingElement(styleable.annotatedElement)
- addOriginatingElement(styleableChildInfo.element)
+ addOriginatingElementFixed(styleable.annotatedElement)
+ addOriginatingElementFixed(styleableChildInfo.element)
}
/*
@@ -223,7 +223,7 @@ internal class StyleExtensionsKotlinFile(
receiver(extendableStyleBuilderTypeName)
val subExtendableStyleBuilderTypeName = EXTENDABLE_STYLE_BUILDER_CLASS_NAME.toKPoet().parameterizedBy(
- styleableChildInfo.type.asTypeName()
+ styleableChildInfo.type.typeNameKotlin()
)
val builderParameter = parameter(
"init", LambdaTypeName.get(
@@ -239,8 +239,8 @@ internal class StyleExtensionsKotlinFile(
subExtendableStyleBuilderTypeName,
builderParameter
)
- addOriginatingElement(styleable.annotatedElement)
- addOriginatingElement(styleableChildInfo.element)
+ addOriginatingElementFixed(styleable.annotatedElement)
+ addOriginatingElementFixed(styleableChildInfo.element)
}
}
@@ -272,11 +272,11 @@ internal class StyleExtensionsKotlinFile(
addRequiresApiAnnotation(this, attr)
// TODO Make sure that this works correctly when the view code is in Kotlin and already using Kotlin types
- parameter("value", JavaTypeName.get(attr.targetType).toKPoet().copy(nullable = attr.targetFormat.isNullable)) {
+ parameter("value", attr.targetType.typeNameKotlin().copy(nullable = attr.targetFormat.isNullable)) {
// Filter out the Nullable annotation since we defer to idiomatic Kotlin by attaching
// the nullability to the type.
attr.targetFormat.valueAnnotation?.takeIf { it != AndroidClassNames.NULLABLE }?.let {
- addAnnotation(it)
+ addAnnotation(it.toKPoet())
}
}
@@ -287,8 +287,8 @@ internal class StyleExtensionsKotlinFile(
attr.styleableResId.kotlinCode
)
- addOriginatingElement(styleable.annotatedElement)
- addOriginatingElement(attr.element)
+ addOriginatingElementFixed(styleable.annotatedElement)
+ addOriginatingElementFixed(attr.element)
}
}
@@ -310,8 +310,8 @@ internal class StyleExtensionsKotlinFile(
attr.styleableResId.kotlinCode
)
- addOriginatingElement(styleable.annotatedElement)
- addOriginatingElement(attr.element)
+ addOriginatingElementFixed(styleable.annotatedElement)
+ addOriginatingElementFixed(attr.element)
}
// Adds a special Dp method that automatically converts a dp value to pixels for dimensions
@@ -337,8 +337,8 @@ internal class StyleExtensionsKotlinFile(
attr.styleableResId.kotlinCode
)
- addOriginatingElement(styleable.annotatedElement)
- addOriginatingElement(attr.element)
+ addOriginatingElementFixed(styleable.annotatedElement)
+ addOriginatingElementFixed(attr.element)
}
}
@@ -361,8 +361,8 @@ internal class StyleExtensionsKotlinFile(
attr.styleableResId.kotlinCode
)
- addOriginatingElement(styleable.annotatedElement)
- addOriginatingElement(attr.element)
+ addOriginatingElementFixed(styleable.annotatedElement)
+ addOriginatingElementFixed(attr.element)
}
}
}
@@ -377,7 +377,7 @@ internal class StyleExtensionsKotlinFile(
returns(STYLE_CLASS_NAME.toKPoet())
val extendableStyleBuilderTypeName = EXTENDABLE_STYLE_BUILDER_CLASS_NAME.toKPoet().parameterizedBy(
- styleable.viewElementType.asTypeName()
+ styleable.viewElementType.typeNameKotlin()
)
val builderParam = parameter(
@@ -394,7 +394,7 @@ internal class StyleExtensionsKotlinFile(
builderParam
)
- addOriginatingElement(styleable.annotatedElement)
+ addOriginatingElementFixed(styleable.annotatedElement)
}
}
diff --git a/paris-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/paris-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider
new file mode 100644
index 00000000..4ce631de
--- /dev/null
+++ b/paris-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider
@@ -0,0 +1 @@
+com.airbnb.paris.processor.ParisProcessorProvider
\ No newline at end of file
diff --git a/paris-processor/src/test/java/KspResourceScannerTest.kt b/paris-processor/src/test/java/KspResourceScannerTest.kt
new file mode 100644
index 00000000..72221821
--- /dev/null
+++ b/paris-processor/src/test/java/KspResourceScannerTest.kt
@@ -0,0 +1,45 @@
+import com.airbnb.paris.processor.android_resource_scanner.KspResourceScanner
+import org.junit.Test
+import strikt.api.expectThat
+import strikt.assertions.isEqualTo
+
+class KspResourceScannerTest {
+ @Test
+ fun findMatchingImportPackage_TypeAlias() {
+ val import = KspResourceScanner.findMatchingImportPackage(
+ importedNames = listOf("com.airbnb.paris.test.R2 as typeAliasedR"),
+ annotationReference = "typeAliasedR.layout.my_layout",
+ annotationReferencePrefix = "typeAliasedR",
+ packageName = "com.airbnb.paris"
+ )
+
+ expectThat(import.fullyQualifiedReference).isEqualTo("com.airbnb.paris.test.R2.layout.my_layout")
+ }
+
+ @Test
+ fun findMatchingImportPackage_TypeAliasDoesNotMatch() {
+ val import = KspResourceScanner.findMatchingImportPackage(
+ importedNames = listOf("com.airbnb.paris.test.R2 as typeAliasedR2"),
+ annotationReference = "typeAliasedR.layout.my_layout",
+ annotationReferencePrefix = "typeAliasedR",
+ packageName = "com.airbnb.paris"
+ )
+
+ // falls back to annotation reference, since import should not match
+ expectThat(import.fullyQualifiedReference).isEqualTo("typeAliasedR.layout.my_layout")
+ }
+
+ @Test
+ fun findMatchingImportPackage_fullyStaticImport() {
+ val import = KspResourceScanner.findMatchingImportPackage(
+ importedNames = listOf("com.airbnb.n2.comp.designsystem.hostdls.R2.styleable.n2_CarouselCheckedActionCard_n2_layoutStyle"),
+ annotationReference = "n2_CarouselCheckedActionCard_n2_layoutStyle",
+ annotationReferencePrefix = "n2_CarouselCheckedActionCard_n2_layoutStyle",
+ packageName = "com.airbnb.n2.comp.designsystem.hostdls"
+ )
+
+ // falls back to annotation reference, since import should not match
+ expectThat(import.fullyQualifiedReference).isEqualTo("com.airbnb.n2.comp.designsystem.hostdls.R2.styleable.n2_CarouselCheckedActionCard_n2_layoutStyle")
+ }
+
+}
\ No newline at end of file
diff --git a/paris-test-lib/build.gradle b/paris-test-lib/build.gradle
index 2c235b95..71d85807 100644
--- a/paris-test-lib/build.gradle
+++ b/paris-test-lib/build.gradle
@@ -10,12 +10,6 @@ android {
minSdkVersion rootProject.MIN_SDK_VERSION
targetSdkVersion rootProject.TARGET_SDK_VERSION
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-
- javaCompileOptions {
- annotationProcessorOptions {
- includeCompileClasspath false
- }
- }
}
compileOptions {
@@ -32,7 +26,6 @@ dependencies {
implementation project(':paris')
implementation deps.appcompat
- implementation deps.kotlin
kapt project(':paris-processor')
}
diff --git a/paris-test-lib/src/main/AndroidManifest.xml b/paris-test-lib/src/main/AndroidManifest.xml
index 4d8670c4..38d72213 100644
--- a/paris-test-lib/src/main/AndroidManifest.xml
+++ b/paris-test-lib/src/main/AndroidManifest.xml
@@ -1,3 +1 @@
-
+
diff --git a/paris-test/build.gradle b/paris-test/build.gradle
index 5e0ce091..d7aa6c4d 100644
--- a/paris-test/build.gradle
+++ b/paris-test/build.gradle
@@ -1,5 +1,3 @@
-import org.gradle.internal.jvm.Jvm
-
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
@@ -11,12 +9,6 @@ android {
minSdkVersion rootProject.MIN_SDK_VERSION
targetSdkVersion rootProject.TARGET_SDK_VERSION
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-
- javaCompileOptions {
- annotationProcessorOptions {
- includeCompileClasspath false
- }
- }
}
testOptions {
@@ -25,7 +17,6 @@ android {
}
}
-
compileOptions {
sourceCompatibility rootProject.JAVA_SOURCE_VERSION
targetCompatibility rootProject.JAVA_TARGET_VERSION
@@ -36,40 +27,32 @@ android {
}
}
+sourceCompatibility = rootProject.JAVA_SOURCE_VERSION
+targetCompatibility = rootProject.JAVA_TARGET_VERSION
+
dependencies {
implementation project(':paris')
implementation project(':paris-test-lib')
+ implementation "com.google.devtools.ksp:symbol-processing-api:$KSP_VERSION"
implementation deps.appcompat
- implementation deps.kotlin
kapt project(':paris-processor')
testImplementation project(':paris-processor')
- testImplementation files(getRuntimeJar())
- testImplementation files(Jvm.current().getToolsJar())
+ testImplementation files(rootProject.file("libs/rt.jar"))
+ testImplementation files(rootProject.file("libs/tools.jar"))
testImplementation deps.junit
testImplementation deps.testingCompile
+ testImplementation deps.kotlinCompileTesting
+ testImplementation "io.github.java-diff-utils:java-diff-utils:4.5"
+ testImplementation "io.strikt:strikt-core:0.31.0"
androidTestImplementation deps.espresso
}
-static def getRuntimeJar() {
- try {
- final File javaBase = new File(System.getProperty("java.home")).getCanonicalFile();
- File runtimeJar = new File(javaBase, "lib/rt.jar");
- if (runtimeJar.exists()) {
- return runtimeJar
- }
- runtimeJar = new File(javaBase, "jre/lib/rt.jar");
- return runtimeJar.exists() ? runtimeJar : null;
- } catch (IOException e) {
- throw new RuntimeException(e)
- }
-}
-
// Java files in the "resources" folder are not included in the build for some reason (seems like source files are skipped?)
// This files are needed to test the annotation processor, so we manually copy them into the build.
task('copyDebugTestResources', type: Copy) {
diff --git a/paris-test/src/main/java/com/airbnb/paris/test/PackageConfig.kt b/paris-test/src/main/java/com/airbnb/paris/test/PackageConfig.kt
new file mode 100644
index 00000000..b3daf2e1
--- /dev/null
+++ b/paris-test/src/main/java/com/airbnb/paris/test/PackageConfig.kt
@@ -0,0 +1,6 @@
+package com.airbnb.paris.test
+
+import com.airbnb.paris.annotations.ParisConfig
+
+@ParisConfig(rClass = R::class)
+class PackageConfig
\ No newline at end of file
diff --git a/paris-test/src/main/res/values/styles.xml b/paris-test/src/main/res/values/styles.xml
index 1183834f..c6649778 100644
--- a/paris-test/src/main/res/values/styles.xml
+++ b/paris-test/src/main/res/values/styles.xml
@@ -1,5 +1,5 @@
-
+