├── .github └── workflows │ └── build_test.yml ├── .gitignore ├── .idea └── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASING.md ├── blessedDeps.gradle ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libs ├── rt.jar └── tools.jar ├── paris-annotations ├── .gitignore ├── build.gradle ├── gradle.properties └── src │ └── main │ └── java │ └── com │ └── airbnb │ └── paris │ └── annotations │ ├── AfterStyle.kt │ ├── Attr.kt │ ├── BeforeStyle.kt │ ├── Fraction.kt │ ├── GeneratedStyleableClass.kt │ ├── GeneratedStyleableModule.kt │ ├── LayoutDimension.kt │ ├── ParisConfig.java │ ├── Style.kt │ ├── Styleable.kt │ └── StyleableChild.kt ├── paris-processor ├── .gitignore ├── build.gradle ├── gradle.properties └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── airbnb │ │ │ └── paris │ │ │ └── processor │ │ │ ├── BaseProcessor.kt │ │ │ ├── Config.kt │ │ │ ├── Format.kt │ │ │ ├── KspProcessorProvider.kt │ │ │ ├── ParisProcessor.kt │ │ │ ├── RFinder.kt │ │ │ ├── StyleablesTree.kt │ │ │ ├── Timer.kt │ │ │ ├── android_resource_scanner │ │ │ ├── AndroidResourceId.kt │ │ │ ├── JavacResourceScanner.kt │ │ │ ├── KspResourceScanner.kt │ │ │ └── ResourceScanner.kt │ │ │ ├── framework │ │ │ ├── AndroidClassNames.kt │ │ │ ├── JavaPoetExtensions.kt │ │ │ ├── JavaSkyMemoizer.kt │ │ │ ├── KotlinPoetConverters.kt │ │ │ ├── KotlinPoetExtensions.kt │ │ │ ├── Log.kt │ │ │ ├── Memoizer.kt │ │ │ ├── SkyExtensions.kt │ │ │ ├── SkyJavaClass.kt │ │ │ ├── SkyKotlinFile.kt │ │ │ └── models │ │ │ │ ├── SkyMethodModel.kt │ │ │ │ ├── SkyModel.kt │ │ │ │ ├── SkyPropertyModel.kt │ │ │ │ └── SkyStaticPropertyModel.kt │ │ │ ├── models │ │ │ ├── AfterStyleInfo.kt │ │ │ ├── AttrInfo.kt │ │ │ ├── BaseStyleableInfo.kt │ │ │ ├── BeforeStyleInfo.kt │ │ │ ├── StyleInfo.kt │ │ │ ├── StyleStaticMethodInfo.kt │ │ │ ├── StyleStaticPropertyInfo.kt │ │ │ ├── StyleableChildInfo.kt │ │ │ └── StyleableInfo.kt │ │ │ ├── utils │ │ │ ├── ParisProcessorUtils.kt │ │ │ └── XProcessingUtils.kt │ │ │ └── writers │ │ │ ├── BaseStyleBuilderJavaClass.kt │ │ │ ├── ModuleJavaClass.kt │ │ │ ├── ParisJavaClass.kt │ │ │ ├── StyleApplierJavaClass.kt │ │ │ ├── StyleBuilderJavaClass.kt │ │ │ └── StyleExtensionsKotlinFile.kt │ └── resources │ │ └── META-INF │ │ └── services │ │ ├── com.google.devtools.ksp.processing.SymbolProcessorProvider │ │ └── javax.annotation.processing.Processor │ └── test │ ├── java │ └── KspResourceScannerTest.kt │ └── kotlin │ └── com │ └── airbnb │ └── paris │ └── processor │ └── utils │ └── ParisProcessorUtilsTest.kt ├── paris-test-lib ├── .gitignore ├── README.md ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── res │ └── values │ └── attrs.xml ├── paris-test ├── .gitignore ├── build.gradle └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── airbnb │ │ └── paris │ │ └── test │ │ ├── StyleApplierUtilsTest.kt │ │ └── ViewStyleBuilderTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── airbnb │ │ │ └── paris │ │ │ └── test │ │ │ ├── MyOtherView.java │ │ │ ├── PackageConfig.kt │ │ │ └── R2.java │ └── res │ │ ├── color │ │ └── format_color_state_list.xml │ │ ├── drawable │ │ └── format_drawable.xml │ │ ├── font │ │ └── format_font.ttf │ │ └── values │ │ ├── attrs.xml │ │ ├── booleans.xml │ │ ├── formats.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ ├── java │ └── com │ │ └── airbnb │ │ └── paris │ │ └── test │ │ ├── CompilationMode.kt │ │ ├── ParisProcessorTest.kt │ │ └── ResourceTest.kt │ └── resources │ ├── at_style_style_field │ ├── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ └── output │ │ ├── MyViewStyleApplier.java │ │ └── Paris.java │ ├── attr_requires_api │ ├── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ └── output │ │ ├── MyViewStyleApplier.java │ │ └── Paris.java │ ├── attr_requires_api_default_value │ ├── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ └── output │ │ ├── MyViewStyleApplier.java │ │ └── Paris.java │ ├── attrs │ ├── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ └── output │ │ ├── MyViewStyleApplier.java │ │ └── Paris.java │ ├── attrs_r_class_import_as_type_alias │ └── input │ │ ├── MyView.kt │ │ └── PackageInfo.java │ ├── attrs_r_class_import_fully_qualified │ └── input │ │ ├── MyView.kt │ │ └── PackageInfo.java │ ├── default_values │ ├── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ └── output │ │ ├── MyViewStyleApplier.java │ │ └── Paris.java │ ├── empty_default_style │ ├── input │ │ ├── MyViewWithoutStyle.java │ │ └── PackageInfo.java │ └── output │ │ ├── MyViewWithoutStyleStyleApplier.java │ │ └── Paris.java │ ├── error_attr_non_res_default_value │ └── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ ├── error_attr_non_res_value │ └── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ ├── error_attr_wrong_default_value_type │ └── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ ├── error_attr_wrong_value_type │ └── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ ├── error_no_default_style │ └── input │ │ ├── MyViewWithoutStyle.java │ │ └── PackageInfo.java │ ├── error_non_final_style_field │ └── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ ├── error_non_static_style_field │ └── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ ├── error_private_style_field │ └── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ ├── error_style_field_invalid_type │ └── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ ├── error_styleable_child_wrong_value_type │ └── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ ├── error_styleable_outside_package_no_R │ └── input │ │ └── MyView.java │ ├── error_styleable_outside_package_with_attr_and_namespaced_resources │ └── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ ├── error_two_default_styles │ └── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ ├── overridden_protected_function │ └── input │ │ ├── MyView.kt │ │ ├── MyViewSuper.kt │ │ └── PackageInfo.java │ ├── style_extension_generation │ ├── input │ │ ├── MyView.kt │ │ └── PackageInfo.java │ └── output │ │ └── MyViewStyleExtensions.kt │ ├── style_extension_generation_java │ ├── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ └── output │ │ └── MyViewStyleExtensions.kt │ ├── style_extension_generation_jvm_static │ ├── input │ │ ├── MyView.kt │ │ └── PackageInfo.java │ └── output │ │ └── MyViewStyleExtensions.kt │ ├── style_in_kotlin_companion_object │ └── input │ │ ├── MyView.kt │ │ └── PackageInfo.java │ ├── styleable_fields │ ├── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ └── output │ │ ├── MyViewStyleApplier.java │ │ └── Paris.java │ ├── styleable_in_other_module_single_attr │ ├── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ └── output │ │ ├── MyViewStyleApplier.java │ │ └── Paris.java │ ├── styleable_minimal │ ├── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ └── output │ │ ├── MyViewStyleApplier.java │ │ └── Paris.java │ ├── styleable_outside_package_single_attr │ ├── input │ │ ├── MyView.java │ │ └── PackageInfo.java │ └── output │ │ ├── MyViewStyleApplier.java │ │ └── Paris.java │ └── styles │ ├── input │ ├── MyView.java │ └── PackageInfo.java │ └── output │ ├── MyViewStyleApplier.java │ └── Paris.java ├── paris ├── .gitignore ├── build.gradle ├── gradle.properties └── src │ ├── androidTest │ ├── java │ │ └── com │ │ │ └── airbnb │ │ │ └── paris │ │ │ ├── MapTypedArrayWrapperTest.kt │ │ │ ├── MultiTypedArrayWrapperTest.kt │ │ │ ├── StyleApplierTest.kt │ │ │ ├── StyleApplierUtilsTest.kt │ │ │ ├── proxies │ │ │ ├── BaseViewMappings.kt │ │ │ ├── DeprecatedTextViewProxyTest.kt │ │ │ ├── DeprecatedTextViewStyleApplierTest.kt │ │ │ ├── DeprecatedViewStyleApplierTest.kt │ │ │ ├── GenericViewProxyTests.kt │ │ │ ├── ImageViewMappings.kt │ │ │ ├── ImageViewStyleApplierTest.kt │ │ │ ├── ImageViewStyleApplier_StyleBuilderTest.kt │ │ │ ├── TextViewMappings.kt │ │ │ ├── TextViewStyleApplier_StyleBuilderTest.kt │ │ │ ├── ViewMappings.kt │ │ │ └── ViewStyleApplier_StyleBuilderTest.kt │ │ │ └── spannables │ │ │ ├── SpannableBuilderTest.kt │ │ │ └── StyleConverterTest.kt │ └── res │ │ ├── drawable │ │ ├── format_drawable.xml │ │ └── format_drawable_2.xml │ │ └── values │ │ ├── booleans.xml │ │ ├── formats.xml │ │ ├── styles.xml │ │ └── text_view_proxy_style_applier_text.xml │ ├── debug │ └── res │ │ ├── font │ │ └── roboto_regular.ttf │ │ └── values │ │ ├── test_common.xml │ │ ├── test_formats.xml │ │ ├── test_image_view_style_extensions.xml │ │ ├── test_text_view_style_applier.xml │ │ ├── test_text_view_style_extensions.xml │ │ ├── test_typed_array_typed_array_wrapper.xml │ │ ├── test_view_group_style_extensions.xml │ │ ├── test_view_style_builder.xml │ │ ├── test_view_style_extensions.xml │ │ ├── test_with_attr_function_view.xml │ │ ├── test_with_attr_property_setter_view.xml │ │ ├── test_with_internal_attr_function_view.xml │ │ ├── test_with_internal_attr_property_setter_view.xml │ │ ├── test_with_styleable_child_internal_view.xml │ │ ├── test_with_styleable_child_kotlin_view.xml │ │ ├── test_with_styleable_child_kotlin_view_style_extensions.xml │ │ ├── test_with_styleable_child_lateinit_view_style_extensions.xml │ │ ├── test_with_styleable_child_property_delegate_view.xml │ │ ├── test_with_styleable_child_property_delegate_view_style_extensions.xml │ │ ├── test_with_styleable_child_view.xml │ │ └── test_with_styleable_child_view_style_extensions.xml │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── airbnb │ │ │ └── paris │ │ │ ├── ExtendableStyleBuilder.kt │ │ │ ├── StyleApplier.kt │ │ │ ├── StyleApplierUtils.kt │ │ │ ├── StyleBuilder.kt │ │ │ ├── attribute_values │ │ │ ├── ColorValue.kt │ │ │ ├── DpValue.kt │ │ │ ├── ResourceId.kt │ │ │ └── Styles.kt │ │ │ ├── proxies │ │ │ ├── BaseProxy.kt │ │ │ ├── ImageViewProxy.kt │ │ │ ├── Proxy.kt │ │ │ ├── TextViewProxy.kt │ │ │ ├── ViewGroupProxy.kt │ │ │ └── ViewProxy.kt │ │ │ ├── spannables │ │ │ ├── SpannableBuilder.kt │ │ │ └── StyleConverter.kt │ │ │ ├── styles │ │ │ ├── AttributeSetStyle.kt │ │ │ ├── EmptyStyle.kt │ │ │ ├── MultiStyle.kt │ │ │ ├── ProgrammaticStyle.kt │ │ │ ├── ResourceStyle.kt │ │ │ └── Style.kt │ │ │ ├── typed_array_wrappers │ │ │ ├── EmptyTypedArrayWrapper.kt │ │ │ ├── MapTypedArrayWrapper.kt │ │ │ ├── MultiTypedArrayWrapper.kt │ │ │ ├── TypedArrayTypedArrayWrapper.kt │ │ │ └── TypedArrayWrapper.kt │ │ │ └── utils │ │ │ ├── ContextExtensions.kt │ │ │ ├── IntExtensions.kt │ │ │ ├── ResourcesExtensions.kt │ │ │ ├── StyleBuilderFunction.java │ │ │ └── ViewExtensions.kt │ └── res │ │ ├── anim │ │ └── null_.xml │ │ ├── color │ │ └── null_.xml │ │ ├── drawable │ │ └── null_.xml │ │ ├── font │ │ └── null_.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── ids.xml │ │ └── strings.xml │ └── testDebug │ ├── java │ └── com │ │ └── airbnb │ │ └── paris │ │ ├── StyleBuilderTest.kt │ │ ├── proxies │ │ ├── ImageViewStyleExtensionsTest.kt │ │ ├── TextViewProxyTest.kt │ │ ├── TextViewStyleApplierTest.kt │ │ ├── TextViewStyleBuilderTest.kt │ │ ├── TextViewStyleExtensionsTest.kt │ │ ├── ViewGroupProxyTest.kt │ │ ├── ViewGroupStyleApplierTest.kt │ │ ├── ViewGroupStyleBuilderTest.kt │ │ ├── ViewGroupStyleExtensionsTest.kt │ │ ├── ViewProxyTest.kt │ │ ├── ViewStyleApplierTest.kt │ │ ├── ViewStyleBuilderTest.kt │ │ └── ViewStyleExtensionsTest.kt │ │ ├── styles │ │ ├── MultiStyleTest.kt │ │ ├── ProgrammaticStyleTest.kt │ │ └── ResourceStyleTest.kt │ │ ├── typed_array_wrappers │ │ ├── EmptyTypedArrayWrapperSpec.kt │ │ ├── MapTypedArrayWrapperTest.kt │ │ ├── MultiTypedArrayWrapperTest.kt │ │ └── TypedArrayTypedArrayWrapperTest.kt │ │ ├── utils │ │ ├── ShadowResourcesCompat.kt │ │ └── TypefaceEquals.kt │ │ └── views │ │ ├── java │ │ ├── WithStyleFieldView.java │ │ ├── WithStyleFieldViewExtensionsTest.kt │ │ ├── WithStyleableChildExtensionsTest.kt │ │ └── WithStyleableChildView.java │ │ └── kotlin │ │ ├── WithAttrFunctionView.kt │ │ ├── WithAttrFunctionViewStyleExtensionsTest.kt │ │ ├── WithAttrPropertySetterView.kt │ │ ├── WithAttrPropertySetterViewStyleExtensionsTest.kt │ │ ├── WithInternalAttrFunctionView.kt │ │ ├── WithInternalAttrFunctionViewStyleExtensionsTest.kt │ │ ├── WithInternalAttrPropertySetterView.kt │ │ ├── WithInternalAttrPropertySetterViewStyleExtensionsTest.kt │ │ ├── WithJvmStaticStylePropertyView.kt │ │ ├── WithJvmStaticStylePropertyViewExtensionsTest.kt │ │ ├── WithStyleInternalPropertyView.kt │ │ ├── WithStyleInternalPropertyViewExtensionsTest.kt │ │ ├── WithStylePropertyView.kt │ │ ├── WithStylePropertyViewExtensionsTest.kt │ │ ├── WithStyleableChildInternalView.kt │ │ ├── WithStyleableChildInternalViewStyleExtensionsTest.kt │ │ ├── WithStyleableChildKotlinView.kt │ │ ├── WithStyleableChildKotlinViewStyleExtensionsTest.kt │ │ ├── WithStyleableChildLateinitView.kt │ │ ├── WithStyleableChildLateinitViewStyleExtensionsTest.kt │ │ ├── WithStyleableChildPropertyDelegateView.kt │ │ └── WithStyleableChildPropertyDelegateViewStyleExtensionsTest.kt │ └── resources │ └── com │ └── airbnb │ └── paris │ └── robolectric.properties ├── sample ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── airbnb │ │ └── paris │ │ └── sample │ │ ├── MainActivity.kt │ │ ├── SectionView.kt │ │ ├── SpannableActivity.java │ │ └── TextStyles.kt │ └── res │ ├── drawable-mdpi │ └── eiffel_header.jpg │ ├── drawable │ └── ic_order_selected_gray.xml │ ├── layout │ ├── activity_main.xml │ ├── activity_spannable.xml │ ├── view_marquee.xml │ └── view_section.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ ├── styles.xml │ └── styles_view_section.xml ├── settings.gradle └── update_processor_tests.rb /.github/workflows/build_test.yml: -------------------------------------------------------------------------------- 1 | name: Build/Test 2 | 3 | on: 4 | # Trigger on every pull request 5 | pull_request: 6 | jobs: 7 | build-test: 8 | runs-on: macos-11 9 | steps: 10 | - name: Checkout Paris 11 | uses: actions/checkout@v2 12 | - uses: actions/setup-java@v2 13 | name: Setting up Java 11 14 | with: 15 | distribution: liberica 16 | java-version: '11' 17 | - name: Build / Unit tests / Lint 18 | run: "./gradlew check --stacktrace" 19 | - name: Run UI Tests 20 | uses: reactivecircus/android-emulator-runner@v2 21 | with: 22 | emulator-build: 7425822 23 | api-level: 21 24 | target: default 25 | arch: x86_64 26 | emulator-options: -no-skin -no-window 27 | script: ./gradlew connectedDebugAndroidTest --stacktrace 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle files 2 | .gradle 3 | /build 4 | 5 | # Local configuration file (sdk path, etc) 6 | local.properties 7 | 8 | # IntelliJ 9 | *.iml 10 | .idea/* 11 | !.idea/codeStyles 12 | 13 | # Android Studio captures folder 14 | /captures 15 | 16 | # External native build folder generated in Android Studio 2.2 and later 17 | .externalNativeBuild 18 | 19 | # macOS 20 | .DS_Store 21 | 22 | # Vim 23 | *~ 24 | .swp 25 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for considering contributing to Paris! 4 | 5 | ## Testing 6 | 7 | Paris has an extensive suite of tests. Most of them are located in the `paris` module (for Paris classes) or in `paris-test` (for code generation). Due to the nature of this project a good share of the tests are integration tests and have to be run on an emulator or device. 8 | 9 | This command runs all the tests across modules: 10 | ``` 11 | ./gradlew test connectedAndroidTest 12 | ``` 13 | 14 | Please make sure that all tests are passing before proposing changes, and add or update tests whenever possible. 15 | 16 | If you update the annotation processor tests you may find the `update_processor_test_resources.rb` script very useful for updating the existing tests with your changes. 17 | 18 | ## How to Submit Changes 19 | 20 | First make sure all tests are passing, then create a pull request on Github. Explain why you are proposing the change, what issues it addresses, why you chose particular solutions, etc. The more context and details the easier it will be to review. 21 | 22 | When possible/appropriate changes should include tests and be well documented. 23 | 24 | ## How to Report a Bug or Request an Enhancement 25 | 26 | Create an issue on Github and tag it with `bug` or `enhancement` as appropriate. Please provide as much detail as possible. In the case of bugs that should include an easy way to reproduce the issue. 27 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | Releasing 2 | ======== 3 | 4 | 1. Bump the VERSION_NAME property in `gradle.properties` based on Major.Minor.Patch naming scheme 5 | 2. Update `CHANGELOG.md` for the impending release. 6 | 3. Update the `README.md` with the new version. 7 | 4. `git commit -am "Prepare for release X.Y.Z"` (where X.Y.Z is the version you set in step 1) 8 | 5. `git push` 9 | 6. Create a new release on Github 10 | 1. Tag version `vX.Y.Z` 11 | 2. Release title `vX.Y.Z` 12 | 3. Paste the content from `CHANGELOG.md` as the description 13 | 7. `./gradlew clean uploadArchives --no-daemon --no-parallel` 14 | 8. Visit [Sonatype Nexus](https://oss.sonatype.org/) and promote the artifact. 15 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | ANDROID_PLUGIN_VERSION = '7.4.0' 4 | BUTTERKNIFE_VERSION = '10.2.3' 5 | KOTLIN_VERSION = '1.8.10' 6 | // Run "./gradlew dependencyUpdates" to check for updates 7 | VERSIONS_VERSION = '0.42.0' 8 | KSP_VERSION = '1.8.10-1.0.9' 9 | } 10 | 11 | repositories { 12 | google() 13 | gradlePluginPortal() 14 | mavenCentral() 15 | maven { url "https://oss.sonatype.org/content/repositories/snapshots" } 16 | } 17 | 18 | dependencies { 19 | classpath "com.android.tools.build:gradle:$ANDROID_PLUGIN_VERSION" 20 | classpath "com.jakewharton:butterknife-gradle-plugin:$BUTTERKNIFE_VERSION" 21 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION" 22 | classpath "com.github.ben-manes:gradle-versions-plugin:$VERSIONS_VERSION" 23 | classpath 'com.vanniktech:gradle-maven-publish-plugin:0.14.2' 24 | // Dokka is needed on classpath for vanniktech publish plugin 25 | classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.0" 26 | } 27 | } 28 | 29 | plugins { 30 | id "com.google.devtools.ksp" version "$KSP_VERSION" 31 | } 32 | 33 | apply plugin: "com.github.ben-manes.versions" 34 | 35 | allprojects { 36 | repositories { 37 | google() 38 | mavenCentral() 39 | } 40 | 41 | configurations.all { 42 | exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jre7' 43 | } 44 | } 45 | 46 | subprojects { project -> 47 | apply from: "$rootDir/blessedDeps.gradle" 48 | } 49 | 50 | task clean(type: Delete) { 51 | delete rootProject.buildDir 52 | } 53 | 54 | dependencyUpdates.resolutionStrategy { 55 | componentSelection { rules -> 56 | rules.all { ComponentSelection selection -> 57 | boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'preview'].any { qualifier -> 58 | selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/ 59 | } 60 | if (rejected) { 61 | selection.reject('Release candidate') 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION_NAME=2.0.2 2 | GROUP=com.airbnb.android 3 | POM_DESCRIPTION=Paris is a system for creating and applying styles to views in Android. 4 | POM_URL=https://github.com/airbnb/paris 5 | POM_SCM_URL=https://github.com/airbnb/paris 6 | POM_SCM_CONNECTION=scm:git@github.com:airbnb/paris.git 7 | POM_SCM_DEV_CONNECTION=scm:git@github.com:airbnb/paris.git 8 | POM_LICENSE_NAME=Apache License 2.0 9 | POM_LICENSE_URL=https://github.com/airbnb/paris/blob/master/LICENSE 10 | POM_LICENSE_DIST=repo 11 | POM_DEVELOPER_ID=airbnb 12 | POM_DEVELOPER_NAME=Airbnb 13 | POM_DEVELOPER_EMAIL=android@airbnb.com 14 | POM_INCEPTION_YEAR=2017 15 | 16 | android.useAndroidX=true 17 | org.gradle.parallel=true 18 | org.gradle.incremental=true 19 | org.gradle.configureondemand=false 20 | kotlin.incremental=true 21 | kapt.includeCompileClasspath=false 22 | 23 | # Dokka fails without a larger metaspace https://github.com/Kotlin/dokka/issues/1405 24 | org.gradle.jvmargs=-XX:MaxMetaspaceSize=1g 25 | org.gradle.daemon=true 26 | #org.gradle.debug=true 27 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airbnb/paris/d8b5edbc56253bcdd0d0c57930d2e91113dd0f37/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jul 27 13:22:56 CDT 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip 7 | -------------------------------------------------------------------------------- /libs/rt.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airbnb/paris/d8b5edbc56253bcdd0d0c57930d2e91113dd0f37/libs/rt.jar -------------------------------------------------------------------------------- /libs/tools.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airbnb/paris/d8b5edbc56253bcdd0d0c57930d2e91113dd0f37/libs/tools.jar -------------------------------------------------------------------------------- /paris-annotations/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /paris-annotations/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'kotlin' 3 | apply plugin: "com.vanniktech.maven.publish" 4 | 5 | sourceCompatibility = rootProject.JAVA_SOURCE_VERSION 6 | targetCompatibility = rootProject.JAVA_TARGET_VERSION 7 | 8 | dependencies { 9 | } 10 | -------------------------------------------------------------------------------- /paris-annotations/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Paris annotations 2 | POM_ARTIFACT_ID=paris-annotations 3 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /paris-annotations/src/main/java/com/airbnb/paris/annotations/AfterStyle.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.annotations 2 | 3 | @Target(AnnotationTarget.FUNCTION) 4 | annotation class AfterStyle 5 | -------------------------------------------------------------------------------- /paris-annotations/src/main/java/com/airbnb/paris/annotations/Attr.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.annotations 2 | 3 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_SETTER) 4 | annotation class Attr( 5 | val value: Int, 6 | val defaultValue: Int = -1 7 | ) 8 | -------------------------------------------------------------------------------- /paris-annotations/src/main/java/com/airbnb/paris/annotations/BeforeStyle.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.annotations 2 | 3 | @Target(AnnotationTarget.FUNCTION) 4 | annotation class BeforeStyle 5 | -------------------------------------------------------------------------------- /paris-annotations/src/main/java/com/airbnb/paris/annotations/Fraction.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.annotations 2 | 3 | @Target(AnnotationTarget.VALUE_PARAMETER) 4 | annotation class Fraction( 5 | val base: Int = 1, 6 | val pbase: Int = 1 7 | ) 8 | -------------------------------------------------------------------------------- /paris-annotations/src/main/java/com/airbnb/paris/annotations/GeneratedStyleableClass.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.annotations 2 | 3 | import kotlin.reflect.KClass 4 | 5 | /** 6 | * DO NOT USE. This annotation is meant to be used by generated classes only 7 | */ 8 | @Target(AnnotationTarget.CLASS) 9 | annotation class GeneratedStyleableClass(val value: KClass<*>) 10 | -------------------------------------------------------------------------------- /paris-annotations/src/main/java/com/airbnb/paris/annotations/GeneratedStyleableModule.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.annotations 2 | 3 | /** 4 | * DO NOT USE. This annotation is meant to be used by generated classes only 5 | */ 6 | @Target(AnnotationTarget.CLASS) 7 | annotation class GeneratedStyleableModule(val value: Array) 8 | -------------------------------------------------------------------------------- /paris-annotations/src/main/java/com/airbnb/paris/annotations/LayoutDimension.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.annotations 2 | 3 | @Target(AnnotationTarget.VALUE_PARAMETER) 4 | annotation class LayoutDimension -------------------------------------------------------------------------------- /paris-annotations/src/main/java/com/airbnb/paris/annotations/ParisConfig.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Place this annotation on a single class or interface within your module to specify configuration options for that module. 10 | */ 11 | @Retention(RetentionPolicy.CLASS) 12 | @Target(ElementType.TYPE) 13 | public @interface ParisConfig { 14 | 15 | String defaultStyleNameFormat() default ""; 16 | 17 | Class rClass() default Void.class; 18 | 19 | /** 20 | * This is an experimental gradle flag (android.namespacedRClass=true). Setting to true allows Paris to generate code compatible with R files that 21 | * only have resources from the module the resource was declared in. 22 | */ 23 | boolean namespacedResourcesEnabled() default false; 24 | 25 | /** 26 | * By default no Paris class is generated if a module contains no @Styleable classes. 27 | * However, if this is set to true a Paris class will still be generated in that case, using only 28 | * the @Styleables that are discovered on the class path. 29 | */ 30 | boolean aggregateStyleablesOnClassPath() default false; 31 | } 32 | -------------------------------------------------------------------------------- /paris-annotations/src/main/java/com/airbnb/paris/annotations/Style.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.annotations 2 | 3 | import kotlin.annotation.AnnotationTarget.FIELD 4 | import kotlin.annotation.AnnotationTarget.FUNCTION 5 | 6 | @Target(FIELD, FUNCTION) 7 | annotation class Style(val isDefault: Boolean = false) 8 | -------------------------------------------------------------------------------- /paris-annotations/src/main/java/com/airbnb/paris/annotations/Styleable.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.annotations 2 | 3 | /** 4 | * @param value The name of the styleable resource. 5 | * @param emptyDefaultStyle Set to true if the view does not have a default style. 6 | * Will only be used if [ParisConfig.namespacedResourcesEnabled] is true. Default: false. 7 | */ 8 | @Target(AnnotationTarget.CLASS) 9 | annotation class Styleable(val value: String = "", val emptyDefaultStyle: Boolean = false) 10 | -------------------------------------------------------------------------------- /paris-annotations/src/main/java/com/airbnb/paris/annotations/StyleableChild.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.annotations 2 | 3 | @Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY) 4 | annotation class StyleableChild( 5 | val value: Int, 6 | val defaultValue: Int = -1 7 | ) 8 | -------------------------------------------------------------------------------- /paris-processor/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /paris-processor/build.gradle: -------------------------------------------------------------------------------- 1 | import org.gradle.internal.jvm.Jvm 2 | 3 | apply plugin: 'java' 4 | apply plugin: 'kotlin' 5 | apply plugin: 'kotlin-kapt' 6 | apply plugin: "com.vanniktech.maven.publish" 7 | 8 | sourceCompatibility = rootProject.JAVA_SOURCE_VERSION 9 | targetCompatibility = rootProject.JAVA_TARGET_VERSION 10 | 11 | dependencies { 12 | implementation project(':paris-annotations') 13 | implementation deps.ksp 14 | implementation deps.kspImpl 15 | implementation deps.xProcessing 16 | // Compiler needed to resolve resource references in annotations 17 | implementation "org.jetbrains.kotlin:kotlin-compiler-embeddable:$KOTLIN_VERSION" 18 | 19 | implementation deps.androidAnnotations 20 | 21 | compileOnly deps.incapRuntime 22 | kapt deps.incapProcessor 23 | 24 | compileOnly files(rootProject.file("libs/tools.jar")) 25 | 26 | testImplementation deps.xProcessingTesting 27 | testImplementation deps.junit 28 | testImplementation "io.strikt:strikt-core:0.34.1" 29 | testImplementation deps.kotlinTest 30 | } 31 | 32 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) { 33 | kotlinOptions { 34 | jvmTarget = "1.8" 35 | freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" 36 | freeCompilerArgs += "-Xopt-in=kotlin.contracts.ExperimentalContracts" 37 | freeCompilerArgs += "-Xopt-in=androidx.room.compiler.processing.ExperimentalProcessingApi" 38 | freeCompilerArgs += "-Xopt-in=com.google.devtools.ksp.KspExperimental" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /paris-processor/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Paris processor 2 | POM_ARTIFACT_ID=paris-processor 3 | POM_PACKAGING=jar -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/Config.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor 2 | 3 | import com.airbnb.paris.processor.framework.className 4 | 5 | internal const val PARIS_PACKAGE_NAME = "com.airbnb.paris" 6 | internal const val PARIS_MODULES_PACKAGE_NAME = "com.airbnb.paris.modules" 7 | internal const val PARIS_KOTLIN_EXTENSIONS_PACKAGE_NAME = "com.airbnb.paris.extensions" 8 | 9 | internal const val PARIS_SIMPLE_CLASS_NAME = "Paris" 10 | internal const val STYLE_APPLIER_SIMPLE_CLASS_NAME_FORMAT = "%sStyleApplier" 11 | internal const val MODULE_SIMPLE_CLASS_NAME_FORMAT = "GeneratedModule_%s" 12 | internal const val EXTENSIONS_FILE_NAME_FORMAT = "%sStyleExtensions" 13 | 14 | internal val STYLE_CLASS_NAME = "$PARIS_PACKAGE_NAME.styles.Style".className() 15 | internal val STYLE_APPLIER_CLASS_NAME = "$PARIS_PACKAGE_NAME.StyleApplier".className() 16 | internal val STYLE_BUILDER_CLASS_NAME = "$PARIS_PACKAGE_NAME.StyleBuilder".className() 17 | internal val EXTENDABLE_STYLE_BUILDER_CLASS_NAME = "$PARIS_PACKAGE_NAME.ExtendableStyleBuilder".className() 18 | internal val STYLE_APPLIER_UTILS_CLASS_NAME = "$PARIS_PACKAGE_NAME.StyleApplierUtils".className() 19 | internal val TYPED_ARRAY_WRAPPER_CLASS_NAME = "$PARIS_PACKAGE_NAME.typed_array_wrappers.TypedArrayWrapper".className() 20 | internal val STYLE_BUILDER_FUNCTION_CLASS_NAME = "$PARIS_PACKAGE_NAME.utils.StyleBuilderFunction".className() 21 | internal val CONTEXT_EXTENSIONS_CLASS_NAME = "$PARIS_PACKAGE_NAME.utils.ContextExtensionsKt".className() 22 | internal val RESOURCES_EXTENSIONS_CLASS_NAME = "$PARIS_PACKAGE_NAME.utils.ResourcesExtensionsKt".className() 23 | internal val SPANNABLE_BUILDER_CLASS_NAME = "$PARIS_PACKAGE_NAME.spannables.SpannableBuilder".className() 24 | internal val PROXY_CLASS_NAME = "$PARIS_PACKAGE_NAME.proxies.Proxy".className() 25 | -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/KspProcessorProvider.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor 2 | 3 | import com.google.devtools.ksp.processing.SymbolProcessor 4 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment 5 | import com.google.devtools.ksp.processing.SymbolProcessorProvider 6 | 7 | class ParisProcessorProvider : SymbolProcessorProvider { 8 | override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { 9 | return ParisProcessor(environment) 10 | } 11 | } -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/StyleablesTree.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor 2 | 3 | import androidx.room.compiler.processing.XElement 4 | import androidx.room.compiler.processing.XTypeElement 5 | import com.airbnb.paris.processor.models.BaseStyleableInfo 6 | import com.squareup.javapoet.ClassName 7 | 8 | internal class StyleablesTree( 9 | val processor: ParisProcessor, 10 | private val styleablesInfo: List 11 | ) { 12 | 13 | // This is a map of the View class qualified name to the StyleApplier class details 14 | // eg. "android.view.View" -> "com.airbnb.paris.ViewStyleApplier".className() 15 | private val viewQualifiedNameToStyleApplierClassName = mutableMapOf() 16 | 17 | /** 18 | * Traverses the class hierarchy of the given View type to find and return the first 19 | * corresponding style applier 20 | */ 21 | internal fun findStyleApplier(viewTypeElement: XTypeElement, errorContext: (() -> String)? = null): StyleApplierDetails { 22 | return findStyleApplierRecursive(viewTypeElement) 23 | ?: error("Could not find style applier for ${viewTypeElement.qualifiedName} ${viewTypeElement.type}. " + 24 | errorContext?.invoke()?.let { "$it. " }.orEmpty() + 25 | "Available types are ${styleablesInfo.map { it.viewElementType }}") 26 | } 27 | 28 | private fun findStyleApplierRecursive(viewTypeElement: XTypeElement): StyleApplierDetails? { 29 | return viewQualifiedNameToStyleApplierClassName.getOrPut(viewTypeElement) { 30 | 31 | val type = viewTypeElement.type 32 | // Check to see if the view type is handled by a styleable class 33 | val styleableInfo = styleablesInfo.find { type.isSameType(it.viewElementType) } 34 | if (styleableInfo != null) { 35 | StyleApplierDetails( 36 | annotatedElement = styleableInfo.annotatedElement, 37 | className = styleableInfo.styleApplierClassName 38 | ) 39 | } else { 40 | val superType = viewTypeElement.superType?.typeElement ?: return@getOrPut null 41 | findStyleApplier(superType) 42 | } 43 | } 44 | } 45 | } 46 | 47 | data class StyleApplierDetails( 48 | val annotatedElement: XElement, 49 | val className: ClassName 50 | ) 51 | -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/Timer.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor 2 | 3 | import androidx.room.compiler.processing.XMessager 4 | import javax.tools.Diagnostic 5 | import kotlin.math.pow 6 | import kotlin.math.roundToInt 7 | 8 | class Timer(val name: String) { 9 | private val timingSteps = mutableListOf() 10 | private var startNanos: Long? = null 11 | private var lastTimingNanos: Long? = null 12 | 13 | 14 | fun start() { 15 | timingSteps.clear() 16 | startNanos = System.nanoTime() 17 | lastTimingNanos = startNanos 18 | } 19 | 20 | fun markStepCompleted(stepDescription: String) { 21 | val nowNanos = System.nanoTime() 22 | val lastNanos = lastTimingNanos ?: error("Timer was not started") 23 | lastTimingNanos = nowNanos 24 | 25 | timingSteps.add(TimingStep(nowNanos - lastNanos, stepDescription)) 26 | } 27 | 28 | fun finishAndPrint(messager: XMessager) { 29 | val start = startNanos ?: error("Timer was not started") 30 | val message = buildString { 31 | appendLine("$name finished in ${formatNanos(System.nanoTime() - start)}") 32 | timingSteps.forEach { step -> 33 | appendLine(" - ${step.description} (${formatNanos(step.durationNanos)})") 34 | } 35 | } 36 | 37 | messager.printMessage(Diagnostic.Kind.NOTE, message) 38 | } 39 | 40 | private class TimingStep(val durationNanos: Long, val description: String) 41 | 42 | private fun formatNanos(nanos: Long): String { 43 | val diffMs = nanos.div(1_000_000.0).roundTo(3) 44 | return "$diffMs ms" 45 | } 46 | 47 | private fun Double.roundTo(numFractionDigits: Int): Double { 48 | val factor = 10.0.pow(numFractionDigits.toDouble()) 49 | return (this * factor).roundToInt() / factor 50 | } 51 | } -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/AndroidResourceId.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor.android_resource_scanner 2 | 3 | import com.airbnb.paris.processor.framework.AndroidClassNames 4 | import com.airbnb.paris.processor.framework.JavaCodeBlock 5 | import com.airbnb.paris.processor.framework.KotlinCodeBlock 6 | import com.airbnb.paris.processor.framework.toKPoet 7 | import com.squareup.javapoet.ClassName 8 | 9 | /** 10 | * @param className Like com.example.R.styleable 11 | * @param resourceName Like title_view 12 | */ 13 | class AndroidResourceId(val value: Int, val className: ClassName, val resourceName: String) { 14 | 15 | val rClassName: ClassName = className.topLevelClassName() 16 | 17 | val code: JavaCodeBlock = if (rClassName == AndroidClassNames.R) { 18 | JavaCodeBlock.of("\$L.\$N", className, resourceName) 19 | } else { 20 | JavaCodeBlock.of("\$T.\$N", className, resourceName) 21 | } 22 | 23 | val kotlinCode: KotlinCodeBlock = if (rClassName == AndroidClassNames.R) { 24 | KotlinCodeBlock.of("%L.%N", className.toKPoet(), resourceName) 25 | } else { 26 | KotlinCodeBlock.of("%T.%N", className.toKPoet(), resourceName) 27 | } 28 | 29 | override fun equals(other: Any?): Boolean { 30 | return other is AndroidResourceId && value == other.value 31 | } 32 | 33 | override fun hashCode(): Int { 34 | return value 35 | } 36 | 37 | override fun toString(): String { 38 | throw UnsupportedOperationException("Please use value or code explicitly") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/android_resource_scanner/ResourceScanner.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor.android_resource_scanner 2 | 3 | import androidx.room.compiler.processing.XElement 4 | import kotlin.reflect.KClass 5 | 6 | interface ResourceScanner { 7 | /** 8 | * Returns the [AndroidResourceId] that is used as an annotation value of the given [XElement] 9 | */ 10 | fun getId( 11 | annotation: KClass, 12 | element: XElement, 13 | value: Int 14 | ): AndroidResourceId? 15 | } -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/framework/AndroidClassNames.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor.framework 2 | 3 | internal object AndroidClassNames { 4 | 5 | val ATTRIBUTE_SET = "android.util.AttributeSet".className() 6 | val BUILD = "android.os.Build".className() 7 | val R = "android.R".className() 8 | val CONTEXT = "android.content.Context".className() 9 | val RESOURCES = "android.content.res.Resources".className() 10 | val VIEW = "android.view.View".className() 11 | val ANY_RES = "androidx.annotation.AnyRes".className() 12 | val ARRAY_RES = "androidx.annotation.ArrayRes".className() 13 | val BOOL_RES = "androidx.annotation.BoolRes".className() 14 | val COLOR_INT = "androidx.annotation.ColorInt".className() 15 | val COLOR_RES = "androidx.annotation.ColorRes".className() 16 | val DIMEN_RES = "androidx.annotation.DimenRes".className() 17 | val DIMENSION = "androidx.annotation.Dimension".className() 18 | val DRAWABLE_RES = "androidx.annotation.DrawableRes".className() 19 | val FONT_RES = "androidx.annotation.FontRes".className() 20 | val FRACTION_RES = "androidx.annotation.FractionRes".className() 21 | val INTEGER_RES = "androidx.annotation.IntegerRes".className() 22 | val NULLABLE = "androidx.annotation.Nullable".className() 23 | val PX = "androidx.annotation.Px".className() 24 | val STRING_RES = "androidx.annotation.StringRes".className() 25 | val STYLE_RES = "androidx.annotation.StyleRes".className() 26 | val UI_THREAD = "androidx.annotation.UiThread".className() 27 | } 28 | -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/framework/JavaSkyMemoizer.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor.framework 2 | 3 | import androidx.room.compiler.processing.XType 4 | import com.airbnb.paris.processor.BaseProcessor 5 | 6 | open class JavaSkyMemoizer(val processor: BaseProcessor) { 7 | 8 | val androidViewClassTypeX: XType by lazy { 9 | processor.environment.requireType(AndroidClassNames.VIEW) 10 | } 11 | } -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/framework/Log.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor.framework 2 | 3 | import androidx.room.compiler.processing.XElement 4 | 5 | 6 | class Message(val severity: Severity, val message: String, val element: XElement?) { 7 | 8 | enum class Severity { 9 | Note, Warning, Error 10 | } 11 | } 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/framework/Memoizer.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor.framework 2 | 3 | import androidx.room.compiler.processing.XRawType 4 | import androidx.room.compiler.processing.XType 5 | import androidx.room.compiler.processing.XTypeElement 6 | import com.airbnb.paris.processor.PROXY_CLASS_NAME 7 | import com.airbnb.paris.processor.ParisProcessor 8 | import com.airbnb.paris.processor.STYLE_CLASS_NAME 9 | 10 | class Memoizer(processor: ParisProcessor) : JavaSkyMemoizer(processor) { 11 | 12 | val proxyClassType: XType by lazy { processor.environment.requireType(PROXY_CLASS_NAME) } 13 | 14 | val styleClassTypeX: XType by lazy { processor.environment.requireType(STYLE_CLASS_NAME) } 15 | 16 | val rStyleTypeElementX: XTypeElement? by lazy { 17 | val rElement = processor.RElement ?: error("R Class not found") 18 | processor.environment.findType("${rElement.qualifiedName}.style")?.typeElement 19 | } 20 | } -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyJavaClass.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor.framework 2 | 3 | import androidx.room.compiler.processing.XElement 4 | import androidx.room.compiler.processing.XFiler 5 | import androidx.room.compiler.processing.addOriginatingElement 6 | import com.airbnb.paris.processor.BaseProcessor 7 | import com.squareup.javapoet.JavaFile 8 | import com.squareup.javapoet.TypeSpec 9 | 10 | internal abstract class SkyJavaClass(val processor: BaseProcessor) { 11 | 12 | protected abstract val packageName: String 13 | protected abstract val name: String 14 | protected abstract val block: TypeSpec.Builder.() -> Unit 15 | protected abstract val originatingElements: List 16 | 17 | fun build(): TypeSpec { 18 | val builder = TypeSpec.classBuilder(name) 19 | originatingElements.forEach { 20 | builder.addOriginatingElement(it) 21 | } 22 | builder.block() 23 | return builder.build() 24 | } 25 | 26 | fun write(mode: XFiler.Mode = XFiler.Mode.Aggregating) { 27 | val javaFile = JavaFile.builder(packageName, build()).build() 28 | processor.filer.write(javaFile, mode) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/framework/SkyKotlinFile.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor.framework 2 | 3 | import androidx.room.compiler.processing.XFiler 4 | import com.airbnb.paris.processor.BaseProcessor 5 | import com.squareup.kotlinpoet.FileSpec 6 | 7 | 8 | internal abstract class SkyKotlinFile( val processor: BaseProcessor) { 9 | 10 | protected abstract val packageName: String 11 | protected abstract val name: String 12 | protected abstract val block: FileSpec.Builder.() -> Unit 13 | 14 | fun build(): FileSpec { 15 | return FileSpec.builder(packageName, name).run { 16 | block() 17 | build() 18 | } 19 | } 20 | 21 | /** 22 | * If this module is being processed with kapt then the file is written, otherwise this is a no-op. 23 | */ 24 | fun write(mode: XFiler.Mode = XFiler.Mode.Aggregating) { 25 | processor.filer.write(build(), mode) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyMethodModel.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor.framework.models 2 | 3 | import androidx.room.compiler.processing.XElement 4 | import androidx.room.compiler.processing.XMethodElement 5 | import androidx.room.compiler.processing.XTypeElement 6 | import androidx.room.compiler.processing.isMethod 7 | import com.airbnb.paris.processor.BaseProcessor 8 | 9 | abstract class SkyMethodModel private constructor( 10 | val enclosingElement: XTypeElement, 11 | val element: XMethodElement, 12 | ) : SkyModel { 13 | val jvmName: String get() = element.jvmName 14 | 15 | protected constructor(element: XMethodElement) : this( 16 | element.enclosingElement as XTypeElement, 17 | element 18 | ) 19 | } 20 | 21 | typealias SkyStaticMethodModel = SkyMethodModel 22 | 23 | abstract class SkyMethodModelFactory( 24 | processor: BaseProcessor, 25 | annotationClass: Class 26 | ) : JavaSkyModelFactory(processor, annotationClass) { 27 | 28 | override fun filter(element: XElement): Boolean = element.isMethod() 29 | } 30 | 31 | typealias SkyStaticMethodModelFactory = SkyMethodModelFactory 32 | -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/framework/models/SkyModel.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor.framework.models 2 | 3 | import androidx.room.compiler.processing.XElement 4 | import androidx.room.compiler.processing.XRoundEnv 5 | import com.airbnb.paris.processor.BaseProcessor 6 | 7 | interface SkyModel 8 | 9 | abstract class JavaSkyModelFactory( 10 | val processor: BaseProcessor, 11 | private val annotationClass: Class 12 | ) { 13 | 14 | var models = emptyList() 15 | private set 16 | 17 | var latest = emptyList() 18 | private set 19 | 20 | fun process(roundEnv: XRoundEnv) { 21 | roundEnv.getElementsAnnotatedWith(annotationClass.canonicalName) 22 | .filter(::filter) 23 | .mapNotNull { 24 | @Suppress("UNCHECKED_CAST") 25 | elementToModel(it as E) 26 | } 27 | .let { 28 | models += it 29 | latest = it 30 | } 31 | } 32 | 33 | open fun filter(element: XElement): Boolean = true 34 | 35 | abstract fun elementToModel(element: E): T? 36 | } 37 | -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/models/AfterStyleInfo.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor.models 2 | 3 | import androidx.room.compiler.processing.XMethodElement 4 | import com.airbnb.paris.annotations.AfterStyle 5 | import com.airbnb.paris.processor.ParisProcessor 6 | import com.airbnb.paris.processor.STYLE_CLASS_NAME 7 | import com.airbnb.paris.processor.framework.models.SkyMethodModel 8 | import com.airbnb.paris.processor.framework.models.SkyMethodModelFactory 9 | import com.airbnb.paris.processor.utils.isSameTypeName 10 | 11 | internal class AfterStyleInfoExtractor(val parisProcessor: ParisProcessor) : SkyMethodModelFactory(parisProcessor, AfterStyle::class.java) { 12 | 13 | override fun elementToModel(element: XMethodElement): AfterStyleInfo? { 14 | 15 | if (element.isPrivate() || element.isProtected()) { 16 | parisProcessor.logError(element) { 17 | "Methods annotated with @AfterStyle can't be private or protected." 18 | } 19 | return null 20 | } 21 | 22 | val parameterType = element.parameters.firstOrNull()?.type 23 | 24 | if (parameterType == null || !parameterType.isSameTypeName(STYLE_CLASS_NAME)) { 25 | parisProcessor.logError(element) { 26 | "Methods annotated with @AfterStyle must have a single Style parameter." 27 | } 28 | return null 29 | } 30 | 31 | return AfterStyleInfo(element) 32 | } 33 | } 34 | 35 | internal class AfterStyleInfo(element: XMethodElement) : SkyMethodModel(element) 36 | -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/models/BeforeStyleInfo.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor.models 2 | 3 | import androidx.room.compiler.processing.XMethodElement 4 | import com.airbnb.paris.annotations.BeforeStyle 5 | import com.airbnb.paris.processor.ParisProcessor 6 | import com.airbnb.paris.processor.framework.models.SkyMethodModel 7 | import com.airbnb.paris.processor.framework.models.SkyMethodModelFactory 8 | 9 | internal class BeforeStyleInfoExtractor(val parisProcessor: ParisProcessor) : SkyMethodModelFactory(parisProcessor, BeforeStyle::class.java) { 10 | 11 | override fun elementToModel(element: XMethodElement): BeforeStyleInfo? { 12 | if (element.isPrivate() || element.isProtected()) { 13 | parisProcessor.logError(element) { 14 | "Methods annotated with @BeforeStyle can't be private or protected." 15 | } 16 | return null 17 | } 18 | 19 | 20 | val parameterType = element.parameters.firstOrNull()?.type 21 | // TODO: 2/21/21 BeforeStyle doesn't seem tested in the project?! 22 | if (parameterType == null || parisProcessor.memoizer.styleClassTypeX.isAssignableFrom(parameterType)) { 23 | parisProcessor.logError(element) { 24 | "Methods annotated with @BeforeStyle must have a single Style parameter." 25 | } 26 | return null 27 | } 28 | 29 | return BeforeStyleInfo(element) 30 | } 31 | } 32 | 33 | internal class BeforeStyleInfo(element: XMethodElement) : SkyMethodModel(element) 34 | 35 | -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/models/StyleStaticMethodInfo.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor.models 2 | 3 | import androidx.room.compiler.processing.XMethodElement 4 | import com.airbnb.paris.annotations.Style 5 | import com.airbnb.paris.processor.ParisProcessor 6 | import com.airbnb.paris.processor.framework.JavaCodeBlock 7 | import com.airbnb.paris.processor.framework.KotlinCodeBlock 8 | import com.airbnb.paris.processor.framework.models.SkyStaticMethodModel 9 | import com.airbnb.paris.processor.framework.models.SkyStaticMethodModelFactory 10 | import com.airbnb.paris.processor.framework.toKPoet 11 | import com.airbnb.paris.processor.utils.ParisProcessorUtils 12 | 13 | internal class StyleStaticMethodInfoExtractor(val parisProcessor: ParisProcessor) : 14 | SkyStaticMethodModelFactory(parisProcessor, Style::class.java) { 15 | 16 | override fun elementToModel(element: XMethodElement): StyleStaticMethodInfo? { 17 | // TODO Get Javadoc from field/method and add it to the generated methods 18 | 19 | if (!element.isStatic() || element.isPrivate() || element.isProtected()) { 20 | parisProcessor.logError(element) { 21 | "Methods annotated with @Style must be static and can't be private or protected." 22 | } 23 | return null 24 | } 25 | 26 | val style = element.getAnnotation(Style::class) 27 | val isDefault = style!!.value.isDefault 28 | 29 | val enclosingElement = element.enclosingElement 30 | 31 | val elementName = element.name 32 | 33 | val formattedName = ParisProcessorUtils.reformatStyleFieldOrMethodName(elementName) 34 | 35 | val targetType = element.parameters[0].type.typeName 36 | 37 | val javadoc = JavaCodeBlock.of("@see \$T#\$N(\$T)\n", enclosingElement.className, elementName, targetType) 38 | val kdoc = KotlinCodeBlock.of("@see %T.%N\n", enclosingElement.className.toKPoet(), elementName) 39 | 40 | return StyleStaticMethodInfo( 41 | element, 42 | elementName, 43 | formattedName, 44 | javadoc, 45 | kdoc, 46 | isDefault 47 | ) 48 | } 49 | } 50 | 51 | internal class StyleStaticMethodInfo( 52 | element: XMethodElement, 53 | override val elementName: String, 54 | override val formattedName: String, 55 | override val javadoc: JavaCodeBlock, 56 | override val kdoc: KotlinCodeBlock, 57 | override val isDefault: Boolean = false 58 | ) : SkyStaticMethodModel(element), StyleInfo 59 | -------------------------------------------------------------------------------- /paris-processor/src/main/java/com/airbnb/paris/processor/utils/ParisProcessorUtils.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor.utils 2 | 3 | // This is purposefully left public to facilitate integrations with Paris 4 | // TODO BREAKING Refactor this to be an object with @JvmStatic functions 5 | class ParisProcessorUtils { 6 | 7 | companion object { 8 | /** 9 | * Format the name of a @Style annotated field or method to match what the style applier and 10 | * builder will use. 11 | * 12 | * "Style" suffixes are removed to make it possible to use in field or method names while 13 | * avoiding the redundancy of having it be part of style builder methods. 14 | * 15 | * Examples: 16 | * MY_RED -> MyRed 17 | * myRed -> MyRed 18 | * MY_RED_STYLE -> MyRed 19 | * myRedStyle -> MyRed 20 | */ 21 | @JvmStatic 22 | fun reformatStyleFieldOrMethodName(name: String): String { 23 | // Converts any name to CamelCase 24 | val isNameAllCaps = name.all { it.isUpperCase() || !it.isLetter() } 25 | return name 26 | .foldRightIndexed("") { index, c, acc -> 27 | if (c == '_') { 28 | acc 29 | } else { 30 | if (index == 0) { 31 | c.uppercaseChar() + acc 32 | } else if (name[index - 1] != '_') { 33 | if (isNameAllCaps) { 34 | c.lowercaseChar() + acc 35 | } else { 36 | c + acc 37 | } 38 | } else { 39 | c.uppercaseChar() + acc 40 | } 41 | } 42 | } 43 | .let { 44 | if (it != "Style") { 45 | it.removeSuffix("Style") 46 | } else { 47 | it 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /paris-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider: -------------------------------------------------------------------------------- 1 | com.airbnb.paris.processor.ParisProcessorProvider -------------------------------------------------------------------------------- /paris-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor: -------------------------------------------------------------------------------- 1 | com.airbnb.paris.processor.ParisProcessor 2 | -------------------------------------------------------------------------------- /paris-processor/src/test/java/KspResourceScannerTest.kt: -------------------------------------------------------------------------------- 1 | import com.airbnb.paris.processor.android_resource_scanner.KspResourceScanner 2 | import org.junit.Test 3 | import strikt.api.expectThat 4 | import strikt.assertions.isEqualTo 5 | 6 | class KspResourceScannerTest { 7 | @Test 8 | fun findMatchingImportPackage_TypeAlias() { 9 | val import = KspResourceScanner.findMatchingImportPackage( 10 | importedNames = listOf("com.airbnb.paris.test.R2 as typeAliasedR"), 11 | annotationReference = "typeAliasedR.layout.my_layout", 12 | annotationReferencePrefix = "typeAliasedR", 13 | packageName = "com.airbnb.paris" 14 | ) 15 | 16 | expectThat(import.fullyQualifiedReference).isEqualTo("com.airbnb.paris.test.R2.layout.my_layout") 17 | } 18 | 19 | @Test 20 | fun findMatchingImportPackage_TypeAliasDoesNotMatch() { 21 | val import = KspResourceScanner.findMatchingImportPackage( 22 | importedNames = listOf("com.airbnb.paris.test.R2 as typeAliasedR2"), 23 | annotationReference = "typeAliasedR.layout.my_layout", 24 | annotationReferencePrefix = "typeAliasedR", 25 | packageName = "com.airbnb.paris" 26 | ) 27 | 28 | // falls back to annotation reference, since import should not match 29 | expectThat(import.fullyQualifiedReference).isEqualTo("typeAliasedR.layout.my_layout") 30 | } 31 | 32 | @Test 33 | fun findMatchingImportPackage_fullyStaticImport() { 34 | val import = KspResourceScanner.findMatchingImportPackage( 35 | importedNames = listOf("com.airbnb.n2.comp.designsystem.hostdls.R2.styleable.n2_CarouselCheckedActionCard_n2_layoutStyle"), 36 | annotationReference = "n2_CarouselCheckedActionCard_n2_layoutStyle", 37 | annotationReferencePrefix = "n2_CarouselCheckedActionCard_n2_layoutStyle", 38 | packageName = "com.airbnb.n2.comp.designsystem.hostdls" 39 | ) 40 | 41 | // falls back to annotation reference, since import should not match 42 | expectThat(import.fullyQualifiedReference).isEqualTo("com.airbnb.n2.comp.designsystem.hostdls.R2.styleable.n2_CarouselCheckedActionCard_n2_layoutStyle") 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /paris-processor/src/test/kotlin/com/airbnb/paris/processor/utils/ParisProcessorUtilsTest.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.processor.utils 2 | 3 | import com.airbnb.paris.processor.utils.ParisProcessorUtils.Companion.reformatStyleFieldOrMethodName 4 | import io.kotlintest.matchers.shouldBe 5 | import io.kotlintest.specs.StringSpec 6 | 7 | class ParisProcessorUtilsTest : StringSpec({ 8 | 9 | "upper snake case style names should be converted to upper camel" { 10 | reformatStyleFieldOrMethodName("MY_RED") shouldBe "MyRed" 11 | } 12 | 13 | "lower camel case style names should be converted to upper camel" { 14 | reformatStyleFieldOrMethodName("myRed") shouldBe "MyRed" 15 | } 16 | 17 | "upper camel case style names shouldn't change" { 18 | reformatStyleFieldOrMethodName("MyRed") shouldBe "MyRed" 19 | } 20 | 21 | "Style suffix should be removed from style names" { 22 | reformatStyleFieldOrMethodName("MY_RED_STYLE") shouldBe "MyRed" 23 | reformatStyleFieldOrMethodName("myRedStyle") shouldBe "MyRed" 24 | reformatStyleFieldOrMethodName("MyRedStyle") shouldBe "MyRed" 25 | } 26 | 27 | "style whose whole name is style/Style/STYLE doesn't change" { 28 | reformatStyleFieldOrMethodName("style") shouldBe "Style" 29 | reformatStyleFieldOrMethodName("Style") shouldBe "Style" 30 | reformatStyleFieldOrMethodName("STYLE") shouldBe "Style" 31 | } 32 | }) 33 | -------------------------------------------------------------------------------- /paris-test-lib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /paris-test-lib/README.md: -------------------------------------------------------------------------------- 1 | This module is used to test accessing resources from another module. In particular, with namespaced resources turned on Paris must generate 2 | references to R files that contain the resource definition. -------------------------------------------------------------------------------- /paris-test-lib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | apply plugin: 'com.jakewharton.butterknife' 5 | 6 | android { 7 | compileSdkVersion rootProject.COMPILE_SDK_VERSION 8 | 9 | defaultConfig { 10 | minSdkVersion rootProject.MIN_SDK_VERSION 11 | targetSdkVersion rootProject.TARGET_SDK_VERSION 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | } 14 | 15 | compileOptions { 16 | sourceCompatibility rootProject.JAVA_SOURCE_VERSION 17 | targetCompatibility rootProject.JAVA_TARGET_VERSION 18 | } 19 | 20 | kotlinOptions { 21 | jvmTarget = '1.8' 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation project(':paris') 27 | 28 | implementation deps.appcompat 29 | 30 | kapt project(':paris-processor') 31 | } 32 | -------------------------------------------------------------------------------- /paris-test-lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /paris-test-lib/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /paris-test/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /paris-test/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | 5 | android { 6 | compileSdkVersion rootProject.COMPILE_SDK_VERSION 7 | 8 | defaultConfig { 9 | minSdkVersion rootProject.MIN_SDK_VERSION 10 | targetSdkVersion rootProject.TARGET_SDK_VERSION 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | } 13 | 14 | testOptions { 15 | unitTests { 16 | includeAndroidResources = true 17 | } 18 | } 19 | 20 | compileOptions { 21 | sourceCompatibility rootProject.JAVA_SOURCE_VERSION 22 | targetCompatibility rootProject.JAVA_TARGET_VERSION 23 | } 24 | 25 | kotlinOptions { 26 | jvmTarget = '1.8' 27 | } 28 | } 29 | 30 | sourceCompatibility = rootProject.JAVA_SOURCE_VERSION 31 | targetCompatibility = rootProject.JAVA_TARGET_VERSION 32 | 33 | dependencies { 34 | implementation project(':paris') 35 | implementation project(':paris-test-lib') 36 | implementation "com.google.devtools.ksp:symbol-processing-api:$KSP_VERSION" 37 | 38 | implementation deps.appcompat 39 | 40 | kapt project(':paris-processor') 41 | 42 | testImplementation project(':paris-processor') 43 | 44 | testImplementation files(rootProject.file("libs/rt.jar")) 45 | testImplementation files(rootProject.file("libs/tools.jar")) 46 | 47 | testImplementation deps.junit 48 | testImplementation deps.testingCompile 49 | testImplementation deps.kotlinCompileTesting 50 | testImplementation "io.github.java-diff-utils:java-diff-utils:4.5" 51 | testImplementation "io.strikt:strikt-core:0.31.0" 52 | 53 | androidTestImplementation deps.espresso 54 | } 55 | 56 | // Java files in the "resources" folder are not included in the build for some reason (seems like source files are skipped?) 57 | // This files are needed to test the annotation processor, so we manually copy them into the build. 58 | task('copyDebugTestResources', type: Copy) { 59 | from("${projectDir}/src/test/resources") 60 | into("${buildDir}/intermediates/sourceFolderJavaResources/debug") 61 | } 62 | 63 | task('copyReleaseTestResources', type: Copy) { 64 | from("${projectDir}/src/test/resources") 65 | into("${buildDir}/intermediates/sourceFolderJavaResources/release") 66 | } 67 | 68 | preBuild.dependsOn copyReleaseTestResources 69 | preBuild.dependsOn copyDebugTestResources 70 | -------------------------------------------------------------------------------- /paris-test/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /paris-test/src/main/java/com/airbnb/paris/test/MyOtherView.java: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.test; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import com.airbnb.paris.annotations.Attr; 9 | import com.airbnb.paris.annotations.Styleable; 10 | import com.airbnb.paris.annotations.StyleableChild; 11 | 12 | @Styleable("MyView") 13 | public class MyOtherView extends View { 14 | 15 | @StyleableChild(R2.styleable.MyView_titleStyle) 16 | public TextView title; 17 | 18 | public MyOtherView(Context context) { 19 | super(context); 20 | init(); 21 | } 22 | 23 | public MyOtherView(Context context, AttributeSet attrs) { 24 | super(context, attrs); 25 | init(); 26 | } 27 | 28 | public MyOtherView(Context context, AttributeSet attrs, int defStyle) { 29 | super(context, attrs, defStyle); 30 | init(); 31 | } 32 | 33 | private void init() { 34 | title = new TextView(getContext()); 35 | } 36 | 37 | @Attr(value = R2.styleable.MyView_active, defaultValue = R2.bool.active) 38 | public void setActive(boolean active) { 39 | // Nothing to do 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /paris-test/src/main/java/com/airbnb/paris/test/PackageConfig.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.test 2 | 3 | import com.airbnb.paris.annotations.ParisConfig 4 | 5 | @ParisConfig(rClass = R::class) 6 | class PackageConfig -------------------------------------------------------------------------------- /paris-test/src/main/res/color/format_color_state_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /paris-test/src/main/res/drawable/format_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /paris-test/src/main/res/font/format_font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/airbnb/paris/d8b5edbc56253bcdd0d0c57930d2e91113dd0f37/paris-test/src/main/res/font/format_font.ttf -------------------------------------------------------------------------------- /paris-test/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /paris-test/src/main/res/values/booleans.xml: -------------------------------------------------------------------------------- 1 | 2 | true 3 | 4 | -------------------------------------------------------------------------------- /paris-test/src/main/res/values/formats.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | true 31 | #FF0000 32 | 17dp 33 | 0 34 | 0x01 35 | 1.2 36 | 5% 37 | 42 38 | Hello 39 | Hello 40 | 41 | Tour Eiffel 42 | Le Louvre 43 | Arc de Triomphe 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /paris-test/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Paris Test 3 | 4 | -------------------------------------------------------------------------------- /paris-test/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 15 | 16 | 20 | 21 | 25 | 26 | 29 | 30 | 33 | 34 | 37 | 38 | 41 | 42 | 45 | 46 | 49 | 50 | 53 | 54 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_text_view_style_applier.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | 12 | 15 | 16 | 19 | 20 | 23 | 24 | 27 | 28 | 32 | 33 | 36 | 37 | 40 | 41 | 45 | 46 | 49 | 50 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_text_view_style_extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_typed_array_typed_array_wrapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 | 19 | 20 | 23 | 24 | 27 | 28 | 33 | 34 | 37 | 38 | 41 | 42 | 45 | 46 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_view_group_style_extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_view_style_builder.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0 5 | 1 6 | 2 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_view_style_extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_with_attr_function_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_with_attr_property_setter_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_with_internal_attr_function_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_with_internal_attr_property_setter_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_with_styleable_child_internal_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_with_styleable_child_kotlin_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_with_styleable_child_kotlin_view_style_extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_with_styleable_child_lateinit_view_style_extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_with_styleable_child_property_delegate_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_with_styleable_child_property_delegate_view_style_extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_with_styleable_child_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/debug/res/values/test_with_styleable_child_view_style_extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /paris/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /paris/src/main/java/com/airbnb/paris/ExtendableStyleBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris 2 | 3 | import android.view.View 4 | 5 | /** 6 | * Meant to be extended with attribute and linked style functions 7 | */ 8 | class ExtendableStyleBuilder : StyleBuilder, StyleApplier<*, V>>() { 9 | 10 | // Makes the builder public so that extensions can access it 11 | public override var builder = super.builder 12 | 13 | // Makes the function public so that extensions can access it 14 | @Suppress("RedundantOverride") 15 | public override fun consumeProgrammaticStyleBuilder() { 16 | super.consumeProgrammaticStyleBuilder() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /paris/src/main/java/com/airbnb/paris/attribute_values/ColorValue.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.attribute_values 2 | 3 | import androidx.annotation.ColorInt 4 | 5 | internal data class ColorValue(@ColorInt val colorValue: Int) 6 | 7 | -------------------------------------------------------------------------------- /paris/src/main/java/com/airbnb/paris/attribute_values/DpValue.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.attribute_values 2 | 3 | import androidx.annotation.Dimension 4 | 5 | internal data class DpValue(@Dimension(unit = Dimension.DP) val dpValue: Int) 6 | -------------------------------------------------------------------------------- /paris/src/main/java/com/airbnb/paris/attribute_values/ResourceId.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.attribute_values 2 | 3 | import androidx.annotation.AnyRes 4 | 5 | internal data class ResourceId(@AnyRes val resId: Int) 6 | -------------------------------------------------------------------------------- /paris/src/main/java/com/airbnb/paris/attribute_values/Styles.kt: -------------------------------------------------------------------------------- 1 | package com.airbnb.paris.attribute_values 2 | 3 | import com.airbnb.paris.styles.Style 4 | 5 | internal data class Styles(val list: MutableList 10 | 11 | 17 | 18 | 23 | 24 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /sample/src/main/res/values/styles_view_section.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | 7 | include ':sample', ':paris-annotations', ':paris-processor', ':paris', ':paris-test', ':paris-test-lib' 8 | 9 | 10 | --------------------------------------------------------------------------------