From 76994563c264447545724ee0c1f53168cfdeecb7 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Tue, 25 Feb 2020 13:31:47 -0800 Subject: [PATCH 1/3] adds proto-kotlin and protoc-plugin-kotlin --- kotlin/protobuf/.gitignore | 3 + kotlin/protobuf/CONTRIBUTING.md | 26 + kotlin/protobuf/LICENSE | 13 + kotlin/protobuf/OWNERS | 4 + kotlin/protobuf/README.md | 1 + kotlin/protobuf/build.gradle | 118 +++ .../kotlin/com/example/tutorial/AddPerson.kt | 92 +++ .../kotlin/com/example/tutorial/ListPeople.kt | 52 ++ .../example/src/main/proto/addressbook.proto | 29 + .../protobuf/src/main/dist/protoc-gen-kotlin | 26 + .../src/main/dist/protoc-gen-kotlin.bat | 86 ++ .../com/google/protobuf/kotlin/DslList.kt | 30 + .../java/com/google/protobuf/kotlin/DslMap.kt | 30 + .../protobuf/kotlin/ExtendableProtoDslLite.kt | 111 +++ .../protobuf/kotlin/ExtendableProtoDslV3.kt | 108 +++ .../protobuf/kotlin/ExtensionListLite.kt | 46 ++ .../google/protobuf/kotlin/ExtensionListV3.kt | 47 ++ .../protobuf/kotlin/OneofRepresentation.kt | 41 + .../com/google/protobuf/kotlin/ProtoDsl.kt | 25 + .../google/protobuf/kotlin/ProtoDslMarker.kt | 25 + .../ExtendableMessageLiteExtensions.kt | 45 + .../ExtendableMessageV3Extensions.kt | 42 + .../AbstractOrNullPropertyGenerator.kt | 154 ++++ .../generator/ComponentExtensionGenerator.kt | 52 ++ .../protobuf/kotlin/generator/Deprecation.kt | 45 + .../kotlin/generator/DslComponentGenerator.kt | 79 ++ .../protobuf/kotlin/generator/DslGenerator.kt | 238 ++++++ .../kotlin/generator/ExtensionGenerator.kt | 56 ++ .../generator/FieldOrNullPropertyGenerator.kt | 35 + .../kotlin/generator/GeneratorRunner.kt | 121 +++ .../MapFieldMapProxyDslComponentGenerator.kt | 133 +++ .../MapRepresentativeExtensionGenerator.kt | 434 ++++++++++ .../OneofCaseDslComponentGenerator.kt | 58 ++ .../kotlin/generator/OneofClassGenerator.kt | 414 ++++++++++ .../generator/OneofOrNullPropertyGenerator.kt | 43 + .../generator/ProtoFileKotlinApiGenerator.kt | 85 ++ .../generator/RecursiveExtensionGenerator.kt | 83 ++ ...atedFieldListProxyDslComponentGenerator.kt | 132 +++ ...epeatedRepresentativeExtensionGenerator.kt | 269 ++++++ ...ingletonFieldClearDslComponentGenerator.kt | 53 ++ ...ngletonFieldHazzerDslComponentGenerator.kt | 51 ++ ...letonFieldPropertyDslComponentGenerator.kt | 86 ++ .../generator/WrapperPropertyGenerator.kt | 201 +++++ .../kotlin/OneofRepresentationTest.kt | 86 ++ .../generator/AbstractGeneratedCodeTest.kt | 463 +++++++++++ .../generator/AndroidLiteGeneratedCodeTest.kt | 35 + .../kotlin/generator/AndroidManifest.xml | 9 + .../kotlin/generator/DslGeneratorTest.kt | 171 ++++ .../Example3OneofClass.kt.expected.enclosed | 60 ++ .../Example3OneofClass.kt.expected.toplevel | 16 + .../FieldOrNullPropertyGeneratorTest.kt | 213 +++++ .../kotlin/generator/GeneratedCodeTest.kt | 38 + .../GeneratorRunnerExample2.kt.expected | 106 +++ .../GeneratorRunnerExample2Lite.kt.expected | 106 +++ .../GeneratorRunnerExample3.kt.expected | 767 ++++++++++++++++++ .../kotlin/generator/GeneratorRunnerTest.kt | 191 +++++ .../kotlin/generator/LiteGeneratedCodeTest.kt | 37 + ...pFieldMapProxyDslComponentGeneratorTest.kt | 94 +++ ...MapRepresentativeExtensionGeneratorTest.kt | 406 +++++++++ .../OneofCaseDslComponentGeneratorTest.kt | 73 ++ .../generator/OneofClassGeneratorTest.kt | 254 ++++++ .../OneofOrNullPropertyGeneratorTest.kt | 150 ++++ .../ProtoFileKotlinApiGeneratorTest.kt | 178 ++++ .../RecursiveExtensionGeneratorTest.kt | 105 +++ ...FieldListProxyDslComponentGeneratorTest.kt | 93 +++ ...tedRepresentativeExtensionGeneratorTest.kt | 213 +++++ ...etonFieldClearDslComponentGeneratorTest.kt | 78 ++ ...tonFieldHazzerDslComponentGeneratorTest.kt | 105 +++ ...nFieldPropertyDslComponentGeneratorTest.kt | 80 ++ .../generator/WrapperPropertyGeneratorTest.kt | 149 ++++ .../src/test/proto/testing/evil_names.proto | 57 ++ .../proto/testing/evil_names_proto2.proto | 50 ++ .../src/test/proto/testing/example2.proto | 17 + .../src/test/proto/testing/example3.proto | 43 + .../proto/testing/java_multiple_files.proto | 10 + kotlin/protoc-plugin-common/.gitignore | 3 + kotlin/protoc-plugin-common/CONTRIBUTING.md | 26 + kotlin/protoc-plugin-common/LICENSE | 202 +++++ kotlin/protoc-plugin-common/OWNERS | 6 + kotlin/protoc-plugin-common/README.md | 1 + kotlin/protoc-plugin-common/build.gradle | 46 ++ .../common/graph/TopologicalSortGraph.java | 43 + .../google/common/sort/PartialOrdering.java | 38 + .../google/common/sort/TopologicalSort.java | 315 +++++++ .../kotlin/protoc/AbstractGeneratorRunner.kt | 96 +++ .../protobuf/kotlin/protoc/ClassSimpleName.kt | 60 ++ .../protobuf/kotlin/protoc/CodeGenerators.kt | 84 ++ .../protobuf/kotlin/protoc/ConstantName.kt | 29 + .../protobuf/kotlin/protoc/Declarations.kt | 141 ++++ .../protobuf/kotlin/protoc/DescriptorUtil.kt | 141 ++++ .../protobuf/kotlin/protoc/GeneratorConfig.kt | 181 +++++ .../kotlin/protoc/JavaPackagePolicy.kt | 36 + .../protobuf/kotlin/protoc/KtPoetUtil.kt | 48 ++ .../kotlin/protoc/MemberSimpleName.kt | 110 +++ .../kotlin/protoc/MessageComponent.kt | 200 +++++ .../kotlin/protoc/ProtoEnumValueName.kt | 31 + .../protobuf/kotlin/protoc/ProtoFieldName.kt | 97 +++ .../protobuf/kotlin/protoc/ProtoFileName.kt | 46 ++ .../protobuf/kotlin/protoc/ProtoMethodName.kt | 35 + .../kotlin/protoc/ProtoServiceName.kt | 33 + .../kotlin/protoc/ProtoTypeSimpleName.kt | 45 + .../google/protobuf/kotlin/protoc/Scope.kt | 62 ++ .../protobuf/kotlin/protoc/TypeNames.kt | 28 + .../protoc/testing/DeclarationsSubject.kt | 74 ++ .../kotlin/protoc/testing/FileSpecSubject.kt | 43 + .../kotlin/protoc/testing/FunSpecSubject.kt | 39 + .../kotlin/protoc/testing/TypeSpecSubject.kt | 39 + .../kotlin/protoc/ClassSimpleNameTest.kt | 66 ++ .../kotlin/protoc/ConstantNameTest.kt | 38 + .../kotlin/protoc/DeclarationsTest.kt | 187 +++++ .../kotlin/protoc/DescriptorUtilTest.kt | 157 ++++ .../kotlin/protoc/GeneratorConfigTest.kt | 383 +++++++++ .../kotlin/protoc/JavaPackagePolicyTest.kt | 80 ++ .../kotlin/protoc/MemberSimpleNameTest.kt | 48 ++ .../kotlin/protoc/MessageComponentTest.kt | 173 ++++ .../kotlin/protoc/ProtoEnumValueNameTest.kt | 39 + .../kotlin/protoc/ProtoFieldNameTest.kt | 97 +++ .../kotlin/protoc/ProtoFileNameTest.kt | 43 + .../kotlin/protoc/ProtoTypeSimpleNameTest.kt | 45 + .../protobuf/kotlin/protoc/ScopeTest.kt | 49 ++ .../protoc/testing/DeclarationsSubjectTest.kt | 117 +++ .../protoc/testing/FileSpecSubjectTest.kt | 62 ++ .../protoc/testing/FunSpecSubjectTest.kt | 64 ++ .../protoc/testing/TypeSpecSubjectTest.kt | 63 ++ .../src/test/proto/testing/example2.proto | 10 + .../src/test/proto/testing/example3.proto | 37 + .../has_explicit_outer_class_name.proto | 8 + .../has_nested_class_name_conflict.proto | 9 + .../has_outer_class_name_conflict.proto | 7 + .../proto/testing/implicit_java_package.proto | 5 + .../testing/proto-file-with-hyphen.proto | 5 + .../proto_api_1_explicit_package.proto | 9 + .../proto_api_1_implicit_package.proto | 5 + 133 files changed, 12459 insertions(+) create mode 100644 kotlin/protobuf/.gitignore create mode 100644 kotlin/protobuf/CONTRIBUTING.md create mode 100644 kotlin/protobuf/LICENSE create mode 100644 kotlin/protobuf/OWNERS create mode 100644 kotlin/protobuf/README.md create mode 100644 kotlin/protobuf/build.gradle create mode 100644 kotlin/protobuf/example/src/main/kotlin/com/example/tutorial/AddPerson.kt create mode 100644 kotlin/protobuf/example/src/main/kotlin/com/example/tutorial/ListPeople.kt create mode 100644 kotlin/protobuf/example/src/main/proto/addressbook.proto create mode 100644 kotlin/protobuf/src/main/dist/protoc-gen-kotlin create mode 100644 kotlin/protobuf/src/main/dist/protoc-gen-kotlin.bat create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/DslList.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/DslMap.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslLite.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslV3.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtensionListLite.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtensionListV3.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/OneofRepresentation.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ProtoDsl.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ProtoDslMarker.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageLiteExtensions.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageV3Extensions.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/AbstractOrNullPropertyGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ComponentExtensionGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/Deprecation.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/DslComponentGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/DslGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ExtensionGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/GeneratorRunner.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofClassGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGenerator.kt create mode 100644 kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/WrapperPropertyGenerator.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/OneofRepresentationTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AbstractGeneratedCodeTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AndroidLiteGeneratedCodeTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AndroidManifest.xml create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/DslGeneratorTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.enclosed create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.toplevel create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGeneratorTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratedCodeTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2.kt.expected create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2Lite.kt.expected create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample3.kt.expected create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/LiteGeneratedCodeTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGeneratorTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGeneratorTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGeneratorTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofClassGeneratorTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGeneratorTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGeneratorTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGeneratorTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGeneratorTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGeneratorTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGeneratorTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGeneratorTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGeneratorTest.kt create mode 100644 kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/WrapperPropertyGeneratorTest.kt create mode 100644 kotlin/protobuf/src/test/proto/testing/evil_names.proto create mode 100644 kotlin/protobuf/src/test/proto/testing/evil_names_proto2.proto create mode 100644 kotlin/protobuf/src/test/proto/testing/example2.proto create mode 100644 kotlin/protobuf/src/test/proto/testing/example3.proto create mode 100644 kotlin/protobuf/src/test/proto/testing/java_multiple_files.proto create mode 100644 kotlin/protoc-plugin-common/.gitignore create mode 100644 kotlin/protoc-plugin-common/CONTRIBUTING.md create mode 100644 kotlin/protoc-plugin-common/LICENSE create mode 100644 kotlin/protoc-plugin-common/OWNERS create mode 100644 kotlin/protoc-plugin-common/README.md create mode 100644 kotlin/protoc-plugin-common/build.gradle create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/common/graph/TopologicalSortGraph.java create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/common/sort/PartialOrdering.java create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/common/sort/TopologicalSort.java create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/AbstractGeneratorRunner.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ClassSimpleName.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/CodeGenerators.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ConstantName.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/Declarations.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/DescriptorUtil.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/GeneratorConfig.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicy.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/KtPoetUtil.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/MemberSimpleName.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/MessageComponent.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueName.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFieldName.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFileName.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoMethodName.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoServiceName.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleName.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/Scope.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/TypeNames.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubject.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubject.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubject.kt create mode 100644 kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubject.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ClassSimpleNameTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ConstantNameTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/DeclarationsTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/DescriptorUtilTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/GeneratorConfigTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicyTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/MemberSimpleNameTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/MessageComponentTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueNameTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFieldNameTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFileNameTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleNameTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ScopeTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubjectTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubjectTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubjectTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubjectTest.kt create mode 100644 kotlin/protoc-plugin-common/src/test/proto/testing/example2.proto create mode 100644 kotlin/protoc-plugin-common/src/test/proto/testing/example3.proto create mode 100644 kotlin/protoc-plugin-common/src/test/proto/testing/has_explicit_outer_class_name.proto create mode 100644 kotlin/protoc-plugin-common/src/test/proto/testing/has_nested_class_name_conflict.proto create mode 100644 kotlin/protoc-plugin-common/src/test/proto/testing/has_outer_class_name_conflict.proto create mode 100644 kotlin/protoc-plugin-common/src/test/proto/testing/implicit_java_package.proto create mode 100644 kotlin/protoc-plugin-common/src/test/proto/testing/proto-file-with-hyphen.proto create mode 100644 kotlin/protoc-plugin-common/src/test/proto/testing/proto_api_1_explicit_package.proto create mode 100644 kotlin/protoc-plugin-common/src/test/proto/testing/proto_api_1_implicit_package.proto diff --git a/kotlin/protobuf/.gitignore b/kotlin/protobuf/.gitignore new file mode 100644 index 000000000000..a024d8237ce8 --- /dev/null +++ b/kotlin/protobuf/.gitignore @@ -0,0 +1,3 @@ +# Gradle +build +.gradle diff --git a/kotlin/protobuf/CONTRIBUTING.md b/kotlin/protobuf/CONTRIBUTING.md new file mode 100644 index 000000000000..086d53916b56 --- /dev/null +++ b/kotlin/protobuf/CONTRIBUTING.md @@ -0,0 +1,26 @@ +# Contributing + +Want to contribute? Great! First, read this page (including the small print at the end). + +### Before you contribute +Before we can use your code, you must sign the +[Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1) +(CLA), which you can do online. The CLA is necessary mainly because you own the +copyright to your changes, even after your contribution becomes part of our +codebase, so we need your permission to use and distribute your code. We also +need to be sure of various other things—for instance that you'll tell us if you +know that your code infringes on other people's patents. You don't have to sign +the CLA until after you've submitted your code for review and a member has +approved it, but you must do it before we can put your code into our codebase. +Before you start working on a larger contribution, you should get in touch with +us first through the issue tracker with your idea so that we can help out and +possibly guide you. Coordinating up front makes it much easier to avoid +frustration later on. + +### Code reviews +All submissions, including submissions by project members, require review. We +use Github pull requests for this purpose. + +### The small print +Contributions made by corporations are covered by a different agreement than +the one above, the Software Grant and Corporate Contributor License Agreement. diff --git a/kotlin/protobuf/LICENSE b/kotlin/protobuf/LICENSE new file mode 100644 index 000000000000..e34cad70a94e --- /dev/null +++ b/kotlin/protobuf/LICENSE @@ -0,0 +1,13 @@ +Copyright 2019 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/kotlin/protobuf/OWNERS b/kotlin/protobuf/OWNERS new file mode 100644 index 000000000000..3ed0e6916bb6 --- /dev/null +++ b/kotlin/protobuf/OWNERS @@ -0,0 +1,4 @@ +# PTAL at go/owners#best-practices +lowasser +kmb +mdb-group:kotlin-google-eng diff --git a/kotlin/protobuf/README.md b/kotlin/protobuf/README.md new file mode 100644 index 000000000000..03423cca514b --- /dev/null +++ b/kotlin/protobuf/README.md @@ -0,0 +1 @@ +# proto-kotlin: Google's Kotlin bindings for protobuf diff --git a/kotlin/protobuf/build.gradle b/kotlin/protobuf/build.gradle new file mode 100644 index 000000000000..80d87673c7a5 --- /dev/null +++ b/kotlin/protobuf/build.gradle @@ -0,0 +1,118 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version'1.3.61' + id 'com.google.protobuf' version '0.8.8' + + // Generate IntelliJ IDEA's .idea & .iml project files + // Starting with 0.8.4 of protobuf-gradle-plugin, *.proto and the gen output files are added + // to IntelliJ as sources. It is no longer necessary to add them manually to the idea {} block + // to jump to definitions from Java and Kotlin files. + // For best results, install the Protobuf and Kotlin plugins for IntelliJ. + id 'idea' + + // Provide convenience executables for trying out the examples. + id 'application' + id 'maven-publish' +} + +apply plugin : "java" + +mainClassName = 'com.google.protobuf.kotlin.generator.GeneratorRunner' +applicationName = 'proto-kotlin' + +// Generate the jar file to be used by our custom plugin +jar { + manifest { + attributes 'Main-Class': mainClassName + } + from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } +} + +repositories { + google() + jcenter() + mavenCentral() + mavenLocal() +} + +// Feel free to delete the comment at the next line. It is just for safely +// updating the version in our release process. +def coroutinesVersion = '1.3.3' +def kotlinVersion = '1.3.61' + +dependencies { + // Kotlin + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}" + + // Protobuf + compile "io.grpc:protoc-plugin-kotlin:0.1" + compile 'com.google.protobuf:protobuf-gradle-plugin:0.8.11' + compile 'com.google.protobuf:protobuf-java:3.11.0' + + compile 'com.squareup:kotlinpoet:1.5.0' + compile "org.jetbrains.kotlin:kotlin-reflect:1.3.61" + + // Testing + testImplementation "com.google.truth:truth:1.0.1" + testImplementation "com.google.truth.extensions:truth-proto-extension:1.0" + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.3.2' + testImplementation "org.jetbrains.kotlin:kotlin-android-extensions:$kotlinVersion" + testImplementation "junit:junit:4.12" + testImplementation "com.google.jimfs:jimfs:1.1" +} + +protobuf { + protoc { artifact = 'com.google.protobuf:protoc:3.11.0' } + plugins { + // Specify protoc to generate using our kotlin plugin + kotlin { + path = 'build/install/proto-kotlin/bin/proto-kotlin' + } + } + generateProtoTasks { + all().each { task -> + // Generate the protoc binary first so we can generate the files for + // our tests + if (task.name.startsWith('generateTestProto')) { + task.dependsOn { installDist } + } + task.plugins { + // Add kotlin output without any option. This yields + // "--custom_out=/path/to/output" on the protoc commandline. + kotlin {} + } + } + } +} + +def artifactForGradlePlugin(MavenPublication pub, String os, String arch) { + if (os == "windows") { + pub.artifact("src/main/dist/protoc-gen-kotlin.bat" as File) { + classifier os + "-" + arch + extension "exe" + } + } else { + pub.artifact("src/main/dist/protoc-gen-kotlin" as File) { + classifier os + "-" + arch + extension "exe" + } + } +} + +publishing { + publications { + maven(MavenPublication) { + groupId = 'io.grpc' + artifactId = 'proto-kotlin' + version = '0.1' + from components.java + + // Generate the artifacts expected by protobuf-gradle-plugin + artifactForGradlePlugin(it, 'linux', 'aarch_64') + artifactForGradlePlugin(it, 'linux', 'x86_32') + artifactForGradlePlugin(it, 'linux', 'x86_64') + artifactForGradlePlugin(it, 'osx', 'x86_64') + artifactForGradlePlugin(it, 'windows', 'x86_32') + artifactForGradlePlugin(it, 'windows', 'x86_64') + } + } +} diff --git a/kotlin/protobuf/example/src/main/kotlin/com/example/tutorial/AddPerson.kt b/kotlin/protobuf/example/src/main/kotlin/com/example/tutorial/AddPerson.kt new file mode 100644 index 000000000000..1a325ca4f819 --- /dev/null +++ b/kotlin/protobuf/example/src/main/kotlin/com/example/tutorial/AddPerson.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.tutorial + +import com.example.tutorial.AddressBookProtos.AddressBook +import com.example.tutorial.AddressBookProtos.Person +import com.example.tutorial.AddressBookProtosKt.PersonKt.phoneNumber +import com.example.tutorial.AddressBookProtosKt.person +import java.io.BufferedReader +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.InputStreamReader +import java.io.PrintStream +import kotlin.system.exitProcess + +object AddPerson { + fun promptForAddress(input: BufferedReader, output: PrintStream): Person { + return person { + output.print("Enter person ID: ") + id = input.readLine().toInt() + + output.print("Enter name: ") + name = input.readLine() + + output.print("Enter email address (blank for none): ") + val email = input.readLine() + if (email.isNotEmpty()) { + this.email = email + } + + while (true) { + output.print("Enter a phone number (or leave blank to finish): ") + val number = input.readLine() + if (number.isEmpty()) { + break + } + + phone += phoneNumber { + this.number = number + output.print("Is this a mobile, home, or work phone? ") + when (input.readLine()) { + "mobile" -> type = Person.PhoneType.MOBILE + "home" -> type = Person.PhoneType.HOME + "work" -> type = Person.PhoneType.WORK + else -> output.println("Unknown phone type. Using default.") + } + } + } + } + } + + @JvmStatic + fun main(args: Array) { + if (args.size != 1) { + System.err.println("Usage: AddPerson ADDRESS_BOOK_FILE") + exitProcess(-1) + } + + val addressBook = try { + FileInputStream(args.single()).use { + AddressBook.parseFrom(it) + } + } catch (e: FileNotFoundException) { + AddressBook.getDefaultInstance() + } + + val newAddressBook = addressBook.copy { + BufferedReader(InputStreamReader(System.`in`, Charsets.UTF_8)).use { + person += promptForAddress(it, System.out) + } + } + + FileOutputStream(args.single()).use { + newAddressBook.writeTo(it) + } + } +} \ No newline at end of file diff --git a/kotlin/protobuf/example/src/main/kotlin/com/example/tutorial/ListPeople.kt b/kotlin/protobuf/example/src/main/kotlin/com/example/tutorial/ListPeople.kt new file mode 100644 index 000000000000..1835f2545855 --- /dev/null +++ b/kotlin/protobuf/example/src/main/kotlin/com/example/tutorial/ListPeople.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.tutorial + +import com.example.tutorial.AddressBookProtos.AddressBook +import java.io.FileInputStream +import kotlin.system.exitProcess + +object ListPeople { + fun print(addressBook: AddressBook) { + for (person in addressBook.personList) { + println("Person ID: ${person.id}") + println(" Name: ${person.name}") + if (person.hasEmail()) { + println(" E-mail address: ${person.email}") + } + for (phoneNumber in person.phoneList) { + when (phoneNumber.type) { + AddressBookProtos.Person.PhoneType.MOBILE -> print(" Mobile phone #: ") + AddressBookProtos.Person.PhoneType.HOME -> print(" Home phone #: ") + AddressBookProtos.Person.PhoneType.WORK -> print(" Work phone #: ") + } + println(phoneNumber.number) + } + } + } + + @JvmStatic + fun main(args: Array) { + if (args.size != 1) { + System.err.println("Usage: ListPeople ADDRESS_BOOK_FILE") + exitProcess(-1) + } + print(FileInputStream(args.single()).use { + AddressBook.parseFrom(it) + }) + } +} \ No newline at end of file diff --git a/kotlin/protobuf/example/src/main/proto/addressbook.proto b/kotlin/protobuf/example/src/main/proto/addressbook.proto new file mode 100644 index 000000000000..1cacaaa855cc --- /dev/null +++ b/kotlin/protobuf/example/src/main/proto/addressbook.proto @@ -0,0 +1,29 @@ +syntax = "proto2"; + +package tutorial; + +option java_package = "com.example.tutorial"; +option java_outer_classname = "AddressBookProtos"; + +message Person { + required string name = 1; + required int32 id = 2; + optional string email = 3; + + enum PhoneType { + MOBILE = 0; + HOME = 1; + WORK = 2; + } + + message PhoneNumber { + required string number = 1; + optional PhoneType type = 2 [default = HOME]; + } + + repeated PhoneNumber phone = 4; +} + +message AddressBook { + repeated Person person = 1; +} diff --git a/kotlin/protobuf/src/main/dist/protoc-gen-kotlin b/kotlin/protobuf/src/main/dist/protoc-gen-kotlin new file mode 100644 index 000000000000..346792d5aa71 --- /dev/null +++ b/kotlin/protobuf/src/main/dist/protoc-gen-kotlin @@ -0,0 +1,26 @@ +#!/bin/bash +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`" >/dev/null +APP_HOME="`pwd -P`" +APP_BASE_NAME=`basename "$0"` + +# Get jar file name from current filename by stripping os and arch +JAR_FILENAME=${APP_BASE_NAME%.*} +JAR_FILENAME=${JAR_FILENAME/-linux-aarch_64/} +JAR_FILENAME=${JAR_FILENAME/-linux-x86_32/} +JAR_FILENAME=${JAR_FILENAME/-linux-x86_64/} +JAR_FILENAME=${JAR_FILENAME/-osx-x86_64/} + +# Call the jar and pass our args +java -jar $JAR_FILENAME.jar $1 $2 diff --git a/kotlin/protobuf/src/main/dist/protoc-gen-kotlin.bat b/kotlin/protobuf/src/main/dist/protoc-gen-kotlin.bat new file mode 100644 index 000000000000..9b0a9c53d11f --- /dev/null +++ b/kotlin/protobuf/src/main/dist/protoc-gen-kotlin.bat @@ -0,0 +1,86 @@ +@REM +@REM Copyright 2020 Google LLC +@REM +@REM Licensed under the Apache License, Version 2.0 (the "License"); +@REM you may not use this file except in compliance with the License. +@REM You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, software +@REM distributed under the License is distributed on an "AS IS" BASIS, +@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@REM See the License for the specific language governing permissions and +@REM limitations under the License. +@REM + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem proto-kotlin startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME%.. + +@rem Add default JVM options here. You can also use JAVA_OPTS and PROTO_KOTLIN_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +setlocal ENABLEDELAYEDEXPANSION +set JAR_FILENAME=%~n0.jar +@rem Get jar file name from current filename by stripping os and arch +call set JAR_FILENAME=%%JAR_FILENAME:-windows-x86_32=%% +call set JAR_FILENAME=%%JAR_FILENAME:-windows-x86_64=%% +@rem Execute proto-kotlin +"%JAVA_EXE%" -jar "%JAR_FILENAME%" %1 %2 + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable PROTO_KOTLIN_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%PROTO_KOTLIN_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/DslList.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/DslList.kt new file mode 100644 index 000000000000..93bd76aa7b7b --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/DslList.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin + +/** + * A simple wrapper around a [List] with an extra generic parameter that can be used to disambiguate + * extension methods. + * + *

This class is used by Kotlin protocol buffer extensions, and its constructor is public only + * because generated message code is in a different compilation unit. Others should not use this + * class directly in any way. + */ +@Suppress("unused") // the unused type parameter +class DslList(private val delegate: List) : List by delegate + +// TODO(lowasser): investigate making this an inline class diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/DslMap.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/DslMap.kt new file mode 100644 index 000000000000..ae38b13815b3 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/DslMap.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin + +/** + * A simple wrapper around a [Map] with an extra generic parameter that can be used to disambiguate + * extension methods. + * + *

This class is used by Kotlin protocol buffer extensions, and its constructor is public only + * because generated message code is in a different compilation unit. Others should not use this + * class directly in any way. + */ +@Suppress("unused") // the unused type parameter +class DslMap(private val delegate: Map) : Map by delegate + +// TODO(lowasser): investigate making this an inline class diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslLite.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslLite.kt new file mode 100644 index 000000000000..59c91c14cf97 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslLite.kt @@ -0,0 +1,111 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin + +import com.google.protobuf.ByteString +import com.google.protobuf.ExtensionLite +import com.google.protobuf.GeneratedMessageLite +import com.google.protobuf.MessageLite + +/** Superclass of DSLs for the creation of protos in the lite runtime. */ +abstract class ExtendableProtoDslLite< + M : GeneratedMessageLite.ExtendableMessage, + B : GeneratedMessageLite.ExtendableBuilder>( + builder: B +) : ProtoDsl(builder) { + // expose the builder to the below extension functions + internal val builder: B get() = _proto_dsl_builder + + /** Get the current value of the proto extension in this DSL. */ + operator fun get(extension: ExtensionLite): T { + return if (extension.isRepeated) { + @Suppress("UNCHECKED_CAST") + ExtensionListLite(builder, extension as ExtensionLite>) as T + } else { + builder.getExtension(extension) + } + } + + /** Gets the values of the repeated proto extension in this DSL. */ + @JvmName("getRepeatedExtension") // to avoid erasure issues + operator fun get(extension: ExtensionLite>): ExtensionListLite = + ExtensionListLite(builder, extension) + + /** Analog to [GeneratedMessageLite.ExtendableBuilder.hasExtension]. */ + operator fun contains(extension: ExtensionLite): Boolean = + builder.hasExtension(extension) + + /** Adds a value to this repeated extension. */ + fun , E> + ExtensionListLite.add(element: E) { + addElement(element) + } + + /** Adds the values to this repeated extension. */ + fun , E> + ExtensionListLite.addAll(elements: Iterable) { + elements.forEach { add(it) } + } + + /** Adds a value to this repeated extension. */ + operator fun , E> + ExtensionListLite.plusAssign(element: E) { + add(element) + } + + /** Adds the values to this repeated extension. */ + operator fun , E> + ExtensionListLite.plusAssign(elements: Iterable) { + elements.forEach { add(it) } + } + + /** Sets the value of one element of this repeated extension. */ + operator fun , E> + ExtensionListLite.set(index: Int, element: E) { + setElement(index, element) + } + + /** Clears all elements of this repeated extension. */ + fun , E> + ExtensionListLite.clear() { + clearElements() + } + + /** Sets the value of the proto extension. */ + @JvmName("setMessageExtension") // to avoid issues with erasure + operator fun set(extension: ExtensionLite, value: T) { + builder.setExtension(extension, value) + } + + /** Sets the value of the proto extension. */ + @JvmName("setByteStringExtension") // to avoid issues with erasure + operator fun set(extension: ExtensionLite, value: ByteString) { + builder.setExtension(extension, value) + } + + /** Sets the value of the proto extension. */ + @JvmName("setComparableExtension") // to avoid issues with erasure + // Comparable covers String, Enum, and all primitives + operator fun > set(extension: ExtensionLite, value: T) { + builder.setExtension(extension, value) + } + + /** Clears the specified extension. */ + fun clearExtension(extension: ExtensionLite) { + builder.clearExtension(extension) + } +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslV3.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslV3.kt new file mode 100644 index 000000000000..2927f9ed91ea --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslV3.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// not our choice +@file:Suppress("FINITE_BOUNDS_VIOLATION_IN_JAVA") + +package com.google.protobuf.kotlin + +import com.google.protobuf.ByteString +import com.google.protobuf.ExtensionLite +import com.google.protobuf.GeneratedMessageV3 +import com.google.protobuf.Message + +/** Superclass of DSLs for the creation of extendable protos in the full runtime. */ +abstract class ExtendableProtoDslV3< + M : GeneratedMessageV3.ExtendableMessage, + B : GeneratedMessageV3.ExtendableBuilder + >(builder: B) : ProtoDsl(builder) { + // expose the builder to the below extension functions + internal val builder: B get() = _proto_dsl_builder + + /** Get the current value of the proto extension in this DSL. */ + operator fun get(extension: ExtensionLite): T { + return if (extension.isRepeated) { + @Suppress("UNCHECKED_CAST") + ExtensionListV3(builder, extension as ExtensionLite>) as T + } else { + builder.getExtension(extension) + } + } + + /** Gets the values of the repeated proto extension in this DSL. */ + @JvmName("getRepeatedExtension") // to avoid issues with erasure + operator fun get(extension: ExtensionLite>): ExtensionListV3 = + ExtensionListV3(builder, extension) + + /** Analog to [GeneratedMessageV3.ExtendableBuilder.hasExtension]. */ + operator fun contains(extension: ExtensionLite): Boolean = builder.hasExtension(extension) + + /** Adds an element to this proto extension. */ + fun ExtensionListV3.add(element: E) { + addElement(element) + } + + /** Adds elements to this proto extension. */ + fun ExtensionListV3.addAll(elements: Iterable) { + elements.forEach { add(it) } + } + + /** Adds an element to this proto extension. */ + operator fun ExtensionListV3.plusAssign(element: E) { + addElement(element) + } + + /** Adds elements to this proto extension. */ + operator fun ExtensionListV3.plusAssign(elements: Iterable) { + elements.forEach { add(it) } + } + + /** Sets the specified element of the proto extension. */ + operator fun ExtensionListV3.set(index: Int, element: E) { + setElement(index, element) + } + + /** Clears the elements of the proto extension. */ + fun ExtensionListV3.clear() { + clearElements() + } + + // everything except repeated fields + + /** Sets the value of the proto extension. */ + @JvmName("setMessageExtension") // to avoid issues with erasure + operator fun set(extension: ExtensionLite, value: T) { + builder.setExtension(extension, value) + } + + /** Sets the value of the proto extension. */ + @JvmName("setByteStringExtension") // to avoid issues with erasure + operator fun set(extension: ExtensionLite, value: ByteString) { + builder.setExtension(extension, value) + } + + /** Sets the value of the proto extension. */ + @JvmName("setComparableExtension") // to avoid issues with erasure + // Comparable covers String, Enum, and all primitives + operator fun > set(extension: ExtensionLite, value: T) { + builder.setExtension(extension, value) + } + + /** Clears the value of the proto extension. */ + fun clearExtension(extension: ExtensionLite) { + builder.clearExtension(extension) + } +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtensionListLite.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtensionListLite.kt new file mode 100644 index 000000000000..364d2818bf6d --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtensionListLite.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin + +import com.google.protobuf.ExtensionLite +import com.google.protobuf.GeneratedMessageLite + +/** An unmodifiable view of a repeated extension field in a proto builder in the lite runtime. */ +class ExtensionListLite< + E, + M : GeneratedMessageLite.ExtendableMessage, + B : GeneratedMessageLite.ExtendableBuilder> internal constructor( + private val builder: B, + private val extension: ExtensionLite> +) : AbstractList(), RandomAccess { + override val size: Int + get() = builder.getExtensionCount(extension) + + override fun get(index: Int): E = builder.getExtension(extension, index) + + internal fun addElement(e: E) { + builder.addExtension(extension, e) + } + + internal fun setElement(index: Int, e: E) { + builder.setExtension(extension, index, e) + } + + internal fun clearElements() { + builder.clearExtension(extension) + } +} \ No newline at end of file diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtensionListV3.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtensionListV3.kt new file mode 100644 index 000000000000..2394107d0990 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtensionListV3.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin + +import com.google.protobuf.ExtensionLite +import com.google.protobuf.GeneratedMessageV3 + +/** An unmodifiable view of a repeated extension field in a proto builder. */ +@Suppress("FINITE_BOUNDS_VIOLATION_IN_JAVA", "unused") +class ExtensionListV3< + E, + M : GeneratedMessageV3.ExtendableMessage, + B : GeneratedMessageV3.ExtendableBuilder> internal constructor( + internal val builder: B, + internal val extension: ExtensionLite> +) : AbstractList(), RandomAccess { + override val size: Int + get() = builder.getExtensionCount(extension) + + override fun get(index: Int): E = builder.getExtension(extension, index) + + internal fun addElement(e: E) { + builder.addExtension(extension, e) + } + + internal fun setElement(index: Int, e: E) { + builder.setExtension(extension, index, e) + } + + internal fun clearElements() { + builder.clearExtension(extension) + } +} \ No newline at end of file diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/OneofRepresentation.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/OneofRepresentation.kt new file mode 100644 index 000000000000..c40a1458c405 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/OneofRepresentation.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin + +/** + * A representation of a choice of a oneof option in a proto message, including the value associated + * with the chosen field. + * + * User code should never need to reference this type. They should, instead, reference the subtypes + * generated by the Kotlin proto extension generator. + */ +abstract class OneofRepresentation> { + + abstract val value: T + abstract val case: C + + override fun equals(other: Any?): Boolean = + other is OneofRepresentation<*, *> && + case == other.case && // enum cases should be uniquely associated with a specific oneof + value == other.value + + override fun hashCode(): Int = + 31 * case.hashCode() + value.hashCode() + + override fun toString(): String = + "OneofRepresentation(case=$case,value=$value)" +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ProtoDsl.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ProtoDsl.kt new file mode 100644 index 000000000000..b52ec9a45133 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ProtoDsl.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin + +import com.google.protobuf.MessageLite + +/** Superclass of DSLs for the creation of protos, shared between the lite and full runtimes. */ +abstract class ProtoDsl(builder: B) { + @Suppress("PropertyName") // we specifically want to avoid conflict with any subclasses + protected val _proto_dsl_builder: B = builder +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ProtoDslMarker.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ProtoDslMarker.kt new file mode 100644 index 000000000000..d00142d0e7b6 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ProtoDslMarker.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin + +/** + * Indicates an API that is part of a DSL to generate protocol buffer messages. + */ +@DslMarker +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.BINARY) +annotation class ProtoDslMarker diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageLiteExtensions.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageLiteExtensions.kt new file mode 100644 index 000000000000..367e59c8e0d2 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageLiteExtensions.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.extensions + +import com.google.protobuf.ExtensionLite +import com.google.protobuf.GeneratedMessageLite + + +/** Gets the value of the proto extension. */ +operator fun < + M : GeneratedMessageLite.ExtendableMessage, + MOrBT : GeneratedMessageLite.ExtendableMessageOrBuilder, + T + > MOrBT.get(extension: ExtensionLite): T = getExtension(extension) + +/** Sets the current value of the proto extension in this builder. */ +operator fun < + M : GeneratedMessageLite.ExtendableMessage, + B : GeneratedMessageLite.ExtendableBuilder, + T + > B.set(extension: ExtensionLite, value: T) { + setExtension(extension, value) +} + +/** Returns true if the specified extension is set. */ +operator fun < + M : GeneratedMessageLite.ExtendableMessage, + MorBT : GeneratedMessageLite.ExtendableMessageOrBuilder + > MorBT.contains( + extension: ExtensionLite +): Boolean = hasExtension(extension) diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageV3Extensions.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageV3Extensions.kt new file mode 100644 index 000000000000..ea530ef80d20 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageV3Extensions.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.extensions + +import com.google.protobuf.ExtensionLite +import com.google.protobuf.GeneratedMessageV3 + +/** Sets the current value of the proto extension in this builder.*/ +operator fun < + M : GeneratedMessageV3.ExtendableMessage, + B : GeneratedMessageV3.ExtendableBuilder, + T + > B.set(extension: ExtensionLite, value: T) { + setExtension(extension, value) +} + +/** Gets the current value of the proto extension. */ +operator fun < + M : GeneratedMessageV3.ExtendableMessage, + MorBT : GeneratedMessageV3.ExtendableMessageOrBuilder, + T + > MorBT.get(extension: ExtensionLite): T = getExtension(extension) + +/** Returns true if the specified extension is set on this builder. */ +operator fun < + M : GeneratedMessageV3.ExtendableMessage, + MorBT : GeneratedMessageV3.ExtendableMessageOrBuilder + > MorBT.contains(extension: ExtensionLite): Boolean = hasExtension(extension) diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/AbstractOrNullPropertyGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/AbstractOrNullPropertyGenerator.kt new file mode 100644 index 000000000000..ed9cfd65aabd --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/AbstractOrNullPropertyGenerator.kt @@ -0,0 +1,154 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.Descriptors.Descriptor +import com.google.protobuf.kotlin.protoc.Declarations +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.Scope +import com.google.protobuf.kotlin.protoc.SingletonFieldDescriptor +import com.google.protobuf.kotlin.protoc.builder +import com.google.protobuf.kotlin.protoc.declarations +import com.google.protobuf.kotlin.protoc.of +import com.google.protobuf.kotlin.protoc.simpleName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec + +/** Superclass of generators for OrNull properties. */ +abstract class AbstractOrNullPropertyGenerator { + + abstract fun shouldGenerate( + field: SingletonFieldDescriptor + ): Boolean + + /** Generate logic for "does have this field set." */ + abstract fun GeneratorConfig.has( + receiver: CodeBlock, + field: SingletonFieldDescriptor + ): CodeBlock + + private fun GeneratorConfig.orNullProperty( + field: SingletonFieldDescriptor, + receiverTy: TypeName?, + receiverCode: CodeBlock, + mutable: Boolean + ): PropertySpec { + val orNullName = field.propertySimpleName.withSuffix("OrNull") + val orNullType = field.type().copy(nullable = true) + val builder = + PropertySpec + .builder(orNullName, orNullType) + .addKdoc( + "The %L field of the proto %L, or null if it isn't set.", + field.fieldName, field.containingType.simpleName + ) + + receiverTy?.let { builder.receiver(it) } + + builder.getter( + getterBuilder() + .addStatement( + "return if (%L) %L.%N else null", + has(receiverCode, field), + receiverCode, + field.property() + ) + .build() + ) + + if (mutable) { + val param = ParameterSpec.of("_newValue", orNullType) + builder.mutable(true) + builder.setter( + setterBuilder() + .addParameter(param) + .beginControlFlow("if (%N == null)", param) + .addStatement("%L.%L()", receiverCode, field.propertySimpleName.withPrefix("clear")) + .nextControlFlow("else") + .addStatement("%L.%N = %N", receiverCode, field.property(), param) + .endControlFlow() + .build() + ) + } + + return builder.build() + } + + val forMessageOrBuilder: ComponentExtensionGenerator = + ComponentGenerator( + receiverType = { it.orBuilderClass() }, + generateIfRequired = false, + mutable = false + ) + + val forBuilder: ComponentExtensionGenerator = + ComponentGenerator( + receiverType = { it.builderClass() }, + generateIfRequired = true, + mutable = true + ) + + private inner class ComponentGenerator( + val receiverType: GeneratorConfig.(Descriptor) -> TypeName, + val mutable: Boolean, + val generateIfRequired: Boolean + ) : ComponentExtensionGenerator(SingletonFieldDescriptor::class) { + override fun GeneratorConfig.componentExtensions( + component: SingletonFieldDescriptor, + enclosingScope: Scope, + extensionScope: Scope + ): Declarations = declarations { + if (shouldGenerate(component) && (generateIfRequired || !component.isRequired())) { + addTopLevelProperty( + orNullProperty( + component, + receiverType(component.containingType), + CodeBlock.of("this"), + mutable + ) + ) + } + } + } + + val forDsl: DslComponentGenerator = + object : DslComponentGenerator(SingletonFieldDescriptor::class) { + override fun GeneratorConfig.generate( + component: SingletonFieldDescriptor, + builderCode: CodeBlock, + dslBuilder: TypeSpec.Builder, + companionBuilder: TypeSpec.Builder, + enclosingScope: Scope, + extensionScope: Scope, + dslType: TypeName + ) { + if (shouldGenerate(component)) { + dslBuilder.addProperty( + orNullProperty( + field = component, + mutable = true, + receiverCode = builderCode, + receiverTy = null // instance property + ) + ) + } + } + } +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ComponentExtensionGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ComponentExtensionGenerator.kt new file mode 100644 index 000000000000..9652fe584037 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ComponentExtensionGenerator.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.Descriptors +import com.google.protobuf.Descriptors.FieldDescriptor +import com.google.protobuf.kotlin.protoc.Declarations +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.MessageComponent +import com.google.protobuf.kotlin.protoc.OneofComponent +import com.google.protobuf.kotlin.protoc.Scope +import com.google.protobuf.kotlin.protoc.declarations +import com.google.protobuf.kotlin.protoc.specialize +import kotlin.reflect.KClass + +/** Generates extensions for a specific type of component of a message. */ +abstract class ComponentExtensionGenerator( + private val componentType: KClass +) : ExtensionGenerator() { + final override fun GeneratorConfig.shallowExtensions( + descriptor: Descriptors.Descriptor, + enclosingScope: Scope, + extensionScope: Scope + ): Declarations = declarations { + val components = + descriptor.fields.map(FieldDescriptor::specialize) + + descriptor.oneofs.map(::OneofComponent) + for (component in components.filterIsInstance(componentType.java)) { + merge(componentExtensions(component, enclosingScope, extensionScope)) + } + } + + abstract fun GeneratorConfig.componentExtensions( + component: T, + enclosingScope: Scope, + extensionScope: Scope + ): Declarations +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/Deprecation.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/Deprecation.kt new file mode 100644 index 000000000000..154c284481cf --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/Deprecation.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.SpecializedFieldDescriptor +import com.squareup.kotlinpoet.AnnotationSpec + +/** + * Helpers for annotations related to deprecated proto fields. + */ +object Deprecation { + private val suppressDeprecation = + AnnotationSpec + .builder(Suppress::class) + .addMember("%S", "DEPRECATION") + .build() + + /** + * Returns annotations for public APIs related to deprecation of this field, if any. + */ + fun apiAnnotations(descriptor: SpecializedFieldDescriptor): List = + if (descriptor.isDeprecated) { + listOf( + AnnotationSpec.builder(Deprecated::class) + .addMember("message = %S", "Field ${descriptor.fieldName} is deprecated") + .build() + ) + } else { + listOf() + } +} \ No newline at end of file diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/DslComponentGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/DslComponentGenerator.kt new file mode 100644 index 000000000000..c025f004b447 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/DslComponentGenerator.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.MessageComponent +import com.google.protobuf.kotlin.protoc.Scope +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec +import kotlin.reflect.KClass +import kotlin.reflect.full.safeCast + +/** + * Generates a set of members in a DSL for a message type, for a specific component of that + * message's definition, such as a field. + */ +abstract class DslComponentGenerator( + /** The type of component this generator can generate code for. */ + private val componentType: KClass +) { + + /** + * Generate declarations for the specified component, if this generator knows how to handle it. + * + * @param component The component to generate declarations for. + * @param builderCode A [CodeBlock] to get access to the underlying + * [com.google.protobuf.Message.Builder] in this DSL. + * @param dslBuilder A builder for the DSL class, to add declarations to. + * @param companionBuilder A builder for the companion object of the DSL class, to add + * declarations to. + * @param enclosingScope The scope containing the DSL class. + * @param extensionScope The scope containing extensions for the message type. + * @param dslType The fully qualified name of the DSL class. + */ + fun GeneratorConfig.generateIfApplicable( + component: MessageComponent, + builderCode: CodeBlock, + dslBuilder: TypeSpec.Builder, + companionBuilder: TypeSpec.Builder, + enclosingScope: Scope, + extensionScope: Scope, + dslType: TypeName + ) { + componentType.safeCast(component)?.let { + generate( + it, builderCode, dslBuilder, companionBuilder, enclosingScope, extensionScope, dslType + ) + } + } + + /** + * Generate declarations for the specified component, which is of the type handled by this + * generator. + */ + abstract fun GeneratorConfig.generate( + component: C, + builderCode: CodeBlock, + dslBuilder: TypeSpec.Builder, + companionBuilder: TypeSpec.Builder, + enclosingScope: Scope, + extensionScope: Scope, + dslType: TypeName + ) +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/DslGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/DslGenerator.kt new file mode 100644 index 000000000000..956369fef20f --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/DslGenerator.kt @@ -0,0 +1,238 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.Descriptors.Descriptor +import com.google.protobuf.kotlin.ProtoDsl +import com.google.protobuf.kotlin.ProtoDslMarker +import com.google.protobuf.kotlin.protoc.ClassSimpleName +import com.google.protobuf.kotlin.protoc.Declarations +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.MemberSimpleName +import com.google.protobuf.kotlin.protoc.OneofComponent +import com.google.protobuf.kotlin.protoc.Scope +import com.google.protobuf.kotlin.protoc.builder +import com.google.protobuf.kotlin.protoc.classBuilder +import com.google.protobuf.kotlin.protoc.declarations +import com.google.protobuf.kotlin.protoc.messageClassSimpleName +import com.google.protobuf.kotlin.protoc.of +import com.google.protobuf.kotlin.protoc.specialize +import com.squareup.kotlinpoet.ANY +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.LambdaTypeName +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.UNIT +import com.squareup.kotlinpoet.asTypeName + +/** Generates a DSL class and factory method for a message. */ +class DslGenerator( + private val extendableDslSuperclass: ClassName, + /** Any suffix, e.g. "Dsl", to add to the DSL class for the message type. */ + private val dslClassNameSuffix: String = "Dsl", + /** Any prefix, e.g. "create", to add to the factory method for the message type. */ + private val dslFactoryNamePrefix: String = "", + /** If a "copy with modifications" method should be generated, what to name it. */ + private val copyName: MemberSimpleName? = null, + /** Generators for the members of the DSL. */ + private vararg val componentGenerators: DslComponentGenerator<*> +) : ExtensionGenerator() { + + companion object { + // so as not to conflict with any proto fields named builder + private val BUILDER_SIMPLE_NAME = MemberSimpleName("builder_") + + private val DEFAULT_DSL_SUPERCLASS = ProtoDsl::class.asTypeName() + + private const val DEFAULT_DSL_BUILDER_PROPERTY_NAME = "_proto_dsl_builder" + } + + /** Generate the DSL as a peer of the extension class, not inside it. */ + override val generateWithinExtensionClass: Boolean + get() = false + + /** A simple name for the DSL class for this message. */ + private val Descriptor.dslClassSimpleName: ClassSimpleName + get() = messageClassSimpleName.withSuffix(dslClassNameSuffix) + + /** A simple name for the factory method for this message. */ + private val Descriptor.dslFactorySimpleName: MemberSimpleName + get() = messageClassSimpleName.asMemberWithPrefix(dslFactoryNamePrefix) + + private fun TypeSpec.isEmpty(): Boolean = + propertySpecs.isEmpty() && + funSpecs.isEmpty() && + typeSpecs.isEmpty() && + superinterfaces.isEmpty() && + superclass == ANY + + override fun GeneratorConfig.shallowExtensions( + descriptor: Descriptor, + enclosingScope: Scope, + extensionScope: Scope + ): Declarations = + declarations { + val builderClass = descriptor.builderClass() + val messageClass = descriptor.messageClass() + + // what is the DSL class' name and what scope is it defined in? + val dslClassSimpleName: ClassSimpleName = descriptor.dslClassSimpleName + val dslScope: Scope = scope(enclosingScope, extensionScope) + + val dslClass = dslScope.nestedClass(dslClassSimpleName) + + // the constructor parameter for the underlying builder + val builderParam = ParameterSpec.of(BUILDER_SIMPLE_NAME, builderClass) + + val dslClassSpecBuilder = + TypeSpec.classBuilder(dslClassSimpleName) + .addAnnotation(ProtoDslMarker::class) + .primaryConstructor( + FunSpec.constructorBuilder() + .addModifiers(KModifier.INTERNAL) + .addAnnotation(PublishedApi::class) + .addParameter(builderParam) + .build() + ) + .addKdoc( + "A DSL type for creating %L messages. To use it, call the [%L] method.", + descriptor.name, + descriptor.dslFactorySimpleName + ) + + if (descriptor.isExtendable) { + dslClassSpecBuilder + .superclass(extendableDslSuperclass.parameterizedBy(messageClass, builderClass)) + } else { + dslClassSpecBuilder + .superclass(DEFAULT_DSL_SUPERCLASS.parameterizedBy(builderClass)) + } + dslClassSpecBuilder.addSuperclassConstructorParameter("%N", builderParam) + + val dslBuilderProperty = + PropertySpec.builder(BUILDER_SIMPLE_NAME, builderClass) + .addModifiers(KModifier.INTERNAL) + .addAnnotation(PublishedApi::class) + .getter( + FunSpec.getterBuilder() + .addStatement("return %L", DEFAULT_DSL_BUILDER_PROPERTY_NAME) + .build() + ) + .build() + dslClassSpecBuilder.addProperty(dslBuilderProperty) + val dslBuilderCode = CodeBlock.of("%N", dslBuilderProperty) + + val dslBuildFun = + FunSpec.builder("build") + .returns(messageClass) + .addModifiers(KModifier.INTERNAL) + .addAnnotation(PublishedApi::class) + // to allow the public inline factory method to call it + .addStatement("return %N.build()", dslBuilderProperty) + .addKdoc("Builds the %L.", descriptor.name) + .build() + dslClassSpecBuilder.addFunction(dslBuildFun) + + val companionObjectBuilder = TypeSpec.companionObjectBuilder() + + // pieces of the message to generate API for + val components = + descriptor.fields.map { it.specialize() } + descriptor.oneofs.map(::OneofComponent) + + for (generator in componentGenerators) { + generator.run { + for (component in components) { + generateIfApplicable( + component, + dslBuilderCode, + dslClassSpecBuilder, + companionObjectBuilder, + enclosingScope, + extensionScope, + dslClass + ) + } + } + } + + // add in the companion object + val companionObject = companionObjectBuilder.build() + if (!companionObject.isEmpty()) { + // avoiding companion objects makes it easier to optimize (no static initializers), so + // don't add one unless necessary. + dslClassSpecBuilder.addType(companionObject) + } + + val dslClassSpec = dslClassSpecBuilder.build() + + // parameter for the DSL factory method + val dslFactoryMethodCallback = ParameterSpec.of( + "block", + LambdaTypeName.get( + receiver = dslClass, + returnType = UNIT + ) + ) + + addFunction( + FunSpec + .builder(descriptor.dslFactorySimpleName) + .addModifiers(KModifier.INLINE) + .addParameter(dslFactoryMethodCallback) + .addAnnotation(JvmSynthetic::class) // uncallable from Java + .addKdoc("Entry point for creating %L messages in Kotlin.", descriptor.name) + .addStatement( + "return %N(%T.newBuilder()).apply(%N).%N()", + dslClassSpec, + messageClass, + dslFactoryMethodCallback, + dslBuildFun + ) + .build() + ) + + if (copyName != null) { + addTopLevelFunction( + FunSpec + .builder(copyName) + .addModifiers(KModifier.INLINE) + .addAnnotation(JvmSynthetic::class) // uncallable from Java + .receiver(messageClass) + .returns(messageClass) + .addParameter(dslFactoryMethodCallback) + .addKdoc( + "Creates a %T equivalent to this one, but with the specified modifications.", + messageClass + ) + .addStatement( + "return %T(toBuilder()).apply(%N).%N()", + dslClass, + dslFactoryMethodCallback, + dslBuildFun + ) + .build() + ) + } + + addType(dslClassSpec) + } +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ExtensionGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ExtensionGenerator.kt new file mode 100644 index 000000000000..636ba426dac3 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ExtensionGenerator.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.Descriptors.Descriptor +import com.google.protobuf.kotlin.protoc.Declarations +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.Scope + +/** + * A strategy for generating Kotlin augmentations atop the existing Java code for a message, + * given its descriptor. + */ +abstract class ExtensionGenerator { + /** + * Generate extensions for the specified message, specifically excluding any sub-messages, as + * a set of [Declarations]. + * + * @param descriptor The message to generate extensions for. + * @param enclosingScope The scope containing extensions for this message type. + * @param extensionScope The scope of the extension object for this message type. + */ + abstract fun GeneratorConfig.shallowExtensions( + descriptor: Descriptor, + enclosingScope: Scope, + extensionScope: Scope + ): Declarations + + /** + * True if we should generate this code inside the extension object. False if we should generate + * it as a peer of the extension object, at the same level. + */ + open val generateWithinExtensionClass: Boolean + get() = true + + /** + * The scope in which this extension generator generates its APIs. Can be useful for cross- + * referencing APIs generated by one generator in another. + */ + fun scope(enclosingScope: Scope, extensionScope: Scope): Scope = + if (generateWithinExtensionClass) extensionScope else enclosingScope +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGenerator.kt new file mode 100644 index 000000000000..bfce4fa5327a --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGenerator.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.OneofOptionFieldDescriptor +import com.google.protobuf.kotlin.protoc.SingletonFieldDescriptor +import com.squareup.kotlinpoet.CodeBlock + +/** Generates OrNull nullable extension properties for fields with hazzers. */ +class FieldOrNullPropertyGenerator( + private val skipOneofFields: Boolean = true +) : AbstractOrNullPropertyGenerator() { + override fun shouldGenerate(field: SingletonFieldDescriptor): Boolean = + field.hasHazzer() && !(skipOneofFields && field is OneofOptionFieldDescriptor) + + override fun GeneratorConfig.has( + receiver: CodeBlock, + field: SingletonFieldDescriptor + ): CodeBlock = CodeBlock.of("%L.%N()", receiver, field.hazzer()!!) +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/GeneratorRunner.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/GeneratorRunner.kt new file mode 100644 index 000000000000..0f0cc5699a5f --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/GeneratorRunner.kt @@ -0,0 +1,121 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.Descriptors +import com.google.protobuf.kotlin.protoc.AbstractGeneratorRunner +import com.google.protobuf.kotlin.protoc.ClassSimpleName +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.JavaPackagePolicy +import com.google.protobuf.kotlin.protoc.MemberSimpleName +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FileSpec + +/** Runner for the proto API generator. */ +class GeneratorRunner(runtimeVariant: RuntimeVariant) : AbstractGeneratorRunner() { + enum class RuntimeVariant( + packageName: String, + dslSuperclassSimpleName: String + ) { + FULL("com.google.protobuf.kotlin", "ExtendableProtoDslV3"), + LITE("com.google.protobuf.kotlin", "ExtendableProtoDslLite"); + + val extendableDslSuperclass: ClassName = ClassName(packageName, dslSuperclassSimpleName) + } + + companion object { + @JvmStatic + fun main(args: Array) { + var runtimeVariant = RuntimeVariant.FULL + if (args.size == 3) { + runtimeVariant = when (args[2]) { + "lite" -> RuntimeVariant.LITE + "full" -> RuntimeVariant.FULL + else -> throw IllegalArgumentException( + "Expected first command line arg to be 'lite' or 'full' but was '${args[0]}'" + ) + } + GeneratorRunner(runtimeVariant).doMain(args.drop(1).toTypedArray()) + } else { + GeneratorRunner(runtimeVariant).doMain(args) + } + } + } + + override fun generateCodeForFile(file: Descriptors.FileDescriptor): List = + listOf(generator.generate(file)) + + private val config = GeneratorConfig( + javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE, + aggressiveInlining = false + ) + + private val oneofClassGenerator = OneofClassGenerator( + oneofClassSuffix = "Oneof", + oneofSubclassSuffix = "", + notSetSimpleName = ClassSimpleName("NotSet"), + propertySuffix = "", + extensionFactories = *arrayOf( + OneofClassGenerator::orBuilderVal, + OneofClassGenerator::builderVar + ) + ) + + val generator = ProtoFileKotlinApiGenerator( + config = config, + generator = RecursiveExtensionGenerator( + extensionSuffix = "Kt", + generators = *arrayOf( + oneofClassGenerator, + DslGenerator( + extendableDslSuperclass = runtimeVariant.extendableDslSuperclass, + dslClassNameSuffix = "Dsl", + dslFactoryNamePrefix = "", + copyName = MemberSimpleName("copy"), + componentGenerators = *arrayOf( + SingletonFieldPropertyDslComponentGenerator, + SingletonFieldHazzerDslComponentGenerator, + SingletonFieldClearDslComponentGenerator, + OneofCaseDslComponentGenerator, + oneofClassGenerator.dslPropertyGenerator, + RepeatedFieldListProxyDslComponentGenerator( + proxyTypeNameSuffix = "Proxy", + extensionGenerators = *arrayOf( + RepeatedRepresentativeExtensionGenerator.AddElement.add, + RepeatedRepresentativeExtensionGenerator.AddElement.plusAssign, + RepeatedRepresentativeExtensionGenerator.AddAllIterable.addAll, + RepeatedRepresentativeExtensionGenerator.AddAllIterable.plusAssign, + RepeatedRepresentativeExtensionGenerator.SetOperator, + RepeatedRepresentativeExtensionGenerator.Clear + ) + ), + MapFieldMapProxyDslComponentGenerator( + proxyTypeNameSuffix = "Proxy", + extensionGenerators = *arrayOf( + MapRepresentativeExtensionGenerator.Put.put, + MapRepresentativeExtensionGenerator.Put.setOperator, + MapRepresentativeExtensionGenerator.RemoveKey.remove, + MapRepresentativeExtensionGenerator.PutAllMap.putAll, + MapRepresentativeExtensionGenerator.Clear + ) + ) + ) + ) + ) + ) + ) +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGenerator.kt new file mode 100644 index 000000000000..7fccb23b52de --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGenerator.kt @@ -0,0 +1,133 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.DslMap +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.MapFieldDescriptor +import com.google.protobuf.kotlin.protoc.Scope +import com.google.protobuf.kotlin.protoc.UnqualifiedScope +import com.google.protobuf.kotlin.protoc.builder +import com.google.protobuf.kotlin.protoc.classBuilder +import com.google.protobuf.kotlin.protoc.fieldName +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.asTypeName + +/** + * Generates a DSL property for a map field, of type `DslMap`, + * which is a thin unmodifiable wrapper around the map from the backing builder. In addition, + * provides extension methods for that type that modify the contents of that map field. + */ +class MapFieldMapProxyDslComponentGenerator( + private val proxyTypeNameSuffix: String = "Proxy", + private val propertyNameSuffix: String = "", + private vararg val extensionGenerators: MapRepresentativeExtensionGenerator +) : DslComponentGenerator(MapFieldDescriptor::class) { + + override fun GeneratorConfig.generate( + component: MapFieldDescriptor, + builderCode: CodeBlock, + dslBuilder: TypeSpec.Builder, + companionBuilder: TypeSpec.Builder, + enclosingScope: Scope, + extensionScope: Scope, + dslType: TypeName + ) { + val proxyClassSimpleName = component.fieldName.toClassSimpleNameWithSuffix(proxyTypeNameSuffix) + + dslBuilder.addType( + TypeSpec + .classBuilder(proxyClassSimpleName) + .addKdoc( + "A behaviorless, uninstantiable type to represent the field %L in generics.", + component.fieldName + ) + .primaryConstructor( + FunSpec + .constructorBuilder() + .addModifiers(KModifier.PRIVATE) + .build() + ) + .build() + ) + + val proxyClassName = UnqualifiedScope.nestedClass(proxyClassSimpleName) + + val propertyType = + DslMap::class + .asTypeName() + .parameterizedBy(component.keyType(), component.valueType(), proxyClassName) + + val propertyName = component.propertySimpleName.withSuffix(propertyNameSuffix) + dslBuilder.addProperty( + PropertySpec + .builder(propertyName, propertyType) + .addKdoc( + """ + |A [%T] view of the map field %L. + | + |While the view object itself is a `Map`, not a `MutableMap`, within the context of the + |DSL it has extension methods and operator overloads that mutate it appropriately. + |For example, writing `%L[k] = v` adds a mapping from `k` to `v` in the map field, + |but that code only compiles within the DSL. + """.trimMargin(), + Map::class, + component.descriptor.fieldName, + component.propertySimpleName + ) + .addAnnotations(Deprecation.apiAnnotations(component)) + .getter( + getterBuilder() + .addStatement( + "return %T(%L.%L())", + DslMap::class, + builderCode, + component.mapGetterSimpleName + ) + .build() + ) + .build() + ) + + for (extensionGenerator in extensionGenerators) { + extensionGenerator.run { + generate( + { name -> + funSpecBuilder(name) + .receiver(propertyType) + .addAnnotation( + AnnotationSpec + .builder(JvmName::class) + .addMember("%S", name + component.propertySimpleName) + .build() + ) + }, + component, + builderCode, + propertyName + ).writeToEnclosingType(dslBuilder) + } + } + } +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGenerator.kt new file mode 100644 index 000000000000..2289b8009b8f --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGenerator.kt @@ -0,0 +1,434 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.Declarations +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.MapFieldDescriptor +import com.google.protobuf.kotlin.protoc.MemberSimpleName +import com.google.protobuf.kotlin.protoc.declarations +import com.google.protobuf.kotlin.protoc.of +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.asTypeName + +/** A generator for extension methods on a type representing a map field of a proto message. */ +abstract class MapRepresentativeExtensionGenerator private constructor() { + companion object { + private val PAIR = Pair::class.asTypeName() + private val ITERABLE = Iterable::class.asTypeName() + private val MAP = Map::class.asTypeName() + } + + /** + * Generates an extension function that delegates its implementation to modifying the appropriate + * repeated field in a backing builder. + * + * @param funSpec Creates a [FunSpec.Builder] for a method with the specified name, with an + * appropriate receiver type. + * @param mapField Indicates which repeated field to modify in the backing builder. + * @param builderCode Code to access the backing builder. + */ + abstract fun GeneratorConfig.generate( + funSpec: (MemberSimpleName) -> FunSpec.Builder, + mapField: MapFieldDescriptor, + builderCode: CodeBlock, + proxyPropertyName: MemberSimpleName + ): Declarations + + protected fun GeneratorConfig.keyParameter(field: MapFieldDescriptor): ParameterSpec = + ParameterSpec.of("key", field.keyType()) + + /** Generates a set operator to put an entry in the map field. */ + class Put private constructor( + private val name: MemberSimpleName, + /** + * A CodeBlock template string that takes an %L parameter and generates code to put the pair + * (k, v) into it. + */ + private val putKV: String, + private vararg val modifiers: KModifier + ) : MapRepresentativeExtensionGenerator() { + companion object { + /** Generates a normal function named `put`. */ + val put = Put(MemberSimpleName("put"), "%L.put(k, v)") + + /** Generates the `set` operator (`map[key] = value`). */ + val setOperator = Put(MemberSimpleName.OPERATOR_SET, "%L[k] = v", KModifier.OPERATOR) + } + + override fun GeneratorConfig.generate( + funSpec: (MemberSimpleName) -> FunSpec.Builder, + mapField: MapFieldDescriptor, + builderCode: CodeBlock, + proxyPropertyName: MemberSimpleName + ): Declarations = declarations { + val keyParameter = keyParameter(mapField) + val newValueParameter = ParameterSpec.of("newValue", mapField.valueType()) + addFunction( + funSpec(name) + .addKdoc( + """ + Puts the specified key-value pair into the map field %L, overwriting any previous + value associated with the key. + + For example, `$putKV` maps the key `k` to the value `v` in %L. + + Calling this method with any receiver but [%L] may have undefined behavior. + """.trimIndent(), + mapField.fieldName, + proxyPropertyName, + mapField.fieldName, + proxyPropertyName + ) + .addModifiers(*modifiers) + .addParameter(keyParameter) + .addParameter(newValueParameter) + .addStatement( + "%L.%L(%N, %N)", + builderCode, + mapField.putterSimpleName, + keyParameter, + newValueParameter + ) + .build() + ) + } + } + + /** Generates a function to remove a key from the map field. */ + class RemoveKey private constructor( + private val name: MemberSimpleName, + /** + * A CodeBlock template string that takes an %L parameter and generates code to remove the key + * k from it. + */ + private val removeK: String, + private vararg val modifiers: KModifier + ) : MapRepresentativeExtensionGenerator() { + companion object { + /** Generates a normal method named `remove`. */ + val remove: RemoveKey = RemoveKey(MemberSimpleName("remove"), "%L.remove(k)") + + /** Generates a `-=` operator to remove a key. */ + val minusAssign: RemoveKey = + RemoveKey(MemberSimpleName.OPERATOR_MINUS_ASSIGN, "%L -= k", KModifier.OPERATOR) + } + + override fun GeneratorConfig.generate( + funSpec: (MemberSimpleName) -> FunSpec.Builder, + mapField: MapFieldDescriptor, + builderCode: CodeBlock, + proxyPropertyName: MemberSimpleName + ): Declarations = declarations { + val keyParameter = keyParameter(mapField) + addFunction( + funSpec(this@RemoveKey.name) + .addKdoc( + """ + Removes the entry for the specified key from the map field %L, if one exists. + Otherwise, does nothing. + + For example, `$removeK` removes the key `k` from %L. + + Calling this method with any receiver but [%L] may have undefined behavior. + """.trimIndent(), + mapField.fieldName, + proxyPropertyName, + mapField.fieldName, + proxyPropertyName + ) + .addModifiers(*modifiers) + .addParameter(keyParameter) + .addStatement( + "%L.%L(%N)", + builderCode, + mapField.removerSimpleName, + keyParameter + ) + .build() + ) + } + } + + /** Generates a function to remove zero or more keys from the map field. */ + class RemoveAll private constructor( + private val name: MemberSimpleName, + private val makeParameter: (keyType: TypeName) -> ParameterSpec, + /** + * A CodeBlock template string that takes an %L parameter and generates code to remove the keys + * k1, k2, and k3 from it. + */ + private val removeK123: String, + private vararg val modifiers: KModifier + ) : MapRepresentativeExtensionGenerator() { + companion object { + /** Generates a function named `removeAll` to remove an `Iterable`. */ + val removeAllIterable = RemoveAll( + MemberSimpleName("removeAll"), + { iterableKeysParam(it) }, + "%L.removeAll(listOf(k1, k2, k3))" + ) + + /** Generates a function named `removeAll` to remove a vararg of keys. */ + val removeAllVararg = RemoveAll( + MemberSimpleName("removeAll"), + { ParameterSpec.of("keys", it, KModifier.VARARG) }, + "%L.removeAll(k1, k2, k3)" + ) + + /** Generates a `-=` operator function to remove an `Iterable`. */ + val minusAssign = RemoveAll( + MemberSimpleName.OPERATOR_MINUS_ASSIGN, + { iterableKeysParam(it) }, + "%L -= listOf(k1, k2, k3)", + KModifier.OPERATOR + ) + + private fun iterableKeysParam(keyType: TypeName) = + ParameterSpec.of("keys", ITERABLE.parameterizedBy(keyType)) + } + + override fun GeneratorConfig.generate( + funSpec: (MemberSimpleName) -> FunSpec.Builder, + mapField: MapFieldDescriptor, + builderCode: CodeBlock, + proxyPropertyName: MemberSimpleName + ): Declarations = declarations { + val keysParameter = makeParameter(mapField.keyType()) + addFunction( + funSpec(this@RemoveAll.name) + .addModifiers(*modifiers) + .addKdoc( + """ + Removes the entries for each of the specified keys from the map field %L. If there is + no entry for a given key, it is skipped. + + For example, `$removeK123` removes entries for `k1`, `k2`, and `k3` from %L. + + Calling this method with any receiver but [%L] may have undefined behavior. + """.trimIndent(), + mapField.fieldName, + proxyPropertyName, + mapField.fieldName, + proxyPropertyName + ) + .addParameter(keysParameter) + .beginControlFlow("for (value in %N)", keysParameter) + .addStatement( + "%L.%L(%N)", + builderCode, + mapField.removerSimpleName, + keysParameter + ) + .endControlFlow() + .build() + ) + } + } + + /** Generates a function to put the contents of a `Map` into a map field. */ + class PutAllMap private constructor( + private val name: MemberSimpleName, + /** + * A CodeBlock template string that takes an %L parameter and generates code to put the entries + * (k1, v1) and (k2, v2) into it. + */ + private val putAllKV12: String, + private vararg val modifiers: KModifier + ) : MapRepresentativeExtensionGenerator() { + companion object { + /** Generates a normal function named `putAll`. */ + val putAll: PutAllMap = PutAllMap( + MemberSimpleName("putAll"), + "%L.putAll(mapOf(k1 to v1, k2 to v2))" + ) + + /** Generates a `+=` operator function accepting a map. */ + val plusAssign: PutAllMap = + PutAllMap( + MemberSimpleName.OPERATOR_PLUS_ASSIGN, + "%L += mapOf(k1 to v1, k2 to v2)", + KModifier.OPERATOR + ) + } + + override fun GeneratorConfig.generate( + funSpec: (MemberSimpleName) -> FunSpec.Builder, + mapField: MapFieldDescriptor, + builderCode: CodeBlock, + proxyPropertyName: MemberSimpleName + ): Declarations = declarations { + val putAllParameter = + ParameterSpec.of( + "map", + MAP.parameterizedBy(mapField.keyType(), mapField.valueType()) + ) + addFunction( + funSpec(this@PutAllMap.name) + .addModifiers(*modifiers) + .addKdoc( + """ + Puts the entries in the given map into the map field %L. If there is already an entry + for a key in the map, it is overwritten. + + For example, `$putAllKV12` is equivalent to `%L[k1] = v1; %L[k2] = v2`. + + Calling this method with any receiver but [%L] may have undefined behavior. + """.trimIndent(), + mapField.fieldName, + proxyPropertyName, + proxyPropertyName, + proxyPropertyName, + proxyPropertyName + ) + .addParameter(putAllParameter) + .addStatement( + "%L.%L(%N)", + builderCode, + mapField.putAllSimpleName, + putAllParameter + ) + .build() + ) + } + } + + /** Generates a function to put a collection of key-value pairs into a map field. */ + class PutAllPairs private constructor( + val name: MemberSimpleName, + val makeParameter: (TypeName, TypeName) -> ParameterSpec, + /** + * A CodeBlock template string that takes an %L parameter and generates code to put the entries + * (k1, v1) and (k2, v2) into it. + */ + private val putAllKV12: String, + val modifiers: Array = arrayOf() + ) : MapRepresentativeExtensionGenerator() { + companion object { + /** Generates a function named `putAll` accepting a vararg of key-value pairs. */ + val putAllVarargs: PutAllPairs = PutAllPairs( + name = MemberSimpleName("putAll"), + makeParameter = { keyTy, valueTy -> + ParameterSpec + .of("pairs", PAIR.parameterizedBy(keyTy, valueTy), KModifier.VARARG) + }, + putAllKV12 = "%L.putAll(k1 to v1, k2 to v2)" + ) + + /** Generates a function named `putAll` accepting an `Iterable>`. */ + val putAllIterable: PutAllPairs = PutAllPairs( + name = MemberSimpleName("putAll"), + makeParameter = { keyTy, valueTy -> + ParameterSpec.of( + "pairs", + ITERABLE.parameterizedBy(PAIR.parameterizedBy(keyTy, valueTy)) + ) + }, + putAllKV12 = "%L.putAll(listOf(k1 to v1, k2 to v2))" + ) + + /** Generates a `+=` operator accepting an `Iterable>`. */ + val plusAssignIterable: PutAllPairs = PutAllPairs( + name = MemberSimpleName.OPERATOR_PLUS_ASSIGN, + makeParameter = { keyTy, valueTy -> + ParameterSpec.of( + "pairs", + ITERABLE.parameterizedBy(PAIR.parameterizedBy(keyTy, valueTy)) + ) + }, + putAllKV12 = "%L += listOf(k1 to v1, k2 to v2)", + modifiers = arrayOf(KModifier.OPERATOR) + ) + } + + override fun GeneratorConfig.generate( + funSpec: (MemberSimpleName) -> FunSpec.Builder, + mapField: MapFieldDescriptor, + builderCode: CodeBlock, + proxyPropertyName: MemberSimpleName + ): Declarations = declarations { + val parameter = makeParameter(mapField.keyType(), mapField.valueType()) + + addFunction( + funSpec(this@PutAllPairs.name) + .addModifiers(*modifiers) + .addParameter(parameter) + .addKdoc( + """ + Puts the specified key-value pairs into the map field %L. If there is already an entry + for a key in the map, it is overwritten. + + For example, `$putAllKV12` is equivalent to `%L[k1] = v1; %L[k2] = v2`. + + Calling this method with any receiver but [%L] may have undefined behavior. + """.trimIndent(), + mapField.fieldName, + proxyPropertyName, + proxyPropertyName, + proxyPropertyName, + proxyPropertyName + ) + .beginControlFlow("for ((k, v) in %N)", parameter) + .addStatement( + "%L.%L(k, v)", builderCode, mapField.putterSimpleName + ) + .endControlFlow() + .build() + ) + } + } + + /** Generates a `clear` function for the map field. */ + object Clear : MapRepresentativeExtensionGenerator() { + private val CLEAR_SIMPLE_NAME = MemberSimpleName("clear") + + override fun GeneratorConfig.generate( + funSpec: (MemberSimpleName) -> FunSpec.Builder, + mapField: MapFieldDescriptor, + builderCode: CodeBlock, + proxyPropertyName: MemberSimpleName + ): Declarations = declarations { + addFunction( + funSpec(CLEAR_SIMPLE_NAME) + .addKdoc( + """ + Removes all entries from the map field %L. + + For example, `%L.clear()` deletes all entries previously part of %L. + + Calling this method with any receiver but [%L] may have undefined behavior. + """.trimIndent(), + mapField.fieldName, + proxyPropertyName, + mapField.fieldName, + proxyPropertyName + ) + .addStatement( + "%L.%L()", + builderCode, + mapField.clearerSimpleName + ) + .build() + ) + } + } +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGenerator.kt new file mode 100644 index 000000000000..f310ebd876c4 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGenerator.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.OneofComponent +import com.google.protobuf.kotlin.protoc.Scope +import com.google.protobuf.kotlin.protoc.builder +import com.google.protobuf.kotlin.protoc.casePropertySimpleName +import com.google.protobuf.kotlin.protoc.oneofName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec + +/** + * Generates the property `MessageDsl.oneofCase` to match the one built into the message type. + */ +object OneofCaseDslComponentGenerator : + DslComponentGenerator(OneofComponent::class) { + override fun GeneratorConfig.generate( + component: OneofComponent, + builderCode: CodeBlock, + dslBuilder: TypeSpec.Builder, + companionBuilder: TypeSpec.Builder, + enclosingScope: Scope, + extensionScope: Scope, + dslType: TypeName + ) { + val oneof = component.descriptor + val caseEnum = oneof.caseEnum() + dslBuilder.addProperty( + PropertySpec + .builder(oneof.casePropertySimpleName, caseEnum) + .addKdoc("An enum reflecting which field of the oneof %L is set.", oneof.oneofName) + .getter( + getterBuilder() + .addStatement("return %L.%N", builderCode, oneof.caseProperty()) + .build() + ) + .build() + ) + } +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofClassGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofClassGenerator.kt new file mode 100644 index 000000000000..adc2b394f923 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofClassGenerator.kt @@ -0,0 +1,414 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.common.annotations.VisibleForTesting +import com.google.protobuf.Descriptors.OneofDescriptor +import com.google.protobuf.kotlin.OneofRepresentation +import com.google.protobuf.kotlin.protoc.ClassSimpleName +import com.google.protobuf.kotlin.protoc.Declarations +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.MemberSimpleName +import com.google.protobuf.kotlin.protoc.OneofComponent +import com.google.protobuf.kotlin.protoc.OneofOptionFieldDescriptor +import com.google.protobuf.kotlin.protoc.Scope +import com.google.protobuf.kotlin.protoc.builder +import com.google.protobuf.kotlin.protoc.classBuilder +import com.google.protobuf.kotlin.protoc.declarations +import com.google.protobuf.kotlin.protoc.nestedClass +import com.google.protobuf.kotlin.protoc.objectBuilder +import com.google.protobuf.kotlin.protoc.of +import com.google.protobuf.kotlin.protoc.oneofName +import com.google.protobuf.kotlin.protoc.options +import com.google.protobuf.kotlin.protoc.propertySimpleName +import com.squareup.kotlinpoet.ANY +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.MemberName +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.STAR +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.TypeVariableName +import com.squareup.kotlinpoet.UNIT +import com.squareup.kotlinpoet.asTypeName + +/** + * Generates a class to represent a oneof for a message class, such as: + * + * class MyOneof { + * class OptionA(val value: Int): MyOneof + * class OptionB(val value: String): MyOneof + * object NotSet: MyOneof + * } + * + * ...and associated utilities to get and set that oneof with this representation. + */ +class OneofClassGenerator( + val oneofClassSuffix: String = "", + val oneofSubclassSuffix: String = "", + val notSetSimpleName: ClassSimpleName = ClassSimpleName("NotSet"), + val propertySuffix: String = "", + vararg extensionFactories: OneofClassGenerator.() -> OneofClassExtensionGenerator +) : ComponentExtensionGenerator(OneofComponent::class) { + companion object { + private val VALUE_PROPERTY_NAME = MemberSimpleName("value") + private val CASE_PROPERTY_NAME = MemberSimpleName("case") + private val WRITE_TO_METHOD_NAME = MemberSimpleName("writeTo") + private val READ_FROM_METHOD_NAME = MemberSimpleName("readFrom") + } + + private val extensions by lazy { + extensionFactories.map { extension -> this@OneofClassGenerator.extension() } + } + + /** Simple name for the class for the oneof. */ + private val OneofDescriptor.classSimpleName + get() = oneofName.toClassSimpleNameWithSuffix(oneofClassSuffix) + + /** Simple name for the subclass for this particular option of the oneof. */ + private val OneofOptionFieldDescriptor.classSimpleName: ClassSimpleName + get() = fieldName.toClassSimpleNameWithSuffix(oneofSubclassSuffix) + + /** + * Fully qualified name for the subclass for this particular option of the oneof. + * + * @param oneofClass The fully qualified name for the class for the oneof. + */ + private fun OneofOptionFieldDescriptor.className(oneofClass: ClassName): ClassName = + oneofClass.nestedClass(classSimpleName) + + /** Builds an extension function on the oneof class to copy its contents into a builder. */ + @VisibleForTesting + fun GeneratorConfig.writeToFunction(oneof: OneofDescriptor): FunSpec { + val builderParam = ParameterSpec.of("builder", oneof.containingType.builderClass()) + val typeParam = TypeVariableName("T", ANY) + val valueProperty = PropertySpec + .builder(VALUE_PROPERTY_NAME, typeParam, KModifier.OVERRIDE) + .build() + val caseProperty = PropertySpec + .builder(CASE_PROPERTY_NAME, oneof.caseEnum(), KModifier.OVERRIDE) + .build() + + val builder = funSpecBuilder(WRITE_TO_METHOD_NAME) + .addParameter(builderParam) + .returns(UNIT) + .addModifiers(KModifier.INTERNAL) + + builder.beginControlFlow("when (%N)", caseProperty) + + for (option in oneof.options()) { + // · represents a space which must not be broken + builder.addStatement( + "%M -> %N.%N·= %N as %T", + option.caseEnumMember(), + builderParam, + option.property(), + valueProperty, + option.type() + ) + } + builder + .addStatement("else -> %N.%N()", builderParam, oneof.builderClear()) + .endControlFlow() + + return builder.build() + } + + /** + * Builds a function to live on the oneof class' companion object to copy a message or builder's + * oneof value into a oneof representation object. + */ + @VisibleForTesting + fun GeneratorConfig.readFromFunction(oneof: OneofDescriptor, oneofClassName: ClassName): FunSpec { + val orBuilder = oneof.containingType.orBuilderClass() + val orBuilderParam = ParameterSpec.of("input", orBuilder) + + val readFromBuilder = + funSpecBuilder(READ_FROM_METHOD_NAME) + .addModifiers(KModifier.INTERNAL) + .addAnnotation(PublishedApi::class) + .addKdoc( + "Extract an object representation of the set field of the oneof %L.", + oneof.oneofName + ) + .returns(oneofClassName.parameterizedBy(STAR)) + .addParameter(orBuilderParam) + + // Copy the case to a typed local variable, which lets us convert it from either a + // platform-nullable type or a nonnull type to a nonnull type without warnings. + + readFromBuilder.addStatement( + "val _theCase: %T = %N.%N", + oneof.caseEnum(), + orBuilderParam, + oneof.caseProperty() + ) + + readFromBuilder.beginControlFlow("return when (_theCase)") + + for (option in oneof.options()) { + val optionProperty = + PropertySpec + .builder(simpleName = option.propertySimpleName, type = option.type()) + .receiver(orBuilder) + .build() + readFromBuilder.addStatement( + "%M -> %T(%N.%N)", + option.caseEnumMember(), + option.className(oneofClassName), + orBuilderParam, + optionProperty + ) + } + readFromBuilder.addStatement( + "else -> %T", + oneofClassName.nestedClass(notSetSimpleName) + ).endControlFlow() + + return readFromBuilder.build() + } + + /** Generates a class for this option of the oneof. */ + @VisibleForTesting + fun GeneratorConfig.oneofOptionClass( + option: OneofOptionFieldDescriptor, + oneofClassName: ClassName + ): TypeSpec { + val oneof = option.oneof + val optionType = option.type() + val valueParam = ParameterSpec.of(VALUE_PROPERTY_NAME, optionType) + + return TypeSpec + .classBuilder(option.classSimpleName) + .addAnnotations(Deprecation.apiAnnotations(option)) + .addKdoc( + "A representation of the %L option of the %L oneof.", + option.fieldName, + oneof.oneofName + ) + .superclass(oneofClassName.parameterizedBy(optionType)) + .primaryConstructor( + FunSpec + .constructorBuilder() + .addParameter(valueParam) + .build() + ) + .addSuperclassConstructorParameter(valueParam.name) + .addSuperclassConstructorParameter("%M", option.caseEnumMember()) + .build() + } + + /** Generates the class (or object) to represent "this oneof is not set." */ + @VisibleForTesting + fun GeneratorConfig.notSetClass( + oneof: OneofDescriptor, + oneofClassName: ClassName + ): TypeSpec { + return TypeSpec.objectBuilder(notSetSimpleName) + .addSuperclassConstructorParameter("Unit") + .addSuperclassConstructorParameter("%M", oneof.notSet()) + .superclass(oneofClassName.parameterizedBy(UNIT)) + .addKdoc("Represents the absence of a value for %L.", oneof.oneofName) + .build() + } + + override fun GeneratorConfig.componentExtensions( + component: OneofComponent, + enclosingScope: Scope, + extensionScope: Scope + ): Declarations = declarations { + val oneof = component.descriptor + val oneofClassSimpleName = oneof.classSimpleName + val oneofClassName = + scope(enclosingScope, extensionScope).nestedClass(oneofClassSimpleName) + val typeParam = TypeVariableName("T", ANY) + val caseEnum = oneof.caseEnum() + + val valueParam = ParameterSpec + .builder(VALUE_PROPERTY_NAME, typeParam, KModifier.OVERRIDE) + .build() + val valueProperty = PropertySpec.builder(VALUE_PROPERTY_NAME, typeParam, KModifier.OVERRIDE) + .initializer("%N", valueParam) + .build() + val caseParam = ParameterSpec + .builder(CASE_PROPERTY_NAME, caseEnum, KModifier.OVERRIDE) + .build() + val caseProperty = PropertySpec.builder(CASE_PROPERTY_NAME, caseEnum, KModifier.OVERRIDE) + .initializer("%N", caseParam) + .build() + + val oneofClassBuilder = + TypeSpec + .classBuilder(oneofClassSimpleName) + .addModifiers(KModifier.ABSTRACT) + .addTypeVariable(typeParam) + .addKdoc("Representation of a value for the oneof %L.", oneof.oneofName) + .primaryConstructor( + FunSpec + .constructorBuilder() + .addParameter(valueParam) + .addParameter(caseParam) + .build() + ) + .addProperty(valueProperty) + .addProperty(caseProperty) + .superclass(OneofRepresentation::class.asTypeName().parameterizedBy(typeParam, caseEnum)) + .addFunction(writeToFunction(oneof)) + + for (option in oneof.options()) { + oneofClassBuilder.addType(oneofOptionClass(option, oneofClassName)) + } + + oneofClassBuilder.addType(notSetClass(oneof, oneofClassName)) + + oneofClassBuilder + .addType( + TypeSpec + .companionObjectBuilder() + .addFunction(readFromFunction(oneof, oneofClassName)) + .build() + ) + + addType(oneofClassBuilder.build()) + + for (extension in extensions) { + extension.run { + merge(generate(oneof, oneofClassName)) + } + } + } + + /** A generator for extension properties that make use of the generated oneof class. */ + abstract inner class OneofClassExtensionGenerator { + abstract fun GeneratorConfig.generate( + oneof: OneofDescriptor, + oneofClass: ClassName + ): Declarations + } + + /** Generates a val property for this oneof on the message's OrBuilder interface. */ + val orBuilderVal = object : OneofClassExtensionGenerator() { + override fun GeneratorConfig.generate( + oneof: OneofDescriptor, + oneofClass: ClassName + ) = declarations { + addTopLevelProperty( + PropertySpec + .builder( + oneof.propertySimpleName.withSuffix(propertySuffix), + oneofClass.parameterizedBy(STAR) + ) + .receiver(oneof.containingType.orBuilderClass()) + .getter( + getterBuilder() + .addStatement("return %T.%L(this)", oneofClass, READ_FROM_METHOD_NAME) + .build() + ) + .addKdoc("The value of the oneof %L.", oneof.oneofName) + .build() + ) + } + } + + /** Generates a mutable var property for this oneof on the message builder class. */ + val builderVar = object : OneofClassExtensionGenerator() { + override fun GeneratorConfig.generate( + oneof: OneofDescriptor, + oneofClass: ClassName + ) = declarations { + val newOneofParam = ParameterSpec.of("newOneofParam", oneofClass.parameterizedBy(STAR)) + addTopLevelProperty( + PropertySpec + .builder( + oneof.propertySimpleName.withSuffix(propertySuffix), + oneofClass.parameterizedBy(STAR) + ) + .getter( + getterBuilder() + .addStatement("return %T.%L(this)", oneofClass, READ_FROM_METHOD_NAME) + .build() + ) + .addKdoc("The value of the oneof %L.", oneof.oneofName) + .receiver(oneof.containingType.builderClass()) + .mutable(true) + .setter( + setterBuilder() + .addParameter(newOneofParam) + .addStatement("%N.%L(this)", newOneofParam, WRITE_TO_METHOD_NAME) + .build() + ) + .build() + ) + } + } + + /** Generates a DSL property for this oneof using the generated oneof class. */ + val dslPropertyGenerator: DslComponentGenerator = + object : DslComponentGenerator(OneofComponent::class) { + override fun GeneratorConfig.generate( + component: OneofComponent, + builderCode: CodeBlock, + dslBuilder: TypeSpec.Builder, + companionBuilder: TypeSpec.Builder, + enclosingScope: Scope, + extensionScope: Scope, + dslType: TypeName + ) { + val oneof = component.descriptor + val oneofClass = oneofClass(oneof, enclosingScope, extensionScope) + val propertyName = oneof.propertySimpleName.withSuffix(propertySuffix) + val setterParam = ParameterSpec.of("newValue", oneofClass) + + dslBuilder.addProperty( + PropertySpec + .builder( + propertyName, + oneofClass.parameterizedBy(STAR) + ) + .mutable(true) + .addKdoc("An object representing a value assigned to the oneof %L.", oneof.oneofName) + .getter( + getterBuilder() + .addStatement("return %T.%L(%L)", oneofClass, READ_FROM_METHOD_NAME, builderCode) + .build() + ) + .setter( + setterBuilder() + .addParameter(setterParam) + .addStatement("%N.%L(%L)", setterParam, WRITE_TO_METHOD_NAME, builderCode) + .build() + ) + .build() + ) + } + } + + /** + * Returns the fully qualified name of the oneof class generated by this generator for + * the specified oneof. + */ + private fun oneofClass( + oneof: OneofDescriptor, + enclosingScope: Scope, + extensionScope: Scope + ): ClassName = scope(enclosingScope, extensionScope).nestedClass(oneof.classSimpleName) +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGenerator.kt new file mode 100644 index 000000000000..36c749ae9e7e --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGenerator.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.OneofOptionFieldDescriptor +import com.google.protobuf.kotlin.protoc.SingletonFieldDescriptor +import com.squareup.kotlinpoet.CodeBlock + +/** + * Generates `MessageOrBuilder.oneofOptionOrNull` extension properties. + */ +object OneofOrNullPropertyGenerator : AbstractOrNullPropertyGenerator() { + override fun shouldGenerate(field: SingletonFieldDescriptor): Boolean = + field is OneofOptionFieldDescriptor + + override fun GeneratorConfig.has( + receiver: CodeBlock, + field: SingletonFieldDescriptor + ): CodeBlock { + val option = field as OneofOptionFieldDescriptor + return CodeBlock.of( + "%L.%N == %M", + receiver, + option.oneof.caseProperty(), + option.caseEnumMember() + ) + } +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGenerator.kt new file mode 100644 index 000000000000..297f5b173837 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGenerator.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.Descriptors.FileDescriptor +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.builder +import com.google.protobuf.kotlin.protoc.declarations +import com.google.protobuf.kotlin.protoc.objectBuilder +import com.google.protobuf.kotlin.protoc.outerClassSimpleName +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.TypeSpec + +/** + * Logic to generate an entire file of Kotlin extensions for a proto file specified as a + * FileDescriptor. + */ +class ProtoFileKotlinApiGenerator( + val generator: RecursiveExtensionGenerator, + val config: GeneratorConfig +) { + + fun generate(fileDescriptor: FileDescriptor): FileSpec { + val packageName = config.javaPackage(fileDescriptor) + val multipleFiles = fileDescriptor.options.javaMultipleFiles + val outerExtensionClassName = + fileDescriptor.outerClassSimpleName.withSuffix(generator.extensionSuffix) + val outerExtensionClassScope = packageName.nestedScope(outerExtensionClassName) + + val builder = FileSpec.builder(packageName, outerExtensionClassName) + + if (config.aggressiveInlining) { + builder.addAnnotation( + AnnotationSpec.builder(Suppress::class).addMember("%S", "NOTHING_TO_INLINE").build() + ) + } + + builder.addAnnotation( + AnnotationSpec + .builder(Suppress::class) + .useSiteTarget(UseSiteTarget.FILE) + .addMember("%S", "DEPRECATION") + .build() + ) + + if (multipleFiles) { + for (descriptor in fileDescriptor.messageTypes) { + generator.generate(config, descriptor, packageName).writeAllAtTopLevel(builder) + } + } else { + val declarations = declarations { + for (descriptor in fileDescriptor.messageTypes) { + merge(generator.generate(config, descriptor, outerExtensionClassScope)) + } + } + + builder.addType( + TypeSpec.objectBuilder(outerExtensionClassName) + .addKdoc("Kotlin extensions for protos defined in the file %L.", fileDescriptor.fullName) + .apply { declarations.writeToEnclosingType(this) } + .build() + ) + + declarations.writeOnlyTopLevel(builder) + } + + return builder.build() + } +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGenerator.kt new file mode 100644 index 000000000000..e8ea24d953fa --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGenerator.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.Descriptors.Descriptor +import com.google.protobuf.kotlin.protoc.Declarations +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.Scope +import com.google.protobuf.kotlin.protoc.declarations +import com.google.protobuf.kotlin.protoc.messageClassSimpleName +import com.google.protobuf.kotlin.protoc.objectBuilder +import com.squareup.kotlinpoet.TypeSpec + +/** + * Given generators for extension APIs on a single message, generates the APIs for all nested + * messages, within appropriately named objects. + */ +class RecursiveExtensionGenerator( + val extensionSuffix: String, + private vararg val generators: ExtensionGenerator +) { + fun generate( + config: GeneratorConfig, + descriptor: Descriptor, + scope: Scope + ): Declarations = declarations { + if (descriptor.options.mapEntry) { + return@declarations + } + with(config) { + val nestedSimpleName = descriptor.messageClassSimpleName.withSuffix(extensionSuffix) + val nestedScope = scope.nestedScope(nestedSimpleName) + for (generator in generators) { + if (!generator.generateWithinExtensionClass) { + generator.run { + merge(shallowExtensions(descriptor, scope, nestedScope)) + } + } + } + + val nestedDeclarations = declarations { + for (nestedType in descriptor.nestedTypes) { + merge(generate(config, nestedType, nestedScope)) + } + } + + mergeTopLevelOnly(nestedDeclarations) + + addType( + TypeSpec + .objectBuilder(nestedSimpleName) + .addKdoc("Kotlin extensions around the proto message type %L.", descriptor.fullName) + .apply { + for (generator in generators) { + if (generator.generateWithinExtensionClass) { + val decls = generator.run { + shallowExtensions(descriptor, scope, nestedScope) + } + mergeTopLevelOnly(decls) + decls.writeToEnclosingType(this) + } + } + nestedDeclarations.writeToEnclosingType(this) + } + .build() + ) + } + } +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGenerator.kt new file mode 100644 index 000000000000..9186c7bea9c8 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGenerator.kt @@ -0,0 +1,132 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.DslList +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.RepeatedFieldDescriptor +import com.google.protobuf.kotlin.protoc.Scope +import com.google.protobuf.kotlin.protoc.UnqualifiedScope +import com.google.protobuf.kotlin.protoc.builder +import com.google.protobuf.kotlin.protoc.classBuilder +import com.google.protobuf.kotlin.protoc.fieldName +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.asTypeName + +/** + * Generates a DSL property for a repeated field, of type `DslList`, + * which is a thin unmodifiable wrapper around the list from the backing builder. In addition, + * provides extension methods for that type that modify the contents of that repeated field. + */ +class RepeatedFieldListProxyDslComponentGenerator( + private val proxyTypeNameSuffix: String, + private val propertyNameSuffix: String = "", + private vararg val extensionGenerators: RepeatedRepresentativeExtensionGenerator +) : DslComponentGenerator(RepeatedFieldDescriptor::class) { + + override fun GeneratorConfig.generate( + component: RepeatedFieldDescriptor, + builderCode: CodeBlock, + dslBuilder: TypeSpec.Builder, + companionBuilder: TypeSpec.Builder, + enclosingScope: Scope, + extensionScope: Scope, + dslType: TypeName + ) { + val proxyClassSimpleName = component.fieldName.toClassSimpleNameWithSuffix(proxyTypeNameSuffix) + + dslBuilder.addType( + TypeSpec + .classBuilder(proxyClassSimpleName) + .addKdoc( + "An uninstantiable, behaviorless type to represent the field %L in generics.", + component.fieldName + ) + .primaryConstructor( + FunSpec + .constructorBuilder() + .addModifiers(KModifier.PRIVATE) + .build() + ) + .build() + ) + + val proxyClassName = UnqualifiedScope.nestedClass(proxyClassSimpleName) + + val propertyType = + DslList::class.asTypeName().parameterizedBy(component.elementType(), proxyClassName) + val propertyName = component.propertySimpleName.withSuffix(propertyNameSuffix) + dslBuilder.addProperty( + PropertySpec + .builder(propertyName, propertyType) + .addKdoc( + """ + |A [%T] view of the repeated field %L. + | + |While the view object itself is a `List`, not a `MutableList`, within the context of the + |DSL it has extension methods and operator overloads that mutate it appropriately. + |For example, writing `%L += element` adds that element to the repeated field, but that + |code only compiles within the DSL. + """.trimMargin(), + List::class, + component.descriptor.fieldName, + propertyName + ) + .addAnnotations(Deprecation.apiAnnotations(component)) + .getter( + FunSpec + .getterBuilder() + .addModifiers(KModifier.INLINE) + .addStatement( + "return %T(%L.%L())", + DslList::class, + builderCode, + component.listGetterSimpleName() + ) + .build() + ) + .build() + ) + + for (extensionGenerator in extensionGenerators) { + extensionGenerator.run { + generate( + { name -> + funSpecBuilder(name) + .receiver(propertyType) + .addAnnotation( + AnnotationSpec + .builder(JvmName::class) + .addMember("%S", name + component.propertySimpleName) + .build() + ) + }, + component, + builderCode, + propertyName + ).writeToEnclosingType(dslBuilder) + } + } + } +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGenerator.kt new file mode 100644 index 000000000000..3b159f21c5bf --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGenerator.kt @@ -0,0 +1,269 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.Declarations +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.MemberSimpleName +import com.google.protobuf.kotlin.protoc.RepeatedFieldDescriptor +import com.google.protobuf.kotlin.protoc.declarations +import com.google.protobuf.kotlin.protoc.of +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.INT +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.UNIT +import com.squareup.kotlinpoet.asTypeName + +/** A generator for extension methods on a type representing a repeated field of a proto message. */ +abstract class RepeatedRepresentativeExtensionGenerator private constructor() { + /** + * Generates an extension function that delegates its implementation to modifying the appropriate + * repeated field in a backing builder. + * + * @param funSpec Creates a [FunSpec.Builder] for a method with the specified name, with an + * appropriate receiver type. + * @param repeatedField Indicates which repeated field to modify in the backing builder. + * @param builderCode Code to access the backing builder. + */ + abstract fun GeneratorConfig.generate( + funSpec: (MemberSimpleName) -> FunSpec.Builder, + repeatedField: RepeatedFieldDescriptor, + builderCode: CodeBlock, + proxyPropertyName: MemberSimpleName + ): Declarations + + companion object { + private val ITERABLE = Iterable::class.asTypeName() + + protected val indexParam = ParameterSpec.of("index", INT) + + protected fun GeneratorConfig.newValueParameter( + repeatedField: RepeatedFieldDescriptor + ): ParameterSpec = ParameterSpec.of("newValue", repeatedField.elementType()) + } + + /** Generates an extension function to provide the set operator. */ + object SetOperator : RepeatedRepresentativeExtensionGenerator() { + override fun GeneratorConfig.generate( + funSpec: (MemberSimpleName) -> FunSpec.Builder, + repeatedField: RepeatedFieldDescriptor, + builderCode: CodeBlock, + proxyPropertyName: MemberSimpleName + ): Declarations = declarations { + val newValueParam = newValueParameter(repeatedField) + addFunction( + funSpec(MemberSimpleName.OPERATOR_SET) + .addModifiers(KModifier.OPERATOR) + .returns(UNIT) + .addParameter(indexParam) + .addParameter(newValueParam) + .addKdoc( + """ + Sets the element of the repeated field %L at a given index. + + For example, `%L[i] = value` sets the `i`th element of %L to `value`. + """.trimIndent(), + repeatedField.fieldName, + proxyPropertyName, + repeatedField.fieldName + ) + .addStatement( + "%L.%L(%N, %N)", + builderCode, + repeatedField.setterSimpleName, + indexParam, + newValueParam + ) + .build() + ) + } + } + + /** Generates an extension function to provide a function to add a single element. */ + class AddElement private constructor( + private val name: MemberSimpleName, + /** CodeBlock template that, given a %L parameter, adds an element named `value` to it. */ + private val addValue: String, + private vararg val modifiers: KModifier + ) : RepeatedRepresentativeExtensionGenerator() { + companion object { + /** Generates a normal function with the name `add`. */ + val add = AddElement(MemberSimpleName("add"), "%L.add(value)") + + val plusAssign = AddElement( + MemberSimpleName.OPERATOR_PLUS_ASSIGN, "%L += value", KModifier.OPERATOR + ) + } + + override fun GeneratorConfig.generate( + funSpec: (MemberSimpleName) -> FunSpec.Builder, + repeatedField: RepeatedFieldDescriptor, + builderCode: CodeBlock, + proxyPropertyName: MemberSimpleName + ): Declarations = declarations { + val newValueParam = newValueParameter(repeatedField) + addFunction( + funSpec(this@AddElement.name) + .addModifiers(*modifiers) + .returns(UNIT) + .addKdoc( + """ + Adds an element to the repeated field %L. + + For example, `$addValue` adds `value` to %L. + """.trimIndent(), + repeatedField.fieldName, + proxyPropertyName, + repeatedField.fieldName + ) + .addParameter(newValueParam) + .addStatement( + "%L.%L(%N)", + builderCode, + repeatedField.adderSimpleName, + newValueParam + ) + .build() + ) + } + } + + /** Generates an extension function to add an iterable of elements. */ + class AddAllIterable private constructor( + private val name: MemberSimpleName, + /** CodeBlock template that, given a %L parameter, adds an iterable named `values` to it. */ + private val addAllValues: String, + private vararg val modifiers: KModifier + ) : RepeatedRepresentativeExtensionGenerator() { + companion object { + /** Generates a normal function with the name `addAll`. */ + val addAll = AddAllIterable(MemberSimpleName("addAll"), "%L.addAll(values)") + + /** Generates an operator function for the `+=` operator, adding an iterable of elements. */ + val plusAssign = AddAllIterable( + MemberSimpleName.OPERATOR_PLUS_ASSIGN, "%L += values", KModifier.OPERATOR + ) + } + + override fun GeneratorConfig.generate( + funSpec: (MemberSimpleName) -> FunSpec.Builder, + repeatedField: RepeatedFieldDescriptor, + builderCode: CodeBlock, + proxyPropertyName: MemberSimpleName + ): Declarations = declarations { + val newValuesParam = + ParameterSpec + .of("newValues", ITERABLE.parameterizedBy(repeatedField.elementType())) + addFunction( + funSpec(this@AddAllIterable.name) + .addModifiers(*modifiers) + .returns(UNIT) + .addKdoc( + """ + Adds elements to the repeated field %L. + + For example, `$addAllValues` adds each of the elements of `values`, in order, to %L. + """.trimIndent(), + repeatedField.fieldName, + proxyPropertyName, + repeatedField.fieldName + ) + .addParameter(newValuesParam) + .addStatement( + "%L.%L(%N)", + builderCode, + repeatedField.addAllSimpleName, + newValuesParam + ) + .build() + ) + } + } + + /** Generates an extension function, named `addAll`, to add a vararg set of elements. */ + object AddAllVararg : RepeatedRepresentativeExtensionGenerator() { + private val addAllSimpleName = MemberSimpleName("addAll") + + override fun GeneratorConfig.generate( + funSpec: (MemberSimpleName) -> FunSpec.Builder, + repeatedField: RepeatedFieldDescriptor, + builderCode: CodeBlock, + proxyPropertyName: MemberSimpleName + ): Declarations = declarations { + val newValuesParam = + ParameterSpec.of("newValues", repeatedField.elementType(), KModifier.VARARG) + + addFunction( + funSpec(addAllSimpleName) + .returns(UNIT) + .addKdoc( + """ + Add some elements to the repeated field %L. + + For example, `%L.addAll(v1, v2, v3)` adds `v1`, `v2`, and `v3`, in order, to %L. + """.trimIndent(), + repeatedField.fieldName, + proxyPropertyName, + repeatedField.fieldName + ) + .addParameter(newValuesParam) + .addStatement( + "%L.%L(%N.asList())", + builderCode, + repeatedField.addAllSimpleName, + newValuesParam + ) + .build() + ) + } + } + + /** + * Generates an extension function, named `clear`, to clear the contents of the repeated field. + */ + object Clear : RepeatedRepresentativeExtensionGenerator() { + private val clearSimpleName = MemberSimpleName("clear") + + override fun GeneratorConfig.generate( + funSpec: (MemberSimpleName) -> FunSpec.Builder, + repeatedField: RepeatedFieldDescriptor, + builderCode: CodeBlock, + proxyPropertyName: MemberSimpleName + ): Declarations = declarations { + addFunction( + funSpec(clearSimpleName) + .addKdoc( + """ + Removes all elements from the repeated field %L. + + For example, `%L.clear()` deletes all elements from %L. + """.trimIndent(), + repeatedField.fieldName, + proxyPropertyName, + repeatedField.fieldName + ) + .addStatement( + "%L.%L()", builderCode, repeatedField.clearerSimpleName + ) + .build() + ) + } + } +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGenerator.kt new file mode 100644 index 000000000000..8d3455d01467 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGenerator.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.generator.Deprecation.apiAnnotations +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.Scope +import com.google.protobuf.kotlin.protoc.SingletonFieldDescriptor +import com.google.protobuf.kotlin.protoc.fieldName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec + +/** + * Generates clear methods for singleton fields in a proto. (Other fields need clear methods, too, + * but may expose different APIs.) + */ +object SingletonFieldClearDslComponentGenerator : + DslComponentGenerator(SingletonFieldDescriptor::class) { + + override fun GeneratorConfig.generate( + component: SingletonFieldDescriptor, + builderCode: CodeBlock, + dslBuilder: TypeSpec.Builder, + companionBuilder: TypeSpec.Builder, + enclosingScope: Scope, + extensionScope: Scope, + dslType: TypeName + ) { + val clearName = component.clearerSimpleName + dslBuilder.addFunction( + funSpecBuilder(clearName) + .addAnnotations(apiAnnotations(component)) + .addStatement("%L.%L()", builderCode, clearName) + .addKdoc("Clears any value assigned to the %L field.", component.descriptor.fieldName) + .build() + ) + } +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGenerator.kt new file mode 100644 index 000000000000..af3cfa6d0d56 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGenerator.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.Scope +import com.google.protobuf.kotlin.protoc.SingletonFieldDescriptor +import com.google.protobuf.kotlin.protoc.fieldName +import com.squareup.kotlinpoet.BOOLEAN +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec + +/** Generates hazzers in the DSL for fields that have them in the builder. */ +object SingletonFieldHazzerDslComponentGenerator : + DslComponentGenerator(SingletonFieldDescriptor::class) { + + override fun GeneratorConfig.generate( + component: SingletonFieldDescriptor, + builderCode: CodeBlock, + dslBuilder: TypeSpec.Builder, + companionBuilder: TypeSpec.Builder, + enclosingScope: Scope, + extensionScope: Scope, + dslType: TypeName + ) { + component.hazzer()?.let { hazzer -> + dslBuilder.addFunction( + funSpecBuilder(component.hazzerSimpleName) + .returns(BOOLEAN) + .addStatement("return %L.%N()", builderCode, hazzer) + .addKdoc("True if the %L field has been set.", component.descriptor.fieldName) + .build() + ) + } + } +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGenerator.kt new file mode 100644 index 000000000000..066ac96720c3 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGenerator.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.Scope +import com.google.protobuf.kotlin.protoc.SingletonFieldDescriptor +import com.google.protobuf.kotlin.protoc.builder +import com.google.protobuf.kotlin.protoc.of +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec + +/** + * Generates a property for a singleton field in the DSL that simply delegates to the builder. + * `var myField: FieldType get() = builder.myField set(myValue) { builder.myField = myValue }` + */ +object SingletonFieldPropertyDslComponentGenerator : + DslComponentGenerator(SingletonFieldDescriptor::class) { + + override fun GeneratorConfig.generate( + component: SingletonFieldDescriptor, + builderCode: CodeBlock, + dslBuilder: TypeSpec.Builder, + companionBuilder: TypeSpec.Builder, + enclosingScope: Scope, + extensionScope: Scope, + dslType: TypeName + ) { + val type = component.type() + val newValueParam = ParameterSpec.of("newValue", type) + val propertyGetterName = component.getterSimpleName + val propertySetterName = component.setterSimpleName + + // We explicitly assign JvmNames to the getter and setter to avoid naming conflicts, + // and explicitly call the getter and setter instead of using the inferred property for the same + // reason. + + dslBuilder.addProperty( + PropertySpec + .builder(component.propertySimpleName, type) + .mutable(true) + .addAnnotations(Deprecation.apiAnnotations(component)) + .addKdoc("The %L field.", component) + .getter( + getterBuilder() + .addAnnotation( + AnnotationSpec.builder(JvmName::class) + .addMember("%S", propertyGetterName.toString()) + .build() + ) + .addStatement("return %L.%L()", builderCode, propertyGetterName) + .build() + ) + .setter( + setterBuilder() + .addAnnotation( + AnnotationSpec.builder(JvmName::class) + .addMember("%S", propertySetterName.toString()) + .build() + ) + .addParameter(newValueParam) + .addStatement("%L.%L(%N)", builderCode, propertySetterName, newValueParam) + .build() + ) + .build() + ) + } +} diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/WrapperPropertyGenerator.kt b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/WrapperPropertyGenerator.kt new file mode 100644 index 000000000000..d7584a919726 --- /dev/null +++ b/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/WrapperPropertyGenerator.kt @@ -0,0 +1,201 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.BoolValue +import com.google.protobuf.BytesValue +import com.google.protobuf.Descriptors.Descriptor +import com.google.protobuf.DoubleValue +import com.google.protobuf.FloatValue +import com.google.protobuf.Int32Value +import com.google.protobuf.Int64Value +import com.google.protobuf.StringValue +import com.google.protobuf.UInt32Value +import com.google.protobuf.UInt64Value +import com.google.protobuf.kotlin.protoc.Declarations +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.Scope +import com.google.protobuf.kotlin.protoc.SingletonFieldDescriptor +import com.google.protobuf.kotlin.protoc.TypeNames.BYTE_STRING +import com.google.protobuf.kotlin.protoc.builder +import com.google.protobuf.kotlin.protoc.declarations +import com.google.protobuf.kotlin.protoc.fieldName +import com.google.protobuf.kotlin.protoc.of +import com.squareup.kotlinpoet.BOOLEAN +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.DOUBLE +import com.squareup.kotlinpoet.FLOAT +import com.squareup.kotlinpoet.INT +import com.squareup.kotlinpoet.LONG +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.asTypeName + +/** + * Given a field of a proto wrapper type like `Int32Value`, which is primarily used to distinguish + * absent and set-to-default values of a proto scalar type, generates an extension property that + * views it as a nullable property of the appropriate Kotlin type. For example, an `Int32Value` + * proto field is represented as a Kotlin property of type `Int?`. + */ +class WrapperPropertyGenerator(private val propertyNameSuffix: String = "Value") { + companion object { + private val STRING = String::class.asTypeName() + + private val mapping: Map = mapOf( + DoubleValue.getDescriptor().fullName to DOUBLE, + FloatValue.getDescriptor().fullName to FLOAT, + Int64Value.getDescriptor().fullName to LONG, + UInt64Value.getDescriptor().fullName to LONG, + Int32Value.getDescriptor().fullName to INT, + UInt32Value.getDescriptor().fullName to INT, + BoolValue.getDescriptor().fullName to BOOLEAN, + StringValue.getDescriptor().fullName to STRING, + BytesValue.getDescriptor().fullName to BYTE_STRING + ) + + private val names: Map = mapOf( + DoubleValue.getDescriptor().fullName to "double", + FloatValue.getDescriptor().fullName to "float", + Int64Value.getDescriptor().fullName to "int64", + UInt64Value.getDescriptor().fullName to "uint64", + Int32Value.getDescriptor().fullName to "int32", + UInt32Value.getDescriptor().fullName to "uint32", + BoolValue.getDescriptor().fullName to "bool", + StringValue.getDescriptor().fullName to "string", + BytesValue.getDescriptor().fullName to "bytes" + ) + } + + private fun GeneratorConfig.wrapperProperty( + component: SingletonFieldDescriptor, + mutable: Boolean, + receiverType: TypeName?, + orBuilderCode: CodeBlock + ): PropertySpec? { + if (!component.isMessage()) { + return null + } + val componentType: String = component.descriptor.messageType.fullName + val valueType = mapping[componentType] ?: return null + val valuePropertyName = component.propertySimpleName.withSuffix(propertyNameSuffix) + + val nullableValueType = valueType.copy(nullable = true) + + val propertyBuilder = + PropertySpec + .builder(valuePropertyName, nullableValueType) + + receiverType?.let { propertyBuilder.receiver(it) } + + propertyBuilder.addKdoc( + "A nullable view of the field %L of the message %L as an optional %L.", + component.descriptor.fieldName, + component.containingType.fullName, + names.getValue(componentType) + ) + + val wrapperProperty = component.property() + val wrapperHazzer = component.hazzer()!! + + propertyBuilder.getter( + getterBuilder() + .addStatement( + "return if (%L.%N()) %L.%N.value else null", + orBuilderCode, + wrapperHazzer, + orBuilderCode, + wrapperProperty + ) + .build() + ) + + if (mutable) { + val setterParam = ParameterSpec.of("newValue", nullableValueType) + propertyBuilder.mutable(true) + propertyBuilder.setter( + setterBuilder() + .addParameter(setterParam) + .beginControlFlow("if (%N == null)", setterParam) + .addStatement( + "%L.%L()", + orBuilderCode, + component.propertySimpleName.withPrefix("clear") + ) + .nextControlFlow("else") + .addStatement( + "%L.%N = %T.newBuilder().setValue(%N).build()", + orBuilderCode, + component.property(), + component.type(), + setterParam + ) + .endControlFlow() + .build() + ) + } + + return propertyBuilder.build() + } + + val forMessageOrBuilder: ComponentExtensionGenerator = + PropertyGenerator({ it.orBuilderClass() }, mutable = false) + + val forBuilder: ComponentExtensionGenerator = + PropertyGenerator({ it.builderClass() }, mutable = true) + + val forDsl: DslComponentGenerator = DslPropertyGenerator() + + private inner class PropertyGenerator( + val receiverType: GeneratorConfig.(Descriptor) -> TypeName, + val mutable: Boolean + ) : ComponentExtensionGenerator(SingletonFieldDescriptor::class) { + override fun GeneratorConfig.componentExtensions( + component: SingletonFieldDescriptor, + enclosingScope: Scope, + extensionScope: Scope + ): Declarations = declarations { + wrapperProperty( + component = component, + mutable = mutable, + orBuilderCode = CodeBlock.of("this"), + receiverType = receiverType(component.containingType) + )?.let { addTopLevelProperty(it) } + } + } + + private inner class DslPropertyGenerator : + DslComponentGenerator(SingletonFieldDescriptor::class) { + override fun GeneratorConfig.generate( + component: SingletonFieldDescriptor, + builderCode: CodeBlock, + dslBuilder: TypeSpec.Builder, + companionBuilder: TypeSpec.Builder, + enclosingScope: Scope, + extensionScope: Scope, + dslType: TypeName + ) { + wrapperProperty( + component = component, + receiverType = null, // instance property on the DSL class + orBuilderCode = builderCode, + mutable = true + )?.let { dslBuilder.addProperty(it) } + } + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/OneofRepresentationTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/OneofRepresentationTest.kt new file mode 100644 index 000000000000..c32b5d0cec1f --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/OneofRepresentationTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin + +import com.google.common.truth.Truth.assertThat +import com.google.protobuf.Int32Value +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests to verify an example subclass of [OneofRepresentation] actually compiles. */ +@RunWith(JUnit4::class) +class OneofRepresentationTest { + enum class MyOneofCase { + MY_INT, MY_STRING, MY_MESSAGE, NOT_SET + } + + abstract class MyOneof : OneofRepresentation() { + class MyInt(override val value: Int) : MyOneof() { + override val case: MyOneofCase + get() = MyOneofCase.MY_INT + } + class MyString(override val value: String) : MyOneof() { + override val case: MyOneofCase + get() = MyOneofCase.MY_STRING + } + class MyMessage(override val value: Int32Value) : MyOneof() { + override val case: MyOneofCase + get() = MyOneofCase.MY_MESSAGE + } + object NotSet : MyOneof() { + override val value: Unit + get() = Unit + override val case: MyOneofCase + get() = MyOneofCase.NOT_SET + } + } + + data class MyExample( + private val myOneofCase: MyOneofCase, + private val myInt: Int, + private val myString: String, + private val myMessage: Int32Value? + ) { + companion object { + /** a faux "constructor" from the oneof */ + operator fun invoke(myOneof: MyOneof<*>): MyExample = when (myOneof) { + is MyOneof.MyInt -> MyExample(MyOneofCase.MY_INT, myOneof.value, "", null) + is MyOneof.MyString -> MyExample(MyOneofCase.MY_STRING, 0, myOneof.value, null) + is MyOneof.MyMessage -> MyExample(MyOneofCase.MY_MESSAGE, 0, "", myOneof.value) + else -> MyExample(MyOneofCase.NOT_SET, 0, "", null) + } + } + + val myOneof: MyOneof<*> + get() = when (myOneofCase) { + MyOneofCase.MY_INT -> MyOneof.MyInt(myInt) + MyOneofCase.MY_STRING -> MyOneof.MyString(myString) + MyOneofCase.MY_MESSAGE -> MyOneof.MyMessage(myMessage!!) + else -> MyOneof.NotSet + } + } + + @Test + fun myExample() { + val someOneof = MyOneof.MyString("apple") + assertThat(someOneof).isEqualTo(MyOneof.MyString("apple")) + val myExample = MyExample(MyOneofCase.MY_STRING, 0, "apple", null) + assertThat(MyExample(someOneof)).isEqualTo(myExample) + assertThat(myExample.myOneof).isEqualTo(someOneof) + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AbstractGeneratedCodeTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AbstractGeneratedCodeTest.kt new file mode 100644 index 000000000000..0dbafe2bfb83 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AbstractGeneratedCodeTest.kt @@ -0,0 +1,463 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.extensions.proto.LiteProtoSubject +import com.google.protobuf.kotlin.extensions.contains +import com.google.protobuf.kotlin.extensions.get +import com.google.protobuf.kotlin.generator.EvilNamesOuterClass.EvilNames +import com.google.protobuf.kotlin.generator.EvilNamesOuterClassKt.evilNames +import com.google.protobuf.kotlin.generator.Example2.int32Extension +import com.google.protobuf.kotlin.generator.Example2.repeatedStringExtension +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage +import org.junit.Test + +/** Supertype of tests for generated code for `example3.proto`. */ +abstract class AbstractGeneratedCodeTest { + abstract fun assertThat(message: ExampleMessage): LiteProtoSubject + abstract fun assertThat(message: EvilNames): LiteProtoSubject + abstract fun assertThat(message: Example2.ExampleProto2Message): LiteProtoSubject + + @Test + fun dslSetScalarField() { + assertThat( + Example3Kt.exampleMessage { + int32Field = 5 + } + ).isEqualTo(ExampleMessage.newBuilder().setInt32Field(5).build()) + } + + @Test + fun dslSetMessageField() { + val exampleMessage = Example3Kt.exampleMessage { + subMessageField = Example3Kt.ExampleMessageKt.subMessage { + } + } + assertThat(exampleMessage) + .isEqualTo( + ExampleMessage.newBuilder() + .setSubMessageField(ExampleMessage.SubMessage.getDefaultInstance()) + .build() + ) + assertThat(exampleMessage.hasSubMessageField()).isTrue() + } + + @Test + fun dslMessageFieldHazzer() { + val exampleMessage = Example3Kt.exampleMessage { + assertThat(hasSubMessageField()).isFalse() + subMessageField = Example3Kt.ExampleMessageKt.subMessage { + } + assertThat(hasSubMessageField()).isTrue() + } + assertThat(exampleMessage) + .isEqualTo( + ExampleMessage.newBuilder() + .setSubMessageField(ExampleMessage.SubMessage.getDefaultInstance()) + .build() + ) + assertThat(exampleMessage.hasSubMessageField()).isTrue() + } + + @Test + fun dslRepeatedFieldAdd() { + val exampleMessage = Example3Kt.exampleMessage { + repeatedStringField.add("foobar") + repeatedStringField.add("quux") + } + assertThat(exampleMessage).isEqualTo( + ExampleMessage.newBuilder() + .addRepeatedStringField("foobar") + .addRepeatedStringField("quux") + .build() + ) + } + + @Test + fun dslRepeatedFieldPlusAssignElement() { + val exampleMessage = Example3Kt.exampleMessage { + repeatedStringField += "foobar" + repeatedStringField += "quux" + } + assertThat(exampleMessage).isEqualTo( + ExampleMessage.newBuilder() + .addRepeatedStringField("foobar") + .addRepeatedStringField("quux") + .build() + ) + } + + @Test + fun dslRepeatedFieldQuery() { + val exampleMessage = Example3Kt.exampleMessage { + repeatedStringField += "foobar" + repeatedStringField += "quux" + assertThat(repeatedStringField).containsExactly("foobar", "quux").inOrder() + assertThat(repeatedStringField[0]).isEqualTo("foobar") + assertThat(repeatedStringField.size).isEqualTo(2) + } + assertThat(exampleMessage).isEqualTo( + ExampleMessage.newBuilder() + .addRepeatedStringField("foobar") + .addRepeatedStringField("quux") + .build() + ) + } + + @Test + fun dslRepeatedFieldSet() { + val exampleMessage = Example3Kt.exampleMessage { + repeatedStringField += "foobar" + repeatedStringField += "quux" + repeatedStringField[0] = "banana" + } + assertThat(exampleMessage).isEqualTo( + ExampleMessage.newBuilder() + .addRepeatedStringField("banana") + .addRepeatedStringField("quux") + .build() + ) + } + + @Test + fun dslRepeatedFieldClear() { + val exampleMessage = Example3Kt.exampleMessage { + repeatedStringField += "foobar" + repeatedStringField.clear() + } + assertThat(exampleMessage).isEqualTo(ExampleMessage.getDefaultInstance()) + } + + @Test + fun dslMapFieldPut() { + val exampleMessage = Example3Kt.exampleMessage { + mapField.put(5L, ExampleMessage.SubMessage.getDefaultInstance()) + } + assertThat(exampleMessage) + .isEqualTo( + ExampleMessage.newBuilder() + .putMapField(5L, ExampleMessage.SubMessage.getDefaultInstance()) + .build() + ) + } + + @Test + fun dslMapFieldSet() { + val exampleMessage = Example3Kt.exampleMessage { + mapField[5L] = ExampleMessage.SubMessage.getDefaultInstance() + } + assertThat(exampleMessage) + .isEqualTo( + ExampleMessage.newBuilder() + .putMapField(5L, ExampleMessage.SubMessage.getDefaultInstance()) + .build() + ) + } + + @Test + fun dslMapFieldPutAll() { + val exampleMessage = Example3Kt.exampleMessage { + mapField.putAll(mapOf(5L to ExampleMessage.SubMessage.getDefaultInstance())) + } + assertThat(exampleMessage) + .isEqualTo( + ExampleMessage.newBuilder() + .putMapField(5L, ExampleMessage.SubMessage.getDefaultInstance()) + .build() + ) + } + + @Test + fun dslMapFieldQuery() { + val exampleMessage = Example3Kt.exampleMessage { + mapField[5L] = ExampleMessage.SubMessage.getDefaultInstance() + assertThat(mapField[5L]).isNotNull() + assertThat(mapField[3L]).isNull() + assertThat(mapField.size).isEqualTo(1) + assertThat(mapField).containsExactly(5L, ExampleMessage.SubMessage.getDefaultInstance()) + } + assertThat(exampleMessage) + .isEqualTo( + ExampleMessage.newBuilder() + .putMapField(5L, ExampleMessage.SubMessage.getDefaultInstance()) + .build() + ) + } + + @Test + fun dslMapFieldClear() { + val exampleMessage = Example3Kt.exampleMessage { + mapField[5L] = ExampleMessage.SubMessage.getDefaultInstance() + mapField.clear() + } + assertThat(exampleMessage).isEqualTo(ExampleMessage.getDefaultInstance()) + } + + @Test + fun dslCopy() { + assertThat( + Example3Kt.exampleMessage { + int32Field = 5 + }.copy { + int32Field = 4 + stringField = "foo" + } + ).isEqualTo(ExampleMessage.newBuilder().setInt32Field(4).setStringField("foo").build()) + } + + @Test + fun oneofNotSet() { + assertThat(ExampleMessage.getDefaultInstance().myOneof) + .isEqualTo(Example3Kt.ExampleMessageKt.MyOneofOneof.NotSet) + } + + @Test + fun oneofString() { + assertThat( + Example3Kt.exampleMessage { + stringOneofOption = "foo" + }.myOneof + ).isEqualTo(Example3Kt.ExampleMessageKt.MyOneofOneof.StringOneofOption("foo")) + } + + @Test + fun oneofSetDsl() { + val exampleMessage = Example3Kt.exampleMessage { + myOneof = Example3Kt.ExampleMessageKt.MyOneofOneof.StringOneofOption("bar") + } + assertThat(exampleMessage.myOneofCase).isEqualTo(ExampleMessage.MyOneofCase.STRING_ONEOF_OPTION) + assertThat(exampleMessage.stringOneofOption).isEqualTo("bar") + } + + @Test + fun oneofSetBuilder() { + val builder = ExampleMessage.newBuilder() + builder.myOneof = + Example3Kt.ExampleMessageKt.MyOneofOneof.SubMessageOneofOption( + ExampleMessage.SubMessage.getDefaultInstance() + ) + val exampleMessage = builder.build() + assertThat(exampleMessage.myOneofCase) + .isEqualTo(ExampleMessage.MyOneofCase.SUB_MESSAGE_ONEOF_OPTION) + } + + @Test + fun evilNames() { + val evil = evilNames { + isInitialized = true + initialized = false + bar = "bar" + hasFoo = true + `class` = "classy" + `in` = -5 + `object` = "objectified" + long = true + serializedSize = true + cachedSize = "foo" + } + assertThat(evil).isEqualTo( + EvilNames + .newBuilder() + .setIsInitialized(true) + .setInitialized(false) + .setBar("bar") + .setHasFoo(true) + .setClass_("classy") + .setIn(-5) + .setObject("objectified") + .setLong(true) + .setSerializedSize_(true) + .setCachedSize_("foo") + .build() + ) + + } + + @Test + fun scalarExtensionDslSet() { + assertThat( + Example2Kt.exampleProto2Message { + requiredStringField = "foo" + this[int32Extension] = 5 + } + ).isEqualTo( + Example2.ExampleProto2Message.newBuilder() + .setRequiredStringField("foo") + .setExtension(int32Extension, 5) + .build() + ) + } + + @Test + fun scalarExtensionDslContains() { + Example2Kt.exampleProto2Message { + requiredStringField = "foo" + assertThat(int32Extension in this).isFalse() + this[int32Extension] = 5 + assertThat(int32Extension in this).isTrue() + } + } + + @Test + fun scalarExtensionDslGet() { + assertThat( + Example2Kt.exampleProto2Message { + requiredStringField = "foo" + this[int32Extension] = 5 + assertThat(this[int32Extension]).isEqualTo(5) + } + ).isEqualTo( + Example2.ExampleProto2Message.newBuilder() + .setRequiredStringField("foo") + .setExtension(int32Extension, 5) + .build() + ) + } + + @Test + fun scalarExtensionGetFromProto() { + val proto = Example2Kt.exampleProto2Message { + requiredStringField = "foo" + this[int32Extension] = 5 + } + assertThat(proto[int32Extension]).isEqualTo(5) + } + + @Test + fun scalarExtensionContainsFromProto() { + assertThat( + int32Extension in Example2Kt.exampleProto2Message { + requiredStringField = "foo" + this[int32Extension] = 5 + } + ).isTrue() + assertThat( + int32Extension in Example2Kt.exampleProto2Message { + requiredStringField = "foo" + } + ).isFalse() + } + + @Test + fun scalarExtensionClear() { + assertThat( + Example2Kt.exampleProto2Message { + requiredStringField = "foo" + this[int32Extension] = 5 + clearExtension(int32Extension) + } + ).isEqualTo( + Example2.ExampleProto2Message.newBuilder() + .setRequiredStringField("foo") + .build() + ) + } + + @Test + fun repeatedExtensionPlusAssign() { + assertThat( + Example2Kt.exampleProto2Message { + requiredStringField = "foo" + this[repeatedStringExtension] += "bar" + } + ).isEqualTo( + Example2.ExampleProto2Message.newBuilder() + .setRequiredStringField("foo") + .addExtension(repeatedStringExtension, "bar") + .build() + ) + } + + @Test + fun repeatedExtensionPlusAssignIterable() { + assertThat( + Example2Kt.exampleProto2Message { + requiredStringField = "foo" + this[repeatedStringExtension] += listOf("bar") + } + ).isEqualTo( + Example2.ExampleProto2Message.newBuilder() + .setRequiredStringField("foo") + .addExtension(repeatedStringExtension, "bar") + .build() + ) + } + + @Test + fun repeatedExtensionSet() { + assertThat( + Example2Kt.exampleProto2Message { + requiredStringField = "foo" + this[repeatedStringExtension] += "bar" + this[repeatedStringExtension][0] = "quux" + } + ).isEqualTo( + Example2.ExampleProto2Message.newBuilder() + .setRequiredStringField("foo") + .addExtension(repeatedStringExtension, "quux") + .build() + ) + } + + @Test + fun repeatedExtensionGetFromDsl() { + Example2Kt.exampleProto2Message { + requiredStringField = "foo" + assertThat(this[repeatedStringExtension]).isEmpty() + this[repeatedStringExtension] += "bar" + assertThat(this[repeatedStringExtension]).containsExactly("bar") + this[repeatedStringExtension] += "quux" + assertThat(this[repeatedStringExtension]).containsExactly("bar", "quux").inOrder() + } + } + + @Test + fun repeatedExtensionClear() { + assertThat( + Example2Kt.exampleProto2Message { + requiredStringField = "foo" + this[repeatedStringExtension] += "bar" + this[repeatedStringExtension].clear() + this[repeatedStringExtension] += "quux" + } + ).isEqualTo( + Example2.ExampleProto2Message.newBuilder() + .setRequiredStringField("foo") + .addExtension(repeatedStringExtension, "quux") + .build() + ) + } + + @Test + fun repeatedExtensionGetFromProto() { + val proto = Example2Kt.exampleProto2Message { + requiredStringField = "foo" + this[repeatedStringExtension] += "bar" + this[repeatedStringExtension] += "quux" + } + assertThat(proto[repeatedStringExtension]).containsExactly("bar", "quux").inOrder() + } + + @Test + fun javaMultipleFilesFactory() { + @Suppress("RemoveRedundantQualifierName") + assertThat(com.google.protobuf.kotlin.generator.multipleFilesMessageA { }).isEqualTo( + MultipleFilesMessageA.getDefaultInstance() + ) + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AndroidLiteGeneratedCodeTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AndroidLiteGeneratedCodeTest.kt new file mode 100644 index 000000000000..6f5a7fd4ff4b --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AndroidLiteGeneratedCodeTest.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.common.truth.extensions.proto.LiteProtoSubject +import com.google.common.truth.extensions.proto.LiteProtoTruth +import com.google.protobuf.kotlin.generator.EvilNamesOuterClass.EvilNames +import com.google.protobuf.kotlin.generator.Example2 +import com.google.protobuf.kotlin.generator.Example3 +import org.junit.runner.RunWith + +class AndroidLiteGeneratedCodeTest : AbstractGeneratedCodeTest() { + override fun assertThat(message: Example3.ExampleMessage): LiteProtoSubject = + LiteProtoTruth.assertThat(message) + + override fun assertThat(message: EvilNames): LiteProtoSubject = + LiteProtoTruth.assertThat(message) + + override fun assertThat(message: Example2.ExampleProto2Message): LiteProtoSubject = + LiteProtoTruth.assertThat(message) +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AndroidManifest.xml b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AndroidManifest.xml new file mode 100644 index 000000000000..589230da0ef5 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/DslGeneratorTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/DslGeneratorTest.kt new file mode 100644 index 000000000000..7f99c1c84075 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/DslGeneratorTest.kt @@ -0,0 +1,171 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.ClassSimpleName +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.JavaPackagePolicy +import com.google.protobuf.kotlin.protoc.MemberSimpleName +import com.google.protobuf.kotlin.protoc.PackageScope +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.google.protobuf.kotlin.generator.Example3 +import com.squareup.kotlinpoet.ClassName +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [DslGenerator]. */ +@RunWith(JUnit4::class) +class DslGeneratorTest { + private val javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE + private val config = GeneratorConfig(javaPackagePolicy, aggressiveInlining = false) + + @Test + fun simpleDslNoComponents() { + val dslGenerator = DslGenerator( + extendableDslSuperclass = ClassName("com.google.protobuf.kotlin", "ProtoDsl"), + dslClassNameSuffix = "DslClassNameSuffix", + dslFactoryNamePrefix = "factoryNamePrefix", + copyName = null + ) + val enclosingScope = PackageScope(Example3.ExampleMessage::class.java.`package`.name) + val extensionScope = enclosingScope.nestedScope(ClassSimpleName("Extensions")) + with(config) { + dslGenerator.run { + val decls = shallowExtensions( + descriptor = Example3.ExampleMessage.getDescriptor(), + enclosingScope = enclosingScope, + extensionScope = extensionScope + ) + assertThat(decls) + .generatesEnclosed( + """ +import com.google.protobuf.kotlin.ProtoDsl +import com.google.protobuf.kotlin.ProtoDslMarker +import com.google.protobuf.kotlin.generator.Example3 +import com.google.protobuf.kotlin.generator.ExampleMessageDslClassNameSuffix +import kotlin.PublishedApi +import kotlin.Unit +import kotlin.jvm.JvmSynthetic + +/** + * Entry point for creating ExampleMessage messages in Kotlin. + */ +@JvmSynthetic +inline fun factoryNamePrefixExampleMessage(block: ExampleMessageDslClassNameSuffix.() -> Unit) = + ExampleMessageDslClassNameSuffix(Example3.ExampleMessage.newBuilder()).apply(block).build() + +/** + * A DSL type for creating ExampleMessage messages. To use it, call the + * [factoryNamePrefixExampleMessage] method. + */ +@ProtoDslMarker +class ExampleMessageDslClassNameSuffix @PublishedApi internal constructor( + builder_: Example3.ExampleMessage.Builder +) : ProtoDsl(builder_) { + @PublishedApi + internal val builder_: Example3.ExampleMessage.Builder + get() = _proto_dsl_builder + + /** + * Builds the ExampleMessage. + */ + @PublishedApi + internal fun build(): Example3.ExampleMessage = builder_.build() +} + """ + ) + // note we explicitly expect there to be no companion object by default + assertThat(decls).generatesNoTopLevelMembers() + } + } + } + + @Test + fun copyDslNoComponents() { + val dslGenerator = DslGenerator( + extendableDslSuperclass = ClassName("com.google.protobuf.kotlin", "ProtoDsl"), + dslClassNameSuffix = "DslClassNameSuffix", + dslFactoryNamePrefix = "factoryNamePrefix", + copyName = MemberSimpleName("copyMethod") + ) + val enclosingScope = PackageScope(Example3.ExampleMessage::class.java.`package`.name) + val extensionScope = enclosingScope.nestedScope(ClassSimpleName("Extensions")) + with(config) { + dslGenerator.run { + val decls = shallowExtensions( + descriptor = Example3.ExampleMessage.getDescriptor(), + enclosingScope = enclosingScope, + extensionScope = extensionScope + ) + assertThat(decls) + .generatesEnclosed( + """ +import com.google.protobuf.kotlin.ProtoDsl +import com.google.protobuf.kotlin.ProtoDslMarker +import com.google.protobuf.kotlin.generator.Example3 +import com.google.protobuf.kotlin.generator.ExampleMessageDslClassNameSuffix +import kotlin.PublishedApi +import kotlin.Unit +import kotlin.jvm.JvmSynthetic + +/** + * Entry point for creating ExampleMessage messages in Kotlin. + */ +@JvmSynthetic +inline fun factoryNamePrefixExampleMessage(block: ExampleMessageDslClassNameSuffix.() -> Unit) = + ExampleMessageDslClassNameSuffix(Example3.ExampleMessage.newBuilder()).apply(block).build() + +/** + * A DSL type for creating ExampleMessage messages. To use it, call the + * [factoryNamePrefixExampleMessage] method. + */ +@ProtoDslMarker +class ExampleMessageDslClassNameSuffix @PublishedApi internal constructor( + builder_: Example3.ExampleMessage.Builder +) : ProtoDsl(builder_) { + @PublishedApi + internal val builder_: Example3.ExampleMessage.Builder + get() = _proto_dsl_builder + + /** + * Builds the ExampleMessage. + */ + @PublishedApi + internal fun build(): Example3.ExampleMessage = builder_.build() +} + """ + ) + assertThat(decls).generatesTopLevel( + """ +import com.google.protobuf.kotlin.generator.Example3 +import com.google.protobuf.kotlin.generator.ExampleMessageDslClassNameSuffix +import kotlin.Unit +import kotlin.jvm.JvmSynthetic + +/** + * Creates a Example3.ExampleMessage equivalent to this one, but with the specified modifications. + */ +@JvmSynthetic +inline fun Example3.ExampleMessage.copyMethod(block: ExampleMessageDslClassNameSuffix.() -> Unit): + Example3.ExampleMessage = ExampleMessageDslClassNameSuffix(toBuilder()).apply(block).build() + """ + ) + } + } + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.enclosed b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.enclosed new file mode 100644 index 000000000000..264a4c280b19 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.enclosed @@ -0,0 +1,60 @@ +import com.google.protobuf.kotlin.OneofRepresentation +import com.google.protobuf.kotlin.generator.Example3 +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage.MyOneofCase.MYONEOF_NOT_SET +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage.MyOneofCase.STRING_ONEOF_OPTION +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage.MyOneofCase.SUB_MESSAGE_ONEOF_OPTION +import kotlin.Any +import kotlin.PublishedApi +import kotlin.String +import kotlin.Unit + +/** + * Representation of a value for the oneof my_oneof. + */ +abstract class MyOneofOneofClass( + override val value: T, + override val case: Example3.ExampleMessage.MyOneofCase +) : OneofRepresentation() { + internal fun writeTo(builder: Example3.ExampleMessage.Builder) { + when (case) { + STRING_ONEOF_OPTION -> builder.stringOneofOption = value as String + SUB_MESSAGE_ONEOF_OPTION -> builder.subMessageOneofOption = value as + Example3.ExampleMessage.SubMessage + else -> builder.clearMyOneof() + } + } + + /** + * A representation of the string_oneof_option option of the my_oneof oneof. + */ + class StringOneofOptionOneofClass( + value: String + ) : MyOneofOneofClass(value, STRING_ONEOF_OPTION) + + /** + * A representation of the sub_message_oneof_option option of the my_oneof oneof. + */ + class SubMessageOneofOptionOneofClass( + value: Example3.ExampleMessage.SubMessage + ) : MyOneofOneofClass(value, SUB_MESSAGE_ONEOF_OPTION) + + /** + * Represents the absence of a value for my_oneof. + */ + object NotSetName : MyOneofOneofClass(Unit, MYONEOF_NOT_SET) + + companion object { + /** + * Extract an object representation of the set field of the oneof my_oneof. + */ + @PublishedApi + internal fun readFrom(input: Example3.ExampleMessageOrBuilder): MyOneofOneofClass<*> { + val _theCase: Example3.ExampleMessage.MyOneofCase = input.myOneofCase + return when (_theCase) { + STRING_ONEOF_OPTION -> StringOneofOptionOneofClass(input.stringOneofOption) + SUB_MESSAGE_ONEOF_OPTION -> SubMessageOneofOptionOneofClass(input.subMessageOneofOption) + else -> NotSetName + } + } + } +} \ No newline at end of file diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.toplevel b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.toplevel new file mode 100644 index 000000000000..a1a09823c522 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.toplevel @@ -0,0 +1,16 @@ +import com.google.protobuf.kotlin.generator.Example3 + +/** + * The value of the oneof my_oneof. + */ +var Example3.ExampleMessage.Builder.myOneofProperty: MyOneofOneofClass<*> + get() = MyOneofOneofClass.readFrom(this) + set(newOneofParam) { + newOneofParam.writeTo(this) + } + +/** + * The value of the oneof my_oneof. + */ +val Example3.ExampleMessageOrBuilder.myOneofProperty: MyOneofOneofClass<*> + get() = MyOneofOneofClass.readFrom(this) \ No newline at end of file diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGeneratorTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGeneratorTest.kt new file mode 100644 index 000000000000..a0fa775c1b58 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGeneratorTest.kt @@ -0,0 +1,213 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.ClassSimpleName +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.JavaPackagePolicy +import com.google.protobuf.kotlin.protoc.OneofOptionFieldDescriptor +import com.google.protobuf.kotlin.protoc.SingletonFieldDescriptor +import com.google.protobuf.kotlin.protoc.UnqualifiedScope +import com.google.protobuf.kotlin.protoc.classBuilder +import com.google.protobuf.kotlin.protoc.specialize +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.google.protobuf.kotlin.generator.Example2 +import com.google.protobuf.kotlin.generator.Example3 +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.TypeSpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [FieldOrNullPropertyGenerator]. */ +@RunWith(JUnit4::class) +class FieldOrNullPropertyGeneratorTest { + private val javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE + private val config = GeneratorConfig(javaPackagePolicy, aggressiveInlining = false) + + private val fieldDescriptor = Example3.ExampleMessage.getDescriptor().findFieldByNumber( + Example3.ExampleMessage.SUB_MESSAGE_FIELD_FIELD_NUMBER + ).specialize() as SingletonFieldDescriptor + + private val oneofOptionDescriptor = Example3.ExampleMessage.getDescriptor().findFieldByNumber( + Example3.ExampleMessage.SUB_MESSAGE_ONEOF_OPTION_FIELD_NUMBER + ).specialize() as OneofOptionFieldDescriptor + + private val requiredFieldDescriptor = + Example2.ExampleProto2Message.getDescriptor().findFieldByNumber( + Example2.ExampleProto2Message.REQUIRED_STRING_FIELD_FIELD_NUMBER + ).specialize() as SingletonFieldDescriptor + + @Test + fun forMessageOrBuilder() { + val declarations = FieldOrNullPropertyGenerator().forMessageOrBuilder.run { + config.componentExtensions(fieldDescriptor, UnqualifiedScope, UnqualifiedScope) + } + + assertThat(declarations).generatesNoEnclosedMembers() + assertThat(declarations).generatesTopLevel( + """ + import com.google.protobuf.kotlin.generator.Example3 + + /** + * The sub_message_field field of the proto ExampleMessage, or null if it isn't set. + */ + val Example3.ExampleMessageOrBuilder.subMessageFieldOrNull: Example3.ExampleMessage.SubMessage? + get() = if (this.hasSubMessageField()) this.subMessageField else null + """ + ) + } + + @Test + fun forMessageOrBuilderSkipsRequiredFields() { + val declarations = FieldOrNullPropertyGenerator().forMessageOrBuilder.run { + config.componentExtensions(requiredFieldDescriptor, UnqualifiedScope, UnqualifiedScope) + } + + assertThat(declarations).generatesNoEnclosedMembers() + assertThat(declarations).generatesNoTopLevelMembers() + } + + @Test + fun skipOneOf() { + val declarations = + FieldOrNullPropertyGenerator(skipOneofFields = true).forMessageOrBuilder.run { + config.componentExtensions(oneofOptionDescriptor, UnqualifiedScope, UnqualifiedScope) + } + + assertThat(declarations).generatesNoEnclosedMembers() + assertThat(declarations).generatesNoTopLevelMembers() + } + + @Test + fun skipOneOfSetToFalse() { + val declarations = + FieldOrNullPropertyGenerator(skipOneofFields = false).forMessageOrBuilder.run { + config.componentExtensions(oneofOptionDescriptor, UnqualifiedScope, UnqualifiedScope) + } + + assertThat(declarations).generatesNoEnclosedMembers() + assertThat(declarations).generatesTopLevel( + """ + import com.google.protobuf.kotlin.generator.Example3 + + /** + * The sub_message_oneof_option field of the proto ExampleMessage, or null if it isn't set. + */ + val Example3.ExampleMessageOrBuilder.subMessageOneofOptionOrNull: + Example3.ExampleMessage.SubMessage? + get() = if (this.hasSubMessageOneofOption()) this.subMessageOneofOption else null + """ + ) + } + + @Test + fun forBuilder() { + val declarations = FieldOrNullPropertyGenerator().forBuilder.run { + config.componentExtensions(fieldDescriptor, UnqualifiedScope, UnqualifiedScope) + } + + assertThat(declarations).generatesNoEnclosedMembers() + assertThat(declarations).generatesTopLevel( + """ + import com.google.protobuf.kotlin.generator.Example3 + + /** + * The sub_message_field field of the proto ExampleMessage, or null if it isn't set. + */ + var Example3.ExampleMessage.Builder.subMessageFieldOrNull: Example3.ExampleMessage.SubMessage? + get() = if (this.hasSubMessageField()) this.subMessageField else null + set(_newValue) { + if (_newValue == null) { + this.clearSubMessageField() + } else { + this.subMessageField = _newValue + } + } + """ + ) + } + + @Test + fun forBuilderIncludesRequiredFields() { + val declarations = FieldOrNullPropertyGenerator().forBuilder.run { + config.componentExtensions(requiredFieldDescriptor, UnqualifiedScope, UnqualifiedScope) + } + + assertThat(declarations).generatesNoEnclosedMembers() + assertThat(declarations).generatesTopLevel( + """ + import com.google.protobuf.kotlin.generator.Example2 + import kotlin.String + + /** + * The required_string_field field of the proto ExampleProto2Message, or null if it isn't set. + */ + var Example2.ExampleProto2Message.Builder.requiredStringFieldOrNull: String? + get() = if (this.hasRequiredStringField()) this.requiredStringField else null + set(_newValue) { + if (_newValue == null) { + this.clearRequiredStringField() + } else { + this.requiredStringField = _newValue + } + } + """ + ) + } + + @Test + fun forDsl() { + val dslSimpleName = ClassSimpleName("DslClass") + val dslBuilder = TypeSpec.classBuilder(dslSimpleName) + val dslCompanionBuilder = TypeSpec.companionObjectBuilder() + + with(config) { + FieldOrNullPropertyGenerator().forDsl.run { + generate( + component = fieldDescriptor, + dslType = UnqualifiedScope.nestedClass(dslSimpleName), + extensionScope = UnqualifiedScope, + enclosingScope = UnqualifiedScope, + dslBuilder = dslBuilder, + companionBuilder = dslCompanionBuilder, + builderCode = CodeBlock.of("myBuilder") + ) + } + } + + val subMessage = Example3.ExampleMessage.SubMessage::class.qualifiedName!! + assertThat(dslBuilder.build()).generates( + """ +class DslClass { + /** + * The sub_message_field field of the proto ExampleMessage, or null if it isn't set. + */ + var subMessageFieldOrNull: $subMessage? + get() = if (myBuilder.hasSubMessageField()) myBuilder.subMessageField else null + set(_newValue) { + if (_newValue == null) { + myBuilder.clearSubMessageField() + } else { + myBuilder.subMessageField = _newValue + } + } +} + """ + ) + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratedCodeTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratedCodeTest.kt new file mode 100644 index 000000000000..14f45fefe469 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratedCodeTest.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.common.truth.extensions.proto.LiteProtoSubject +import com.google.common.truth.extensions.proto.ProtoTruth +import com.google.protobuf.kotlin.generator.EvilNamesOuterClass.EvilNames +import com.google.protobuf.kotlin.generator.Example2 +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for generated code for the proto file `example3.proto`. */ +@RunWith(JUnit4::class) +class GeneratedCodeTest : AbstractGeneratedCodeTest() { + override fun assertThat(message: ExampleMessage): LiteProtoSubject = + ProtoTruth.assertThat(message) + + override fun assertThat(message: EvilNames): LiteProtoSubject = + ProtoTruth.assertThat(message) + + override fun assertThat(message: Example2.ExampleProto2Message): LiteProtoSubject = + ProtoTruth.assertThat(message) +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2.kt.expected b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2.kt.expected new file mode 100644 index 000000000000..18e4ea9ded67 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2.kt.expected @@ -0,0 +1,106 @@ +@file:Suppress("DEPRECATION") + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.ExtendableProtoDslV3 +import com.google.protobuf.kotlin.ProtoDslMarker +import kotlin.Boolean +import kotlin.Int +import kotlin.PublishedApi +import kotlin.String +import kotlin.Suppress +import kotlin.Unit +import kotlin.jvm.JvmName +import kotlin.jvm.JvmSynthetic + +/** + * Kotlin extensions for protos defined in the file testing/example2.proto. + */ +object Example2Kt { + /** + * Entry point for creating ExampleProto2Message messages in Kotlin. + */ + @JvmSynthetic + inline fun exampleProto2Message(block: ExampleProto2MessageDsl.() -> Unit) = + ExampleProto2MessageDsl(Example2.ExampleProto2Message.newBuilder()).apply(block).build() + + /** + * A DSL type for creating ExampleProto2Message messages. To use it, call the + * [exampleProto2Message] method. + */ + @ProtoDslMarker + class ExampleProto2MessageDsl @PublishedApi internal constructor( + builder_: Example2.ExampleProto2Message.Builder + ) : ExtendableProtoDslV3(builder_) { + @PublishedApi + internal val builder_: Example2.ExampleProto2Message.Builder + get() = _proto_dsl_builder + + /** + * The protobuf.kotlin.generator.ExampleProto2Message.optional_int32_field field. + */ + var optionalInt32Field: Int + @JvmName("getOptionalInt32Field") + get() = builder_.getOptionalInt32Field() + @JvmName("setOptionalInt32Field") + set(newValue) { + builder_.setOptionalInt32Field(newValue) + } + + /** + * The protobuf.kotlin.generator.ExampleProto2Message.required_string_field field. + */ + var requiredStringField: String + @JvmName("getRequiredStringField") + get() = builder_.getRequiredStringField() + @JvmName("setRequiredStringField") + set(newValue) { + builder_.setRequiredStringField(newValue) + } + + /** + * Builds the ExampleProto2Message. + */ + @PublishedApi + internal fun build(): Example2.ExampleProto2Message = builder_.build() + + /** + * True if the optional_int32_field field has been set. + */ + fun hasOptionalInt32Field(): Boolean = builder_.hasOptionalInt32Field() + + /** + * True if the required_string_field field has been set. + */ + fun hasRequiredStringField(): Boolean = builder_.hasRequiredStringField() + + /** + * Clears any value assigned to the optional_int32_field field. + */ + fun clearOptionalInt32Field() { + builder_.clearOptionalInt32Field() + } + + /** + * Clears any value assigned to the required_string_field field. + */ + fun clearRequiredStringField() { + builder_.clearRequiredStringField() + } + } + + /** + * Kotlin extensions around the proto message type protobuf.kotlin.generator.ExampleProto2Message. + */ + object ExampleProto2MessageKt +} + +/** + * Creates a Example2.ExampleProto2Message equivalent to this one, but with the specified + * modifications. + */ +@JvmSynthetic +inline fun Example2.ExampleProto2Message.copy(block: Example2Kt.ExampleProto2MessageDsl.() -> Unit): + Example2.ExampleProto2Message = + Example2Kt.ExampleProto2MessageDsl(toBuilder()).apply(block).build() diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2Lite.kt.expected b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2Lite.kt.expected new file mode 100644 index 000000000000..8f7fe831ae3f --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2Lite.kt.expected @@ -0,0 +1,106 @@ +@file:Suppress("DEPRECATION") + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.ExtendableProtoDslLite +import com.google.protobuf.kotlin.ProtoDslMarker +import kotlin.Boolean +import kotlin.Int +import kotlin.PublishedApi +import kotlin.String +import kotlin.Suppress +import kotlin.Unit +import kotlin.jvm.JvmName +import kotlin.jvm.JvmSynthetic + +/** + * Kotlin extensions for protos defined in the file testing/example2.proto. + */ +object Example2Kt { + /** + * Entry point for creating ExampleProto2Message messages in Kotlin. + */ + @JvmSynthetic + inline fun exampleProto2Message(block: ExampleProto2MessageDsl.() -> Unit) = + ExampleProto2MessageDsl(Example2.ExampleProto2Message.newBuilder()).apply(block).build() + + /** + * A DSL type for creating ExampleProto2Message messages. To use it, call the + * [exampleProto2Message] method. + */ + @ProtoDslMarker + class ExampleProto2MessageDsl @PublishedApi internal constructor( + builder_: Example2.ExampleProto2Message.Builder + ) : ExtendableProtoDslLite(builder_) { + @PublishedApi + internal val builder_: Example2.ExampleProto2Message.Builder + get() = _proto_dsl_builder + + /** + * The protobuf.kotlin.generator.ExampleProto2Message.optional_int32_field field. + */ + var optionalInt32Field: Int + @JvmName("getOptionalInt32Field") + get() = builder_.getOptionalInt32Field() + @JvmName("setOptionalInt32Field") + set(newValue) { + builder_.setOptionalInt32Field(newValue) + } + + /** + * The protobuf.kotlin.generator.ExampleProto2Message.required_string_field field. + */ + var requiredStringField: String + @JvmName("getRequiredStringField") + get() = builder_.getRequiredStringField() + @JvmName("setRequiredStringField") + set(newValue) { + builder_.setRequiredStringField(newValue) + } + + /** + * Builds the ExampleProto2Message. + */ + @PublishedApi + internal fun build(): Example2.ExampleProto2Message = builder_.build() + + /** + * True if the optional_int32_field field has been set. + */ + fun hasOptionalInt32Field(): Boolean = builder_.hasOptionalInt32Field() + + /** + * True if the required_string_field field has been set. + */ + fun hasRequiredStringField(): Boolean = builder_.hasRequiredStringField() + + /** + * Clears any value assigned to the optional_int32_field field. + */ + fun clearOptionalInt32Field() { + builder_.clearOptionalInt32Field() + } + + /** + * Clears any value assigned to the required_string_field field. + */ + fun clearRequiredStringField() { + builder_.clearRequiredStringField() + } + } + + /** + * Kotlin extensions around the proto message type protobuf.kotlin.generator.ExampleProto2Message. + */ + object ExampleProto2MessageKt +} + +/** + * Creates a Example2.ExampleProto2Message equivalent to this one, but with the specified + * modifications. + */ +@JvmSynthetic +inline fun Example2.ExampleProto2Message.copy(block: Example2Kt.ExampleProto2MessageDsl.() -> Unit): + Example2.ExampleProto2Message = + Example2Kt.ExampleProto2MessageDsl(toBuilder()).apply(block).build() \ No newline at end of file diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample3.kt.expected b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample3.kt.expected new file mode 100644 index 000000000000..e78907c112a4 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample3.kt.expected @@ -0,0 +1,767 @@ +@file:Suppress("DEPRECATION") + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.Int32Value +import com.google.protobuf.kotlin.DslList +import com.google.protobuf.kotlin.DslMap +import com.google.protobuf.kotlin.OneofRepresentation +import com.google.protobuf.kotlin.ProtoDsl +import com.google.protobuf.kotlin.ProtoDslMarker +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage.DeprecatedOneofCase.DEPRECATEDONEOF_NOT_SET +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage.DeprecatedOneofCase.DEPRECATED_ONEOF_OPTION +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage.MyOneofCase.MYONEOF_NOT_SET +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage.MyOneofCase.STRING_ONEOF_OPTION +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage.MyOneofCase.SUB_MESSAGE_ONEOF_OPTION +import kotlin.Any +import kotlin.Boolean +import kotlin.Deprecated +import kotlin.Int +import kotlin.Long +import kotlin.PublishedApi +import kotlin.String +import kotlin.Suppress +import kotlin.Unit +import kotlin.collections.Iterable +import kotlin.collections.Map +import kotlin.jvm.JvmName +import kotlin.jvm.JvmSynthetic + +/** + * Kotlin extensions for protos defined in the file testing/example3.proto. + */ +object Example3Kt { + /** + * Entry point for creating ExampleMessage messages in Kotlin. + */ + @JvmSynthetic + inline fun exampleMessage(block: ExampleMessageDsl.() -> Unit) = + ExampleMessageDsl(Example3.ExampleMessage.newBuilder()).apply(block).build() + + /** + * A DSL type for creating ExampleMessage messages. To use it, call the [exampleMessage] method. + */ + @ProtoDslMarker + class ExampleMessageDsl @PublishedApi internal constructor( + builder_: Example3.ExampleMessage.Builder + ) : ProtoDsl(builder_) { + @PublishedApi + internal val builder_: Example3.ExampleMessage.Builder + get() = _proto_dsl_builder + + /** + * The protobuf.kotlin.generator.ExampleMessage.int32_field field. + */ + var int32Field: Int + @JvmName("getInt32Field") + get() = builder_.getInt32Field() + @JvmName("setInt32Field") + set(newValue) { + builder_.setInt32Field(newValue) + } + + /** + * The protobuf.kotlin.generator.ExampleMessage.string_field field. + */ + var stringField: String + @JvmName("getStringField") + get() = builder_.getStringField() + @JvmName("setStringField") + set(newValue) { + builder_.setStringField(newValue) + } + + /** + * The protobuf.kotlin.generator.ExampleMessage.sub_message_field field. + */ + var subMessageField: Example3.ExampleMessage.SubMessage + @JvmName("getSubMessageField") + get() = builder_.getSubMessageField() + @JvmName("setSubMessageField") + set(newValue) { + builder_.setSubMessageField(newValue) + } + + /** + * The protobuf.kotlin.generator.ExampleMessage.string_oneof_option field. + */ + var stringOneofOption: String + @JvmName("getStringOneofOption") + get() = builder_.getStringOneofOption() + @JvmName("setStringOneofOption") + set(newValue) { + builder_.setStringOneofOption(newValue) + } + + /** + * The protobuf.kotlin.generator.ExampleMessage.sub_message_oneof_option field. + */ + var subMessageOneofOption: Example3.ExampleMessage.SubMessage + @JvmName("getSubMessageOneofOption") + get() = builder_.getSubMessageOneofOption() + @JvmName("setSubMessageOneofOption") + set(newValue) { + builder_.setSubMessageOneofOption(newValue) + } + + /** + * The protobuf.kotlin.generator.ExampleMessage.optional_int32 field. + */ + var optionalInt32: Int32Value + @JvmName("getOptionalInt32") + get() = builder_.getOptionalInt32() + @JvmName("setOptionalInt32") + set(newValue) { + builder_.setOptionalInt32(newValue) + } + + /** + * The protobuf.kotlin.generator.ExampleMessage.deprecated_field field. + */ + @Deprecated(message = "Field deprecated_field is deprecated") + var deprecatedField: String + @JvmName("getDeprecatedField") + get() = builder_.getDeprecatedField() + @JvmName("setDeprecatedField") + set(newValue) { + builder_.setDeprecatedField(newValue) + } + + /** + * The protobuf.kotlin.generator.ExampleMessage.deprecated_oneof_option field. + */ + @Deprecated(message = "Field deprecated_oneof_option is deprecated") + var deprecatedOneofOption: String + @JvmName("getDeprecatedOneofOption") + get() = builder_.getDeprecatedOneofOption() + @JvmName("setDeprecatedOneofOption") + set(newValue) { + builder_.setDeprecatedOneofOption(newValue) + } + + /** + * An enum reflecting which field of the oneof my_oneof is set. + */ + val myOneofCase: Example3.ExampleMessage.MyOneofCase + get() = builder_.myOneofCase + + /** + * An enum reflecting which field of the oneof deprecated_oneof is set. + */ + val deprecatedOneofCase: Example3.ExampleMessage.DeprecatedOneofCase + get() = builder_.deprecatedOneofCase + + /** + * An object representing a value assigned to the oneof my_oneof. + */ + var myOneof: ExampleMessageKt.MyOneofOneof<*> + get() = ExampleMessageKt.MyOneofOneof.readFrom(builder_) + set(newValue) { + newValue.writeTo(builder_) + } + + /** + * An object representing a value assigned to the oneof deprecated_oneof. + */ + var deprecatedOneof: ExampleMessageKt.DeprecatedOneofOneof<*> + get() = ExampleMessageKt.DeprecatedOneofOneof.readFrom(builder_) + set(newValue) { + newValue.writeTo(builder_) + } + + /** + * A [kotlin.collections.List] view of the repeated field repeated_string_field. + * + * While the view object itself is a `List`, not a `MutableList`, within the context of the + * DSL it has extension methods and operator overloads that mutate it appropriately. + * For example, writing `repeatedStringField += element` adds that element to the repeated + * field, but that + * code only compiles within the DSL. + */ + val repeatedStringField: DslList + inline get() = DslList(builder_.getRepeatedStringFieldList()) + + /** + * A [kotlin.collections.List] view of the repeated field deprecated_repeated_field. + * + * While the view object itself is a `List`, not a `MutableList`, within the context of the + * DSL it has extension methods and operator overloads that mutate it appropriately. + * For example, writing `deprecatedRepeatedField += element` adds that element to the repeated + * field, but that + * code only compiles within the DSL. + */ + @Deprecated(message = "Field deprecated_repeated_field is deprecated") + val deprecatedRepeatedField: DslList + inline get() = DslList(builder_.getDeprecatedRepeatedFieldList()) + + /** + * A [Map] view of the map field map_field. + * + * While the view object itself is a `Map`, not a `MutableMap`, within the context of the + * DSL it has extension methods and operator overloads that mutate it appropriately. + * For example, writing `mapField[k] = v` adds a mapping from `k` to `v` in the map field, + * but that code only compiles within the DSL. + */ + val mapField: DslMap + get() = DslMap(builder_.getMapFieldMap()) + + /** + * A [Map] view of the map field deprecated_map_field. + * + * While the view object itself is a `Map`, not a `MutableMap`, within the context of the + * DSL it has extension methods and operator overloads that mutate it appropriately. + * For example, writing `deprecatedMapField[k] = v` adds a mapping from `k` to `v` in the map + * field, + * but that code only compiles within the DSL. + */ + @Deprecated(message = "Field deprecated_map_field is deprecated") + val deprecatedMapField: DslMap + get() = DslMap(builder_.getDeprecatedMapFieldMap()) + + /** + * Builds the ExampleMessage. + */ + @PublishedApi + internal fun build(): Example3.ExampleMessage = builder_.build() + + /** + * True if the sub_message_field field has been set. + */ + fun hasSubMessageField(): Boolean = builder_.hasSubMessageField() + + /** + * True if the sub_message_oneof_option field has been set. + */ + fun hasSubMessageOneofOption(): Boolean = builder_.hasSubMessageOneofOption() + + /** + * True if the optional_int32 field has been set. + */ + fun hasOptionalInt32(): Boolean = builder_.hasOptionalInt32() + + /** + * Clears any value assigned to the int32_field field. + */ + fun clearInt32Field() { + builder_.clearInt32Field() + } + + /** + * Clears any value assigned to the string_field field. + */ + fun clearStringField() { + builder_.clearStringField() + } + + /** + * Clears any value assigned to the sub_message_field field. + */ + fun clearSubMessageField() { + builder_.clearSubMessageField() + } + + /** + * Clears any value assigned to the string_oneof_option field. + */ + fun clearStringOneofOption() { + builder_.clearStringOneofOption() + } + + /** + * Clears any value assigned to the sub_message_oneof_option field. + */ + fun clearSubMessageOneofOption() { + builder_.clearSubMessageOneofOption() + } + + /** + * Clears any value assigned to the optional_int32 field. + */ + fun clearOptionalInt32() { + builder_.clearOptionalInt32() + } + + /** + * Clears any value assigned to the deprecated_field field. + */ + @Deprecated(message = "Field deprecated_field is deprecated") + fun clearDeprecatedField() { + builder_.clearDeprecatedField() + } + + /** + * Clears any value assigned to the deprecated_oneof_option field. + */ + @Deprecated(message = "Field deprecated_oneof_option is deprecated") + fun clearDeprecatedOneofOption() { + builder_.clearDeprecatedOneofOption() + } + + /** + * Adds an element to the repeated field repeated_string_field. + * + * For example, `repeatedStringField.add(value)` adds `value` to repeated_string_field. + */ + @JvmName("addRepeatedStringField") + fun DslList.add(newValue: String) { + builder_.addRepeatedStringField(newValue) + } + + /** + * Adds an element to the repeated field repeated_string_field. + * + * For example, `repeatedStringField += value` adds `value` to repeated_string_field. + */ + @JvmName("plusAssignRepeatedStringField") + operator fun DslList.plusAssign(newValue: String) { + builder_.addRepeatedStringField(newValue) + } + + /** + * Adds elements to the repeated field repeated_string_field. + * + * For example, `repeatedStringField.addAll(values)` adds each of the elements of `values`, in + * order, to repeated_string_field. + */ + @JvmName("addAllRepeatedStringField") + fun DslList.addAll(newValues: Iterable) { + builder_.addAllRepeatedStringField(newValues) + } + + /** + * Adds elements to the repeated field repeated_string_field. + * + * For example, `repeatedStringField += values` adds each of the elements of `values`, in order, + * to repeated_string_field. + */ + @JvmName("plusAssignRepeatedStringField") + operator fun DslList.plusAssign(newValues: Iterable) { + builder_.addAllRepeatedStringField(newValues) + } + + /** + * Sets the element of the repeated field repeated_string_field at a given index. + * + * For example, `repeatedStringField[i] = value` sets the `i`th element of repeated_string_field + * to `value`. + */ + @JvmName("setRepeatedStringField") + operator fun DslList.set(index: Int, newValue: String) { + builder_.setRepeatedStringField(index, newValue) + } + + /** + * Removes all elements from the repeated field repeated_string_field. + * + * For example, `repeatedStringField.clear()` deletes all elements from repeated_string_field. + */ + @JvmName("clearRepeatedStringField") + fun DslList.clear() { + builder_.clearRepeatedStringField() + } + + /** + * Adds an element to the repeated field deprecated_repeated_field. + * + * For example, `deprecatedRepeatedField.add(value)` adds `value` to deprecated_repeated_field. + */ + @JvmName("addDeprecatedRepeatedField") + fun DslList.add(newValue: Int) { + builder_.addDeprecatedRepeatedField(newValue) + } + + /** + * Adds an element to the repeated field deprecated_repeated_field. + * + * For example, `deprecatedRepeatedField += value` adds `value` to deprecated_repeated_field. + */ + @JvmName("plusAssignDeprecatedRepeatedField") + operator fun DslList.plusAssign(newValue: Int) { + builder_.addDeprecatedRepeatedField(newValue) + } + + /** + * Adds elements to the repeated field deprecated_repeated_field. + * + * For example, `deprecatedRepeatedField.addAll(values)` adds each of the elements of `values`, + * in order, to deprecated_repeated_field. + */ + @JvmName("addAllDeprecatedRepeatedField") + fun DslList.addAll(newValues: Iterable) { + builder_.addAllDeprecatedRepeatedField(newValues) + } + + /** + * Adds elements to the repeated field deprecated_repeated_field. + * + * For example, `deprecatedRepeatedField += values` adds each of the elements of `values`, in + * order, to deprecated_repeated_field. + */ + @JvmName("plusAssignDeprecatedRepeatedField") + operator fun DslList.plusAssign(newValues: Iterable) { + builder_.addAllDeprecatedRepeatedField(newValues) + } + + /** + * Sets the element of the repeated field deprecated_repeated_field at a given index. + * + * For example, `deprecatedRepeatedField[i] = value` sets the `i`th element of + * deprecated_repeated_field to `value`. + */ + @JvmName("setDeprecatedRepeatedField") + operator fun DslList.set(index: Int, newValue: Int) { + builder_.setDeprecatedRepeatedField(index, newValue) + } + + /** + * Removes all elements from the repeated field deprecated_repeated_field. + * + * For example, `deprecatedRepeatedField.clear()` deletes all elements from + * deprecated_repeated_field. + */ + @JvmName("clearDeprecatedRepeatedField") + fun DslList.clear() { + builder_.clearDeprecatedRepeatedField() + } + + /** + * Puts the specified key-value pair into the map field map_field, overwriting any previous + * value associated with the key. + * + * For example, `mapField.put(k, v)` maps the key `k` to the value `v` in map_field. + * + * Calling this method with any receiver but [mapField] may have undefined behavior. + */ + @JvmName("putMapField") + fun DslMap.put(key: Long, + newValue: Example3.ExampleMessage.SubMessage) { + builder_.putMapField(key, newValue) + } + + /** + * Puts the specified key-value pair into the map field map_field, overwriting any previous + * value associated with the key. + * + * For example, `mapField[k] = v` maps the key `k` to the value `v` in map_field. + * + * Calling this method with any receiver but [mapField] may have undefined behavior. + */ + @JvmName("setMapField") + operator fun DslMap.set(key: Long, + newValue: Example3.ExampleMessage.SubMessage) { + builder_.putMapField(key, newValue) + } + + /** + * Removes the entry for the specified key from the map field map_field, if one exists. + * Otherwise, does nothing. + * + * For example, `mapField.remove(k)` removes the key `k` from map_field. + * + * Calling this method with any receiver but [mapField] may have undefined behavior. + */ + @JvmName("removeMapField") + fun DslMap.remove(key: Long) { + builder_.removeMapField(key) + } + + /** + * Puts the entries in the given map into the map field map_field. If there is already an entry + * for a key in the map, it is overwritten. + * + * For example, `mapField.putAll(mapOf(k1 to v1, k2 to v2))` is equivalent to `mapField[k1] = + * v1; mapField[k2] = v2`. + * + * Calling this method with any receiver but [mapField] may have undefined behavior. + */ + @JvmName("putAllMapField") + fun DslMap.putAll(map: Map) { + builder_.putAllMapField(map) + } + + /** + * Removes all entries from the map field map_field. + * + * For example, `mapField.clear()` deletes all entries previously part of map_field. + * + * Calling this method with any receiver but [mapField] may have undefined behavior. + */ + @JvmName("clearMapField") + fun DslMap.clear() { + builder_.clearMapField() + } + + /** + * Puts the specified key-value pair into the map field deprecated_map_field, overwriting any + * previous + * value associated with the key. + * + * For example, `deprecatedMapField.put(k, v)` maps the key `k` to the value `v` in + * deprecated_map_field. + * + * Calling this method with any receiver but [deprecatedMapField] may have undefined behavior. + */ + @JvmName("putDeprecatedMapField") + fun DslMap.put(key: String, newValue: Int) { + builder_.putDeprecatedMapField(key, newValue) + } + + /** + * Puts the specified key-value pair into the map field deprecated_map_field, overwriting any + * previous + * value associated with the key. + * + * For example, `deprecatedMapField[k] = v` maps the key `k` to the value `v` in + * deprecated_map_field. + * + * Calling this method with any receiver but [deprecatedMapField] may have undefined behavior. + */ + @JvmName("setDeprecatedMapField") + operator fun DslMap.set(key: String, newValue: Int) { + builder_.putDeprecatedMapField(key, newValue) + } + + /** + * Removes the entry for the specified key from the map field deprecated_map_field, if one + * exists. + * Otherwise, does nothing. + * + * For example, `deprecatedMapField.remove(k)` removes the key `k` from deprecated_map_field. + * + * Calling this method with any receiver but [deprecatedMapField] may have undefined behavior. + */ + @JvmName("removeDeprecatedMapField") + fun DslMap.remove(key: String) { + builder_.removeDeprecatedMapField(key) + } + + /** + * Puts the entries in the given map into the map field deprecated_map_field. If there is + * already an entry + * for a key in the map, it is overwritten. + * + * For example, `deprecatedMapField.putAll(mapOf(k1 to v1, k2 to v2))` is equivalent to + * `deprecatedMapField[k1] = v1; deprecatedMapField[k2] = v2`. + * + * Calling this method with any receiver but [deprecatedMapField] may have undefined behavior. + */ + @JvmName("putAllDeprecatedMapField") + fun DslMap.putAll(map: Map) { + builder_.putAllDeprecatedMapField(map) + } + + /** + * Removes all entries from the map field deprecated_map_field. + * + * For example, `deprecatedMapField.clear()` deletes all entries previously part of + * deprecated_map_field. + * + * Calling this method with any receiver but [deprecatedMapField] may have undefined behavior. + */ + @JvmName("clearDeprecatedMapField") + fun DslMap.clear() { + builder_.clearDeprecatedMapField() + } + + /** + * An uninstantiable, behaviorless type to represent the field repeated_string_field in + * generics. + */ + class RepeatedStringFieldProxy private constructor() + + /** + * An uninstantiable, behaviorless type to represent the field deprecated_repeated_field in + * generics. + */ + class DeprecatedRepeatedFieldProxy private constructor() + + /** + * A behaviorless, uninstantiable type to represent the field map_field in generics. + */ + class MapFieldProxy private constructor() + + /** + * A behaviorless, uninstantiable type to represent the field deprecated_map_field in generics. + */ + class DeprecatedMapFieldProxy private constructor() + } + + /** + * Kotlin extensions around the proto message type protobuf.kotlin.generator.ExampleMessage. + */ + object ExampleMessageKt { + /** + * Entry point for creating SubMessage messages in Kotlin. + */ + @JvmSynthetic + inline fun subMessage(block: SubMessageDsl.() -> Unit) = + SubMessageDsl(Example3.ExampleMessage.SubMessage.newBuilder()).apply(block).build() + + /** + * Representation of a value for the oneof my_oneof. + */ + abstract class MyOneofOneof( + override val value: T, + override val case: Example3.ExampleMessage.MyOneofCase + ) : OneofRepresentation() { + internal fun writeTo(builder: Example3.ExampleMessage.Builder) { + when (case) { + STRING_ONEOF_OPTION -> builder.stringOneofOption = value as String + SUB_MESSAGE_ONEOF_OPTION -> builder.subMessageOneofOption = value as + Example3.ExampleMessage.SubMessage + else -> builder.clearMyOneof() + } + } + + /** + * A representation of the string_oneof_option option of the my_oneof oneof. + */ + class StringOneofOption( + value: String + ) : MyOneofOneof(value, STRING_ONEOF_OPTION) + + /** + * A representation of the sub_message_oneof_option option of the my_oneof oneof. + */ + class SubMessageOneofOption( + value: Example3.ExampleMessage.SubMessage + ) : MyOneofOneof(value, SUB_MESSAGE_ONEOF_OPTION) + + /** + * Represents the absence of a value for my_oneof. + */ + object NotSet : MyOneofOneof(Unit, MYONEOF_NOT_SET) + + companion object { + /** + * Extract an object representation of the set field of the oneof my_oneof. + */ + @PublishedApi + internal fun readFrom(input: Example3.ExampleMessageOrBuilder): MyOneofOneof<*> { + val _theCase: Example3.ExampleMessage.MyOneofCase = input.myOneofCase + return when (_theCase) { + STRING_ONEOF_OPTION -> StringOneofOption(input.stringOneofOption) + SUB_MESSAGE_ONEOF_OPTION -> SubMessageOneofOption(input.subMessageOneofOption) + else -> NotSet + } + } + } + } + + /** + * Representation of a value for the oneof deprecated_oneof. + */ + abstract class DeprecatedOneofOneof( + override val value: T, + override val case: Example3.ExampleMessage.DeprecatedOneofCase + ) : OneofRepresentation() { + internal fun writeTo(builder: Example3.ExampleMessage.Builder) { + when (case) { + DEPRECATED_ONEOF_OPTION -> builder.deprecatedOneofOption = value as String + else -> builder.clearDeprecatedOneof() + } + } + + /** + * A representation of the deprecated_oneof_option option of the deprecated_oneof oneof. + */ + @Deprecated(message = "Field deprecated_oneof_option is deprecated") + class DeprecatedOneofOption( + value: String + ) : DeprecatedOneofOneof(value, DEPRECATED_ONEOF_OPTION) + + /** + * Represents the absence of a value for deprecated_oneof. + */ + object NotSet : DeprecatedOneofOneof(Unit, DEPRECATEDONEOF_NOT_SET) + + companion object { + /** + * Extract an object representation of the set field of the oneof deprecated_oneof. + */ + @PublishedApi + internal fun readFrom(input: Example3.ExampleMessageOrBuilder): DeprecatedOneofOneof<*> { + val _theCase: Example3.ExampleMessage.DeprecatedOneofCase = input.deprecatedOneofCase + return when (_theCase) { + DEPRECATED_ONEOF_OPTION -> DeprecatedOneofOption(input.deprecatedOneofOption) + else -> NotSet + } + } + } + } + + /** + * A DSL type for creating SubMessage messages. To use it, call the [subMessage] method. + */ + @ProtoDslMarker + class SubMessageDsl @PublishedApi internal constructor( + builder_: Example3.ExampleMessage.SubMessage.Builder + ) : ProtoDsl(builder_) { + @PublishedApi + internal val builder_: Example3.ExampleMessage.SubMessage.Builder + get() = _proto_dsl_builder + + /** + * Builds the SubMessage. + */ + @PublishedApi + internal fun build(): Example3.ExampleMessage.SubMessage = builder_.build() + } + + /** + * Kotlin extensions around the proto message type + * protobuf.kotlin.generator.ExampleMessage.SubMessage. + */ + object SubMessageKt + } +} + +/** + * Creates a Example3.ExampleMessage equivalent to this one, but with the specified modifications. + */ +@JvmSynthetic +inline fun Example3.ExampleMessage.copy(block: Example3Kt.ExampleMessageDsl.() -> Unit): + Example3.ExampleMessage = Example3Kt.ExampleMessageDsl(toBuilder()).apply(block).build() + +/** + * Creates a Example3.ExampleMessage.SubMessage equivalent to this one, but with the specified + * modifications. + */ +@JvmSynthetic +inline + fun Example3.ExampleMessage.SubMessage.copy(block: Example3Kt.ExampleMessageKt.SubMessageDsl.() -> + Unit): Example3.ExampleMessage.SubMessage = + Example3Kt.ExampleMessageKt.SubMessageDsl(toBuilder()).apply(block).build() + +/** + * The value of the oneof my_oneof. + */ +val Example3.ExampleMessageOrBuilder.myOneof: Example3Kt.ExampleMessageKt.MyOneofOneof<*> + get() = Example3Kt.ExampleMessageKt.MyOneofOneof.readFrom(this) + +/** + * The value of the oneof my_oneof. + */ +var Example3.ExampleMessage.Builder.myOneof: Example3Kt.ExampleMessageKt.MyOneofOneof<*> + get() = Example3Kt.ExampleMessageKt.MyOneofOneof.readFrom(this) + set(newOneofParam) { + newOneofParam.writeTo(this) + } + +/** + * The value of the oneof deprecated_oneof. + */ +val Example3.ExampleMessageOrBuilder.deprecatedOneof: + Example3Kt.ExampleMessageKt.DeprecatedOneofOneof<*> + get() = Example3Kt.ExampleMessageKt.DeprecatedOneofOneof.readFrom(this) + +/** + * The value of the oneof deprecated_oneof. + */ +var Example3.ExampleMessage.Builder.deprecatedOneof: + Example3Kt.ExampleMessageKt.DeprecatedOneofOneof<*> + get() = Example3Kt.ExampleMessageKt.DeprecatedOneofOneof.readFrom(this) + set(newOneofParam) { + newOneofParam.writeTo(this) + } diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerTest.kt new file mode 100644 index 000000000000..68f62f630f0e --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerTest.kt @@ -0,0 +1,191 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.common.jimfs.Configuration +import com.google.common.jimfs.Jimfs +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.extensions.proto.ProtoTruth.assertThat +import com.google.protobuf.ByteString +import com.google.protobuf.DescriptorProtos.FileDescriptorProto +import com.google.protobuf.DescriptorProtos.FileDescriptorSet +import com.google.protobuf.WrappersProto +import com.google.protobuf.kotlin.generator.GeneratorRunner.RuntimeVariant +import com.google.protobuf.kotlin.generator.Example2 +import com.google.protobuf.kotlin.generator.Example3 +import com.google.protobuf.compiler.PluginProtos.CodeGeneratorRequest +import com.google.protobuf.compiler.PluginProtos.CodeGeneratorResponse +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Paths + +/** Tests for [GeneratorRunner]. */ +@RunWith(JUnit4::class) +class GeneratorRunnerTest { + companion object { + private const val EXAMPLE3_DESCRIPTOR_SET_FILE_NAME = "example3-descriptor-set.proto.bin" + private const val WRAPPERS_DESCRIPTOR_SET_FILE_NAME = "wrappers-descriptor-set.proto.bin" + private const val OUTPUT_DIR_NAME = "output" + + private val example3DescriptorProto: FileDescriptorProto = Example3.getDescriptor().toProto() + private val example2DescriptorProto: FileDescriptorProto = Example2.getDescriptor().toProto() + private val wrappersDescriptorProto: FileDescriptorProto = + WrappersProto.getDescriptor().toProto() + + private val example3DescriptorSet = + FileDescriptorSet + .newBuilder() + .addFile(example3DescriptorProto) + .build() + private val wellKnownTypesDescriptorSet = + FileDescriptorSet + .newBuilder() + .addFile(wrappersDescriptorProto) + .build() + } + + @Test + fun runnerCommandLine() { + val fileSystem = Jimfs.newFileSystem(Configuration.unix()) + + Files.write( + fileSystem.getPath(EXAMPLE3_DESCRIPTOR_SET_FILE_NAME), + example3DescriptorSet.toByteArray() + ) + Files.write( + fileSystem.getPath(WRAPPERS_DESCRIPTOR_SET_FILE_NAME), + wellKnownTypesDescriptorSet.toByteArray() + ) + + GeneratorRunner(GeneratorRunner.RuntimeVariant.FULL).mainAsCommandLine( + arrayOf( + OUTPUT_DIR_NAME, + EXAMPLE3_DESCRIPTOR_SET_FILE_NAME, + "--", + EXAMPLE3_DESCRIPTOR_SET_FILE_NAME, + WRAPPERS_DESCRIPTOR_SET_FILE_NAME + ), + fileSystem + ) + + val outputDir = fileSystem.getPath( + OUTPUT_DIR_NAME, + "com/google/protobuf/kotlin/generator" + ) + val outputFile = outputDir.resolve("Example3Kt.kt") + val outputFileContents = Files.readAllLines(outputFile, StandardCharsets.UTF_8) + + val expectedFileContents = Files.readAllLines( + Paths.get( + "src/test/java/com/google/protobuf/kotlin/generator", + "GeneratorRunnerExample3.kt.expected" + ), + StandardCharsets.UTF_8 + ) + + assertThat(outputFileContents).containsExactlyElementsIn(expectedFileContents).inOrder() + } + + @Test + fun runnerProtocPlugin() { + val output = ByteString.newOutput() + GeneratorRunner(GeneratorRunner.RuntimeVariant.FULL).mainAsProtocPlugin( + CodeGeneratorRequest.newBuilder() + .addProtoFile(wrappersDescriptorProto) + .addProtoFile(example3DescriptorProto) + .addFileToGenerate(example3DescriptorProto.name) + .build() + .toByteString() + .newInput(), + output + ) + + val expectedFileContents = Files.readAllLines( + Paths.get( + "src/test/java/com/google/protobuf/kotlin/generator", + "GeneratorRunnerExample3.kt.expected" + ), + StandardCharsets.UTF_8 + ) + + val result = CodeGeneratorResponse.parseFrom(output.toByteString()) + + assertThat(result.fileList.single().content) + .isEqualTo(expectedFileContents.joinToString("\n") + "\n") + assertThat(result).isEqualTo( + CodeGeneratorResponse.newBuilder() + .addFile( + CodeGeneratorResponse.File.newBuilder().apply { + name = "com/google/protobuf/kotlin/generator/Example3Kt.kt" + content = expectedFileContents.joinToString("\n") + "\n" + // we end up having an extra newline + }.build() + ) + .build() + ) + } + + private fun runnerWithExtensions(variant: RuntimeVariant, expectedFileName: String) { + val output = ByteString.newOutput() + GeneratorRunner(variant).mainAsProtocPlugin( + CodeGeneratorRequest.newBuilder() + .addProtoFile(example2DescriptorProto) + .addFileToGenerate(example2DescriptorProto.name) + .build() + .toByteString() + .newInput(), + output + ) + + val expectedFileContents = Files.readAllLines( + Paths.get( + "src/test/java/com/google/protobuf/kotlin/generator", + expectedFileName + ), + StandardCharsets.UTF_8 + ) + + val result = CodeGeneratorResponse.parseFrom(output.toByteString()) + + assertThat(result.fileList.single().content) + .isEqualTo(expectedFileContents.joinToString("\n") + "\n") + assertThat(result).isEqualTo( + CodeGeneratorResponse.newBuilder() + .addFile( + CodeGeneratorResponse.File.newBuilder().apply { + name = "com/google/protobuf/kotlin/generator/Example2Kt.kt" + content = expectedFileContents.joinToString("\n") + "\n" + // we end up having an extra newline + }.build() + ) + .build() + ) + } + + @Test + fun runnerWithExtensionsFull() { + runnerWithExtensions(RuntimeVariant.FULL, "GeneratorRunnerExample2.kt.expected") + } + + @Test + fun runnerWithExtensionsLite() { + runnerWithExtensions(RuntimeVariant.LITE, "GeneratorRunnerExample2Lite.kt.expected") + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/LiteGeneratedCodeTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/LiteGeneratedCodeTest.kt new file mode 100644 index 000000000000..e6f833602a1e --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/LiteGeneratedCodeTest.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.common.truth.extensions.proto.LiteProtoSubject +import com.google.common.truth.extensions.proto.LiteProtoTruth +import com.google.protobuf.kotlin.generator.EvilNamesOuterClass.EvilNames +import com.google.protobuf.kotlin.generator.Example2 +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class LiteGeneratedCodeTest : AbstractGeneratedCodeTest() { + override fun assertThat(message: ExampleMessage): LiteProtoSubject = + LiteProtoTruth.assertThat(message) + + override fun assertThat(message: EvilNames): LiteProtoSubject = + LiteProtoTruth.assertThat(message) + + override fun assertThat(message: Example2.ExampleProto2Message): LiteProtoSubject = + LiteProtoTruth.assertThat(message) +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGeneratorTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGeneratorTest.kt new file mode 100644 index 000000000000..ee274a794664 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGeneratorTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.DslMap +import com.google.protobuf.kotlin.protoc.ClassSimpleName +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.JavaPackagePolicy +import com.google.protobuf.kotlin.protoc.MapFieldDescriptor +import com.google.protobuf.kotlin.protoc.UnqualifiedScope +import com.google.protobuf.kotlin.protoc.classBuilder +import com.google.protobuf.kotlin.protoc.specialize +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.google.protobuf.kotlin.generator.Example3 +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.TypeSpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [MapFieldMapProxyDslComponentGeneratorTest]. */ +@RunWith(JUnit4::class) +class MapFieldMapProxyDslComponentGeneratorTest { + private val javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE + private val config = GeneratorConfig(javaPackagePolicy, aggressiveInlining = false) + + private val mapFieldDescriptor: MapFieldDescriptor = + Example3.ExampleMessage.getDescriptor().findFieldByNumber( + Example3.ExampleMessage.MAP_FIELD_FIELD_NUMBER + ).specialize() as MapFieldDescriptor + + @Test + fun generate() { + val dslSimpleName = ClassSimpleName("DslClass") + val dslBuilder = TypeSpec.classBuilder(dslSimpleName) + val dslCompanionBuilder = TypeSpec.companionObjectBuilder() + val generator = MapFieldMapProxyDslComponentGenerator( + proxyTypeNameSuffix = "ProxySuffix" + ) + + with(config) { + generator.run { + generate( + component = mapFieldDescriptor, + builderCode = CodeBlock.of("myBuilder"), + enclosingScope = UnqualifiedScope, + extensionScope = UnqualifiedScope, + dslType = UnqualifiedScope.nestedClass(dslSimpleName), + companionBuilder = dslCompanionBuilder, + dslBuilder = dslBuilder + ) + } + } + + assertThat(dslCompanionBuilder.build()).generates("companion object") + val dslMap = DslMap::class.qualifiedName!! + val exampleMessage = Example3.ExampleMessage::class.qualifiedName!! + assertThat(dslBuilder.build()).generates( + """ + class DslClass { + /** + * A [kotlin.collections.Map] view of the map field map_field. + * + * While the view object itself is a `Map`, not a `MutableMap`, within the context of the + * DSL it has extension methods and operator overloads that mutate it appropriately. + * For example, writing `mapField[k] = v` adds a mapping from `k` to `v` in the map field, + * but that code only compiles within the DSL. + */ + val mapField: $dslMap + get() = $dslMap(myBuilder.getMapFieldMap()) + + /** + * A behaviorless, uninstantiable type to represent the field map_field in generics. + */ + class MapFieldProxySuffix private constructor() + } + """ + ) + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGeneratorTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGeneratorTest.kt new file mode 100644 index 000000000000..6b6e6d0ed839 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGeneratorTest.kt @@ -0,0 +1,406 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.ClassSimpleName +import com.google.protobuf.kotlin.protoc.Declarations +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.JavaPackagePolicy +import com.google.protobuf.kotlin.protoc.MapFieldDescriptor +import com.google.protobuf.kotlin.protoc.MemberSimpleName +import com.google.protobuf.kotlin.protoc.UnqualifiedScope +import com.google.protobuf.kotlin.protoc.builder +import com.google.protobuf.kotlin.protoc.specialize +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.google.protobuf.kotlin.generator.Example3 +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [MapRepresentativeExtensionGenerator]. */ +@RunWith(JUnit4::class) +class MapRepresentativeExtensionGeneratorTest { + private val javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE + private val config = GeneratorConfig(javaPackagePolicy, aggressiveInlining = false) + private val proxyDummyType = UnqualifiedScope.nestedClass(ClassSimpleName("Proxy")) + + private val mapFieldDescriptor: MapFieldDescriptor = + Example3.ExampleMessage.getDescriptor().findFieldByNumber( + Example3.ExampleMessage.MAP_FIELD_FIELD_NUMBER + ).specialize() as MapFieldDescriptor + + private fun declarations(generator: MapRepresentativeExtensionGenerator): Declarations { + return with(config) { + generator.run { + generate( + { FunSpec.builder(it).receiver(proxyDummyType) }, + mapFieldDescriptor, + CodeBlock.of("myBuilder"), + MemberSimpleName("mapProxy") + ) + } + } + } + + @Test + fun put() { + val declarations = declarations(MapRepresentativeExtensionGenerator.Put.put) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import com.google.protobuf.kotlin.generator.Example3 + import kotlin.Long + + /** + * Puts the specified key-value pair into the map field map_field, overwriting any previous + * value associated with the key. + * + * For example, `mapProxy.put(k, v)` maps the key `k` to the value `v` in map_field. + * + * Calling this method with any receiver but [mapProxy] may have undefined behavior. + */ + fun Proxy.put(key: Long, newValue: Example3.ExampleMessage.SubMessage) { + myBuilder.putMapField(key, newValue) + } + """ + ) + } + + @Test + fun setOperator() { + val declarations = declarations(MapRepresentativeExtensionGenerator.Put.setOperator) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import com.google.protobuf.kotlin.generator.Example3 + import kotlin.Long + + /** + * Puts the specified key-value pair into the map field map_field, overwriting any previous + * value associated with the key. + * + * For example, `mapProxy[k] = v` maps the key `k` to the value `v` in map_field. + * + * Calling this method with any receiver but [mapProxy] may have undefined behavior. + */ + operator fun Proxy.set(key: Long, newValue: Example3.ExampleMessage.SubMessage) { + myBuilder.putMapField(key, newValue) + } + """ + ) + } + + @Test + fun removeKey() { + val declarations = declarations(MapRepresentativeExtensionGenerator.RemoveKey.remove) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import kotlin.Long + + /** + * Removes the entry for the specified key from the map field map_field, if one exists. + * Otherwise, does nothing. + * + * For example, `mapProxy.remove(k)` removes the key `k` from map_field. + * + * Calling this method with any receiver but [mapProxy] may have undefined behavior. + */ + fun Proxy.remove(key: Long) { + myBuilder.removeMapField(key) + } + """ + ) + } + + @Test + fun minusAssignKey() { + val declarations = declarations(MapRepresentativeExtensionGenerator.RemoveKey.minusAssign) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import kotlin.Long + + /** + * Removes the entry for the specified key from the map field map_field, if one exists. + * Otherwise, does nothing. + * + * For example, `mapProxy -= k` removes the key `k` from map_field. + * + * Calling this method with any receiver but [mapProxy] may have undefined behavior. + */ + operator fun Proxy.minusAssign(key: Long) { + myBuilder.removeMapField(key) + } + """ + ) + } + + @Test + fun removeAllIterable() { + val declarations = declarations(MapRepresentativeExtensionGenerator.RemoveAll.removeAllIterable) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import kotlin.Long + import kotlin.collections.Iterable + + /** + * Removes the entries for each of the specified keys from the map field map_field. If there is + * no entry for a given key, it is skipped. + * + * For example, `mapProxy.removeAll(listOf(k1, k2, k3))` removes entries for `k1`, `k2`, and `k3` + * from map_field. + * + * Calling this method with any receiver but [mapProxy] may have undefined behavior. + */ + fun Proxy.removeAll(keys: Iterable) { + for (value in keys) { + myBuilder.removeMapField(keys) + } + } + """ + ) + } + + @Test + fun removeAllVararg() { + val declarations = declarations(MapRepresentativeExtensionGenerator.RemoveAll.removeAllVararg) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import kotlin.Long + + /** + * Removes the entries for each of the specified keys from the map field map_field. If there is + * no entry for a given key, it is skipped. + * + * For example, `mapProxy.removeAll(k1, k2, k3)` removes entries for `k1`, `k2`, and `k3` from + * map_field. + * + * Calling this method with any receiver but [mapProxy] may have undefined behavior. + */ + fun Proxy.removeAll(vararg keys: Long) { + for (value in keys) { + myBuilder.removeMapField(keys) + } + } + """ + ) + } + + @Test + fun minusAssignIterable() { + val declarations = declarations(MapRepresentativeExtensionGenerator.RemoveAll.minusAssign) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import kotlin.Long + import kotlin.collections.Iterable + + /** + * Removes the entries for each of the specified keys from the map field map_field. If there is + * no entry for a given key, it is skipped. + * + * For example, `mapProxy -= listOf(k1, k2, k3)` removes entries for `k1`, `k2`, and `k3` from + * map_field. + * + * Calling this method with any receiver but [mapProxy] may have undefined behavior. + */ + operator fun Proxy.minusAssign(keys: Iterable) { + for (value in keys) { + myBuilder.removeMapField(keys) + } + } + """ + ) + } + + @Test + fun putAllMap() { + val declarations = declarations(MapRepresentativeExtensionGenerator.PutAllMap.putAll) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import com.google.protobuf.kotlin.generator.Example3 + import kotlin.Long + import kotlin.collections.Map + + /** + * Puts the entries in the given map into the map field map_field. If there is already an entry + * for a key in the map, it is overwritten. + * + * For example, `mapProxy.putAll(mapOf(k1 to v1, k2 to v2))` is equivalent to `mapProxy[k1] = v1; + * mapProxy[k2] = v2`. + * + * Calling this method with any receiver but [mapProxy] may have undefined behavior. + */ + fun Proxy.putAll(map: Map) { + myBuilder.putAllMapField(map) + } + """ + ) + } + + @Test + fun plusAssignMap() { + val declarations = declarations(MapRepresentativeExtensionGenerator.PutAllMap.plusAssign) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import com.google.protobuf.kotlin.generator.Example3 + import kotlin.Long + import kotlin.collections.Map + + /** + * Puts the entries in the given map into the map field map_field. If there is already an entry + * for a key in the map, it is overwritten. + * + * For example, `mapProxy += mapOf(k1 to v1, k2 to v2)` is equivalent to `mapProxy[k1] = v1; + * mapProxy[k2] = v2`. + * + * Calling this method with any receiver but [mapProxy] may have undefined behavior. + */ + operator fun Proxy.plusAssign(map: Map) { + myBuilder.putAllMapField(map) + } + """ + ) + } + + @Test + fun putAllVarargPairs() { + val declarations = declarations(MapRepresentativeExtensionGenerator.PutAllPairs.putAllVarargs) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import com.google.protobuf.kotlin.generator.Example3 + import kotlin.Long + import kotlin.Pair + + /** + * Puts the specified key-value pairs into the map field map_field. If there is already an entry + * for a key in the map, it is overwritten. + * + * For example, `mapProxy.putAll(k1 to v1, k2 to v2)` is equivalent to `mapProxy[k1] = v1; + * mapProxy[k2] = v2`. + * + * Calling this method with any receiver but [mapProxy] may have undefined behavior. + */ + fun Proxy.putAll(vararg pairs: Pair) { + for ((k, v) in pairs) { + myBuilder.putMapField(k, v) + } + } + """ + ) + } + + @Test + fun putAllIterablePairs() { + val declarations = declarations(MapRepresentativeExtensionGenerator.PutAllPairs.putAllIterable) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import com.google.protobuf.kotlin.generator.Example3 + import kotlin.Long + import kotlin.Pair + import kotlin.collections.Iterable + + /** + * Puts the specified key-value pairs into the map field map_field. If there is already an entry + * for a key in the map, it is overwritten. + * + * For example, `mapProxy.putAll(listOf(k1 to v1, k2 to v2))` is equivalent to `mapProxy[k1] = v1; + * mapProxy[k2] = v2`. + * + * Calling this method with any receiver but [mapProxy] may have undefined behavior. + */ + fun Proxy.putAll(pairs: Iterable>) { + for ((k, v) in pairs) { + myBuilder.putMapField(k, v) + } + } + """ + ) + } + + @Test + fun plusAssignIterablePairs() { + val declarations = + declarations(MapRepresentativeExtensionGenerator.PutAllPairs.plusAssignIterable) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import com.google.protobuf.kotlin.generator.Example3 + import kotlin.Long + import kotlin.Pair + import kotlin.collections.Iterable + + /** + * Puts the specified key-value pairs into the map field map_field. If there is already an entry + * for a key in the map, it is overwritten. + * + * For example, `mapProxy += listOf(k1 to v1, k2 to v2)` is equivalent to `mapProxy[k1] = v1; + * mapProxy[k2] = v2`. + * + * Calling this method with any receiver but [mapProxy] may have undefined behavior. + */ + operator fun Proxy.plusAssign(pairs: Iterable>) { + for ((k, v) in pairs) { + myBuilder.putMapField(k, v) + } + } + """ + ) + } + + @Test + fun clear() { + val declarations = declarations(MapRepresentativeExtensionGenerator.Clear) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + /** + * Removes all entries from the map field map_field. + * + * For example, `mapProxy.clear()` deletes all entries previously part of map_field. + * + * Calling this method with any receiver but [mapProxy] may have undefined behavior. + */ + fun Proxy.clear() { + myBuilder.clearMapField() + } + """ + ) + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGeneratorTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGeneratorTest.kt new file mode 100644 index 000000000000..5f32a091afa8 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGeneratorTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.ClassSimpleName +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.JavaPackagePolicy +import com.google.protobuf.kotlin.protoc.OneofComponent +import com.google.protobuf.kotlin.protoc.UnqualifiedScope +import com.google.protobuf.kotlin.protoc.classBuilder +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.TypeSpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [OneofCaseDslComponentGenerator]. */ +@RunWith(JUnit4::class) +class OneofCaseDslComponentGeneratorTest { + private val javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE + private val config = GeneratorConfig(javaPackagePolicy, aggressiveInlining = false) + + @Test + fun generate() { + val oneofDescriptor = ExampleMessage.getDescriptor().oneofs.single { it.name == "my_oneof" } + val oneofComponent = OneofComponent(oneofDescriptor) + val dslSimpleName = ClassSimpleName("DslClass") + val dslBuilder = TypeSpec.classBuilder(dslSimpleName) + val dslCompanionBuilder = TypeSpec.companionObjectBuilder() + + with(config) { + OneofCaseDslComponentGenerator.run { + generate( + component = oneofComponent, + dslType = UnqualifiedScope.nestedClass(dslSimpleName), + extensionScope = UnqualifiedScope, + enclosingScope = UnqualifiedScope, + dslBuilder = dslBuilder, + companionBuilder = dslCompanionBuilder, + builderCode = CodeBlock.of("myBuilder") + ) + } + } + + assertThat(dslBuilder.build()).generates( + """ +class DslClass { + /** + * An enum reflecting which field of the oneof my_oneof is set. + */ + val myOneofCase: com.google.protobuf.kotlin.generator.Example3.ExampleMessage.MyOneofCase + get() = myBuilder.myOneofCase +} + """ + ) + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofClassGeneratorTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofClassGeneratorTest.kt new file mode 100644 index 000000000000..3e218c3f5a30 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofClassGeneratorTest.kt @@ -0,0 +1,254 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.ClassSimpleName +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.JavaPackagePolicy +import com.google.protobuf.kotlin.protoc.OneofComponent +import com.google.protobuf.kotlin.protoc.OneofOptionFieldDescriptor +import com.google.protobuf.kotlin.protoc.UnqualifiedScope +import com.google.protobuf.kotlin.protoc.classBuilder +import com.google.protobuf.kotlin.protoc.specialize +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.google.protobuf.kotlin.generator.Example3 +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.TypeSpec +import java.nio.file.Files +import java.nio.file.Paths +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [OneofClassGenerator]. */ +@RunWith(JUnit4::class) +class OneofClassGeneratorTest { + private val javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE + private val config = GeneratorConfig(javaPackagePolicy, aggressiveInlining = false) + private val oneofDescriptor = + ExampleMessage.getDescriptor().oneofs.single { it.name == "my_oneof" } + private val generatorUnderTest = OneofClassGenerator( + oneofClassSuffix = "OneofClass", + oneofSubclassSuffix = "OneofClass", + notSetSimpleName = ClassSimpleName("NotSetName"), + propertySuffix = "Property", + extensionFactories = + *arrayOf(OneofClassGenerator::builderVar, OneofClassGenerator::orBuilderVal) + ) + private val oneofClassName: ClassName = + UnqualifiedScope.nestedClass(ClassSimpleName("MyOneofClass")) + private val example3: String = Example3::class.qualifiedName!! + private val exampleMessage: String = ExampleMessage::class.qualifiedName!! + + @Test + fun writeTo() { + val writeToFun = generatorUnderTest.run { + config.writeToFunction(oneofDescriptor) + } + assertThat(writeToFun).generates( + """ +internal fun writeTo(builder: com.google.protobuf.kotlin.generator.Example3.ExampleMessage.Builder) { + when (case) { + com.google.protobuf.kotlin.generator.Example3.ExampleMessage.MyOneofCase.STRING_ONEOF_OPTION -> builder.stringOneofOption = value as kotlin.String + com.google.protobuf.kotlin.generator.Example3.ExampleMessage.MyOneofCase.SUB_MESSAGE_ONEOF_OPTION -> builder.subMessageOneofOption = value as com.google.protobuf.kotlin.generator.Example3.ExampleMessage.SubMessage + else -> builder.clearMyOneof() + } +} + """ + ) + } + + @Test + fun readFrom() { + val readFromFun = generatorUnderTest.run { + config.readFromFunction(oneofDescriptor, oneofClassName) + } + assertThat(readFromFun).generates( + """ +/** + * Extract an object representation of the set field of the oneof my_oneof. + */ +@kotlin.PublishedApi +internal fun readFrom(input: $example3.ExampleMessageOrBuilder): MyOneofClass<*> { + val _theCase: $exampleMessage.MyOneofCase = input.myOneofCase + return when (_theCase) { + $exampleMessage.MyOneofCase.STRING_ONEOF_OPTION -> MyOneofClass.StringOneofOptionOneofClass(input.stringOneofOption) + $exampleMessage.MyOneofCase.SUB_MESSAGE_ONEOF_OPTION -> MyOneofClass.SubMessageOneofOptionOneofClass(input.subMessageOneofOption) + else -> MyOneofClass.NotSetName + } +} + """ + ) + } + + @Test + fun oneofOptionClass() { + val oneofOption = + ExampleMessage + .getDescriptor() + .findFieldByNumber(ExampleMessage.STRING_ONEOF_OPTION_FIELD_NUMBER) + .specialize() as OneofOptionFieldDescriptor + + val oneofOptionClass = generatorUnderTest.run { + config.oneofOptionClass(oneofOption, oneofClassName) + } + assertThat(oneofOptionClass).generates( + """ +/** + * A representation of the string_oneof_option option of the my_oneof oneof. + */ +class StringOneofOptionOneofClass( + value: kotlin.String +) : MyOneofClass(value, com.google.protobuf.kotlin.generator.Example3.ExampleMessage.MyOneofCase.STRING_ONEOF_OPTION) + """ + ) + } + + @Test + fun notSetObject() { + val generatorWithNotSetObject = OneofClassGenerator( + oneofClassSuffix = "OneofClass", + notSetSimpleName = ClassSimpleName("NotSetName"), + propertySuffix = "Property" + ) + val notSetObject = generatorWithNotSetObject.run { + config.notSetClass(oneofDescriptor, oneofClassName) + } + assertThat(notSetObject).generates( + """ +/** + * Represents the absence of a value for my_oneof. + */ +object NotSetName : MyOneofClass(Unit, com.google.protobuf.kotlin.generator.Example3.ExampleMessage.MyOneofCase.MYONEOF_NOT_SET) + """ + ) + } + + @Test + fun orBuilderVal() { + val declarations = generatorUnderTest.orBuilderVal.run { + config.generate(oneofDescriptor, oneofClassName) + } + + assertThat(declarations).generatesNoEnclosedMembers() + assertThat(declarations).generatesTopLevel( + """ + import $example3 + + /** + * The value of the oneof my_oneof. + */ + val Example3.ExampleMessageOrBuilder.myOneofProperty: MyOneofClass<*> + get() = MyOneofClass.readFrom(this) + """ + ) + } + + @Test + fun builderVar() { + val declarations = generatorUnderTest.builderVar.run { + config.generate(oneofDescriptor, oneofClassName) + } + + assertThat(declarations).generatesNoEnclosedMembers() + assertThat(declarations).generatesTopLevel( + """ + import $example3 + + /** + * The value of the oneof my_oneof. + */ + var Example3.ExampleMessage.Builder.myOneofProperty: MyOneofClass<*> + get() = MyOneofClass.readFrom(this) + set(newOneofParam) { + newOneofParam.writeTo(this) + } + """ + ) + } + + @Test + fun dslProperty() { + val dslGenerator = generatorUnderTest.dslPropertyGenerator + + val dslSimpleName = ClassSimpleName("DslClass") + val dslBuilder = TypeSpec.classBuilder(dslSimpleName) + val dslCompanionBuilder = TypeSpec.companionObjectBuilder() + + dslGenerator.run { + config.generate( + component = OneofComponent(oneofDescriptor), + dslType = UnqualifiedScope.nestedClass(dslSimpleName), + extensionScope = UnqualifiedScope, + enclosingScope = UnqualifiedScope, + builderCode = CodeBlock.of("myBuilder"), + dslBuilder = dslBuilder, + companionBuilder = dslCompanionBuilder + ) + } + + assertThat(dslCompanionBuilder.build()).generates("companion object") + assertThat(dslBuilder.build()).generates( + """ + class DslClass { + /** + * An object representing a value assigned to the oneof my_oneof. + */ + var myOneofProperty: MyOneofOneofClass<*> + get() = MyOneofOneofClass.readFrom(myBuilder) + set(newValue) { + newValue.writeTo(myBuilder) + } + } + """ + ) + } + + @Test + fun integrationTopLevel() { + val actual = generatorUnderTest.run { + config + .componentExtensions(OneofComponent(oneofDescriptor), UnqualifiedScope, UnqualifiedScope) + } + val expectedTopLevel = + Files.readAllLines( + Paths.get( + "src/test/java/com/google/protobuf/kotlin/generator", + "Example3OneofClass.kt.expected.toplevel" + ) + ).joinToString("\n") + assertThat(actual).generatesTopLevel(expectedTopLevel) + } + + @Test + fun integrationEnclosed() { + val actual = generatorUnderTest.run { + config + .componentExtensions(OneofComponent(oneofDescriptor), UnqualifiedScope, UnqualifiedScope) + } + val expectedEnclosed = + Files.readAllLines( + Paths.get( + "src/test/java/com/google/protobuf/kotlin/generator", + "Example3OneofClass.kt.expected.enclosed" + ) + ).joinToString("\n") + assertThat(actual).generatesEnclosed(expectedEnclosed) + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGeneratorTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGeneratorTest.kt new file mode 100644 index 000000000000..7d9b9f58d240 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGeneratorTest.kt @@ -0,0 +1,150 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.ClassSimpleName +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.JavaPackagePolicy +import com.google.protobuf.kotlin.protoc.SingletonFieldDescriptor +import com.google.protobuf.kotlin.protoc.UnqualifiedScope +import com.google.protobuf.kotlin.protoc.classBuilder +import com.google.protobuf.kotlin.protoc.specialize +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.google.protobuf.kotlin.generator.Example3 +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.TypeSpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [OneofOrNullPropertyGenerator]. */ +@RunWith(JUnit4::class) +class OneofOrNullPropertyGeneratorTest { + private val javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE + private val config = GeneratorConfig(javaPackagePolicy, aggressiveInlining = false) + private val example3 = Example3::class.qualifiedName!! + + @Test + fun forMessageOrBuilder() { + val fieldDescriptor = ExampleMessage.getDescriptor().findFieldByNumber( + ExampleMessage.STRING_ONEOF_OPTION_FIELD_NUMBER + ).specialize() as SingletonFieldDescriptor + + val declarations = OneofOrNullPropertyGenerator.forMessageOrBuilder.run { + config.componentExtensions( + fieldDescriptor, + UnqualifiedScope, + UnqualifiedScope + ) + } + assertThat(declarations).generatesNoEnclosedMembers() + assertThat(declarations).generatesTopLevel( + """ +import $example3 +import $example3.ExampleMessage.MyOneofCase.STRING_ONEOF_OPTION +import kotlin.String + +/** + * The string_oneof_option field of the proto ExampleMessage, or null if it isn't set. + */ +val Example3.ExampleMessageOrBuilder.stringOneofOptionOrNull: String? + get() = if (this.myOneofCase == STRING_ONEOF_OPTION) this.stringOneofOption else null + """ + ) + } + @Test + fun forBuilder() { + val fieldDescriptor = ExampleMessage.getDescriptor().findFieldByNumber( + ExampleMessage.STRING_ONEOF_OPTION_FIELD_NUMBER + ).specialize() as SingletonFieldDescriptor + + val declarations = OneofOrNullPropertyGenerator.forBuilder.run { + config.componentExtensions( + fieldDescriptor, + UnqualifiedScope, + UnqualifiedScope + ) + } + assertThat(declarations).generatesNoEnclosedMembers() + assertThat(declarations).generatesTopLevel( + """ +import $example3 +import $example3.ExampleMessage.MyOneofCase.STRING_ONEOF_OPTION +import kotlin.String + +/** + * The string_oneof_option field of the proto ExampleMessage, or null if it isn't set. + */ +var Example3.ExampleMessage.Builder.stringOneofOptionOrNull: String? + get() = if (this.myOneofCase == STRING_ONEOF_OPTION) this.stringOneofOption else null + set(_newValue) { + if (_newValue == null) { + this.clearStringOneofOption() + } else { + this.stringOneofOption = _newValue + } + } + """ + ) + } + + @Test + fun forDsl() { + val fieldDescriptor = ExampleMessage.getDescriptor().findFieldByNumber( + ExampleMessage.STRING_ONEOF_OPTION_FIELD_NUMBER + ).specialize() as SingletonFieldDescriptor + + val dslSimpleName = ClassSimpleName("DslClass") + val dslBuilder = TypeSpec.classBuilder(dslSimpleName) + val dslCompanionBuilder = TypeSpec.companionObjectBuilder() + + with(config) { + OneofOrNullPropertyGenerator.forDsl.run { + generate( + component = fieldDescriptor, + dslType = UnqualifiedScope.nestedClass(dslSimpleName), + extensionScope = UnqualifiedScope, + enclosingScope = UnqualifiedScope, + dslBuilder = dslBuilder, + companionBuilder = dslCompanionBuilder, + builderCode = CodeBlock.of("myBuilder") + ) + } + } + + val myOneofCase = ExampleMessage.MyOneofCase::class.qualifiedName!! + assertThat(dslBuilder.build()).generates( + """ +class DslClass { + /** + * The string_oneof_option field of the proto ExampleMessage, or null if it isn't set. + */ + var stringOneofOptionOrNull: kotlin.String? + get() = if (myBuilder.myOneofCase == $myOneofCase.STRING_ONEOF_OPTION) myBuilder.stringOneofOption else null + set(_newValue) { + if (_newValue == null) { + myBuilder.clearStringOneofOption() + } else { + myBuilder.stringOneofOption = _newValue + } + } +} + """ + ) + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGeneratorTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGeneratorTest.kt new file mode 100644 index 000000000000..055257590f0b --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGeneratorTest.kt @@ -0,0 +1,178 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.Descriptors +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.JavaPackagePolicy +import com.google.protobuf.kotlin.protoc.Scope +import com.google.protobuf.kotlin.protoc.declarations +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.google.protobuf.kotlin.generator.Example3 +import com.google.protobuf.kotlin.generator.JavaMultipleFiles +import com.squareup.kotlinpoet.INT +import com.squareup.kotlinpoet.PropertySpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [ProtoFileKotlinApiGenerator]. */ +@RunWith(JUnit4::class) +class ProtoFileKotlinApiGeneratorTest { + private val extensionGenerator: ExtensionGenerator = object : ExtensionGenerator() { + override fun GeneratorConfig.shallowExtensions( + descriptor: Descriptors.Descriptor, + enclosingScope: Scope, + extensionScope: Scope + ) = declarations { + addProperty(PropertySpec.builder("extensionProperty", INT).build()) + } + + override val generateWithinExtensionClass: Boolean + get() = true + } + + @Test + fun generate() { + val recursiveGenerator = RecursiveExtensionGenerator("Kt", extensionGenerator) + val fileGenerator = ProtoFileKotlinApiGenerator( + generator = recursiveGenerator, + config = GeneratorConfig( + javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE, + aggressiveInlining = false + ) + ) + + val fileSpec = fileGenerator.generate(Example3.getDescriptor()) + assertThat(fileSpec).hasName("Example3Kt") + assertThat(fileSpec).generates( + """ + @file:Suppress("DEPRECATION") + + package ${Example3::class.java.`package`.name} + + import kotlin.Int + import kotlin.Suppress + + /** + * Kotlin extensions for protos defined in the file ${Example3.getDescriptor().file.name}. + */ + object Example3Kt { + /** + * Kotlin extensions around the proto message type protobuf.kotlin.generator.ExampleMessage. + */ + object ExampleMessageKt { + val extensionProperty: Int + + /** + * Kotlin extensions around the proto message type + * protobuf.kotlin.generator.ExampleMessage.SubMessage. + */ + object SubMessageKt { + val extensionProperty: Int + } + } + } + """ + ) + } + + @Test + fun generateMultipleFiles() { + val recursiveGenerator = RecursiveExtensionGenerator("Kt", extensionGenerator) + val fileGenerator = ProtoFileKotlinApiGenerator( + generator = recursiveGenerator, + config = GeneratorConfig( + javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE, + aggressiveInlining = false + ) + ) + + val fileSpec = fileGenerator.generate(JavaMultipleFiles.getDescriptor()) + assertThat(fileSpec).hasName("JavaMultipleFilesKt") + assertThat(fileSpec).generates( + """ + @file:Suppress("DEPRECATION") + + package com.google.protobuf.kotlin.generator + + import kotlin.Int + import kotlin.Suppress + + /** + * Kotlin extensions around the proto message type protobuf.kotlin.generator.MultipleFilesMessageA. + */ + object MultipleFilesMessageAKt { + val extensionProperty: Int + } + + /** + * Kotlin extensions around the proto message type protobuf.kotlin.generator.MultipleFilesMessageB. + */ + object MultipleFilesMessageBKt { + val extensionProperty: Int + } + """ + ) + } + + @Test + fun generateInlining() { + val recursiveGenerator = RecursiveExtensionGenerator("Kt", extensionGenerator) + val fileGenerator = ProtoFileKotlinApiGenerator( + generator = recursiveGenerator, + config = GeneratorConfig( + javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE, + aggressiveInlining = true + ) + ) + + val fileSpec = fileGenerator.generate(Example3.getDescriptor()) + assertThat(fileSpec).hasName("Example3Kt") + assertThat(fileSpec).generates( + """ + @file:Suppress("NOTHING_TO_INLINE") + @file:Suppress("DEPRECATION") + + package com.google.protobuf.kotlin.generator + + import kotlin.Int + import kotlin.Suppress + + /** + * Kotlin extensions for protos defined in the file testing/example3.proto. + */ + object Example3Kt { + /** + * Kotlin extensions around the proto message type protobuf.kotlin.generator.ExampleMessage. + */ + object ExampleMessageKt { + val extensionProperty: Int + + /** + * Kotlin extensions around the proto message type + * protobuf.kotlin.generator.ExampleMessage.SubMessage. + */ + object SubMessageKt { + val extensionProperty: Int + } + } + } + """ + ) + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGeneratorTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGeneratorTest.kt new file mode 100644 index 000000000000..8ad0fd640c91 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGeneratorTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.Descriptors +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.JavaPackagePolicy +import com.google.protobuf.kotlin.protoc.PackageScope +import com.google.protobuf.kotlin.protoc.Scope +import com.google.protobuf.kotlin.protoc.declarations +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.google.protobuf.kotlin.generator.Example3 +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.INT +import com.squareup.kotlinpoet.PropertySpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [RecursiveExtensionGenerator]. */ +@RunWith(JUnit4::class) +class RecursiveExtensionGeneratorTest { + @Test + fun generate() { + val peerGenerator = object : ExtensionGenerator() { + override fun GeneratorConfig.shallowExtensions( + descriptor: Descriptors.Descriptor, + enclosingScope: Scope, + extensionScope: Scope + ) = declarations { + addProperty(PropertySpec.builder("peerProperty", INT).build()) + } + + override val generateWithinExtensionClass: Boolean + get() = false + } + val extensionGenerator = object : ExtensionGenerator() { + override fun GeneratorConfig.shallowExtensions( + descriptor: Descriptors.Descriptor, + enclosingScope: Scope, + extensionScope: Scope + ) = declarations { + addProperty(PropertySpec.builder("extensionProperty", INT).build()) + } + + override val generateWithinExtensionClass: Boolean + get() = true + } + + val recursiveGenerator = RecursiveExtensionGenerator( + "KtExtensions", peerGenerator, extensionGenerator + ) + val genFile = + FileSpec.builder("com.google", "FooBar.kt") + .apply { + recursiveGenerator.generate( + GeneratorConfig(JavaPackagePolicy.OPEN_SOURCE, aggressiveInlining = false), + Example3.ExampleMessage.getDescriptor(), + PackageScope("com.google") + ).writeAllAtTopLevel(this) + } + .build() + assertThat(genFile) + .generates( + """ + package com.google + + import kotlin.Int + + val peerProperty: Int + + /** + * Kotlin extensions around the proto message type protobuf.kotlin.generator.ExampleMessage. + */ + object ExampleMessageKtExtensions { + val extensionProperty: Int + + val peerProperty: Int + + /** + * Kotlin extensions around the proto message type + * protobuf.kotlin.generator.ExampleMessage.SubMessage. + */ + object SubMessageKtExtensions { + val extensionProperty: Int + } + } + """ + ) + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGeneratorTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGeneratorTest.kt new file mode 100644 index 000000000000..c32ec6215470 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGeneratorTest.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.DslList +import com.google.protobuf.kotlin.protoc.ClassSimpleName +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.JavaPackagePolicy +import com.google.protobuf.kotlin.protoc.RepeatedFieldDescriptor +import com.google.protobuf.kotlin.protoc.UnqualifiedScope +import com.google.protobuf.kotlin.protoc.classBuilder +import com.google.protobuf.kotlin.protoc.specialize +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.google.protobuf.kotlin.generator.Example3 +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.TypeSpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [RepeatedFieldListProxyDslComponentGeneratorTest]. */ +@RunWith(JUnit4::class) +class RepeatedFieldListProxyDslComponentGeneratorTest { + private val javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE + private val config = GeneratorConfig(javaPackagePolicy, aggressiveInlining = false) + + private val repeatedFieldDescriptor: RepeatedFieldDescriptor = + Example3.ExampleMessage.getDescriptor().findFieldByNumber( + Example3.ExampleMessage.REPEATED_STRING_FIELD_FIELD_NUMBER + ).specialize() as RepeatedFieldDescriptor + + @Test + fun generate() { + val dslSimpleName = ClassSimpleName("DslClass") + val dslBuilder = TypeSpec.classBuilder(dslSimpleName) + val dslCompanionBuilder = TypeSpec.companionObjectBuilder() + val generator = RepeatedFieldListProxyDslComponentGenerator( + proxyTypeNameSuffix = "ProxySuffix" + ) + + with(config) { + generator.run { + generate( + component = repeatedFieldDescriptor, + builderCode = CodeBlock.of("myBuilder"), + enclosingScope = UnqualifiedScope, + extensionScope = UnqualifiedScope, + dslType = UnqualifiedScope.nestedClass(dslSimpleName), + companionBuilder = dslCompanionBuilder, + dslBuilder = dslBuilder + ) + } + } + + assertThat(dslCompanionBuilder.build()).generates("companion object") + val dslList = DslList::class.qualifiedName!! + assertThat(dslBuilder.build()).generates( + """ + class DslClass { + /** + * A [kotlin.collections.List] view of the repeated field repeated_string_field. + * + * While the view object itself is a `List`, not a `MutableList`, within the context of the + * DSL it has extension methods and operator overloads that mutate it appropriately. + * For example, writing `repeatedStringField += element` adds that element to the repeated field, but that + * code only compiles within the DSL. + */ + val repeatedStringField: $dslList + inline get() = $dslList(myBuilder.getRepeatedStringFieldList()) + + /** + * An uninstantiable, behaviorless type to represent the field repeated_string_field in generics. + */ + class RepeatedStringFieldProxySuffix private constructor() + } + """ + ) + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGeneratorTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGeneratorTest.kt new file mode 100644 index 000000000000..9acf0f41a55c --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGeneratorTest.kt @@ -0,0 +1,213 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.ClassSimpleName +import com.google.protobuf.kotlin.protoc.Declarations +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.JavaPackagePolicy +import com.google.protobuf.kotlin.protoc.MemberSimpleName +import com.google.protobuf.kotlin.protoc.RepeatedFieldDescriptor +import com.google.protobuf.kotlin.protoc.UnqualifiedScope +import com.google.protobuf.kotlin.protoc.builder +import com.google.protobuf.kotlin.protoc.specialize +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FunSpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [RepeatedRepresentativeExtensionGenerator]. */ +@RunWith(JUnit4::class) +class RepeatedRepresentativeExtensionGeneratorTest { + private val javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE + private val config = GeneratorConfig(javaPackagePolicy, aggressiveInlining = false) + private val proxyDummyType = UnqualifiedScope.nestedClass(ClassSimpleName("Proxy")) + + private val repeatedFieldDescriptor: RepeatedFieldDescriptor = + ExampleMessage.getDescriptor().findFieldByNumber( + ExampleMessage.REPEATED_STRING_FIELD_FIELD_NUMBER + ).specialize() as RepeatedFieldDescriptor + + private fun declarations(generator: RepeatedRepresentativeExtensionGenerator): Declarations { + return with(config) { + generator.run { + generate( + { FunSpec.builder(it).receiver(proxyDummyType) }, + repeatedFieldDescriptor, + CodeBlock.of("myBuilder"), + MemberSimpleName("repeatedFieldProperty") + ) + } + } + } + + @Test + fun setOperator() { + val declarations = declarations(RepeatedRepresentativeExtensionGenerator.SetOperator) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import kotlin.Int + import kotlin.String + + /** + * Sets the element of the repeated field repeated_string_field at a given index. + * + * For example, `repeatedFieldProperty[i] = value` sets the `i`th element of repeated_string_field + * to `value`. + */ + operator fun Proxy.set(index: Int, newValue: String) { + myBuilder.setRepeatedStringField(index, newValue) + } + """ + ) + } + + @Test + fun addElement() { + val declarations = declarations(RepeatedRepresentativeExtensionGenerator.AddElement.add) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import kotlin.String + + /** + * Adds an element to the repeated field repeated_string_field. + * + * For example, `repeatedFieldProperty.add(value)` adds `value` to repeated_string_field. + */ + fun Proxy.add(newValue: String) { + myBuilder.addRepeatedStringField(newValue) + } + """ + ) + } + + @Test + fun plusAssignElement() { + val declarations = declarations(RepeatedRepresentativeExtensionGenerator.AddElement.plusAssign) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import kotlin.String + + /** + * Adds an element to the repeated field repeated_string_field. + * + * For example, `repeatedFieldProperty += value` adds `value` to repeated_string_field. + */ + operator fun Proxy.plusAssign(newValue: String) { + myBuilder.addRepeatedStringField(newValue) + } + """ + ) + } + + @Test + fun addAllIterable() { + val declarations = declarations(RepeatedRepresentativeExtensionGenerator.AddAllIterable.addAll) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import kotlin.String + import kotlin.collections.Iterable + + /** + * Adds elements to the repeated field repeated_string_field. + * + * For example, `repeatedFieldProperty.addAll(values)` adds each of the elements of `values`, in + * order, to repeated_string_field. + */ + fun Proxy.addAll(newValues: Iterable) { + myBuilder.addAllRepeatedStringField(newValues) + } + """ + ) + } + + @Test + fun plusAssignIterable() { + val declarations = + declarations(RepeatedRepresentativeExtensionGenerator.AddAllIterable.plusAssign) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import kotlin.String + import kotlin.collections.Iterable + + /** + * Adds elements to the repeated field repeated_string_field. + * + * For example, `repeatedFieldProperty += values` adds each of the elements of `values`, in order, + * to repeated_string_field. + */ + operator fun Proxy.plusAssign(newValues: Iterable) { + myBuilder.addAllRepeatedStringField(newValues) + } + """ + ) + } + + @Test + fun addAllVararg() { + val declarations = declarations(RepeatedRepresentativeExtensionGenerator.AddAllVararg) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + import kotlin.String + + /** + * Add some elements to the repeated field repeated_string_field. + * + * For example, `repeatedFieldProperty.addAll(v1, v2, v3)` adds `v1`, `v2`, and `v3`, in order, to + * repeated_string_field. + */ + fun Proxy.addAll(vararg newValues: String) { + myBuilder.addAllRepeatedStringField(newValues.asList()) + } + """ + ) + } + + @Test + fun clear() { + val declarations = declarations(RepeatedRepresentativeExtensionGenerator.Clear) + + assertThat(declarations).generatesNoTopLevelMembers() + assertThat(declarations).generatesEnclosed( + """ + /** + * Removes all elements from the repeated field repeated_string_field. + * + * For example, `repeatedFieldProperty.clear()` deletes all elements from repeated_string_field. + */ + fun Proxy.clear() { + myBuilder.clearRepeatedStringField() + } + """ + ) + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGeneratorTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGeneratorTest.kt new file mode 100644 index 000000000000..43ec04f452b8 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGeneratorTest.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.ClassSimpleName +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.JavaPackagePolicy +import com.google.protobuf.kotlin.protoc.SingletonFieldDescriptor +import com.google.protobuf.kotlin.protoc.UnqualifiedScope +import com.google.protobuf.kotlin.protoc.classBuilder +import com.google.protobuf.kotlin.protoc.specialize +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.TypeSpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [SingletonFieldClearDslComponentGenerator]. */ +@RunWith(JUnit4::class) +class SingletonFieldClearDslComponentGeneratorTest { + private val javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE + private val config = GeneratorConfig(javaPackagePolicy, aggressiveInlining = false) + + @Test + fun generate() { + val fieldDescriptor = + ExampleMessage + .getDescriptor() + .findFieldByNumber(ExampleMessage.STRING_FIELD_FIELD_NUMBER) + .specialize() as SingletonFieldDescriptor + val dslSimpleName = ClassSimpleName("DslClass") + val dslBuilder = TypeSpec.classBuilder(dslSimpleName) + val dslCompanionBuilder = TypeSpec.companionObjectBuilder() + + with(config) { + SingletonFieldClearDslComponentGenerator.run { + generate( + component = fieldDescriptor, + dslType = UnqualifiedScope.nestedClass(dslSimpleName), + extensionScope = UnqualifiedScope, + enclosingScope = UnqualifiedScope, + dslBuilder = dslBuilder, + companionBuilder = dslCompanionBuilder, + builderCode = CodeBlock.of("myBuilder") + ) + } + } + + assertThat(dslBuilder.build()).generates( + """ + class DslClass { + /** + * Clears any value assigned to the string_field field. + */ + fun clearStringField() { + myBuilder.clearStringField() + } + } + """ + ) + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGeneratorTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGeneratorTest.kt new file mode 100644 index 000000000000..73e115a34fbb --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGeneratorTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.ClassSimpleName +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.JavaPackagePolicy +import com.google.protobuf.kotlin.protoc.SingletonFieldDescriptor +import com.google.protobuf.kotlin.protoc.UnqualifiedScope +import com.google.protobuf.kotlin.protoc.classBuilder +import com.google.protobuf.kotlin.protoc.specialize +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.google.protobuf.kotlin.generator.Example3 +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.TypeSpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [SingletonFieldHazzerDslComponentGenerator]. */ +@RunWith(JUnit4::class) +class SingletonFieldHazzerDslComponentGeneratorTest { + private val javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE + private val config = GeneratorConfig(javaPackagePolicy, aggressiveInlining = false) + + @Test + fun generate() { + val fieldDescriptor = + Example3.ExampleMessage + .getDescriptor() + .findFieldByNumber(Example3.ExampleMessage.SUB_MESSAGE_FIELD_FIELD_NUMBER) + .specialize() as SingletonFieldDescriptor + val dslSimpleName = ClassSimpleName("DslClass") + val dslBuilder = TypeSpec.classBuilder(dslSimpleName) + val dslCompanionBuilder = TypeSpec.companionObjectBuilder() + + with(config) { + SingletonFieldHazzerDslComponentGenerator.run { + generate( + component = fieldDescriptor, + dslType = UnqualifiedScope.nestedClass(dslSimpleName), + extensionScope = UnqualifiedScope, + enclosingScope = UnqualifiedScope, + dslBuilder = dslBuilder, + companionBuilder = dslCompanionBuilder, + builderCode = CodeBlock.of("myBuilder") + ) + } + } + + assertThat(dslBuilder.build()).generates( + """ + class DslClass { + /** + * True if the sub_message_field field has been set. + */ + fun hasSubMessageField(): kotlin.Boolean = myBuilder.hasSubMessageField() + } + """ + ) + } + + @Test + fun generateFieldLackingHazzer() { + val fieldDescriptor = + Example3.ExampleMessage + .getDescriptor() + .findFieldByNumber(Example3.ExampleMessage.INT32_FIELD_FIELD_NUMBER) + .specialize() as SingletonFieldDescriptor + val dslSimpleName = ClassSimpleName("DslClass") + val dslBuilder = TypeSpec.classBuilder(dslSimpleName) + val dslCompanionBuilder = TypeSpec.companionObjectBuilder() + + with(config) { + SingletonFieldHazzerDslComponentGenerator.run { + generate( + component = fieldDescriptor, + dslType = UnqualifiedScope.nestedClass(dslSimpleName), + extensionScope = UnqualifiedScope, + enclosingScope = UnqualifiedScope, + dslBuilder = dslBuilder, + companionBuilder = dslCompanionBuilder, + builderCode = CodeBlock.of("myBuilder") + ) + } + } + + // no generated API + assertThat(dslBuilder.build()).generates("class DslClass") + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGeneratorTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGeneratorTest.kt new file mode 100644 index 000000000000..dac1a94fc039 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGeneratorTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.ClassSimpleName +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.JavaPackagePolicy +import com.google.protobuf.kotlin.protoc.SingletonFieldDescriptor +import com.google.protobuf.kotlin.protoc.UnqualifiedScope +import com.google.protobuf.kotlin.protoc.classBuilder +import com.google.protobuf.kotlin.protoc.specialize +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.TypeSpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [SingletonFieldPropertyDslComponentGenerator]. */ +@RunWith(JUnit4::class) +class SingletonFieldPropertyDslComponentGeneratorTest { + private val javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE + private val config = GeneratorConfig(javaPackagePolicy, aggressiveInlining = false) + + @Test + fun generate() { + val fieldDescriptor = ExampleMessage.getDescriptor() + .findFieldByNumber(ExampleMessage.STRING_FIELD_FIELD_NUMBER) + .specialize() as SingletonFieldDescriptor + val dslSimpleName = ClassSimpleName("DslClass") + val dslBuilder = TypeSpec.classBuilder(dslSimpleName) + val dslCompanionBuilder = TypeSpec.companionObjectBuilder() + + with(config) { + SingletonFieldPropertyDslComponentGenerator.run { + generate( + component = fieldDescriptor, + dslType = UnqualifiedScope.nestedClass(dslSimpleName), + extensionScope = UnqualifiedScope, + enclosingScope = UnqualifiedScope, + dslBuilder = dslBuilder, + companionBuilder = dslCompanionBuilder, + builderCode = CodeBlock.of("myBuilder") + ) + } + } + + assertThat(dslBuilder.build()).generates( + """ + class DslClass { + /** + * The protobuf.kotlin.generator.ExampleMessage.string_field field. + */ + var stringField: kotlin.String + @kotlin.jvm.JvmName("getStringField") + get() = myBuilder.getStringField() + @kotlin.jvm.JvmName("setStringField") + set(newValue) { + myBuilder.setStringField(newValue) + } + } + """ + ) + } +} diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/WrapperPropertyGeneratorTest.kt b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/WrapperPropertyGeneratorTest.kt new file mode 100644 index 000000000000..6acf5e288972 --- /dev/null +++ b/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/WrapperPropertyGeneratorTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.generator + +import com.google.protobuf.kotlin.protoc.ClassSimpleName +import com.google.protobuf.kotlin.protoc.GeneratorConfig +import com.google.protobuf.kotlin.protoc.JavaPackagePolicy +import com.google.protobuf.kotlin.protoc.SingletonFieldDescriptor +import com.google.protobuf.kotlin.protoc.UnqualifiedScope +import com.google.protobuf.kotlin.protoc.classBuilder +import com.google.protobuf.kotlin.protoc.specialize +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.google.protobuf.kotlin.generator.Example3.ExampleMessage +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.TypeSpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [WrapperPropertyGenerator]. */ +@RunWith(JUnit4::class) +class WrapperPropertyGeneratorTest { + private val javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE + private val config = GeneratorConfig( + javaPackagePolicy = javaPackagePolicy, + aggressiveInlining = false + ) + private val generator = WrapperPropertyGenerator( + propertyNameSuffix = "Value" + ) + private val fieldDescriptor = ExampleMessage.getDescriptor().findFieldByNumber( + ExampleMessage.OPTIONAL_INT32_FIELD_NUMBER + ).specialize() as SingletonFieldDescriptor + + @Test + fun orBuilder() { + val declarations = generator.forMessageOrBuilder.run { + config.componentExtensions( + fieldDescriptor, + UnqualifiedScope, + UnqualifiedScope + ) + } + + assertThat(declarations).generatesNoEnclosedMembers() + assertThat(declarations).generatesTopLevel( + """ +import com.google.protobuf.kotlin.generator.Example3 +import kotlin.Int + +/** + * A nullable view of the field optional_int32 of the message + * protobuf.kotlin.generator.ExampleMessage as an optional int32. + */ +val Example3.ExampleMessageOrBuilder.optionalInt32Value: Int? + get() = if (this.hasOptionalInt32()) this.optionalInt32.value else null + """ + ) + } + + @Test + fun builder() { + val declarations = generator.forBuilder.run { + config.componentExtensions( + fieldDescriptor, + UnqualifiedScope, + UnqualifiedScope + ) + } + + assertThat(declarations).generatesNoEnclosedMembers() + assertThat(declarations).generatesTopLevel( + """ +import com.google.protobuf.Int32Value +import com.google.protobuf.kotlin.generator.Example3 +import kotlin.Int + +/** + * A nullable view of the field optional_int32 of the message + * protobuf.kotlin.generator.ExampleMessage as an optional int32. + */ +var Example3.ExampleMessage.Builder.optionalInt32Value: Int? + get() = if (this.hasOptionalInt32()) this.optionalInt32.value else null + set(newValue) { + if (newValue == null) { + this.clearOptionalInt32() + } else { + this.optionalInt32 = Int32Value.newBuilder().setValue(newValue).build() + } + } + """ + ) + } + + @Test + fun dsl() { + val dslSimpleName = ClassSimpleName("DslClass") + val dslBuilder = TypeSpec.classBuilder(dslSimpleName) + val dslCompanionBuilder = TypeSpec.companionObjectBuilder() + + with(config) { + generator.forDsl.run { + generate( + component = fieldDescriptor, + dslType = UnqualifiedScope.nestedClass(dslSimpleName), + extensionScope = UnqualifiedScope, + enclosingScope = UnqualifiedScope, + dslBuilder = dslBuilder, + companionBuilder = dslCompanionBuilder, + builderCode = CodeBlock.of("myBuilder") + ) + } + } + + assertThat(dslCompanionBuilder.build()).generates("companion object") + assertThat(dslBuilder.build()).generates( + """ +class DslClass { + /** + * A nullable view of the field optional_int32 of the message protobuf.kotlin.generator.ExampleMessage as an optional int32. + */ + var optionalInt32Value: kotlin.Int? + get() = if (myBuilder.hasOptionalInt32()) myBuilder.optionalInt32.value else null + set(newValue) { + if (newValue == null) { + myBuilder.clearOptionalInt32() + } else { + myBuilder.optionalInt32 = com.google.protobuf.Int32Value.newBuilder().setValue(newValue).build() + } + } +} + """ + ) + } +} diff --git a/kotlin/protobuf/src/test/proto/testing/evil_names.proto b/kotlin/protobuf/src/test/proto/testing/evil_names.proto new file mode 100644 index 000000000000..54056f643209 --- /dev/null +++ b/kotlin/protobuf/src/test/proto/testing/evil_names.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; + +package protobuf.kotlin.generator; + +option java_package = "com.google.protobuf.kotlin.generator"; + +/* + * We make sure the Kotlin version of this compiles, verifying b/140669339 + * is fixed. + */ +message EvilNames { + bool initialized = 1; + bool has_foo = 2; + string Bar = 3; + bool is_initialized = 4; + + oneof my_oneof { + string + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = + 110; + } + + oneof camelCase { // regression test for b/142341003 + string fooBar = 5; + } + + repeated string ALL_CAPS = 7; + map ALL_CAPS_MAP = 8; + + // Cases where characters follow digit for b/143478601 + bool has_underbar_preceeding_numeric_1foo = 9; + bool has_underbar_preceeding_numeric_42bar = 10; + bool has_underbar_preceeding_numeric_123foo42bar_baz = 11; + + repeated string extension = 12; + + string class = 13; + double int = 14; + bool long = 15; + int64 boolean = 16; + string sealed = 17; + float interface = 18; + int32 in = 19; + string object = 20; + string cached_size = 21; + bool serialized_size = 22; + string value = 23; + int64 index = 24; + repeated string values = 25; + repeated string new_values = 26; + bool builder = 27; + map k = 28; + map v = 29; + map key = 30; + map map = 31; + map pairs = 32; +} diff --git a/kotlin/protobuf/src/test/proto/testing/evil_names_proto2.proto b/kotlin/protobuf/src/test/proto/testing/evil_names_proto2.proto new file mode 100644 index 000000000000..50caca2fc659 --- /dev/null +++ b/kotlin/protobuf/src/test/proto/testing/evil_names_proto2.proto @@ -0,0 +1,50 @@ +syntax = "proto2"; + +package protobuf.kotlin.generator; + +option java_package = "com.google.protobuf.kotlin.generator"; + +/* + * We make sure the Kotlin version of this compiles, verifying b/140669339 + * is fixed. + */ +message EvilNamesProto2 { + optional bool initialized = 1; + optional bool has_foo = 2; + optional string Bar = 3; + optional bool is_initialized = 4; + + oneof my_oneof { + string aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = 10; + } + + oneof camelCase { // regression test for b/142341003 + string fooBar = 5; + } + + repeated string ALL_CAPS = 7; + map ALL_CAPS_MAP = 8; + + // Cases where characters follow digit for b/143478601 + optional bool has_underbar_preceeding_numeric_1foo = 9; + optional bool has_underbar_preceeding_numeric_42bar = 13; + optional bool has_underbar_preceeding_numeric_123foo42bar_baz = 14; + + extensions 100 to max; + + repeated string extension = 12; + repeated int32 class = 15; + optional double int = 16; + optional bool long = 17; + optional int64 boolean = 18; + optional string sealed = 19; + optional float interface = 20; + optional int32 in = 21; + optional string object = 22; + optional string cached_size = 23; + optional bool serialized_size = 24; +} + +message AnotherEvilNamesProto2 { + map class = 1; +} \ No newline at end of file diff --git a/kotlin/protobuf/src/test/proto/testing/example2.proto b/kotlin/protobuf/src/test/proto/testing/example2.proto new file mode 100644 index 000000000000..ffb26ff01e2a --- /dev/null +++ b/kotlin/protobuf/src/test/proto/testing/example2.proto @@ -0,0 +1,17 @@ +syntax = "proto2"; + +package protobuf.kotlin.generator; + +option java_package = "com.google.protobuf.kotlin.generator"; + +message ExampleProto2Message { + optional int32 optional_int32_field = 1; + required string required_string_field = 2; + + extensions 100 to 199; +} + +extend ExampleProto2Message { + optional int32 int32_extension = 100; + repeated string repeated_string_extension = 101; +} diff --git a/kotlin/protobuf/src/test/proto/testing/example3.proto b/kotlin/protobuf/src/test/proto/testing/example3.proto new file mode 100644 index 000000000000..be43acaf4e3b --- /dev/null +++ b/kotlin/protobuf/src/test/proto/testing/example3.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; + +package protobuf.kotlin.generator; + +import "google/protobuf/wrappers.proto"; + +option java_package = "com.google.protobuf.kotlin.generator"; + +message ExampleMessage { + int32 int32_field = 1; + string string_field = 2; + SubMessage sub_message_field = 3; + + oneof my_oneof { + string string_oneof_option = 4; + SubMessage sub_message_oneof_option = 5; + } + + repeated string repeated_string_field = 6; + map map_field = 7; + + message SubMessage {} + + // TODO(lowasser): why does this need the leading .? Other protos don't seem + // to. + .google.protobuf.Int32Value optional_int32 = 8; + + string deprecated_field = 9 [deprecated = true]; + + repeated int32 deprecated_repeated_field = 10 [deprecated = true]; + + map deprecated_map_field = 11 [deprecated = true]; + + oneof deprecated_oneof { + string deprecated_oneof_option = 12 [deprecated = true]; + } +} + +enum ExampleEnum { + DEFAULT = 0; + FOO = 1; + BAR = 2; +} diff --git a/kotlin/protobuf/src/test/proto/testing/java_multiple_files.proto b/kotlin/protobuf/src/test/proto/testing/java_multiple_files.proto new file mode 100644 index 000000000000..777aba38afed --- /dev/null +++ b/kotlin/protobuf/src/test/proto/testing/java_multiple_files.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package protobuf.kotlin.generator; + +option java_package = "com.google.protobuf.kotlin.generator"; +option java_multiple_files = true; + +message MultipleFilesMessageA {} + +message MultipleFilesMessageB {} diff --git a/kotlin/protoc-plugin-common/.gitignore b/kotlin/protoc-plugin-common/.gitignore new file mode 100644 index 000000000000..a024d8237ce8 --- /dev/null +++ b/kotlin/protoc-plugin-common/.gitignore @@ -0,0 +1,3 @@ +# Gradle +build +.gradle diff --git a/kotlin/protoc-plugin-common/CONTRIBUTING.md b/kotlin/protoc-plugin-common/CONTRIBUTING.md new file mode 100644 index 000000000000..086d53916b56 --- /dev/null +++ b/kotlin/protoc-plugin-common/CONTRIBUTING.md @@ -0,0 +1,26 @@ +# Contributing + +Want to contribute? Great! First, read this page (including the small print at the end). + +### Before you contribute +Before we can use your code, you must sign the +[Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1) +(CLA), which you can do online. The CLA is necessary mainly because you own the +copyright to your changes, even after your contribution becomes part of our +codebase, so we need your permission to use and distribute your code. We also +need to be sure of various other things—for instance that you'll tell us if you +know that your code infringes on other people's patents. You don't have to sign +the CLA until after you've submitted your code for review and a member has +approved it, but you must do it before we can put your code into our codebase. +Before you start working on a larger contribution, you should get in touch with +us first through the issue tracker with your idea so that we can help out and +possibly guide you. Coordinating up front makes it much easier to avoid +frustration later on. + +### Code reviews +All submissions, including submissions by project members, require review. We +use Github pull requests for this purpose. + +### The small print +Contributions made by corporations are covered by a different agreement than +the one above, the Software Grant and Corporate Contributor License Agreement. diff --git a/kotlin/protoc-plugin-common/LICENSE b/kotlin/protoc-plugin-common/LICENSE new file mode 100644 index 000000000000..7a4a3ea2424c --- /dev/null +++ b/kotlin/protoc-plugin-common/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/kotlin/protoc-plugin-common/OWNERS b/kotlin/protoc-plugin-common/OWNERS new file mode 100644 index 000000000000..a2394f54ed56 --- /dev/null +++ b/kotlin/protoc-plugin-common/OWNERS @@ -0,0 +1,6 @@ +# Top level ownership +@lowasser **/OWNERS +@jbolinger **/OWNERS +@kevin1e100 **/OWNERS +@bshaffer **/OWNERS + diff --git a/kotlin/protoc-plugin-common/README.md b/kotlin/protoc-plugin-common/README.md new file mode 100644 index 000000000000..a25d95d4a681 --- /dev/null +++ b/kotlin/protoc-plugin-common/README.md @@ -0,0 +1 @@ +# protoc-plugin-kotlin: Common classes for kotlin protobuf diff --git a/kotlin/protoc-plugin-common/build.gradle b/kotlin/protoc-plugin-common/build.gradle new file mode 100644 index 000000000000..1d4a4a21f45f --- /dev/null +++ b/kotlin/protoc-plugin-common/build.gradle @@ -0,0 +1,46 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version'1.3.61' + id 'com.google.protobuf' version '0.8.8' + id 'maven-publish' +} + +repositories { + jcenter() + mavenCentral() +} + +// Feel free to delete the comment at the next line. It is just for safely +// updating the version in our release process. +def kotlinVersion = '1.3.61' + +dependencies { + // Kotlin + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}" + + // Java Protobuf + implementation 'com.google.protobuf:protobuf-java:3.11.0' + + // Misc + implementation 'com.squareup:kotlinpoet:1.5.0' + + // Testing + /** + * @TODO: this is not defined using "testImplementation" because of classes + * in "testing" directory. Find out why! + */ + implementation "com.google.truth:truth:1.0.1" + testImplementation "org.junit.jupiter:junit-jupiter-engine:5.5.2" + testImplementation "org.jetbrains.kotlin:kotlin-reflect:1.3.61" +} + +publishing { + publications { + maven(MavenPublication) { + groupId = 'io.grpc' + artifactId = 'protoc-plugin-kotlin' + version = '0.1' + + from components.java + } + } +} diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/common/graph/TopologicalSortGraph.java b/kotlin/protoc-plugin-common/src/main/java/com/google/common/graph/TopologicalSortGraph.java new file mode 100644 index 000000000000..013247aad776 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/common/graph/TopologicalSortGraph.java @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.graph; + +import static com.google.common.base.Preconditions.checkArgument; +import com.google.common.sort.TopologicalSort; +import com.google.common.sort.PartialOrdering; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public final class TopologicalSortGraph +{ + public static List topologicalOrdering(final Graph graph) { + checkArgument(graph.isDirected(), "Cannot get topological ordering of an undirected graph."); + PartialOrdering partialOrdering = + new PartialOrdering() { + @Override + public Set getPredecessors(N node) { + return graph.predecessors(node); + } + }; + List nodeList = new ArrayList(graph.nodes()); + TopologicalSort.sortLexicographicallyLeast(nodeList, partialOrdering); + return Collections.unmodifiableList(nodeList); + } +} \ No newline at end of file diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/common/sort/PartialOrdering.java b/kotlin/protoc-plugin-common/src/main/java/com/google/common/sort/PartialOrdering.java new file mode 100644 index 000000000000..b7648d9958ed --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/common/sort/PartialOrdering.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.sort; + +import java.util.Set; + +/** + * The interface that imposes a partial order on elements in a DAG to be topologically sorted. + * + * @author Okhtay Ilghami (okhtay@google.com) + */ +public interface PartialOrdering { + + /** + * Returns nodes that are considered "less than" {@code element} for purposes of a {@link + * TopologicalSort}. Transitive predecessors do not need to be included. + * + *

For example, if {@code getPredecessors(a)} includes {@code b} and {@code getPredecessors(b)} + * includes {@code c}, it is not necessary to include {@code c} in {@code getPredecessors(a)}. + * {@code c} is not a "direct" predecessor of {@code a}. + */ + Set getPredecessors(T element); +} + diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/common/sort/TopologicalSort.java b/kotlin/protoc-plugin-common/src/main/java/com/google/common/sort/TopologicalSort.java new file mode 100644 index 000000000000..16f7a71f2ccc --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/common/sort/TopologicalSort.java @@ -0,0 +1,315 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.common.sort; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; + +/** + * Topological sorting. The algorithm is an adaptation of the topological sorting algorithm + * described in TAOCP Section 2.2.3, with a little bit of extra state to provide a stable sort. The + * constructed ordering is guaranteed to be deterministic. + * + *

The elements to be sorted should implement the standard {@link Object#hashCode} and {@link + * Object#equals} methods. + */ +public final class TopologicalSort { + /** + * This exception is thrown whenever the input to our topological sort algorithm is a cyclical + * graph, or the predecessor relation refers to elements that have not been presented as input to + * the topological sort. + */ + public static class CyclicalGraphException extends RuntimeException { + private static final long serialVersionUID = 1L; + // not parameterized because exceptions can't be parameterized + private final List elementsInCycle; + + private CyclicalGraphException(String message, List elementsInCycle) { + super(message); + this.elementsInCycle = elementsInCycle; + } + + /** + * Returns a list of the elements that are part of the cycle, as well as elements that are + * greater than the elements in the cycle, according to the partial ordering. The elements in + * this list are not in a meaningful order. + */ + @SuppressWarnings("unchecked") + public List getElementsInCycle() { + return (List) Collections.unmodifiableList(elementsInCycle); + } + } + + /** + * To bundle an element with a mutable structure of the dependency graph. + * + *

Each {@link InternalElement} counts how many predecessors it has left. Rather than keep a + * list of predecessors, we reverse the relation so that it's easy to navigate to the successors + * when an {@link InternalElement} is selected for sorting. + * + *

This maintains a {@code originalIndex} to allow a "stable" sort based on the original + * position in the input list. + */ + private static final class InternalElement implements Comparable> { + T element; + int originalIndex; + List> successors; + int predecessorCount; + + InternalElement(T element, int originalIndex) { + this.element = element; + this.originalIndex = originalIndex; + this.successors = new ArrayList>(); + } + + @Override + public int compareTo(InternalElement o) { + if (originalIndex < o.originalIndex) { + return -1; + } else if (originalIndex > o.originalIndex) { + return 1; + } + return 0; + } + } + + /** + * Does a topological sorting. If there is more than one possibility the order returned is based + * on the order of the input list (note there are teams relying on this deterministic behavior). + * The input list is mutated to reflect the topological sort. Complexity is O(elements.size() * + * log(elements.size()) + number of dependencies). + * + *

A high-level sketch of toplogical sort from Wikipedia: + * (http://en.wikipedia.org/wiki/Topological_sorting) + * L ← Empty list that will contain the sorted elements + * S ← Set of all nodes with no incoming edges + * while S is non-empty do + * remove a node n from S + * add n to tail of L + * for each node m with an edge e from n to m do + * remove edge e from the graph + * if m has no other incoming edges then + * insert m into S + * if graph has edges then + * return error (graph has at least one cycle) + * else + * return L (a topologically sorted order) + * + * + *

We extend the basic algorithm to traverse {@code S} in a particular order based on the + * original order of the elements to enforce a deterministic result. + * + * @param elements a mutable list of elements to be sorted. + * @param order the partial order between elements. + * @throws CyclicalGraphException if the graph is cyclical or any predecessor is not present in + * the input list. + */ + public static void sort(List elements, PartialOrdering order) { + List> internalElements = internalizeElements(elements, order); + List> sortedElements = new ArrayList>(elements.size()); + + // The "S" set of the above algorithm pseudocode is represented here by both + // readyElementsForCurrentPass and readyElementsForNextPass. + PriorityQueue> readyElementsForCurrentPass = + new PriorityQueue>(); + List> readyElementsForNextPass = new ArrayList>(); + for (InternalElement element : internalElements) { + if (element.predecessorCount == 0) { + readyElementsForNextPass.add(element); + } + } + + while (!readyElementsForNextPass.isEmpty()) { + readyElementsForCurrentPass.addAll(readyElementsForNextPass); + readyElementsForNextPass.clear(); + + while (!readyElementsForCurrentPass.isEmpty()) { + InternalElement currentElement = readyElementsForCurrentPass.poll(); + sortedElements.add(currentElement); + for (InternalElement successor : currentElement.successors) { + successor.predecessorCount--; + if (successor.predecessorCount == 0) { + // Subtle: the original algorithm processed the input element list in multiple passes. + // For each pass, it iterated across the unsorted portion, and added nodes with + // no dependencies to the sorted portion, maintaining the original order of the + // unsorted portion. Once the pass hit the end of the list, it went to the next pass, + // starting at the beginning of the unsorted portion again. + // + // Existing Google code requires this ordering behavior, and to preserve it, + // we queue up some the elements that would otherwise be directly added to + // readyElementsForCurrentPass, and put those elements into + // readyElementsForNextPass. + if (successor.originalIndex < currentElement.originalIndex) { + readyElementsForNextPass.add(successor); + } else { + readyElementsForCurrentPass.add(successor); + } + } + } + } + } + + if (sortedElements.size() != elements.size()) { + List elementsInCycle = new ArrayList(); + for (InternalElement element : internalElements) { + if (element.predecessorCount > 0) { + elementsInCycle.add(element.element); + } + } + throw new CyclicalGraphException( + "Cyclical graphs can not be topologically sorted.", elementsInCycle); + } + + overwriteListWithSortedResults(elements, sortedElements); + } + + /** + * Does a topological sorting. If there is a tie in the topographical ordering, it is resolved in + * favor of the order of the original input list. Thus, an arbitrary "lexicographical topological + * ordering" can be achieved by first lexicographically sorting the input list according to your + * own criteria and then calling this method. + * + *

A high-level sketch of toplogical sort from Wikipedia: + * (http://en.wikipedia.org/wiki/Topological_sorting) + * L ← Empty list that will contain the sorted elements + * S ← Set of all nodes with no incoming edges + * while S is non-empty do + * remove a node n from S + * add n to tail of L + * for each node m with an edge e from n to m do + * remove edge e from the graph + * if m has no other incoming edges then + * insert m into S + * if graph has edges then + * return error (graph has at least one cycle) + * else + * return L (a topologically sorted order) + * + * + *

We extend the basic algorithm to traverse {@code S} in a particular order based on the + * original order of the elements to enforce a deterministic result (lexicographically based on + * the order of elements in the original input list). + * + * @param elements a mutable list of elements to be sorted. + * @param order the partial order between elements. + * @throws CyclicalGraphException if the graph is cyclical or any predecessor is not present in + * the input list. + */ + public static void sortLexicographicallyLeast(List elements, PartialOrdering order) { + List> internalElements = internalizeElements(elements, order); + List> sortedElements = new ArrayList>(elements.size()); + + // The "S" set of the above algorithm pseudocode is represented here by readyElements. + PriorityQueue> readyElements = new PriorityQueue>(); + for (InternalElement element : internalElements) { + if (element.predecessorCount == 0) { + readyElements.add(element); + } + } + + while (!readyElements.isEmpty()) { + InternalElement currentElement = readyElements.poll(); + sortedElements.add(currentElement); + for (InternalElement successor : currentElement.successors) { + successor.predecessorCount--; + if (successor.predecessorCount == 0) { + readyElements.add(successor); + } + } + } + + if (sortedElements.size() != elements.size()) { + List elementsInCycle = new ArrayList(); + for (InternalElement element : internalElements) { + if (element.predecessorCount > 0) { + elementsInCycle.add(element.element); + } + } + throw new CyclicalGraphException( + "Cyclical graphs can not be topologically sorted.", elementsInCycle); + } + + overwriteListWithSortedResults(elements, sortedElements); + } + + /** + * Internalizes the elements of the input list, representing the dependency structure to make + * topological sort easier to compute. + * + * @param elements the list to be sorted. + * @param order the partial ordering used to find the predecessors of each element in the list. + * @return a list of {@link InternalElement}s initialized with dependency structure. + */ + private static List> internalizeElements( + List elements, PartialOrdering order) { + List> internalElements = new ArrayList>(elements.size()); + // Subtle: due to the potential for duplicates in elements, we need to map every element to a + // list of the corresponding InternalElements. + Map>> internalElementsByValue = + new HashMap>>(elements.size()); + int index = 0; + for (T element : elements) { + InternalElement internalElement = new InternalElement(element, index); + internalElements.add(internalElement); + List> lst = internalElementsByValue.get(element); + if (lst == null) { + lst = new ArrayList>(); + internalElementsByValue.put(element, lst); + } + lst.add(internalElement); + index++; + } + + for (InternalElement internalElement : internalElements) { + for (T predecessor : order.getPredecessors(internalElement.element)) { + List> internalPredecessors = internalElementsByValue.get(predecessor); + if (internalPredecessors != null) { + for (InternalElement internalPredecessor : internalPredecessors) { + internalPredecessor.successors.add(internalElement); + internalElement.predecessorCount++; + } + } else { + // Subtle: we must leave the predecessor count incremented here to properly + // be able to report CyclicGraphExceptions. In this case, a predecessor was + // reported by the order relation, but the predecessor was not a member of + // elements. + internalElement.predecessorCount++; + } + } + } + return internalElements; + } + + /** + * To copy the sorted elements back into the input list. + * + * @param elements the list that is being sorted. + * @param sortedResults the internal list that holds the sorted results. + */ + private static void overwriteListWithSortedResults( + List elements, List> sortedResults) { + elements.clear(); + for (InternalElement internalElement : sortedResults) { + elements.add(internalElement.element); + } + } +} + diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/AbstractGeneratorRunner.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/AbstractGeneratorRunner.kt new file mode 100644 index 000000000000..3c034d904107 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/AbstractGeneratorRunner.kt @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.annotations.VisibleForTesting +import com.google.protobuf.DescriptorProtos +import com.google.protobuf.DescriptorProtos.* +import com.google.protobuf.Descriptors.FileDescriptor +import com.google.protobuf.compiler.PluginProtos +import com.squareup.kotlinpoet.FileSpec +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream +import java.nio.file.FileSystem +import java.nio.file.FileSystems +import java.nio.file.Files +import java.nio.file.Path + +/** Superclass for generators. */ +abstract class AbstractGeneratorRunner { + abstract fun generateCodeForFile(file: FileDescriptor): List + + @VisibleForTesting + fun mainAsProtocPlugin(input: InputStream, output: OutputStream) { + val generatorRequest = try { + input.buffered().use { + PluginProtos.CodeGeneratorRequest.parseFrom(it) + } + } catch (failure: Exception) { + throw IOException( + """ + Attempted to run proto extension generator as protoc plugin, but could not read + CodeGeneratorRequest. + """.trimIndent(), + failure + ) + } + output.buffered().use { + CodeGenerators.codeGeneratorResponse { + val descriptorMap = CodeGenerators.descriptorMap(generatorRequest.protoFileList) + generatorRequest.filesToGenerate + .map(descriptorMap::getValue) // compiled descriptors to generate code for + .flatMap(::generateCodeForFile) // generated extensions + }.writeTo(it) + } + } + + @VisibleForTesting + fun mainAsCommandLine(args: Array, fs: FileSystem) { + val dashIndex = args.indexOf("--") + val outputDir = fs.getPath(args[0]) + val toGenerateExtensionsFor = args.slice(1 until dashIndex) + val inTransitiveClosure = args.drop(dashIndex + 1) + + val fileNameToDescriptorSet = + inTransitiveClosure.associateWith { readFileDescriptorSet(fs.getPath(it)) } + + val descriptorMap = CodeGenerators.descriptorMapFromUnsorted( + fileNameToDescriptorSet.values.flatMap { it.fileList } + ) + + toGenerateExtensionsFor + .asSequence() + .map { fileNameToDescriptorSet.getValue(it) } // descriptor sets to output + .flatMap { it.fileList.asSequence() } // descriptors to output + .map { it.fileName } // file names of descriptors to output + .map { descriptorMap.getValue(it) } // compiled descriptors to generate code for + .flatMap { generateCodeForFile(it).asSequence() } // generated extensions + .forEach { it.writeTo(outputDir) } // write to output directory + } + + fun doMain(args: Array) { + if (args.isEmpty()) { + mainAsProtocPlugin(System.`in`, System.out) + } else { + mainAsCommandLine(args, FileSystems.getDefault()) + } + } + + private fun readFileDescriptorSet(path: Path): FileDescriptorSet = + Files.newInputStream(path).buffered().use { FileDescriptorSet.parseFrom(it) } +} \ No newline at end of file diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ClassSimpleName.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ClassSimpleName.kt new file mode 100644 index 000000000000..6f55461e6c0b --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ClassSimpleName.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.base.CaseFormat +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.TypeSpec + +/** + * Represents a simple (unqualified, unnested) name of a Kotlin/Java class, interface, or enum, + * in UpperCamelCase. + */ +data class ClassSimpleName(val name: String) : CharSequence by name { + /** Returns this class name with a suffix. */ + fun withSuffix(suffix: String): ClassSimpleName = ClassSimpleName(name + suffix) + + /** + * Returns a fully qualified class name as a peer of the specified class, with this simple name. + */ + fun asPeer(className: ClassName): ClassName = className.peerClass(name) + + /** Returns a name of a member based on this class name with a prefix. */ + fun asMemberWithPrefix(prefix: String): MemberSimpleName { + return if (prefix.isEmpty()) { + MemberSimpleName(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, name)) + } else { + MemberSimpleName(prefix + name) + } + } + + override fun toString() = name +} + +/** Create a builder for a class with the specified simple name. */ +fun TypeSpec.Companion.classBuilder( + simpleName: ClassSimpleName +): TypeSpec.Builder = classBuilder(simpleName.name) + +/** Create a builder for an object with the specified simple name. */ +fun TypeSpec.Companion.objectBuilder( + simpleName: ClassSimpleName +): TypeSpec.Builder = objectBuilder(simpleName.name) + +/** Given a fully qualified class name, get the fully qualified name of a nested class inside it. */ +fun ClassName.nestedClass(classSimpleName: ClassSimpleName): ClassName = + nestedClass(classSimpleName.name) diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/CodeGenerators.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/CodeGenerators.kt new file mode 100644 index 000000000000..07c40ad67dd7 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/CodeGenerators.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.base.Throwables +import com.google.common.graph.GraphBuilder +import com.google.common.graph.TopologicalSortGraph +import com.google.protobuf.DescriptorProtos.FileDescriptorProto +import com.google.protobuf.Descriptors.FileDescriptor +import com.google.protobuf.compiler.PluginProtos +import com.squareup.kotlinpoet.FileSpec + +internal object CodeGenerators { + fun descriptorMap( + topologicalSortedProtoFileList: List + ): Map { + val descriptorsByName = mutableMapOf() + for (protoFile in topologicalSortedProtoFileList) { + // we should have visited all the dependencies, so they should be present in the map + val dependencies = protoFile.dependencyNames.map(descriptorsByName::getValue) + + // build and link the descriptor for this file to its dependencies + val fileDescriptor = FileDescriptor.buildFrom(protoFile, dependencies.toTypedArray()) + + descriptorsByName[protoFile.fileName] = fileDescriptor + } + return descriptorsByName + } + + fun descriptorMapFromUnsorted( + protoFileList: List + ): Map { + val byFileName = protoFileList.associateBy { it.fileName } + + val depGraph = + GraphBuilder + .directed() + .expectedNodeCount(protoFileList.size) + .build() + + byFileName.keys.forEach { depGraph.addNode(it) } + + for ((fileName, fileDescriptorProto) in byFileName) { + for (dep in fileDescriptorProto.dependencyNames) { + depGraph.putEdge(dep, fileName) + } + } + + return descriptorMap( + TopologicalSortGraph.topologicalOrdering(depGraph) + .map { byFileName.getValue(it) } + ) + } + + fun toCodeGeneratorResponseFile(fileSpec: FileSpec): PluginProtos.CodeGeneratorResponse.File = + PluginProtos.CodeGeneratorResponse.File.newBuilder().also { + it.name = fileSpec.path.toString() + it.content = fileSpec.toString() + }.build() + + inline fun codeGeneratorResponse(build: () -> List): PluginProtos.CodeGeneratorResponse { + val builder = PluginProtos.CodeGeneratorResponse.newBuilder() + try { + builder.addAllFile(build().map { toCodeGeneratorResponseFile(it) }) + } catch (failure: Exception) { + builder.error = Throwables.getStackTraceAsString(failure) + } + return builder.build() + } +} diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ConstantName.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ConstantName.kt new file mode 100644 index 000000000000..6fed01aa07bd --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ConstantName.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.MemberName + +/** Represents a name of an constant, in UPPER_UNDERSCORE. */ +data class ConstantName(val name: String) : CharSequence by name { + override fun toString() = name +} + +/** Returns the fully qualified name of this constant, as a member of the specified class. */ +fun ClassName.member(constantName: ConstantName): MemberName = + MemberName(this, constantName.name) diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/Declarations.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/Declarations.kt new file mode 100644 index 000000000000..04a1ac6c4959 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/Declarations.kt @@ -0,0 +1,141 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec + +/** Create a set of [Declarations] in a DSL style. */ +inline fun declarations(callback: Declarations.Builder.() -> Unit): Declarations = + Declarations.Builder().apply(callback).build() + +/** + * An immutable set of declarations, some of which may be always at the top level, and some of which + * may be in some containing class or namespace (which may be a type or a file). + */ +class Declarations private constructor( + private val atTopLevel: List Unit>, + private val atEnclosing: List +) { + + private data class ForEnclosing( + val onFileSpec: FileSpec.Builder.() -> Unit, + val onTypeSpec: TypeSpec.Builder.() -> Unit + ) + + /** Whether or not there are any declarations designated for the top level. */ + val hasTopLevelDeclarations: Boolean + get() = atTopLevel.isNotEmpty() + + /** Whether or not there are any declarations designated for an enclosing scope. */ + val hasEnclosingScopeDeclarations: Boolean + get() = atEnclosing.isNotEmpty() + + /** + * Writes all declarations to the top level of the specified [FileSpec.Builder], using the top + * level file as an enclosing scope for those declarations. + */ + fun writeAllAtTopLevel(builder: FileSpec.Builder) { + writeOnlyTopLevel(builder) + atEnclosing.forEach { + val callback = it.onFileSpec + builder.callback() + } + } + + /** + * Write only the top-level declarations in these [Declarations] to the specified + * [FileSpec.Builder]. + */ + fun writeOnlyTopLevel(builder: FileSpec.Builder) { + atTopLevel.forEach { builder.it() } + } + + /** + * Write only the declarations for an enclosing scope in these [Declarations] to the specified + * [TypeSpec.Builder]. + */ + fun writeToEnclosingType(builder: TypeSpec.Builder) { + atEnclosing.forEach { + val callback = it.onTypeSpec + builder.callback() + } + } + + /** + * Write only the declarations for an enclosing scope in these [Declarations] to the specified + * [FileSpec.Builder]. + */ + fun writeToEnclosingFile(builder: FileSpec.Builder) { + atEnclosing.forEach { + val callback = it.onFileSpec + builder.callback() + } + } + + /** A builder for a set of [Declarations]. */ + class Builder { + private val atTopLevel: MutableList Unit> = mutableListOf() + private val atEnclosing: MutableList = mutableListOf() + + /** Declare a property at the top level. */ + fun addTopLevelProperty(property: PropertySpec) { + atTopLevel.add { addProperty(property) } + } + + /** Declare a function at the top level. */ + fun addTopLevelFunction(function: FunSpec) { + atTopLevel.add { addFunction(function) } + } + + /** Declare a type at the top level. */ + fun addTopLevelType(type: TypeSpec) { + atTopLevel.add { addType(type) } + } + + /** Declare a type in the enclosing scope. */ + fun addType(type: TypeSpec) { + atEnclosing.add(ForEnclosing({ addType(type) }, { addType(type) })) + } + + /** Declare a function in the enclosing scope. */ + fun addFunction(function: FunSpec) { + atEnclosing.add(ForEnclosing({ addFunction(function) }, { addFunction(function) })) + } + + /** Declare a property in the enclosing scope. */ + fun addProperty(property: PropertySpec) { + atEnclosing.add(ForEnclosing({ addProperty(property) }, { addProperty(property) })) + } + + /** Merge with another set of [Declarations]. */ + fun merge(other: Declarations) { + atTopLevel += other.atTopLevel + atEnclosing += other.atEnclosing + } + + /** Merge only the top-level declarations in the specified [Declarations]. */ + fun mergeTopLevelOnly(other: Declarations) { + atTopLevel += other.atTopLevel + } + + /** Build [Declarations]. */ + fun build() = Declarations(atTopLevel.toList(), atEnclosing.toList()) + } +} diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/DescriptorUtil.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/DescriptorUtil.kt new file mode 100644 index 000000000000..ee312e2f955d --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/DescriptorUtil.kt @@ -0,0 +1,141 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.base.Ascii +import com.google.common.base.CaseFormat.LOWER_UNDERSCORE +import com.google.common.base.CaseFormat.UPPER_CAMEL +import com.google.protobuf.DescriptorProtos.DescriptorProto +import com.google.protobuf.DescriptorProtos.EnumDescriptorProto +import com.google.protobuf.DescriptorProtos.FileDescriptorProto +import com.google.protobuf.Descriptors.Descriptor +import com.google.protobuf.Descriptors.EnumDescriptor +import com.google.protobuf.Descriptors.FieldDescriptor +import com.google.protobuf.Descriptors.FieldDescriptor.JavaType +import com.google.protobuf.Descriptors.FileDescriptor +import com.google.protobuf.Descriptors.OneofDescriptor +import com.google.protobuf.kotlin.protoc.TypeNames.BYTE_STRING +import com.google.protobuf.kotlin.protoc.TypeNames.STRING +import com.squareup.kotlinpoet.BOOLEAN +import com.squareup.kotlinpoet.DOUBLE +import com.squareup.kotlinpoet.FLOAT +import com.squareup.kotlinpoet.INT +import com.squareup.kotlinpoet.LONG +import com.squareup.kotlinpoet.TypeName + +private val JavaType.scalarType: TypeName + get() = when (this) { + JavaType.BOOLEAN -> BOOLEAN + JavaType.INT -> INT + JavaType.LONG -> LONG + JavaType.FLOAT -> FLOAT + JavaType.DOUBLE -> DOUBLE + JavaType.STRING -> STRING + JavaType.BYTE_STRING -> BYTE_STRING + else -> throw IllegalArgumentException("Not a scalar type") + } + +/** + * Returns the fully qualified Kotlin type representing values of this field, assuming that it + * is a scalar field (defined at https://developers.google.com/protocol-buffers/docs/proto3#scalar). + */ +val FieldDescriptor.scalarType: TypeName + get() = javaType.scalarType + +private val JavaType.isJavaPrimitive: Boolean + get() = when (this) { + JavaType.BOOLEAN, JavaType.INT, JavaType.LONG, JavaType.FLOAT, JavaType.DOUBLE -> true + else -> false + } + +/** True if the Java type representing the contents of this field is a primitive. */ +val FieldDescriptor.isJavaPrimitive: Boolean + get() = javaType.isJavaPrimitive + +/** A property name for this oneof, without prefixes or suffixes. */ +val OneofDescriptor.propertySimpleName: MemberSimpleName + get() = oneofName.propertySimpleName + +/** The simple name of the class representing the case of the oneof. */ +val OneofDescriptor.caseEnumSimpleName: ClassSimpleName + get() = oneofName.toClassSimpleNameWithSuffix("Case") + +/** The simple name of the property to get the case of the oneof. */ +val OneofDescriptor.casePropertySimpleName: MemberSimpleName + get() = propertySimpleName.withSuffix("Case") + +/** The name of the enum constant representing that this oneof is unset. */ +val OneofDescriptor.notSetCaseSimpleName: ConstantName + get() = ConstantName(Ascii.toUpperCase(name.filterNot { it == '_' }) + "_NOT_SET") + +/** + * Returns the simple name of the Java class that represents a message type, given its descriptor. + */ +val Descriptor.messageClassSimpleName: ClassSimpleName + get() = toProto().messageClassSimpleName + +/** + * Returns the simple name of the Java class that represents a message type, given its descriptor + * in proto form. + */ +val DescriptorProto.messageClassSimpleName: ClassSimpleName + get() = simpleName.toClassSimpleName() + +/** Returns the name of the Java class representing the proto enum type, given its descriptor. */ +val EnumDescriptor.enumClassSimpleName: ClassSimpleName + get() = toProto().enumClassSimpleName + +/** + * Returns the name of the Java class representing the proto enum type, given its descriptor + * in proto form. + */ +val EnumDescriptorProto.enumClassSimpleName: ClassSimpleName + get() = simpleName.toClassSimpleName() + +val FileDescriptor.outerClassSimpleName: ClassSimpleName + get() = toProto().outerClassSimpleName + +private val FileDescriptorProto.explicitOuterClassSimpleName: ClassSimpleName? + get() = when (val name = options.javaOuterClassname) { + "" -> null + else -> ClassSimpleName(name) + } + +/** The simple name of the outer class of a proto file. */ +val FileDescriptorProto.outerClassSimpleName: ClassSimpleName + get() { + explicitOuterClassSimpleName?.let { return it } + + val defaultOuterClassName = ClassSimpleName( + LOWER_UNDERSCORE.to(UPPER_CAMEL, fileName.name.replace("-", "_")) + ) + + val foundDuplicate = + enumTypeList.any { it.enumClassSimpleName == defaultOuterClassName } || + messageTypeList.any { it.anyHaveNameRecursive(defaultOuterClassName) } + + return if (foundDuplicate) { + defaultOuterClassName.withSuffix("OuterClass") + } else { + defaultOuterClassName + } + } + +private fun DescriptorProto.anyHaveNameRecursive(name: ClassSimpleName): Boolean = + messageClassSimpleName == name || + enumTypeList.any { it.enumClassSimpleName == name } || + nestedTypeList.any { it.anyHaveNameRecursive(name) } diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/GeneratorConfig.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/GeneratorConfig.kt new file mode 100644 index 000000000000..51f7d08e2a40 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/GeneratorConfig.kt @@ -0,0 +1,181 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.protobuf.Descriptors.Descriptor +import com.google.protobuf.Descriptors.EnumDescriptor +import com.google.protobuf.Descriptors.FieldDescriptor +import com.google.protobuf.Descriptors.FieldDescriptor.JavaType +import com.google.protobuf.Descriptors.FileDescriptor +import com.google.protobuf.Descriptors.OneofDescriptor +import com.squareup.kotlinpoet.BOOLEAN +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.MemberName +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeName + +/** + * Configuration for proto code generation, including settings on inlining and on the mapping + * between protos and Java packages. + */ +data class GeneratorConfig( + val javaPackagePolicy: JavaPackagePolicy, + val aggressiveInlining: Boolean +) { + + private val inlineModifiers: Array = + if (aggressiveInlining) arrayOf(KModifier.INLINE) else arrayOf() + + /** Generates a [FunSpec.Builder] with appropriate modifiers. */ + fun funSpecBuilder(name: MemberSimpleName): FunSpec.Builder = + FunSpec.builder(name).addModifiers(*inlineModifiers) + + /** Generates a [FunSpec.Builder] for a getter with appropriate modifiers. */ + fun getterBuilder(): FunSpec.Builder = + FunSpec.getterBuilder().addModifiers(*inlineModifiers) + + /** Generates a [FunSpec.Builder] for a setter with appropriate modifiers. */ + fun setterBuilder(): FunSpec.Builder = + FunSpec.setterBuilder().addModifiers(*inlineModifiers) + + /** Returns the package associated with Java APIs for protos in the specified file. */ + fun javaPackage(fileDescriptor: FileDescriptor): PackageScope = + javaPackagePolicy.javaPackage(fileDescriptor.toProto()) + + // Helpers on FileDescriptor. + + /** Returns the fully qualified name of the outer class generated for this proto file. */ + fun FileDescriptor.outerClass(): ClassName = javaPackage(this).nestedClass(outerClassSimpleName) + + // Helpers on EnumDescriptor. + + /** Returns the fully qualified name of the JVM enum type generated for this proto enum. */ + fun EnumDescriptor.enumClass(): ClassName { + val contType: Descriptor? = containingType + return when { + contType != null -> contType.messageClass().nestedClass(enumClassSimpleName) + file.options.javaMultipleFiles -> javaPackage(file).nestedClass(enumClassSimpleName) + else -> file.outerClass().nestedClass(enumClassSimpleName) + } + } + + // Helpers on Descriptor. + + /** Returns the fully qualified name of the JVM class generated for this message type. */ + fun Descriptor.messageClass(): ClassName { + val contType: Descriptor? = containingType + return when { + contType != null -> contType.messageClass().nestedClass(messageClassSimpleName) + file.options.javaMultipleFiles -> javaPackage(file).nestedClass(messageClassSimpleName) + else -> file.outerClass().nestedClass(messageClassSimpleName) + } + } + + /** + * Returns the fully qualified name of the JVM class for builders of messages of this type. + */ + fun Descriptor.builderClass(): ClassName = messageClass().nestedClass("Builder") + + /** + * Returns the fully qualified name of the JVM interface referring to messages or builders for + * this type. + */ + fun Descriptor.orBuilderClass(): ClassName = + messageClassSimpleName.withSuffix("OrBuilder").asPeer(messageClass()) + + // Helpers on field descriptors. + + private fun fieldType(fieldDescriptor: FieldDescriptor): TypeName = + when (fieldDescriptor.javaType!!) { + JavaType.MESSAGE -> fieldDescriptor.messageType.messageClass() + JavaType.ENUM -> fieldDescriptor.enumType.enumClass() + else -> fieldDescriptor.scalarType + } + + // Helpers on singleton fields. + + /** Returns the fully qualified Kotlin type of values of this field. */ + fun SingletonFieldDescriptor.type(): TypeName = fieldType(descriptor) + + /** + * Returns the Kotlin property for this field, inferred from the getter on the + * associated OrBuilder interface. + * + * Currently a [PropertySpec] to work around https://github.com/square/kotlinpoet/issues/667. + */ + fun SingletonFieldDescriptor.property(): PropertySpec = + PropertySpec.builder(simpleName = propertySimpleName, type = type()) + .receiver(containingType.orBuilderClass()) + .build() + + /** Returns the fully qualified hazzer method of this field, if it has one. */ + fun SingletonFieldDescriptor.hazzer(): FunSpec? = + if (hasHazzer()) { + FunSpec + .builder(simpleName = hazzerSimpleName) + .returns(BOOLEAN) + .receiver(containingType.orBuilderClass()) + .build() + } else { + null + } + + // Helpers on repeated fields. + + /** Returns the fully qualified Kotlin type of elements of this repeated field. */ + fun RepeatedFieldDescriptor.elementType(): TypeName = fieldType(descriptor) + + // Helpers on map fields. + + /** Returns the fully qualified Kotlin type of keys of this map field. */ + fun MapFieldDescriptor.keyType(): TypeName = keyFieldDescriptor.scalarType + + /** Returns the fully qualified Kotlin type of values of this map field. */ + fun MapFieldDescriptor.valueType(): TypeName = fieldType(valueFieldDescriptor) + + // Helpers on oneof fields. + + /** Returns the fully qualified type of the enum for the case of this oneof. */ + fun OneofDescriptor.caseEnum(): ClassName = + containingType.messageClass().nestedClass(caseEnumSimpleName) + + /** + * Returns the Kotlin property for the case of this oneof, inferred from the getter on the + * associated OrBuilder interface. + * + * (This is a [PropertySpec] to work around https://github.com/square/kotlinpoet/issues/667 .) + */ + fun OneofDescriptor.caseProperty(): PropertySpec = + PropertySpec.builder(simpleName = casePropertySimpleName, type = caseEnum()) + .receiver(containingType.orBuilderClass()) + .build() + + /** Returns the fully qualified enum constant for the "not set" case for this oneof. */ + fun OneofDescriptor.notSet(): MemberName = caseEnum().member(notSetCaseSimpleName) + + /** Returns the fully qualified enum constant for this case of the associated oneof. */ + fun OneofOptionFieldDescriptor.caseEnumMember(): MemberName = + oneof.caseEnum().member(enumConstantName) + + /** Returns the Kotlin function that clears this oneof in the builder. */ + fun OneofDescriptor.builderClear(): FunSpec = + FunSpec.builder(simpleName = propertySimpleName.withPrefix("clear")) + .receiver(containingType.builderClass()) + .build() +} diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicy.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicy.kt new file mode 100644 index 000000000000..ef1fd2d5c4c0 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicy.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.protobuf.DescriptorProtos.FileDescriptorProto + +/** + * Describes a policy for converting proto message types to Java classes in the correct package. + */ +enum class JavaPackagePolicy { + OPEN_SOURCE { + override fun javaPackage(fileProto: FileDescriptorProto): PackageScope { + return if (fileProto.options.hasJavaPackage()) { + PackageScope(fileProto.options.javaPackage) + } else { + PackageScope(fileProto.`package`) + } + } + }; + + abstract fun javaPackage(fileProto: FileDescriptorProto): PackageScope +} diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/KtPoetUtil.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/KtPoetUtil.kt new file mode 100644 index 000000000000..5c0fc767729f --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/KtPoetUtil.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.MemberName +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.asClassName +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.reflect.KClass + +fun ParameterSpec.Companion.of( + name: String, + type: TypeName, + vararg modifiers: KModifier +): ParameterSpec = ParameterSpec.builder(name, type, *modifiers).build() + +/** Create a fully qualified [MemberName] in this class with the specified name. */ +fun KClass<*>.member(memberName: String): MemberName = MemberName(asClassName(), memberName) + +private fun path(vararg component: String): Path = + Paths.get(component[0], *component.sliceArray(1 until component.size)) + +val FileSpec.path: Path + get() { + return if (packageName.isEmpty()) { + Paths.get("$name.kt") + } else { + path(*packageName.split('.').toTypedArray(), "$name.kt") + } + } diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/MemberSimpleName.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/MemberSimpleName.kt new file mode 100644 index 000000000000..9b9bd48980a4 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/MemberSimpleName.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.base.CaseFormat +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.MemberName +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeName +import kotlin.reflect.KClass + +/** + * Represents a simple (unqualified, unnested) name of a property or function, in lowerCamelCase. + * This helps us keep track of what names are in what format. + */ +data class MemberSimpleName(val name: String) : CharSequence by name { + companion object { + val OPERATOR_GET = MemberSimpleName("get") + val OPERATOR_SET = MemberSimpleName("set") + val OPERATOR_CONTAINS = MemberSimpleName("contains") + val OPERATOR_PLUS_ASSIGN = MemberSimpleName("plusAssign") + val OPERATOR_MINUS_ASSIGN = MemberSimpleName("minusAssign") + } + + fun withSuffix(suffix: String): MemberSimpleName = MemberSimpleName(name + suffix) + fun withPrefix(prefix: String): MemberSimpleName = + MemberSimpleName(prefix + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, name)) + + operator fun plus(other: MemberSimpleName): MemberSimpleName = + MemberSimpleName(name + CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, other.name)) + + override fun toString() = name +} + +/** Create a builder for a parameter with the specified simple name and type. */ +fun ParameterSpec.Companion.builder( + simpleName: MemberSimpleName, + type: TypeName, + vararg modifiers: KModifier +): ParameterSpec.Builder = builder(simpleName.name, type, *modifiers) + +/** Create a builder for a parameter with the specified simple name and type. */ +fun ParameterSpec.Companion.builder( + simpleName: MemberSimpleName, + type: KClass<*>, + vararg modifiers: KModifier +): ParameterSpec.Builder = builder(simpleName.name, type, *modifiers) + +/** Create a parameter with the specified simple name and type. */ +fun ParameterSpec.Companion.of( + simpleName: MemberSimpleName, + type: TypeName, + vararg modifiers: KModifier +): ParameterSpec = builder(simpleName, type, *modifiers).build() + +/** Create a parameter with the specified simple name and type. */ +fun ParameterSpec.Companion.of( + simpleName: MemberSimpleName, + type: KClass<*>, + vararg modifiers: KModifier +): ParameterSpec = builder(simpleName, type, *modifiers).build() + +/** Create a property with the specified simple name and type. */ +fun PropertySpec.Companion.of( + simpleName: MemberSimpleName, + type: KClass<*>, + vararg modifiers: KModifier +): PropertySpec = builder(simpleName.name, type, *modifiers).build() + +/** Create a property with the specified simple name and type. */ +fun PropertySpec.Companion.of( + simpleName: MemberSimpleName, + type: TypeName, + vararg modifiers: KModifier +): PropertySpec = builder(simpleName, type, *modifiers).build() + +/** Create a builder for a property with the specified simple name and type. */ +fun PropertySpec.Companion.builder( + simpleName: MemberSimpleName, + type: TypeName, + vararg modifiers: KModifier +): PropertySpec.Builder = builder(simpleName.name, type, *modifiers) + +/** Create a builder for a function with the specified simple name. */ +fun FunSpec.Companion.builder( + simpleName: MemberSimpleName +): FunSpec.Builder = builder(simpleName.name) + +/** Create a fully qualified [MemberName] in this class with the specified name. */ +fun ClassName.member(memberSimpleName: MemberSimpleName): MemberName = member(memberSimpleName.name) + +/** Create a fully qualified [MemberName] in this class with the specified name. */ +fun ClassName.member(memberName: String): MemberName = MemberName(this, memberName) diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/MessageComponent.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/MessageComponent.kt new file mode 100644 index 000000000000..9bab3796fb9d --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/MessageComponent.kt @@ -0,0 +1,200 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.protobuf.DescriptorProtos.FieldOptions +import com.google.protobuf.Descriptors.Descriptor +import com.google.protobuf.Descriptors.FieldDescriptor +import com.google.protobuf.Descriptors.FieldDescriptor.JavaType.MESSAGE +import com.google.protobuf.Descriptors.FileDescriptor.Syntax.PROTO2 +import com.google.protobuf.Descriptors.OneofDescriptor + +/** A part of a message definition that might have extensions generated for it. */ +sealed class MessageComponent { + abstract val containingType: Descriptor +} + +/** A oneof descriptor as a [MessageComponent]. */ +class OneofComponent(val descriptor: OneofDescriptor) : MessageComponent() { + override val containingType: Descriptor + get() = descriptor.containingType +} + +/** A field descriptor, narrowed to a category that might have specialized generation code. */ +sealed class SpecializedFieldDescriptor( + val descriptor: FieldDescriptor +) : MessageComponent() { + companion object { + private const val CLEAR_PREFIX = "clear" + } + + val fieldName: ProtoFieldName + get() = descriptor.fieldName + + /** The name of the field as a property, e.g. `myField`. */ + val propertySimpleName: MemberSimpleName + get() = fieldName.propertySimpleName + + val fieldOptions: FieldOptions + get() = descriptor.options + + val isDeprecated: Boolean + get() = fieldOptions.deprecated + + override val containingType: Descriptor + get() = descriptor.containingType + + val javaSimpleName: MemberSimpleName + get() = fieldName.javaSimpleName + + val clearerSimpleName: MemberSimpleName + get() = javaSimpleName.withPrefix(CLEAR_PREFIX) + + override fun toString(): String = descriptor.toString() +} + +/** Specializes a [FieldDescriptor] to a [SpecializedFieldDescriptor]. */ +fun FieldDescriptor.specialize(): SpecializedFieldDescriptor = when { + isMapField -> MapFieldDescriptor(this) + isRepeated -> RepeatedFieldDescriptor(this) + containingOneof != null -> OneofOptionFieldDescriptor(this) + else -> SingletonFieldDescriptor(this) +} + +/** Descriptor for a map field. */ +class MapFieldDescriptor( + descriptor: FieldDescriptor +) : SpecializedFieldDescriptor(descriptor) { + init { + check(descriptor.isMapField) { "Expected a map field but got $descriptor" } + } + + companion object { + private const val GET_MAP_PREFIX = "get" + private const val GET_MAP_SUFFIX = "Map" + private const val PUT_PREFIX = "put" + private const val REMOVE_PREFIX = "remove" + private const val PUT_ALL_PREFIX = "putAll" + } + + /** Descriptor for the map key. */ + val keyFieldDescriptor: FieldDescriptor + get() = + descriptor.messageType.findFieldByNumber(1) + ?: throw IllegalStateException("Could not find key field for map field $descriptor") + + /** Descriptor for the map value. */ + val valueFieldDescriptor: FieldDescriptor + get() = + descriptor.messageType.findFieldByNumber(2) + ?: throw IllegalStateException("Could not find value field for map field $descriptor") + + val mapGetterSimpleName: MemberSimpleName + get() = javaSimpleName.withPrefix(GET_MAP_PREFIX).withSuffix(GET_MAP_SUFFIX) + + val putterSimpleName: MemberSimpleName + get() = javaSimpleName.withPrefix(PUT_PREFIX) + + val removerSimpleName: MemberSimpleName + get() = javaSimpleName.withPrefix(REMOVE_PREFIX) + + val putAllSimpleName: MemberSimpleName + get() = javaSimpleName.withPrefix(PUT_ALL_PREFIX) +} + +/** Descriptor for a repeated field. */ +class RepeatedFieldDescriptor( + descriptor: FieldDescriptor +) : SpecializedFieldDescriptor(descriptor) { + init { + check(descriptor.isRepeated) { "Expected a repeated field but got $descriptor" } + check(!descriptor.isMapField) { "Expected a non-map field but got $descriptor" } + } + + companion object { + private const val GET_PREFIX = "get" + private const val GET_SUFFIX = "List" + private const val ADD_PREFIX = "add" + private const val ADD_ALL_PREFIX = "addAll" + private const val SET_PREFIX = "set" + } + + fun listGetterSimpleName(): MemberSimpleName = + javaSimpleName.withPrefix(GET_PREFIX).withSuffix(GET_SUFFIX) + + val adderSimpleName: MemberSimpleName + get() = javaSimpleName.withPrefix(ADD_PREFIX) + + val addAllSimpleName: MemberSimpleName + get() = javaSimpleName.withPrefix(ADD_ALL_PREFIX) + + val setterSimpleName: MemberSimpleName + get() = javaSimpleName.withPrefix(SET_PREFIX) +} + +/** Descriptor for a non-map, non-repeated field. */ +open class SingletonFieldDescriptor( + descriptor: FieldDescriptor +) : SpecializedFieldDescriptor(descriptor) { + init { + check(!descriptor.isMapField) { "Expected a non-map field but got $descriptor" } + check(!descriptor.isRepeated) { "Expected a non-repeated field but got $descriptor" } + } + + companion object { + private const val GET_PREFIX = "get" + private const val SET_PREFIX = "set" + private const val HAS_PREFIX = "has" + } + + /** True if the Java API for this field includes a hazzer. */ + fun hasHazzer(): Boolean = isMessage() || descriptor.file.syntax == PROTO2 + + /** True if the type of this field is another proto message. */ + fun isMessage(): Boolean = descriptor.javaType == MESSAGE + + /** True if this field is required. */ + fun isRequired(): Boolean = descriptor.isRequired + + val getterSimpleName: MemberSimpleName + get() = javaSimpleName.withPrefix(GET_PREFIX) + + val setterSimpleName: MemberSimpleName + get() = javaSimpleName.withPrefix(SET_PREFIX) + + val hazzerSimpleName: MemberSimpleName + get() = javaSimpleName.withPrefix(HAS_PREFIX) +} + +/** Descriptor for a field that is an option for a oneof. */ +class OneofOptionFieldDescriptor( + descriptor: FieldDescriptor +) : SingletonFieldDescriptor(descriptor) { + init { + check(descriptor.containingOneof != null) + } + + val oneof: OneofDescriptor + get() = descriptor.containingOneof + + val enumConstantName: ConstantName + get() = descriptor.fieldName.toEnumConstantName() +} + +/** Descriptors for the options of this oneof. */ +fun OneofDescriptor.options(): List = + fields.map(::OneofOptionFieldDescriptor) diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueName.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueName.kt new file mode 100644 index 000000000000..1d2cc12b973b --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueName.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.protobuf.Descriptors.EnumValueDescriptor + +/** Represents the unqualified name of a proto enum constant, in UPPER_UNDERSCORE. */ +data class ProtoEnumValueName(val name: String) : CharSequence by name { + val asConstantName: ConstantName + get() = ConstantName(name) + + override fun toString() = name +} + +/** Returns the name of a proto enum constant. */ +val EnumValueDescriptor.protoEnumValueName: ProtoEnumValueName + get() = ProtoEnumValueName(name) diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFieldName.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFieldName.kt new file mode 100644 index 000000000000..fe58651f4539 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFieldName.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.base.Ascii +import com.google.common.base.CaseFormat +import com.google.common.base.CharMatcher +import com.google.protobuf.Descriptors.FieldDescriptor +import com.google.protobuf.Descriptors.OneofDescriptor + +/** Represents the name of a proto field or oneof, which is in lower_underscore. */ +data class ProtoFieldName(private val name: String) : CharSequence by name { + companion object { + // based on compiler/java/internal/helpers.cc + private val SPECIAL_CASES = setOf( + ProtoFieldName("class"), + ProtoFieldName("cached_size"), + ProtoFieldName("serialized_size") + ) + + private val LETTER = CharMatcher.inRange('A', 'Z').or(CharMatcher.inRange('a', 'z')) + private val DIGIT = CharMatcher.inRange('0', '9') + private operator fun CharMatcher.contains(c: Char) = matches(c) + } + + private fun nameToCase(caseFormat: CaseFormat) = CaseFormat.LOWER_UNDERSCORE.to(caseFormat, name) + + val propertySimpleName: MemberSimpleName + get() { + val nameComponents = name.split('_') + val finalCamelCaseName = StringBuilder(name.length) + for (word in nameComponents) { + if (finalCamelCaseName.isEmpty()) { + finalCamelCaseName.append( + Ascii.toLowerCase(word[0]) + ).append(upperCaseAfterNumeric(word), 1, word.length) + } else { + finalCamelCaseName.append( + Ascii.toUpperCase(word[0]) + ).append(upperCaseAfterNumeric(word), 1, word.length) + } + } + return MemberSimpleName(finalCamelCaseName.toString()) + } + + private fun upperCaseAfterNumeric(input: String): String { + val result = StringBuilder(input.length) + var lastCharNum = false + for (char in input) { + if (lastCharNum && char in LETTER) { + result.append(Ascii.toUpperCase(char)) + } else { + result.append(char) + } + lastCharNum = char in DIGIT + } + + return result.toString() + } + + val javaSimpleName: MemberSimpleName + get() = if (this in SPECIAL_CASES) propertySimpleName.withSuffix("_") else propertySimpleName + + fun toClassSimpleNameWithSuffix(suffix: String): ClassSimpleName { + val finalCamelCaseName = StringBuilder(name.length + suffix.length) + for (word in name.split('_')) { + // uppercase the first letter of each word, and leave the rest of the case the same + finalCamelCaseName.append(Ascii.toUpperCase(word[0])).append(word, 1, word.length) + } + finalCamelCaseName.append(suffix) + return ClassSimpleName(finalCamelCaseName.toString()) + } + + fun toEnumConstantName(): ConstantName = ConstantName(Ascii.toUpperCase(name)) + + override fun toString() = name +} + +val FieldDescriptor.fieldName: ProtoFieldName + get() = ProtoFieldName(name) + +val OneofDescriptor.oneofName: ProtoFieldName + get() = ProtoFieldName(name) diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFileName.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFileName.kt new file mode 100644 index 000000000000..bf4001e88ace --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFileName.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.protobuf.DescriptorProtos.FileDescriptorProto +import com.google.protobuf.Descriptors.FileDescriptor +import com.google.protobuf.compiler.PluginProtos + +/** + * Represents the name of a proto file, relative to the root of the source tree, with + * lower_underscore naming. + */ +data class ProtoFileName(private val path: String) : Comparable { + val name: String + get() = path.substringAfterLast('/').removeSuffix(".proto") + + override operator fun compareTo(other: ProtoFileName): Int = path.compareTo(other.path) +} + +/** Returns the filename of the specified file descriptor in proto form. */ +val FileDescriptorProto.fileName: ProtoFileName + get() = ProtoFileName(name) + +/** Returns the filename of the specified file descriptor. */ +val FileDescriptor.fileName: ProtoFileName + get() = toProto().fileName + +val FileDescriptorProto.dependencyNames: List + get() = dependencyList.map(::ProtoFileName) + +val PluginProtos.CodeGeneratorRequest.filesToGenerate: List + get() = fileToGenerateList.map(::ProtoFileName) diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoMethodName.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoMethodName.kt new file mode 100644 index 000000000000..610992a56f73 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoMethodName.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.base.CaseFormat +import com.google.protobuf.DescriptorProtos.MethodDescriptorProto +import com.google.protobuf.Descriptors.MethodDescriptor + +/** Represents the unqualified name of an RPC method in a proto file, in UpperCamelCase. */ +data class ProtoMethodName(val name: String) : CharSequence by name { + fun toMemberSimpleName(): MemberSimpleName = + MemberSimpleName(CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, name)) + + override fun toString() = name +} + +val MethodDescriptor.methodName: ProtoMethodName + get() = toProto().methodName + +val MethodDescriptorProto.methodName: ProtoMethodName + get() = ProtoMethodName(name) diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoServiceName.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoServiceName.kt new file mode 100644 index 000000000000..a0a70351469c --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoServiceName.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.protobuf.DescriptorProtos.ServiceDescriptorProto +import com.google.protobuf.Descriptors.ServiceDescriptor + +/** Represents the unqualified name of an RPC service in a proto file, in CamelCase. */ +data class ProtoServiceName(val name: String) : CharSequence by name { + fun toClassSimpleName(): ClassSimpleName = ClassSimpleName(name) + + override fun toString() = name +} + +val ServiceDescriptor.serviceName: ProtoServiceName + get() = toProto().serviceName + +val ServiceDescriptorProto.serviceName: ProtoServiceName + get() = ProtoServiceName(name) diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleName.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleName.kt new file mode 100644 index 000000000000..a77ca1e388e7 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleName.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.protobuf.DescriptorProtos.DescriptorProto +import com.google.protobuf.DescriptorProtos.EnumDescriptorProto +import com.google.protobuf.Descriptors.Descriptor +import com.google.protobuf.Descriptors.EnumDescriptor + +/** Represents the unqualified name of a proto message or enum, which is in UpperCamelCase. */ +data class ProtoTypeSimpleName(val name: String) : CharSequence by name { + fun toClassSimpleName(): ClassSimpleName = ClassSimpleName(name) + + override fun toString() = name +} + +/** Returns the name of a message type, given its descriptor. */ +val Descriptor.simpleName: ProtoTypeSimpleName + get() = toProto().simpleName + +/** Returns the name of a message type, given its descriptor in proto form. */ +val DescriptorProto.simpleName: ProtoTypeSimpleName + get() = ProtoTypeSimpleName(name) + +/** Returns the name of a proto enum type, given its descriptor. */ +val EnumDescriptor.simpleName: ProtoTypeSimpleName + get() = toProto().simpleName + +/** Returns the name of a proto enum type, given its descriptor in proto form. */ +val EnumDescriptorProto.simpleName: ProtoTypeSimpleName + get() = ProtoTypeSimpleName(name) diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/Scope.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/Scope.kt new file mode 100644 index 000000000000..dae87629f990 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/Scope.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FileSpec + +/** + * Describes a location classes can be nested in, such as a package or another class. This can + * convert a [ClassSimpleName] to a fully qualified [ClassName]. + */ +sealed class Scope { + abstract fun nestedClass(simpleName: ClassSimpleName): ClassName + + fun nestedScope(simpleName: ClassSimpleName): Scope = ClassScope(nestedClass(simpleName)) +} + +/** + * The unqualified, top-level scope. + */ +object UnqualifiedScope : Scope() { + override fun nestedClass(simpleName: ClassSimpleName): ClassName = + ClassName("", simpleName.name) +} + +/** + * The scope of a package. + */ +data class PackageScope(val pkg: String) : Scope() { + override fun nestedClass(simpleName: ClassSimpleName): ClassName = + ClassName(pkg, simpleName.name) +} + +/** + * The scope of a fully qualified class. + */ +class ClassScope(private val className: ClassName) : Scope() { + override fun nestedClass(simpleName: ClassSimpleName): ClassName = + className.nestedClass(simpleName) +} + +/** + * Creates a [FileSpec.Builder] for a class with the specified simple name in the specified package. + */ +fun FileSpec.Companion.builder( + packageName: PackageScope, + simpleName: ClassSimpleName +): FileSpec.Builder = builder(packageName.pkg, simpleName.name) diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/TypeNames.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/TypeNames.kt new file mode 100644 index 000000000000..bee45b74a4c5 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/TypeNames.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.protobuf.ByteString +import com.squareup.kotlinpoet.asTypeName + +object TypeNames { + internal val ITERABLE = Iterable::class.asTypeName() + internal val PAIR = Pair::class.asTypeName() + internal val MAP = Map::class.asTypeName() + internal val STRING = String::class.asTypeName() + val BYTE_STRING = ByteString::class.asTypeName() +} diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubject.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubject.kt new file mode 100644 index 000000000000..ee4bfe22161e --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubject.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc.testing + +import com.google.common.truth.FailureMetadata +import com.google.common.truth.Subject +import com.google.common.truth.Truth.assertAbout +import com.google.protobuf.kotlin.protoc.Declarations +import com.squareup.kotlinpoet.FileSpec + +val declarationsSubjectFactory: Subject.Factory = + Subject.Factory(::DeclarationsSubject) + +/** Make Truth assertions about [declarations]. */ +fun assertThat(declarations: Declarations): DeclarationsSubject = + assertAbout(declarationsSubjectFactory).that(declarations) + +/** A Truth subject for [Declarations]. */ +class DeclarationsSubject( + failureMetadata: FailureMetadata, + private val actual: Declarations +) : Subject(failureMetadata, actual) { + fun generatesTopLevel(indentedCode: String) { + val actualCode = + FileSpec.builder("", "MyDeclarations.kt") + .apply { actual.writeOnlyTopLevel(this) } + .build() + check("topLevel").about(fileSpecs).that(actualCode).generates(indentedCode) + } + + fun generatesEnclosed(indentedCode: String) { + val actualCode = + FileSpec.builder("", "MyDeclarations.kt") + .apply { actual.writeToEnclosingFile(this) } + .build() + check("enclosed").about(fileSpecs).that(actualCode).generates(indentedCode) + } + + fun generatesNoTopLevelMembers() { + val actualCode = + FileSpec.builder("", "MyDeclarations.kt") + .apply { actual.writeOnlyTopLevel(this) } + .build() + check("topLevel") + .withMessage("top level declarations: %s", actualCode) + .that(actual.hasTopLevelDeclarations) + .isFalse() + } + + fun generatesNoEnclosedMembers() { + val actualCode = + FileSpec.builder("", "MyDeclarations.kt") + .apply { actual.writeToEnclosingFile(this) } + .build() + check("enclosed") + .withMessage("enclosed declarations: %s", actualCode) + .that(actual.hasEnclosingScopeDeclarations) + .isFalse() + } +} diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubject.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubject.kt new file mode 100644 index 000000000000..b03fd6313a38 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubject.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc.testing + +import com.google.common.truth.FailureMetadata +import com.google.common.truth.Subject +import com.google.common.truth.Truth.assertAbout +import com.squareup.kotlinpoet.FileSpec + +val fileSpecs: Subject.Factory = Subject.Factory(::FileSpecSubject) + +/** Make Truth assertions about [fileSpec]. */ +fun assertThat(fileSpec: FileSpec): FileSpecSubject = assertAbout(fileSpecs).that(fileSpec) + +/** A Truth subject for [FileSpec]. */ +class FileSpecSubject( + failureMetadata: FailureMetadata, + private val actual: FileSpec +) : Subject(failureMetadata, actual) { + fun generates(indentedCode: String) { + val expectedCode = indentedCode.trimIndent() + val actualCode = actual.toString().trim().lines().joinToString("\n") { it.trimEnd() } + check("code").that(actualCode).isEqualTo(expectedCode) + } + + fun hasName(name: String) { + check("name").that(actual.name).isEqualTo(name) + } +} diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubject.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubject.kt new file mode 100644 index 000000000000..33fd141e0072 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubject.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc.testing + +import com.google.common.truth.FailureMetadata +import com.google.common.truth.Subject +import com.google.common.truth.Truth +import com.squareup.kotlinpoet.FunSpec + +val funSpecs: Subject.Factory = Subject.Factory(::FunSpecSubject) + +/** Make Truth assertions about [funSpec]. */ +fun assertThat(funSpec: FunSpec): FunSpecSubject = Truth.assertAbout(funSpecs).that(funSpec) + +/** A Truth subject for [FunSpec]. */ +class FunSpecSubject( + failureMetadata: FailureMetadata, + private val actual: FunSpec +) : Subject(failureMetadata, actual) { + fun generates(indentedCode: String) { + val expectedCode = indentedCode.trimIndent() + val actualCode = actual.toString().trim() + check("code").that(actualCode).isEqualTo(expectedCode) + } +} diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubject.kt b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubject.kt new file mode 100644 index 000000000000..54958d7d5da1 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubject.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc.testing + +import com.google.common.truth.FailureMetadata +import com.google.common.truth.Subject +import com.google.common.truth.Truth.assertAbout +import com.squareup.kotlinpoet.TypeSpec + +val typeSpecs: Subject.Factory = Subject.Factory(::TypeSpecSubject) + +/** Make Truth assertions about [typeSpec]. */ +fun assertThat(typeSpec: TypeSpec): TypeSpecSubject = assertAbout(typeSpecs).that(typeSpec) + +/** A Truth subject for [TypeSpec]. */ +class TypeSpecSubject( + failureMetadata: FailureMetadata, + private val actual: TypeSpec +) : Subject(failureMetadata, actual) { + fun generates(indentedCode: String) { + val expectedCode = indentedCode.trimIndent() + val actualCode = actual.toString().trim() + check("code").that(actualCode).isEqualTo(expectedCode) + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ClassSimpleNameTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ClassSimpleNameTest.kt new file mode 100644 index 000000000000..b51825d36844 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ClassSimpleNameTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.truth.Truth.assertThat +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.TypeSpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [ClassSimpleName]. */ +@RunWith(JUnit4::class) +class ClassSimpleNameTest { + @Test + fun withSuffix() { + assertThat(ClassSimpleName("FooBar").withSuffix("Baz")) + .isEqualTo(ClassSimpleName("FooBarBaz")) + } + + @Test + fun asPeer() { + val className = ClassName(packageName = "com.google.protobuf", simpleNames = listOf("ByteString")) + val peerName = ClassSimpleName("Peer").asPeer(className) + assertThat(peerName.packageName).isEqualTo("com.google.protobuf") + assertThat(peerName.simpleName).isEqualTo("Peer") + } + + @Test + fun asMemberWithPrefix() { + val simpleName = ClassSimpleName("SimpleName") + + assertThat(simpleName.asMemberWithPrefix("get")) + .isEqualTo(MemberSimpleName("getSimpleName")) + + assertThat(simpleName.asMemberWithPrefix("")) + .isEqualTo(MemberSimpleName("simpleName")) + } + + @Test + fun classBuilder() { + val simpleName = ClassSimpleName("SimpleName") + assertThat(TypeSpec.classBuilder(simpleName).build()).generates("class SimpleName") + } + + @Test + fun objectBuilder() { + val simpleName = ClassSimpleName("SimpleName") + assertThat(TypeSpec.objectBuilder(simpleName).build()).generates("object SimpleName") + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ConstantNameTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ConstantNameTest.kt new file mode 100644 index 000000000000..fc775f727e2f --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ConstantNameTest.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.truth.Truth.assertThat +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.MemberName +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Test for [ConstantName]. */ +@RunWith(JUnit4::class) +class ConstantNameTest { + @Test + fun memberConstantName() { + val className = ClassName(packageName = "com.google.protobuf", simpleNames = listOf("ByteString")) + val memberName: MemberName = className.member(ConstantName("EMPTY")) + assertThat(memberName.canonicalName).isEqualTo("com.google.protobuf.ByteString.EMPTY") + assertThat(memberName.packageName).isEqualTo("com.google.protobuf") + assertThat(memberName.enclosingClassName!!.canonicalName) + .isEqualTo("com.google.protobuf.ByteString") + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/DeclarationsTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/DeclarationsTest.kt new file mode 100644 index 000000000000..b4a4f189ed7a --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/DeclarationsTest.kt @@ -0,0 +1,187 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.truth.Truth.assertThat +import com.google.protobuf.kotlin.protoc.testing.assertThat +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.INT +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [Declarations]. */ +@RunWith(JUnit4::class) +class DeclarationsTest { + private val property = PropertySpec.builder("someProperty", INT).build() + private val function = FunSpec.builder("someFunction").build() + private val type = TypeSpec.objectBuilder("MyObject").build() + + private inline fun someFile(block: FileSpec.Builder.() -> Unit): String { + return FileSpec + .builder("com.foo.bar", "SomeFile.kt") + .apply(block) + .build() + .toString() + .trim() + } + + @Test + fun writeAllAtTopLevel() { + val decls = declarations { + addTopLevelProperty(property) + addFunction(function) + } + assertThat( + someFile { + decls.writeAllAtTopLevel(this) + } + ).isEqualTo( + """ + package com.foo.bar + + import kotlin.Int + + val someProperty: Int + + fun someFunction() { + } + """.trimIndent() + ) + } + + @Test + fun writeOnlyTopLevel() { + val decls = declarations { + addTopLevelProperty(property) + addFunction(function) + } + assertThat( + someFile { + decls.writeOnlyTopLevel(this) + } + ).isEqualTo( + """ + package com.foo.bar + + import kotlin.Int + + val someProperty: Int + """.trimIndent() + ) + } + + private fun FileSpec.Builder.writeToSomeEnclosingObject(decls: Declarations) { + addType( + TypeSpec.objectBuilder("SomeObject") + .apply { decls.writeToEnclosingType(this) } + .build() + ) + } + + @Test + fun writeToEnclosingType() { + val decls = declarations { + addTopLevelProperty(property) + addFunction(function) + } + assertThat( + someFile { + writeToSomeEnclosingObject(decls) + } + ).isEqualTo( + """ + package com.foo.bar + + object SomeObject { + fun someFunction() { + } + } + """.trimIndent() + ) + } + + @Test + fun hasTopLevel() { + assertThat( + declarations { + addTopLevelProperty(property) + }.hasTopLevelDeclarations + ).isTrue() + assertThat( + declarations { + addProperty(property) + }.hasTopLevelDeclarations + ).isFalse() + } + + @Test + fun hasEnclosingScopeDeclarations() { + assertThat( + declarations { + addTopLevelProperty(property) + }.hasEnclosingScopeDeclarations + ).isFalse() + assertThat( + declarations { + addProperty(property) + }.hasEnclosingScopeDeclarations + ).isTrue() + } + + @Test + fun addTopLevelProperty() { + val decls = declarations { + addTopLevelProperty(property) + } + assertThat(decls).generatesTopLevel( + """ + import kotlin.Int + + val someProperty: Int + """ + ) + assertThat(decls).generatesNoEnclosedMembers() + } + + @Test + fun addTopLevelFunction() { + val decls = declarations { + addTopLevelFunction(function) + } + assertThat(decls) + .generatesTopLevel( + """ + fun someFunction() { + } + """ + ) + assertThat(decls).generatesNoEnclosedMembers() + } + + @Test + fun addTopLevelType() { + val decls = declarations { + addTopLevelType(type) + } + assertThat(decls).generatesTopLevel("object MyObject") + assertThat(decls).generatesNoEnclosedMembers() + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/DescriptorUtilTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/DescriptorUtilTest.kt new file mode 100644 index 000000000000..a4a0d5c996fb --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/DescriptorUtilTest.kt @@ -0,0 +1,157 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.truth.Truth.assertThat +import com.google.protobuf.kotlin.protoc.testproto.Example3 +import com.google.protobuf.kotlin.protoc.testproto.Example3.ExampleEnum +import com.google.protobuf.kotlin.protoc.testproto.Example3.ExampleMessage +import com.google.protobuf.kotlin.protoc.testproto.HasNestedClassNameConflictOuterClass +import com.google.protobuf.kotlin.protoc.testproto.HasOuterClassNameConflictOuterClass +import com.google.protobuf.kotlin.protoc.testproto.MyExplicitOuterClassName +import com.google.protos.testing.ProtoFileWithHyphen +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for the methods in DescriptorUtil.kt. */ +@RunWith(JUnit4::class) +class DescriptorUtilTest { + @Test + fun isJavaPrimitiveIntField() { + val int32Field = ExampleMessage.getDescriptor().findFieldByName("int32_field") + assertThat(int32Field.isJavaPrimitive).isTrue() + } + + @Test + fun isJavaPrimitiveStringField() { + val stringField = ExampleMessage.getDescriptor().findFieldByName("string_field") + assertThat(stringField.isJavaPrimitive).isFalse() + } + + @Test + fun isJavaPrimitiveMessageField() { + val subMessageField = ExampleMessage.getDescriptor().findFieldByName("sub_message_field") + assertThat(subMessageField.isJavaPrimitive).isFalse() + } + + @Test + fun scalarTypeIntField() { + val int32Field = ExampleMessage.getDescriptor().findFieldByName("int32_field") + assertThat(int32Field.scalarType.toString()).isEqualTo("kotlin.Int") + } + + @Test + fun scalarTypeStringField() { + val stringField = ExampleMessage.getDescriptor().findFieldByName("string_field") + assertThat(stringField.scalarType.toString()).isEqualTo("kotlin.String") + } + + @Test + fun scalarTypeSubMessageField() { + val subMessageField = + ExampleMessage.getDescriptor().findFieldByName("sub_message_field") + assertThrows(IllegalArgumentException::class.java) { + subMessageField.scalarType + } + } + + @Test + fun oneofPropertySimpleName() { + val oneofDescriptor = ExampleMessage.getDescriptor().oneofs.find { it.name == "my_oneof" }!! + assertThat(oneofDescriptor.propertySimpleName).isEqualTo(MemberSimpleName("myOneof")) + } + + @Test + fun oneofCaseEnumSimpleName() { + val oneofDescriptor = ExampleMessage.getDescriptor().oneofs.find { it.name == "my_oneof" }!! + assertThat(oneofDescriptor.caseEnumSimpleName) + .isEqualTo(ClassSimpleName(ExampleMessage.MyOneofCase::class.simpleName!!)) + } + + @Test + fun oneofCasePropertySimpleName() { + val oneofDescriptor = ExampleMessage.getDescriptor().oneofs.find { it.name == "my_oneof" }!! + + // demonstrate that the inferred property actually exists with this name + assertThat(ExampleMessage.getDefaultInstance().myOneofCase) + .isEqualTo(ExampleMessage.MyOneofCase.MYONEOF_NOT_SET) + + assertThat(oneofDescriptor.casePropertySimpleName).isEqualTo(MemberSimpleName("myOneofCase")) + } + + @Test + fun oneofNotSetCaseSimpleName() { + val oneofDescriptor = ExampleMessage.getDescriptor().oneofs.find { it.name == "my_oneof" }!! + assertThat(oneofDescriptor.notSetCaseSimpleName) + .isEqualTo(ConstantName(ExampleMessage.MyOneofCase.MYONEOF_NOT_SET.name)) + } + + @Test + fun messageClassSimpleName() { + val messageDescriptor = ExampleMessage.getDescriptor() + assertThat(messageDescriptor.messageClassSimpleName) + .isEqualTo(ClassSimpleName(ExampleMessage::class.simpleName!!)) + } + + @Test + fun enumClassSimpleName() { + val enumDescriptor = ExampleEnum.getDescriptor() + assertThat(enumDescriptor.enumClassSimpleName) + .isEqualTo(ClassSimpleName(ExampleEnum::class.simpleName!!)) + } + + @Test + fun outerClassSimpleName_simple() { + val fileDescriptor = Example3.getDescriptor() + assertThat(fileDescriptor.outerClassSimpleName) + .isEqualTo(ClassSimpleName(Example3::class.simpleName!!)) + } + + @Test + fun outerClassSimpleName_hasOuterClassNameConflict() { + val fileDescriptor = HasOuterClassNameConflictOuterClass.getDescriptor() + assertThat(fileDescriptor.fileName.name).isEqualTo("has_outer_class_name_conflict") + assertThat(fileDescriptor.outerClassSimpleName) + .isEqualTo(ClassSimpleName(HasOuterClassNameConflictOuterClass::class.simpleName!!)) + } + + @Test + fun outerClassSimpleName_hasHyphenInFileName() { + val fileDescriptor = ProtoFileWithHyphen.getDescriptor() + assertThat(fileDescriptor.fileName.name).isEqualTo("proto-file-with-hyphen") + assertThat(fileDescriptor.outerClassSimpleName) + .isEqualTo(ClassSimpleName(ProtoFileWithHyphen::class.simpleName!!)) + } + + @Test + fun outerClassSimpleName_hasNestedClassNameConflict() { + val fileDescriptor = HasNestedClassNameConflictOuterClass.getDescriptor() + assertThat(fileDescriptor.fileName.name).isEqualTo("has_nested_class_name_conflict") + assertThat(fileDescriptor.outerClassSimpleName) + .isEqualTo(ClassSimpleName(HasNestedClassNameConflictOuterClass::class.simpleName!!)) + } + + @Test + fun outerClassSimpleName_hasExplicitOuterClassName() { + val fileDescriptor = MyExplicitOuterClassName.getDescriptor() + assertThat(fileDescriptor.fileName.name).isEqualTo("has_explicit_outer_class_name") + assertThat(fileDescriptor.outerClassSimpleName) + .isEqualTo(ClassSimpleName(MyExplicitOuterClassName::class.simpleName!!)) + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/GeneratorConfigTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/GeneratorConfigTest.kt new file mode 100644 index 000000000000..d8475387fcfe --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/GeneratorConfigTest.kt @@ -0,0 +1,383 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.truth.Truth.assertThat +import com.google.protobuf.Descriptors.FileDescriptor +import com.google.protobuf.kotlin.protoc.testproto.Example3 +import com.google.protobuf.kotlin.protoc.testproto.HasOuterClassNameConflictOuterClass +import com.google.protobuf.kotlin.protoc.testproto.MyExplicitOuterClassName +import com.squareup.kotlinpoet.BOOLEAN +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.INT +import com.squareup.kotlinpoet.MemberName.Companion.member +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.asClassName +import com.squareup.kotlinpoet.asTypeName +import kotlin.reflect.KClass +import kotlin.reflect.full.memberFunctions +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import testing.ImplicitJavaPackage + +/** Tests for [GeneratorConfig]. */ +@RunWith(JUnit4::class) +class GeneratorConfigTest { + private val javaPackagePolicy = JavaPackagePolicy.OPEN_SOURCE + private val defaultConfig = GeneratorConfig(javaPackagePolicy, false) + + private fun generateFile(block: Declarations.Builder.() -> Unit): String { + return FileSpec.builder("com.google", "FooBar.kt") + .apply { + declarations(block).writeAllAtTopLevel(this) + } + .build() + .toString() + .trim() + } + + @Test + fun funSpecBuilder() { + with(GeneratorConfig(javaPackagePolicy, aggressiveInlining = false)) { + assertThat( + generateFile { + addFunction(funSpecBuilder(MemberSimpleName("fooBar")).build()) + } + ).isEqualTo( + """ + package com.google + + fun fooBar() { + } + """.trimIndent() + ) + } + with(GeneratorConfig(javaPackagePolicy, aggressiveInlining = true)) { + assertThat( + generateFile { + addFunction(funSpecBuilder(MemberSimpleName("fooBar")).build()) + } + ).isEqualTo( + """ + package com.google + + inline fun fooBar() { + } + """.trimIndent() + ) + } + } + + @Test + fun getterBuilder() { + with(GeneratorConfig(javaPackagePolicy, aggressiveInlining = false)) { + assertThat( + generateFile { + addProperty( + PropertySpec.builder("someProp", INT) + .getter(getterBuilder().addStatement("return 1").build()) + .build() + ) + } + ).isEqualTo( + """ + package com.google + + import kotlin.Int + + val someProp: Int + get() = 1 + """.trimIndent() + ) + } + + with(GeneratorConfig(javaPackagePolicy, aggressiveInlining = true)) { + assertThat( + generateFile { + addProperty( + PropertySpec.builder("someProp", INT) + .getter(getterBuilder().addStatement("return 1").build()) + .build() + ) + } + ).isEqualTo( + """ + package com.google + + import kotlin.Int + + val someProp: Int + inline get() = 1 + """.trimIndent() + ) + } + } + + @Test + fun setterBuilder() { + val param = ParameterSpec.builder("newValue", INT).build() + with(GeneratorConfig(javaPackagePolicy, aggressiveInlining = false)) { + assertThat( + generateFile { + addProperty( + PropertySpec.builder("someProp", INT) + .mutable(true) + .setter(setterBuilder().addParameter(param).build()) + .build() + ) + } + ).isEqualTo( + """ + package com.google + + import kotlin.Int + + var someProp: Int + set(newValue) { + } + """.trimIndent() + ) + } + + with(GeneratorConfig(javaPackagePolicy, aggressiveInlining = true)) { + assertThat( + generateFile { + addProperty( + PropertySpec.builder("someProp", INT) + .mutable(true) + .setter(setterBuilder().addParameter(param).build()) + .build() + ) + } + ).isEqualTo( + """ + package com.google + + import kotlin.Int + + var someProp: Int + inline set(newValue) { + } + """.trimIndent() + ) + } + } + + private val fileDescriptorToTopLevelClass: List>> = + listOf( + Example3.getDescriptor() to Example3::class, + MyExplicitOuterClassName.getDescriptor() to MyExplicitOuterClassName::class, + HasOuterClassNameConflictOuterClass.getDescriptor() to + HasOuterClassNameConflictOuterClass::class, + ImplicitJavaPackage.getDescriptor() to ImplicitJavaPackage::class + ) + + @Test + fun javaPackage() { + with(defaultConfig) { + for ((descriptor, clazz) in fileDescriptorToTopLevelClass) { + assertThat(javaPackage(descriptor)).isEqualTo(PackageScope(clazz.java.`package`.name)) + } + } + } + + @Test + fun outerClass() { + with(defaultConfig) { + for ((descriptor, clazz) in fileDescriptorToTopLevelClass) { + assertThat(descriptor.outerClass()).isEqualTo(clazz.asClassName()) + } + } + } + + @Test + fun enumClass() { + with(defaultConfig) { + assertThat(Example3.ExampleEnum.getDescriptor().enumClass()) + .isEqualTo(Example3.ExampleEnum::class.asClassName()) + } + } + + @Test + fun messageClass() { + with(defaultConfig) { + assertThat(Example3.ExampleMessage.getDescriptor().messageClass()) + .isEqualTo(Example3.ExampleMessage::class.asClassName()) + } + } + + @Test + fun builderClass() { + with(defaultConfig) { + assertThat(Example3.ExampleMessage.getDescriptor().builderClass()) + .isEqualTo(Example3.ExampleMessage.Builder::class.asClassName()) + } + } + + @Test + fun orBuilderClass() { + with(defaultConfig) { + assertThat(Example3.ExampleMessage.getDescriptor().orBuilderClass()) + .isEqualTo(Example3.ExampleMessageOrBuilder::class.asClassName()) + } + } + + @Test + fun singletonFieldTypeScalar() { + val fieldDescriptor = Example3.ExampleMessage.getDescriptor().findFieldByNumber( + Example3.ExampleMessage.INT32_FIELD_FIELD_NUMBER + ).specialize() as SingletonFieldDescriptor + with(defaultConfig) { + assertThat(fieldDescriptor.type()).isEqualTo(Int::class.asClassName()) + } + } + + @Test + fun singletonFieldTypeMessage() { + val fieldDescriptor = Example3.ExampleMessage.getDescriptor().findFieldByNumber( + Example3.ExampleMessage.SUB_MESSAGE_FIELD_FIELD_NUMBER + ).specialize() as SingletonFieldDescriptor + with(defaultConfig) { + assertThat(fieldDescriptor.type()) + .isEqualTo(Example3.ExampleMessage.SubMessage::class.asClassName()) + } + } + + @Test + fun singletonFieldProperty() { + val fieldDescriptor = Example3.ExampleMessage.getDescriptor().findFieldByNumber( + Example3.ExampleMessage.INT32_FIELD_FIELD_NUMBER + ).specialize() as SingletonFieldDescriptor + with(defaultConfig) { + // demonstrate the property with the name + assertThat(Example3.ExampleMessage.getDefaultInstance().int32Field).isEqualTo(0) + assertThat(fieldDescriptor.property().name).isEqualTo("int32Field") + assertThat(fieldDescriptor.property().receiverType) + .isEqualTo(Example3.ExampleMessageOrBuilder::class.asClassName()) + assertThat(fieldDescriptor.property().type).isEqualTo(INT) + } + } + + @Test + fun singletonFieldHazzerAbsent() { + val fieldDescriptor = Example3.ExampleMessage.getDescriptor().findFieldByNumber( + Example3.ExampleMessage.INT32_FIELD_FIELD_NUMBER + ).specialize() as SingletonFieldDescriptor + with(defaultConfig) { + assertThat(fieldDescriptor.hazzer()).isNull() + assertThat(Example3.ExampleMessage::class.memberFunctions.find { it.name == "hasInt32Field" }) + .isNull() + } + } + + @Test + fun singletonFieldHazzerPresent() { + val fieldDescriptor = Example3.ExampleMessage.getDescriptor().findFieldByNumber( + Example3.ExampleMessage.SUB_MESSAGE_FIELD_FIELD_NUMBER + ).specialize() as SingletonFieldDescriptor + with(defaultConfig) { + // demonstrate the hazzer with the correct name + assertThat(Example3.ExampleMessage.getDefaultInstance().hasSubMessageField()).isFalse() + assertThat(fieldDescriptor.hazzer()!!.name).isEqualTo("hasSubMessageField") + assertThat(fieldDescriptor.hazzer()!!.receiverType) + .isEqualTo(Example3.ExampleMessageOrBuilder::class.asTypeName()) + assertThat(fieldDescriptor.hazzer()!!.returnType).isEqualTo(BOOLEAN) + } + } + + @Test + fun repeatedFieldElementType() { + val fieldDescriptor = Example3.ExampleMessage.getDescriptor().findFieldByNumber( + Example3.ExampleMessage.REPEATED_STRING_FIELD_FIELD_NUMBER + ).specialize() as RepeatedFieldDescriptor + with(defaultConfig) { + assertThat(fieldDescriptor.elementType()).isEqualTo(String::class.asClassName()) + } + } + + @Test + fun mapFieldKeyType() { + val fieldDescriptor = Example3.ExampleMessage.getDescriptor().findFieldByNumber( + Example3.ExampleMessage.MAP_FIELD_FIELD_NUMBER + ).specialize() as MapFieldDescriptor + with(defaultConfig) { + assertThat(fieldDescriptor.keyType()).isEqualTo(Long::class.asClassName()) + } + } + + @Test + fun mapFieldValueType() { + val fieldDescriptor = Example3.ExampleMessage.getDescriptor().findFieldByNumber( + Example3.ExampleMessage.MAP_FIELD_FIELD_NUMBER + ).specialize() as MapFieldDescriptor + with(defaultConfig) { + assertThat(fieldDescriptor.valueType()) + .isEqualTo(Example3.ExampleMessage.SubMessage::class.asClassName()) + } + } + + @Test + fun oneofCaseEnum() { + val oneofDescriptor = Example3.ExampleMessage.getDescriptor().oneofs.single() + with(defaultConfig) { + assertThat(oneofDescriptor.caseEnum()) + .isEqualTo(Example3.ExampleMessage.MyOneofCase::class.asClassName()) + } + } + + @Test + fun oneofCaseProperty() { + val oneofDescriptor = Example3.ExampleMessage.getDescriptor().oneofs.single() + with(defaultConfig) { + assertThat(Example3.ExampleMessage.getDefaultInstance().myOneofCase) + .isEqualTo(Example3.ExampleMessage.MyOneofCase.MYONEOF_NOT_SET) + assertThat(oneofDescriptor.caseProperty().name).isEqualTo("myOneofCase") + assertThat(oneofDescriptor.caseProperty().type) + .isEqualTo(Example3.ExampleMessage.MyOneofCase::class.asTypeName()) + assertThat(oneofDescriptor.caseProperty().receiverType) + .isEqualTo(Example3.ExampleMessageOrBuilder::class.asTypeName()) + } + } + + @Test + fun oneofNotSet() { + val oneofDescriptor = Example3.ExampleMessage.getDescriptor().oneofs.single() + with(defaultConfig) { + assertThat(oneofDescriptor.notSet()) + .isEqualTo( + Example3.ExampleMessage.MyOneofCase::class + .member(Example3.ExampleMessage.MyOneofCase.MYONEOF_NOT_SET.name) + ) + } + } + + @Test + fun oneofOptionCaseEnum() { + val fieldDescriptor = Example3.ExampleMessage.getDescriptor().findFieldByNumber( + Example3.ExampleMessage.STRING_ONEOF_OPTION_FIELD_NUMBER + ).specialize() as OneofOptionFieldDescriptor + with(defaultConfig) { + assertThat(fieldDescriptor.caseEnumMember()) + .isEqualTo( + Example3.ExampleMessage.MyOneofCase::class + .member(Example3.ExampleMessage.MyOneofCase.STRING_ONEOF_OPTION.name) + ) + } + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicyTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicyTest.kt new file mode 100644 index 000000000000..e420603ed379 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicyTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.truth.Truth.assertThat +import com.google.protobuf.kotlin.protoc.testproto.Example3 +import com.google.protos.testing.proto2api.ProtoApi1ExplicitPackage +import testing.ImplicitJavaPackage +import testing.ProtoApi1ImplicitPackage +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [JavaPackagePolicy]. */ +@RunWith(JUnit4::class) +class JavaPackagePolicyTest { + @Test + fun explicitJavaPackageGoogle() { + with(JavaPackagePolicy.OPEN_SOURCE) { + assertThat(javaPackage(Example3.getDescriptor().toProto())) + .isEqualTo(PackageScope(Example3::class.java.`package`.name)) + } + } + + @Test + fun explicitJavaPackageExternal() { + with(JavaPackagePolicy.OPEN_SOURCE) { + assertThat(javaPackage(Example3.getDescriptor().toProto())) + .isEqualTo(PackageScope(Example3::class.java.`package`.name)) + } + } + + @Test + fun implicitJavaPackageGoogle() { + with(JavaPackagePolicy.OPEN_SOURCE) { + assertThat(javaPackage(ImplicitJavaPackage.getDescriptor().toProto())) + .isEqualTo( + PackageScope("testing") + ) + } + } + + @Test + fun implicitJavaPackageExternal() { + with(JavaPackagePolicy.OPEN_SOURCE) { + assertThat(javaPackage(ImplicitJavaPackage.getDescriptor().toProto())) + .isEqualTo(PackageScope("testing")) + } + } + + @Test + fun protoApi1ExplicitPackage() { + with(JavaPackagePolicy.OPEN_SOURCE) { + assertThat(javaPackage(ProtoApi1ExplicitPackage.getDescriptor().toProto())) + .isEqualTo(PackageScope(ProtoApi1ExplicitPackage::class.java.`package`.name)) + } + } + + @Test + fun protoApi1ImplicitPackage() { + with(JavaPackagePolicy.OPEN_SOURCE) { + assertThat(javaPackage(ProtoApi1ImplicitPackage.getDescriptor().toProto())) + .isEqualTo(PackageScope(ProtoApi1ImplicitPackage::class.java.`package`.name)) + } + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/MemberSimpleNameTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/MemberSimpleNameTest.kt new file mode 100644 index 000000000000..db58bb422b90 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/MemberSimpleNameTest.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [MemberSimpleName]. */ +@RunWith(JUnit4::class) +class MemberSimpleNameTest { + @Test + fun withPrefix() { + assertThat(MemberSimpleName("myField").withPrefix("get")) + .isEqualTo(MemberSimpleName("getMyField")) + assertThat(MemberSimpleName("field").withPrefix("get")) + .isEqualTo(MemberSimpleName("getField")) + } + + @Test + fun withSuffix() { + assertThat(MemberSimpleName("myField").withSuffix("List")) + .isEqualTo(MemberSimpleName("myFieldList")) + assertThat(MemberSimpleName("field").withSuffix("List")) + .isEqualTo(MemberSimpleName("fieldList")) + } + + @Test + fun plus() { + assertThat(MemberSimpleName("putAll") + MemberSimpleName("myField")) + .isEqualTo(MemberSimpleName("putAllMyField")) + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/MessageComponentTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/MessageComponentTest.kt new file mode 100644 index 000000000000..440176c5b969 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/MessageComponentTest.kt @@ -0,0 +1,173 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.truth.Truth.assertThat +import com.google.protobuf.Descriptors.FieldDescriptor +import com.google.protobuf.kotlin.protoc.testproto.Example2.ExampleProto2Message +import com.google.protobuf.kotlin.protoc.testproto.Example3 +import com.google.protobuf.kotlin.protoc.testproto.Example3.ExampleMessage +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [MessageComponent] and subtypes. */ +@RunWith(JUnit4::class) +class MessageComponentTest { + @Test + fun singletonFieldProto3Scalar() { + val fieldDescriptor = + Example3.ExampleMessage.getDescriptor().findFieldByNumber(ExampleMessage.INT32_FIELD_FIELD_NUMBER) + val singletonDescriptor = fieldDescriptor.specialize() as SingletonFieldDescriptor + with(singletonDescriptor) { + assertThat(fieldName).isEqualTo(ProtoFieldName("int32_field")) + assertThat(propertySimpleName).isEqualTo(MemberSimpleName("int32Field")) + assertThat(isRequired()).isFalse() + assertThat(hasHazzer()).isFalse() + assertThat(isMessage()).isFalse() + } + } + + @Test + fun singletonFieldProto3Message() { + val fieldDescriptor = + ExampleMessage.getDescriptor() + .findFieldByNumber(ExampleMessage.SUB_MESSAGE_FIELD_FIELD_NUMBER) + val singletonDescriptor = fieldDescriptor.specialize() as SingletonFieldDescriptor + with(singletonDescriptor) { + assertThat(fieldName).isEqualTo(ProtoFieldName("sub_message_field")) + assertThat(propertySimpleName).isEqualTo(MemberSimpleName("subMessageField")) + assertThat(isRequired()).isFalse() + assertThat(hasHazzer()).isTrue() + assertThat(isMessage()).isTrue() + } + } + + @Test + fun singletonFieldProto2Required() { + val fieldDescriptor = + ExampleProto2Message.getDescriptor() + .findFieldByNumber(ExampleProto2Message.REQUIRED_STRING_FIELD_FIELD_NUMBER) + val singletonDescriptor = fieldDescriptor.specialize() as SingletonFieldDescriptor + with(singletonDescriptor) { + assertThat(fieldName).isEqualTo(ProtoFieldName("required_string_field")) + assertThat(propertySimpleName).isEqualTo(MemberSimpleName("requiredStringField")) + assertThat(isRequired()).isTrue() + assertThat(hasHazzer()).isTrue() + assertThat(isMessage()).isFalse() + } + } + @Test + fun singletonFieldProto2OptionalScalar() { + val fieldDescriptor = + ExampleProto2Message.getDescriptor() + .findFieldByNumber(ExampleProto2Message.OPTIONAL_INT32_FIELD_FIELD_NUMBER) + val singletonDescriptor = fieldDescriptor.specialize() as SingletonFieldDescriptor + with(singletonDescriptor) { + assertThat(fieldName).isEqualTo(ProtoFieldName("optional_int32_field")) + assertThat(propertySimpleName).isEqualTo(MemberSimpleName("optionalInt32Field")) + assertThat(isRequired()).isFalse() + assertThat(hasHazzer()).isTrue() + assertThat(isMessage()).isFalse() + } + } + + @Test + fun mapField() { + val fieldDescriptor = + ExampleMessage.getDescriptor().findFieldByNumber(ExampleMessage.MAP_FIELD_FIELD_NUMBER) + val mapDescriptor = fieldDescriptor.specialize() as MapFieldDescriptor + with(mapDescriptor) { + assertThat(fieldName).isEqualTo(ProtoFieldName("map_field")) + assertThat(propertySimpleName).isEqualTo(MemberSimpleName("mapField")) + assertThat(keyFieldDescriptor.type).isEqualTo(FieldDescriptor.Type.INT64) + assertThat(valueFieldDescriptor.type).isEqualTo(FieldDescriptor.Type.MESSAGE) + } + } + + @Test + fun repeatedField() { + val fieldDescriptor = + ExampleMessage.getDescriptor() + .findFieldByNumber(ExampleMessage.REPEATED_STRING_FIELD_FIELD_NUMBER) + assertThat(fieldDescriptor.specialize()).isInstanceOf(RepeatedFieldDescriptor::class.java) + } + + @Test + fun oneofField() { + val fieldDescriptor = + ExampleMessage.getDescriptor() + .findFieldByNumber(ExampleMessage.STRING_ONEOF_OPTION_FIELD_NUMBER) + val oneofOptionDescriptor = fieldDescriptor.specialize() as OneofOptionFieldDescriptor + with(oneofOptionDescriptor) { + assertThat(oneof.oneofName).isEqualTo(ProtoFieldName("my_oneof")) + assertThat(enumConstantName) + .isEqualTo(ConstantName(ExampleMessage.MyOneofCase.STRING_ONEOF_OPTION.name)) + assertThat(propertySimpleName).isEqualTo(MemberSimpleName("stringOneofOption")) + assertThat(hasHazzer()).isFalse() + assertThat(isMessage()).isFalse() + assertThat(isRequired()).isFalse() + } + } + + @Test + fun singletonFieldReservedWord() { + val fieldDescriptor = + ExampleMessage.getDescriptor().findFieldByNumber(ExampleMessage.CLASS_FIELD_NUMBER) + val singletonFieldDescriptor = fieldDescriptor.specialize() as SingletonFieldDescriptor + with(singletonFieldDescriptor) { + assertThat(propertySimpleName).isEqualTo(MemberSimpleName("class")) + assertThat(javaSimpleName).isEqualTo(MemberSimpleName("class_")) + assertThat(getterSimpleName).isEqualTo(MemberSimpleName("getClass_")) + assertThat(setterSimpleName).isEqualTo(MemberSimpleName("setClass_")) + assertThat(clearerSimpleName).isEqualTo(MemberSimpleName("clearClass_")) + assertThat(hazzerSimpleName).isEqualTo(MemberSimpleName("hasClass_")) + } + } + + @Test + fun repeatedFieldReservedWord() { + val fieldDescriptor = + ExampleMessage.getDescriptor().findFieldByNumber(ExampleMessage.CACHED_SIZE_FIELD_NUMBER) + val repeatedFieldDescriptor = fieldDescriptor.specialize() as RepeatedFieldDescriptor + with(repeatedFieldDescriptor) { + assertThat(propertySimpleName).isEqualTo(MemberSimpleName("cachedSize")) + assertThat(javaSimpleName).isEqualTo(MemberSimpleName("cachedSize_")) + assertThat(listGetterSimpleName()).isEqualTo(MemberSimpleName("getCachedSize_List")) + assertThat(setterSimpleName).isEqualTo(MemberSimpleName("setCachedSize_")) + assertThat(adderSimpleName).isEqualTo(MemberSimpleName("addCachedSize_")) + assertThat(addAllSimpleName).isEqualTo(MemberSimpleName("addAllCachedSize_")) + assertThat(clearerSimpleName).isEqualTo(MemberSimpleName("clearCachedSize_")) + } + } + + @Test + fun mapFieldReservedWord() { + val fieldDescriptor = + ExampleMessage.getDescriptor().findFieldByNumber(ExampleMessage.SERIALIZED_SIZE_FIELD_NUMBER) + val mapFieldDescriptor = fieldDescriptor.specialize() as MapFieldDescriptor + with(mapFieldDescriptor) { + assertThat(propertySimpleName).isEqualTo(MemberSimpleName("serializedSize")) + assertThat(javaSimpleName).isEqualTo(MemberSimpleName("serializedSize_")) + assertThat(mapGetterSimpleName).isEqualTo(MemberSimpleName("getSerializedSize_Map")) + assertThat(putterSimpleName).isEqualTo(MemberSimpleName("putSerializedSize_")) + assertThat(putAllSimpleName).isEqualTo(MemberSimpleName("putAllSerializedSize_")) + assertThat(removerSimpleName).isEqualTo(MemberSimpleName("removeSerializedSize_")) + assertThat(clearerSimpleName).isEqualTo(MemberSimpleName("clearSerializedSize_")) + } + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueNameTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueNameTest.kt new file mode 100644 index 000000000000..0afcda0d3631 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueNameTest.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.truth.Truth.assertThat +import com.google.protobuf.kotlin.protoc.testproto.Example3 +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [ProtoEnumValueName]. */ +@RunWith(JUnit4::class) +class ProtoEnumValueNameTest { + @Test + fun asConstantName() { + assertThat(ProtoEnumValueName("FOO_BAR").asConstantName) + .isEqualTo(ConstantName("FOO_BAR")) + } + + @Test + fun protoEnumConstantName() { + assertThat(Example3.ExampleEnum.BAR.valueDescriptor.protoEnumValueName) + .isEqualTo(ProtoEnumValueName("BAR")) + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFieldNameTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFieldNameTest.kt new file mode 100644 index 000000000000..2ac2c9de375a --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFieldNameTest.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.truth.Truth.assertThat +import com.google.protobuf.kotlin.protoc.testproto.Example3 +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [ProtoFieldName]. */ +@RunWith(JUnit4::class) +class ProtoFieldNameTest { + @Test + fun propertySimpleName() { + assertThat(ProtoFieldName("foo_bar_baz").propertySimpleName) + .isEqualTo(MemberSimpleName("fooBarBaz")) + } + + @Test + fun propertySimpleNameCharactersFollowDigit() { + assertThat(ProtoFieldName("has_underbar_preceeding_numeric_1foo").propertySimpleName) + .isEqualTo(MemberSimpleName("hasUnderbarPreceedingNumeric1Foo")) + assertThat(ProtoFieldName("has_underbar_preceeding_numeric_42bar").propertySimpleName) + .isEqualTo(MemberSimpleName("hasUnderbarPreceedingNumeric42Bar")) + assertThat(ProtoFieldName("has_underbar_preceeding_numeric_123foo42bar_baz").propertySimpleName) + .isEqualTo(MemberSimpleName("hasUnderbarPreceedingNumeric123Foo42BarBaz")) + } + + @Test + fun toSimpleClassNameWithSuffix() { + assertThat(ProtoFieldName("foo_bar_baz").toClassSimpleNameWithSuffix("Suffix")) + .isEqualTo(ClassSimpleName("FooBarBazSuffix")) + assertThat(ProtoFieldName("fooBar").toClassSimpleNameWithSuffix("Suffix")) + .isEqualTo(ClassSimpleName("FooBarSuffix")) + } + + @Test + fun toEnumConstantName() { + assertThat(ProtoFieldName("foo_bar_baz").toEnumConstantName()) + .isEqualTo(ConstantName("FOO_BAR_BAZ")) + } + + @Test + fun fieldDescriptorName() { + val fieldDescriptor = + Example3.ExampleMessage.getDescriptor().findFieldByName("string_oneof_option") + assertThat(fieldDescriptor.fieldName) + .isEqualTo(ProtoFieldName("string_oneof_option")) + } + + @Test + fun oneofName() { + val oneofDescriptor = + Example3.ExampleMessage + .getDescriptor() + .oneofs + .find { it.name == "my_oneof" }!! + assertThat(oneofDescriptor.oneofName) + .isEqualTo(ProtoFieldName("my_oneof")) + } + + @Test + fun namingEdgeCases() { + assertThat(ProtoFieldName("has_has_foo").propertySimpleName) + .isEqualTo(MemberSimpleName("hasHasFoo")) + assertThat(ProtoFieldName("enum_testEnum").propertySimpleName) + .isEqualTo(MemberSimpleName("enumTestEnum")) + assertThat(ProtoFieldName("Enum_test").propertySimpleName) + .isEqualTo(MemberSimpleName("enumTest")) + } + + @Test + fun javaSimpleName() { + assertThat(ProtoFieldName("has_has_foo").javaSimpleName) + .isEqualTo(MemberSimpleName("hasHasFoo")) + assertThat(ProtoFieldName("class").javaSimpleName).isEqualTo(MemberSimpleName("class_")) + assertThat(ProtoFieldName("cached_size").javaSimpleName) + .isEqualTo(MemberSimpleName("cachedSize_")) + assertThat(ProtoFieldName("serialized_size").javaSimpleName) + .isEqualTo(MemberSimpleName("serializedSize_")) + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFileNameTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFileNameTest.kt new file mode 100644 index 000000000000..e6be14199c6e --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFileNameTest.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.truth.Truth.assertThat +import com.google.protobuf.kotlin.protoc.testproto.Example3 +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [ProtoFileName]. */ +@RunWith(JUnit4::class) +class ProtoFileNameTest { + @Test + fun fileName() { + assertThat(Example3.getDescriptor().fileName) + .isEqualTo( + ProtoFileName( + "testing/example3.proto" + ) + ) + } + + @Test + fun name() { + assertThat(ProtoFileName("foo/bar/baz/quux.proto").name) + .isEqualTo("quux") + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleNameTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleNameTest.kt new file mode 100644 index 000000000000..8a4dcf04b6f4 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleNameTest.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.truth.Truth.assertThat +import com.google.protobuf.kotlin.protoc.testproto.Example3 +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [ProtoTypeSimpleName]. */ +@RunWith(JUnit4::class) +class ProtoTypeSimpleNameTest { + @Test + fun toSimpleClassName() { + assertThat(ProtoTypeSimpleName("FooBarMessage").toClassSimpleName()) + .isEqualTo(ClassSimpleName("FooBarMessage")) + } + + @Test + fun messageName() { + val descriptor = Example3.ExampleMessage.getDescriptor() + assertThat(descriptor.simpleName).isEqualTo(ProtoTypeSimpleName("ExampleMessage")) + } + + @Test + fun enumName() { + val descriptor = Example3.ExampleEnum.getDescriptor() + assertThat(descriptor.simpleName).isEqualTo(ProtoTypeSimpleName("ExampleEnum")) + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ScopeTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ScopeTest.kt new file mode 100644 index 000000000000..5f7c83256569 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ScopeTest.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc + +import com.google.common.truth.Truth.assertThat +import com.squareup.kotlinpoet.ClassName +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [Scope]. */ +@RunWith(JUnit4::class) +class ScopeTest { + @Test + fun unqualifiedScope() { + val name = UnqualifiedScope.nestedClass(ClassSimpleName("FooBar")) + assertThat(name).isEqualTo(ClassName(packageName = "", simpleNames = listOf("FooBar"))) + } + + @Test + fun packageScope() { + val name = PackageScope("com.foo.bar").nestedClass(ClassSimpleName("FooBar")) + assertThat(name).isEqualTo(ClassName(packageName = "com.foo.bar", simpleNames = listOf("FooBar"))) + } + + @Test + fun classScope() { + val name = + ClassScope(ClassName(packageName = "com.foo.bar", simpleNames = listOf("Baz"))) + .nestedClass(ClassSimpleName("Quux")) + assertThat(name).isEqualTo( + ClassName(packageName = "com.foo.bar", simpleNames = listOf("Baz", "Quux")) + ) + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubjectTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubjectTest.kt new file mode 100644 index 000000000000..062926ac427b --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubjectTest.kt @@ -0,0 +1,117 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc.testing + +import com.google.common.truth.ExpectFailure.SimpleSubjectBuilderCallback +import com.google.common.truth.ExpectFailure.expectFailureAbout +import com.google.protobuf.kotlin.protoc.declarations +import com.squareup.kotlinpoet.INT +import com.squareup.kotlinpoet.PropertySpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [DeclarationsSubject]. */ +@RunWith(JUnit4::class) +class DeclarationsSubjectTest { + private val topLevelProperty = PropertySpec.builder("topLevel", INT).build() + private val enclosedProperty = PropertySpec.builder("enclosed", INT).build() + private val decls = declarations { + addTopLevelProperty(topLevelProperty) + addProperty(enclosedProperty) + } + + @Test + fun generatesTopLevel() { + assertThat(decls).generatesTopLevel( + """ + import kotlin.Int + + val topLevel: Int + """ + ) + } + + @Test + fun generatesEnclosed() { + assertThat(decls).generatesEnclosed( + """ + import kotlin.Int + + val enclosed: Int + """ + ) + } + + @Test + fun generatesTopLevelFailure() { + expectFailureAbout( + declarationsSubjectFactory, + SimpleSubjectBuilderCallback { whenTesting -> + whenTesting.that(decls).generatesTopLevel("") + } + ) + } + + @Test + fun generatesEnclosedFailure() { + expectFailureAbout( + declarationsSubjectFactory, + SimpleSubjectBuilderCallback { whenTesting -> + whenTesting.that(decls).generatesEnclosed("") + } + ) + } + + @Test + fun generatesNoTopLevel() { + assertThat( + declarations { + addProperty(enclosedProperty) + } + ).generatesNoTopLevelMembers() + } + + @Test + fun generatesNoEnclosed() { + assertThat( + declarations { + addTopLevelProperty(topLevelProperty) + } + ).generatesNoEnclosedMembers() + } + + @Test + fun generatesNoTopLevelFailure() { + expectFailureAbout( + declarationsSubjectFactory, + SimpleSubjectBuilderCallback { whenTesting -> + whenTesting.that(decls).generatesNoTopLevelMembers() + } + ) + } + + @Test + fun generatesNoEnclosedFailure() { + expectFailureAbout( + declarationsSubjectFactory, + SimpleSubjectBuilderCallback { whenTesting -> + whenTesting.that(decls).generatesNoEnclosedMembers() + } + ) + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubjectTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubjectTest.kt new file mode 100644 index 000000000000..f13b6b98064d --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubjectTest.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc.testing + +import com.google.common.truth.ExpectFailure.SimpleSubjectBuilderCallback +import com.google.common.truth.ExpectFailure.expectFailureAbout +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.INT +import com.squareup.kotlinpoet.PropertySpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [FileSpecSubject]. */ +@RunWith(JUnit4::class) +class FileSpecSubjectTest { + private val fileSpec = + FileSpec.builder(packageName = "", fileName = "FileSpecContents.kt") + .addProperty(PropertySpec.builder("bar", INT).build()) + .build() + + @Test + fun generates() { + com.google.protobuf.kotlin.protoc.testing.assertThat(fileSpec).generates( + """ + import kotlin.Int + + val bar: Int + """ + ) + } + + @Test + fun generatesFailure() { + expectFailureAbout( + com.google.protobuf.kotlin.protoc.testing.fileSpecs, + SimpleSubjectBuilderCallback { whenTesting -> + whenTesting.that(fileSpec).generates("") + } + ) + expectFailureAbout( + com.google.protobuf.kotlin.protoc.testing.fileSpecs, + SimpleSubjectBuilderCallback { whenTesting -> + whenTesting.that(fileSpec).generates("object Foo") + } + ) + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubjectTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubjectTest.kt new file mode 100644 index 000000000000..07b87bdfda49 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubjectTest.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc.testing + +import com.google.common.truth.ExpectFailure +import com.google.common.truth.ExpectFailure.SimpleSubjectBuilderCallback +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.INT +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.asTypeName +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [TypeSpecSubject]. */ +@RunWith(JUnit4::class) +class FunSpecSubjectTest { + private val funSpec = + FunSpec + .builder("foo") + .addParameter(ParameterSpec.builder("bar", INT).build()) + .returns(String::class.asTypeName()) + .build() + + @Test + fun generates() { + com.google.protobuf.kotlin.protoc.testing.assertThat(funSpec).generates( + """ + fun foo(bar: kotlin.Int): kotlin.String { + } + """ + ) + } + + @Test + fun generatesFailure() { + ExpectFailure.expectFailureAbout( + com.google.protobuf.kotlin.protoc.testing.funSpecs, + SimpleSubjectBuilderCallback { whenTesting -> + whenTesting.that(funSpec).generates("") + } + ) + ExpectFailure.expectFailureAbout( + com.google.protobuf.kotlin.protoc.testing.funSpecs, + SimpleSubjectBuilderCallback { whenTesting -> + whenTesting.that(funSpec).generates("fun bar") + } + ) + } +} diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubjectTest.kt b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubjectTest.kt new file mode 100644 index 000000000000..f5c5c39e937c --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubjectTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.protobuf.kotlin.protoc.testing + +import com.google.common.truth.ExpectFailure.SimpleSubjectBuilderCallback +import com.google.common.truth.ExpectFailure.expectFailureAbout +import com.squareup.kotlinpoet.INT +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +/** Tests for [TypeSpecSubject]. */ +@RunWith(JUnit4::class) +class TypeSpecSubjectTest { + private val typeSpec = + TypeSpec + .objectBuilder("Foo") + .addProperty(PropertySpec.builder("bar", INT).build()) + .build() + + @Test + fun generates() { + com.google.protobuf.kotlin.protoc.testing.assertThat(typeSpec).generates( + """ + object Foo { + val bar: kotlin.Int + } + """ + ) + } + + @Test + fun generatesFailure() { + expectFailureAbout( + com.google.protobuf.kotlin.protoc.testing.typeSpecs, + SimpleSubjectBuilderCallback { whenTesting -> + whenTesting.that(typeSpec).generates("") + } + ) + expectFailureAbout( + com.google.protobuf.kotlin.protoc.testing.typeSpecs, + SimpleSubjectBuilderCallback { whenTesting -> + whenTesting.that(typeSpec).generates("object Foo") + } + ) + } +} diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/example2.proto b/kotlin/protoc-plugin-common/src/test/proto/testing/example2.proto new file mode 100644 index 000000000000..7f3387189464 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/proto/testing/example2.proto @@ -0,0 +1,10 @@ +syntax = "proto2"; + +package testing; + +option java_package = "com.google.protobuf.kotlin.protoc.testproto"; + +message ExampleProto2Message { + optional int32 optional_int32_field = 1; + required string required_string_field = 2; +} diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/example3.proto b/kotlin/protoc-plugin-common/src/test/proto/testing/example3.proto new file mode 100644 index 000000000000..645996c5171e --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/proto/testing/example3.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package testing; + +import "google/protobuf/wrappers.proto"; + +option java_package = "com.google.protobuf.kotlin.protoc.testproto"; + +message ExampleMessage { + int32 int32_field = 1; + string string_field = 2; + SubMessage sub_message_field = 3; + + oneof my_oneof { + string string_oneof_option = 4; + SubMessage sub_message_oneof_option = 5; + } + + repeated string repeated_string_field = 6; + map map_field = 7; + + message SubMessage {} + + // TODO(lowasser): why does this need the leading .? Other protos don't seem + // to. + .google.protobuf.Int32Value optional_int32 = 8; + + string class = 9; + repeated string cached_size = 10; + map serialized_size = 11; +} + +enum ExampleEnum { + DEFAULT = 0; + FOO = 1; + BAR = 2; +} diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/has_explicit_outer_class_name.proto b/kotlin/protoc-plugin-common/src/test/proto/testing/has_explicit_outer_class_name.proto new file mode 100644 index 000000000000..109b65d0784a --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/proto/testing/has_explicit_outer_class_name.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +package testing; + +option java_package = "com.google.protobuf.kotlin.protoc.testproto"; +option java_outer_classname = "MyExplicitOuterClassName"; + +message HasExplicitOuterClassName {} diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/has_nested_class_name_conflict.proto b/kotlin/protoc-plugin-common/src/test/proto/testing/has_nested_class_name_conflict.proto new file mode 100644 index 000000000000..848807a2751f --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/proto/testing/has_nested_class_name_conflict.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package testing; + +option java_package = "com.google.protobuf.kotlin.protoc.testproto"; + +message NestedMessageConflicts { + message HasNestedClassNameConflict {} +} diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/has_outer_class_name_conflict.proto b/kotlin/protoc-plugin-common/src/test/proto/testing/has_outer_class_name_conflict.proto new file mode 100644 index 000000000000..244f73be9c35 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/proto/testing/has_outer_class_name_conflict.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package testing; + +option java_package = "com.google.protobuf.kotlin.protoc.testproto"; + +message HasOuterClassNameConflict {} diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/implicit_java_package.proto b/kotlin/protoc-plugin-common/src/test/proto/testing/implicit_java_package.proto new file mode 100644 index 000000000000..ae5c4c564d41 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/proto/testing/implicit_java_package.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +package testing; + +message LivesInImplicitJavaPackage {} diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/proto-file-with-hyphen.proto b/kotlin/protoc-plugin-common/src/test/proto/testing/proto-file-with-hyphen.proto new file mode 100644 index 000000000000..129fdd5c95d6 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/proto/testing/proto-file-with-hyphen.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +package com.google.protos.testing; + +message ProtoFileWithHypen {} diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/proto_api_1_explicit_package.proto b/kotlin/protoc-plugin-common/src/test/proto/testing/proto_api_1_explicit_package.proto new file mode 100644 index 000000000000..f88d5a1eecb1 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/proto/testing/proto_api_1_explicit_package.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package testing; + +option java_package = "com.google.protos.testing.proto2api"; + +message ProtoApi1ExplicitPackageMessage { + +} diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/proto_api_1_implicit_package.proto b/kotlin/protoc-plugin-common/src/test/proto/testing/proto_api_1_implicit_package.proto new file mode 100644 index 000000000000..27aecc2133d5 --- /dev/null +++ b/kotlin/protoc-plugin-common/src/test/proto/testing/proto_api_1_implicit_package.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +package testing; + +message ProtoApi1ImplicitPackageMessage {} From 02958b62a452aba8791482111a733d7bd115c31d Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Tue, 25 Feb 2020 13:38:38 -0800 Subject: [PATCH 2/3] removes unneeded license/owners files --- kotlin/protobuf/CONTRIBUTING.md | 26 --- kotlin/protobuf/LICENSE | 13 -- kotlin/protobuf/OWNERS | 4 - kotlin/protoc-plugin-common/CONTRIBUTING.md | 26 --- kotlin/protoc-plugin-common/LICENSE | 202 -------------------- kotlin/protoc-plugin-common/OWNERS | 6 - 6 files changed, 277 deletions(-) delete mode 100644 kotlin/protobuf/CONTRIBUTING.md delete mode 100644 kotlin/protobuf/LICENSE delete mode 100644 kotlin/protobuf/OWNERS delete mode 100644 kotlin/protoc-plugin-common/CONTRIBUTING.md delete mode 100644 kotlin/protoc-plugin-common/LICENSE delete mode 100644 kotlin/protoc-plugin-common/OWNERS diff --git a/kotlin/protobuf/CONTRIBUTING.md b/kotlin/protobuf/CONTRIBUTING.md deleted file mode 100644 index 086d53916b56..000000000000 --- a/kotlin/protobuf/CONTRIBUTING.md +++ /dev/null @@ -1,26 +0,0 @@ -# Contributing - -Want to contribute? Great! First, read this page (including the small print at the end). - -### Before you contribute -Before we can use your code, you must sign the -[Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1) -(CLA), which you can do online. The CLA is necessary mainly because you own the -copyright to your changes, even after your contribution becomes part of our -codebase, so we need your permission to use and distribute your code. We also -need to be sure of various other things—for instance that you'll tell us if you -know that your code infringes on other people's patents. You don't have to sign -the CLA until after you've submitted your code for review and a member has -approved it, but you must do it before we can put your code into our codebase. -Before you start working on a larger contribution, you should get in touch with -us first through the issue tracker with your idea so that we can help out and -possibly guide you. Coordinating up front makes it much easier to avoid -frustration later on. - -### Code reviews -All submissions, including submissions by project members, require review. We -use Github pull requests for this purpose. - -### The small print -Contributions made by corporations are covered by a different agreement than -the one above, the Software Grant and Corporate Contributor License Agreement. diff --git a/kotlin/protobuf/LICENSE b/kotlin/protobuf/LICENSE deleted file mode 100644 index e34cad70a94e..000000000000 --- a/kotlin/protobuf/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2019 Google Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. \ No newline at end of file diff --git a/kotlin/protobuf/OWNERS b/kotlin/protobuf/OWNERS deleted file mode 100644 index 3ed0e6916bb6..000000000000 --- a/kotlin/protobuf/OWNERS +++ /dev/null @@ -1,4 +0,0 @@ -# PTAL at go/owners#best-practices -lowasser -kmb -mdb-group:kotlin-google-eng diff --git a/kotlin/protoc-plugin-common/CONTRIBUTING.md b/kotlin/protoc-plugin-common/CONTRIBUTING.md deleted file mode 100644 index 086d53916b56..000000000000 --- a/kotlin/protoc-plugin-common/CONTRIBUTING.md +++ /dev/null @@ -1,26 +0,0 @@ -# Contributing - -Want to contribute? Great! First, read this page (including the small print at the end). - -### Before you contribute -Before we can use your code, you must sign the -[Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1) -(CLA), which you can do online. The CLA is necessary mainly because you own the -copyright to your changes, even after your contribution becomes part of our -codebase, so we need your permission to use and distribute your code. We also -need to be sure of various other things—for instance that you'll tell us if you -know that your code infringes on other people's patents. You don't have to sign -the CLA until after you've submitted your code for review and a member has -approved it, but you must do it before we can put your code into our codebase. -Before you start working on a larger contribution, you should get in touch with -us first through the issue tracker with your idea so that we can help out and -possibly guide you. Coordinating up front makes it much easier to avoid -frustration later on. - -### Code reviews -All submissions, including submissions by project members, require review. We -use Github pull requests for this purpose. - -### The small print -Contributions made by corporations are covered by a different agreement than -the one above, the Software Grant and Corporate Contributor License Agreement. diff --git a/kotlin/protoc-plugin-common/LICENSE b/kotlin/protoc-plugin-common/LICENSE deleted file mode 100644 index 7a4a3ea2424c..000000000000 --- a/kotlin/protoc-plugin-common/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/kotlin/protoc-plugin-common/OWNERS b/kotlin/protoc-plugin-common/OWNERS deleted file mode 100644 index a2394f54ed56..000000000000 --- a/kotlin/protoc-plugin-common/OWNERS +++ /dev/null @@ -1,6 +0,0 @@ -# Top level ownership -@lowasser **/OWNERS -@jbolinger **/OWNERS -@kevin1e100 **/OWNERS -@bshaffer **/OWNERS - From 3863279790b76a1986057a3cac8478c0e1c38a34 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Tue, 25 Feb 2020 16:55:08 -0800 Subject: [PATCH 3/3] updates directories and package name --- kotlin/{protobuf => core}/.gitignore | 0 kotlin/{protobuf => core}/README.md | 0 kotlin/{protobuf => core}/build.gradle | 6 +++--- .../src/main/kotlin/com/example/tutorial/AddPerson.kt | 0 .../src/main/kotlin/com/example/tutorial/ListPeople.kt | 0 .../example/src/main/proto/addressbook.proto | 0 kotlin/{protobuf => core}/src/main/dist/protoc-gen-kotlin | 0 .../{protobuf => core}/src/main/dist/protoc-gen-kotlin.bat | 0 .../src/main/java/com/google/protobuf/kotlin/DslList.kt | 0 .../src/main/java/com/google/protobuf/kotlin/DslMap.kt | 0 .../com/google/protobuf/kotlin/ExtendableProtoDslLite.kt | 0 .../java/com/google/protobuf/kotlin/ExtendableProtoDslV3.kt | 0 .../java/com/google/protobuf/kotlin/ExtensionListLite.kt | 0 .../main/java/com/google/protobuf/kotlin/ExtensionListV3.kt | 0 .../java/com/google/protobuf/kotlin/OneofRepresentation.kt | 0 .../src/main/java/com/google/protobuf/kotlin/ProtoDsl.kt | 0 .../main/java/com/google/protobuf/kotlin/ProtoDslMarker.kt | 0 .../kotlin/extensions/ExtendableMessageLiteExtensions.kt | 0 .../kotlin/extensions/ExtendableMessageV3Extensions.kt | 0 .../kotlin/generator/AbstractOrNullPropertyGenerator.kt | 0 .../kotlin/generator/ComponentExtensionGenerator.kt | 0 .../com/google/protobuf/kotlin/generator/Deprecation.kt | 0 .../protobuf/kotlin/generator/DslComponentGenerator.kt | 0 .../com/google/protobuf/kotlin/generator/DslGenerator.kt | 0 .../google/protobuf/kotlin/generator/ExtensionGenerator.kt | 0 .../kotlin/generator/FieldOrNullPropertyGenerator.kt | 0 .../com/google/protobuf/kotlin/generator/GeneratorRunner.kt | 0 .../generator/MapFieldMapProxyDslComponentGenerator.kt | 0 .../kotlin/generator/MapRepresentativeExtensionGenerator.kt | 0 .../kotlin/generator/OneofCaseDslComponentGenerator.kt | 0 .../google/protobuf/kotlin/generator/OneofClassGenerator.kt | 0 .../kotlin/generator/OneofOrNullPropertyGenerator.kt | 0 .../kotlin/generator/ProtoFileKotlinApiGenerator.kt | 0 .../kotlin/generator/RecursiveExtensionGenerator.kt | 0 .../RepeatedFieldListProxyDslComponentGenerator.kt | 0 .../generator/RepeatedRepresentativeExtensionGenerator.kt | 0 .../generator/SingletonFieldClearDslComponentGenerator.kt | 0 .../generator/SingletonFieldHazzerDslComponentGenerator.kt | 0 .../SingletonFieldPropertyDslComponentGenerator.kt | 0 .../protobuf/kotlin/generator/WrapperPropertyGenerator.kt | 0 .../com/google/protobuf/kotlin/OneofRepresentationTest.kt | 0 .../protobuf/kotlin/generator/AbstractGeneratedCodeTest.kt | 0 .../kotlin/generator/AndroidLiteGeneratedCodeTest.kt | 0 .../google/protobuf/kotlin/generator/AndroidManifest.xml | 0 .../google/protobuf/kotlin/generator/DslGeneratorTest.kt | 0 .../generator/Example3OneofClass.kt.expected.enclosed | 0 .../generator/Example3OneofClass.kt.expected.toplevel | 0 .../kotlin/generator/FieldOrNullPropertyGeneratorTest.kt | 0 .../google/protobuf/kotlin/generator/GeneratedCodeTest.kt | 0 .../kotlin/generator/GeneratorRunnerExample2.kt.expected | 0 .../generator/GeneratorRunnerExample2Lite.kt.expected | 0 .../kotlin/generator/GeneratorRunnerExample3.kt.expected | 0 .../google/protobuf/kotlin/generator/GeneratorRunnerTest.kt | 0 .../protobuf/kotlin/generator/LiteGeneratedCodeTest.kt | 0 .../generator/MapFieldMapProxyDslComponentGeneratorTest.kt | 0 .../generator/MapRepresentativeExtensionGeneratorTest.kt | 0 .../kotlin/generator/OneofCaseDslComponentGeneratorTest.kt | 0 .../protobuf/kotlin/generator/OneofClassGeneratorTest.kt | 0 .../kotlin/generator/OneofOrNullPropertyGeneratorTest.kt | 0 .../kotlin/generator/ProtoFileKotlinApiGeneratorTest.kt | 0 .../kotlin/generator/RecursiveExtensionGeneratorTest.kt | 0 .../RepeatedFieldListProxyDslComponentGeneratorTest.kt | 0 .../RepeatedRepresentativeExtensionGeneratorTest.kt | 0 .../SingletonFieldClearDslComponentGeneratorTest.kt | 0 .../SingletonFieldHazzerDslComponentGeneratorTest.kt | 0 .../SingletonFieldPropertyDslComponentGeneratorTest.kt | 0 .../kotlin/generator/WrapperPropertyGeneratorTest.kt | 0 .../src/test/proto/testing/evil_names.proto | 0 .../src/test/proto/testing/evil_names_proto2.proto | 0 .../src/test/proto/testing/example2.proto | 0 .../src/test/proto/testing/example3.proto | 0 .../src/test/proto/testing/java_multiple_files.proto | 0 kotlin/{protoc-plugin-common => util}/.gitignore | 0 kotlin/{protoc-plugin-common => util}/README.md | 0 kotlin/{protoc-plugin-common => util}/build.gradle | 4 ++-- .../java/com/google/common/graph/TopologicalSortGraph.java | 0 .../main/java/com/google/common/sort/PartialOrdering.java | 0 .../main/java/com/google/common/sort/TopologicalSort.java | 0 .../protobuf/kotlin/protoc/AbstractGeneratorRunner.kt | 0 .../com/google/protobuf/kotlin/protoc/ClassSimpleName.kt | 0 .../com/google/protobuf/kotlin/protoc/CodeGenerators.kt | 0 .../java/com/google/protobuf/kotlin/protoc/ConstantName.kt | 0 .../java/com/google/protobuf/kotlin/protoc/Declarations.kt | 0 .../com/google/protobuf/kotlin/protoc/DescriptorUtil.kt | 0 .../com/google/protobuf/kotlin/protoc/GeneratorConfig.kt | 0 .../com/google/protobuf/kotlin/protoc/JavaPackagePolicy.kt | 0 .../java/com/google/protobuf/kotlin/protoc/KtPoetUtil.kt | 0 .../com/google/protobuf/kotlin/protoc/MemberSimpleName.kt | 0 .../com/google/protobuf/kotlin/protoc/MessageComponent.kt | 0 .../com/google/protobuf/kotlin/protoc/ProtoEnumValueName.kt | 0 .../com/google/protobuf/kotlin/protoc/ProtoFieldName.kt | 0 .../java/com/google/protobuf/kotlin/protoc/ProtoFileName.kt | 0 .../com/google/protobuf/kotlin/protoc/ProtoMethodName.kt | 0 .../com/google/protobuf/kotlin/protoc/ProtoServiceName.kt | 0 .../google/protobuf/kotlin/protoc/ProtoTypeSimpleName.kt | 0 .../main/java/com/google/protobuf/kotlin/protoc/Scope.kt | 0 .../java/com/google/protobuf/kotlin/protoc/TypeNames.kt | 0 .../protobuf/kotlin/protoc/testing/DeclarationsSubject.kt | 0 .../protobuf/kotlin/protoc/testing/FileSpecSubject.kt | 0 .../google/protobuf/kotlin/protoc/testing/FunSpecSubject.kt | 0 .../protobuf/kotlin/protoc/testing/TypeSpecSubject.kt | 0 .../google/protobuf/kotlin/protoc/ClassSimpleNameTest.kt | 0 .../com/google/protobuf/kotlin/protoc/ConstantNameTest.kt | 0 .../com/google/protobuf/kotlin/protoc/DeclarationsTest.kt | 0 .../com/google/protobuf/kotlin/protoc/DescriptorUtilTest.kt | 0 .../google/protobuf/kotlin/protoc/GeneratorConfigTest.kt | 0 .../google/protobuf/kotlin/protoc/JavaPackagePolicyTest.kt | 0 .../google/protobuf/kotlin/protoc/MemberSimpleNameTest.kt | 0 .../google/protobuf/kotlin/protoc/MessageComponentTest.kt | 0 .../google/protobuf/kotlin/protoc/ProtoEnumValueNameTest.kt | 0 .../com/google/protobuf/kotlin/protoc/ProtoFieldNameTest.kt | 0 .../com/google/protobuf/kotlin/protoc/ProtoFileNameTest.kt | 0 .../protobuf/kotlin/protoc/ProtoTypeSimpleNameTest.kt | 0 .../java/com/google/protobuf/kotlin/protoc/ScopeTest.kt | 0 .../kotlin/protoc/testing/DeclarationsSubjectTest.kt | 0 .../protobuf/kotlin/protoc/testing/FileSpecSubjectTest.kt | 0 .../protobuf/kotlin/protoc/testing/FunSpecSubjectTest.kt | 0 .../protobuf/kotlin/protoc/testing/TypeSpecSubjectTest.kt | 0 .../src/test/proto/testing/example2.proto | 0 .../src/test/proto/testing/example3.proto | 0 .../test/proto/testing/has_explicit_outer_class_name.proto | 0 .../test/proto/testing/has_nested_class_name_conflict.proto | 0 .../test/proto/testing/has_outer_class_name_conflict.proto | 0 .../src/test/proto/testing/implicit_java_package.proto | 0 .../src/test/proto/testing/proto-file-with-hyphen.proto | 0 .../test/proto/testing/proto_api_1_explicit_package.proto | 0 .../test/proto/testing/proto_api_1_implicit_package.proto | 0 127 files changed, 5 insertions(+), 5 deletions(-) rename kotlin/{protobuf => core}/.gitignore (100%) rename kotlin/{protobuf => core}/README.md (100%) rename kotlin/{protobuf => core}/build.gradle (96%) rename kotlin/{protobuf => core}/example/src/main/kotlin/com/example/tutorial/AddPerson.kt (100%) rename kotlin/{protobuf => core}/example/src/main/kotlin/com/example/tutorial/ListPeople.kt (100%) rename kotlin/{protobuf => core}/example/src/main/proto/addressbook.proto (100%) rename kotlin/{protobuf => core}/src/main/dist/protoc-gen-kotlin (100%) rename kotlin/{protobuf => core}/src/main/dist/protoc-gen-kotlin.bat (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/DslList.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/DslMap.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslLite.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslV3.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/ExtensionListLite.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/ExtensionListV3.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/OneofRepresentation.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/ProtoDsl.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/ProtoDslMarker.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageLiteExtensions.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageV3Extensions.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/AbstractOrNullPropertyGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/ComponentExtensionGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/Deprecation.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/DslComponentGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/DslGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/ExtensionGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/GeneratorRunner.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/OneofClassGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGenerator.kt (100%) rename kotlin/{protobuf => core}/src/main/java/com/google/protobuf/kotlin/generator/WrapperPropertyGenerator.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/OneofRepresentationTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/AbstractGeneratedCodeTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/AndroidLiteGeneratedCodeTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/AndroidManifest.xml (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/DslGeneratorTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.enclosed (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.toplevel (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGeneratorTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/GeneratedCodeTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2.kt.expected (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2Lite.kt.expected (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample3.kt.expected (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/LiteGeneratedCodeTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGeneratorTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGeneratorTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGeneratorTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/OneofClassGeneratorTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGeneratorTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGeneratorTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGeneratorTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGeneratorTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGeneratorTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGeneratorTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGeneratorTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGeneratorTest.kt (100%) rename kotlin/{protobuf => core}/src/test/java/com/google/protobuf/kotlin/generator/WrapperPropertyGeneratorTest.kt (100%) rename kotlin/{protobuf => core}/src/test/proto/testing/evil_names.proto (100%) rename kotlin/{protobuf => core}/src/test/proto/testing/evil_names_proto2.proto (100%) rename kotlin/{protobuf => core}/src/test/proto/testing/example2.proto (100%) rename kotlin/{protobuf => core}/src/test/proto/testing/example3.proto (100%) rename kotlin/{protobuf => core}/src/test/proto/testing/java_multiple_files.proto (100%) rename kotlin/{protoc-plugin-common => util}/.gitignore (100%) rename kotlin/{protoc-plugin-common => util}/README.md (100%) rename kotlin/{protoc-plugin-common => util}/build.gradle (92%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/common/graph/TopologicalSortGraph.java (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/common/sort/PartialOrdering.java (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/common/sort/TopologicalSort.java (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/AbstractGeneratorRunner.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/ClassSimpleName.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/CodeGenerators.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/ConstantName.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/Declarations.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/DescriptorUtil.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/GeneratorConfig.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicy.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/KtPoetUtil.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/MemberSimpleName.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/MessageComponent.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueName.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFieldName.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFileName.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/ProtoMethodName.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/ProtoServiceName.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleName.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/Scope.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/TypeNames.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubject.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubject.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubject.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/main/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubject.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/ClassSimpleNameTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/ConstantNameTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/DeclarationsTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/DescriptorUtilTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/GeneratorConfigTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicyTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/MemberSimpleNameTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/MessageComponentTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueNameTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFieldNameTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFileNameTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleNameTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/ScopeTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubjectTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubjectTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubjectTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubjectTest.kt (100%) rename kotlin/{protoc-plugin-common => util}/src/test/proto/testing/example2.proto (100%) rename kotlin/{protoc-plugin-common => util}/src/test/proto/testing/example3.proto (100%) rename kotlin/{protoc-plugin-common => util}/src/test/proto/testing/has_explicit_outer_class_name.proto (100%) rename kotlin/{protoc-plugin-common => util}/src/test/proto/testing/has_nested_class_name_conflict.proto (100%) rename kotlin/{protoc-plugin-common => util}/src/test/proto/testing/has_outer_class_name_conflict.proto (100%) rename kotlin/{protoc-plugin-common => util}/src/test/proto/testing/implicit_java_package.proto (100%) rename kotlin/{protoc-plugin-common => util}/src/test/proto/testing/proto-file-with-hyphen.proto (100%) rename kotlin/{protoc-plugin-common => util}/src/test/proto/testing/proto_api_1_explicit_package.proto (100%) rename kotlin/{protoc-plugin-common => util}/src/test/proto/testing/proto_api_1_implicit_package.proto (100%) diff --git a/kotlin/protobuf/.gitignore b/kotlin/core/.gitignore similarity index 100% rename from kotlin/protobuf/.gitignore rename to kotlin/core/.gitignore diff --git a/kotlin/protobuf/README.md b/kotlin/core/README.md similarity index 100% rename from kotlin/protobuf/README.md rename to kotlin/core/README.md diff --git a/kotlin/protobuf/build.gradle b/kotlin/core/build.gradle similarity index 96% rename from kotlin/protobuf/build.gradle rename to kotlin/core/build.gradle index 80d87673c7a5..457eebb5d20b 100644 --- a/kotlin/protobuf/build.gradle +++ b/kotlin/core/build.gradle @@ -44,7 +44,7 @@ dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}" // Protobuf - compile "io.grpc:protoc-plugin-kotlin:0.1" + compile "com.google.protobuf:protobuf-kotlin-util:0.1" compile 'com.google.protobuf:protobuf-gradle-plugin:0.8.11' compile 'com.google.protobuf:protobuf-java:3.11.0' @@ -101,8 +101,8 @@ def artifactForGradlePlugin(MavenPublication pub, String os, String arch) { publishing { publications { maven(MavenPublication) { - groupId = 'io.grpc' - artifactId = 'proto-kotlin' + groupId = 'com.google.protobuf' + artifactId = 'protobuf-kotlin' version = '0.1' from components.java diff --git a/kotlin/protobuf/example/src/main/kotlin/com/example/tutorial/AddPerson.kt b/kotlin/core/example/src/main/kotlin/com/example/tutorial/AddPerson.kt similarity index 100% rename from kotlin/protobuf/example/src/main/kotlin/com/example/tutorial/AddPerson.kt rename to kotlin/core/example/src/main/kotlin/com/example/tutorial/AddPerson.kt diff --git a/kotlin/protobuf/example/src/main/kotlin/com/example/tutorial/ListPeople.kt b/kotlin/core/example/src/main/kotlin/com/example/tutorial/ListPeople.kt similarity index 100% rename from kotlin/protobuf/example/src/main/kotlin/com/example/tutorial/ListPeople.kt rename to kotlin/core/example/src/main/kotlin/com/example/tutorial/ListPeople.kt diff --git a/kotlin/protobuf/example/src/main/proto/addressbook.proto b/kotlin/core/example/src/main/proto/addressbook.proto similarity index 100% rename from kotlin/protobuf/example/src/main/proto/addressbook.proto rename to kotlin/core/example/src/main/proto/addressbook.proto diff --git a/kotlin/protobuf/src/main/dist/protoc-gen-kotlin b/kotlin/core/src/main/dist/protoc-gen-kotlin similarity index 100% rename from kotlin/protobuf/src/main/dist/protoc-gen-kotlin rename to kotlin/core/src/main/dist/protoc-gen-kotlin diff --git a/kotlin/protobuf/src/main/dist/protoc-gen-kotlin.bat b/kotlin/core/src/main/dist/protoc-gen-kotlin.bat similarity index 100% rename from kotlin/protobuf/src/main/dist/protoc-gen-kotlin.bat rename to kotlin/core/src/main/dist/protoc-gen-kotlin.bat diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/DslList.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/DslList.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/DslList.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/DslList.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/DslMap.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/DslMap.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/DslMap.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/DslMap.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslLite.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslLite.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslLite.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslLite.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslV3.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslV3.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslV3.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/ExtendableProtoDslV3.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtensionListLite.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/ExtensionListLite.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtensionListLite.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/ExtensionListLite.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtensionListV3.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/ExtensionListV3.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ExtensionListV3.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/ExtensionListV3.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/OneofRepresentation.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/OneofRepresentation.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/OneofRepresentation.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/OneofRepresentation.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ProtoDsl.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/ProtoDsl.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ProtoDsl.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/ProtoDsl.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ProtoDslMarker.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/ProtoDslMarker.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/ProtoDslMarker.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/ProtoDslMarker.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageLiteExtensions.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageLiteExtensions.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageLiteExtensions.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageLiteExtensions.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageV3Extensions.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageV3Extensions.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageV3Extensions.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/extensions/ExtendableMessageV3Extensions.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/AbstractOrNullPropertyGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/AbstractOrNullPropertyGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/AbstractOrNullPropertyGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/AbstractOrNullPropertyGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ComponentExtensionGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/ComponentExtensionGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ComponentExtensionGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/ComponentExtensionGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/Deprecation.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/Deprecation.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/Deprecation.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/Deprecation.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/DslComponentGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/DslComponentGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/DslComponentGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/DslComponentGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/DslGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/DslGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/DslGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/DslGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ExtensionGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/ExtensionGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ExtensionGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/ExtensionGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/GeneratorRunner.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/GeneratorRunner.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/GeneratorRunner.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/GeneratorRunner.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofClassGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/OneofClassGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofClassGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/OneofClassGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGenerator.kt diff --git a/kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/WrapperPropertyGenerator.kt b/kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/WrapperPropertyGenerator.kt similarity index 100% rename from kotlin/protobuf/src/main/java/com/google/protobuf/kotlin/generator/WrapperPropertyGenerator.kt rename to kotlin/core/src/main/java/com/google/protobuf/kotlin/generator/WrapperPropertyGenerator.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/OneofRepresentationTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/OneofRepresentationTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/OneofRepresentationTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/OneofRepresentationTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AbstractGeneratedCodeTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/AbstractGeneratedCodeTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AbstractGeneratedCodeTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/AbstractGeneratedCodeTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AndroidLiteGeneratedCodeTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/AndroidLiteGeneratedCodeTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AndroidLiteGeneratedCodeTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/AndroidLiteGeneratedCodeTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AndroidManifest.xml b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/AndroidManifest.xml similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/AndroidManifest.xml rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/AndroidManifest.xml diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/DslGeneratorTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/DslGeneratorTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/DslGeneratorTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/DslGeneratorTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.enclosed b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.enclosed similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.enclosed rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.enclosed diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.toplevel b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.toplevel similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.toplevel rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/Example3OneofClass.kt.expected.toplevel diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGeneratorTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGeneratorTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGeneratorTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/FieldOrNullPropertyGeneratorTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratedCodeTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/GeneratedCodeTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratedCodeTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/GeneratedCodeTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2.kt.expected b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2.kt.expected similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2.kt.expected rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2.kt.expected diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2Lite.kt.expected b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2Lite.kt.expected similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2Lite.kt.expected rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample2Lite.kt.expected diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample3.kt.expected b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample3.kt.expected similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample3.kt.expected rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerExample3.kt.expected diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/GeneratorRunnerTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/LiteGeneratedCodeTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/LiteGeneratedCodeTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/LiteGeneratedCodeTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/LiteGeneratedCodeTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGeneratorTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGeneratorTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGeneratorTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/MapFieldMapProxyDslComponentGeneratorTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGeneratorTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGeneratorTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGeneratorTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/MapRepresentativeExtensionGeneratorTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGeneratorTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGeneratorTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGeneratorTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/OneofCaseDslComponentGeneratorTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofClassGeneratorTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/OneofClassGeneratorTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofClassGeneratorTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/OneofClassGeneratorTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGeneratorTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGeneratorTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGeneratorTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/OneofOrNullPropertyGeneratorTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGeneratorTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGeneratorTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGeneratorTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/ProtoFileKotlinApiGeneratorTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGeneratorTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGeneratorTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGeneratorTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/RecursiveExtensionGeneratorTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGeneratorTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGeneratorTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGeneratorTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/RepeatedFieldListProxyDslComponentGeneratorTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGeneratorTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGeneratorTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGeneratorTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/RepeatedRepresentativeExtensionGeneratorTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGeneratorTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGeneratorTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGeneratorTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldClearDslComponentGeneratorTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGeneratorTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGeneratorTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGeneratorTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldHazzerDslComponentGeneratorTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGeneratorTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGeneratorTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGeneratorTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/SingletonFieldPropertyDslComponentGeneratorTest.kt diff --git a/kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/WrapperPropertyGeneratorTest.kt b/kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/WrapperPropertyGeneratorTest.kt similarity index 100% rename from kotlin/protobuf/src/test/java/com/google/protobuf/kotlin/generator/WrapperPropertyGeneratorTest.kt rename to kotlin/core/src/test/java/com/google/protobuf/kotlin/generator/WrapperPropertyGeneratorTest.kt diff --git a/kotlin/protobuf/src/test/proto/testing/evil_names.proto b/kotlin/core/src/test/proto/testing/evil_names.proto similarity index 100% rename from kotlin/protobuf/src/test/proto/testing/evil_names.proto rename to kotlin/core/src/test/proto/testing/evil_names.proto diff --git a/kotlin/protobuf/src/test/proto/testing/evil_names_proto2.proto b/kotlin/core/src/test/proto/testing/evil_names_proto2.proto similarity index 100% rename from kotlin/protobuf/src/test/proto/testing/evil_names_proto2.proto rename to kotlin/core/src/test/proto/testing/evil_names_proto2.proto diff --git a/kotlin/protobuf/src/test/proto/testing/example2.proto b/kotlin/core/src/test/proto/testing/example2.proto similarity index 100% rename from kotlin/protobuf/src/test/proto/testing/example2.proto rename to kotlin/core/src/test/proto/testing/example2.proto diff --git a/kotlin/protobuf/src/test/proto/testing/example3.proto b/kotlin/core/src/test/proto/testing/example3.proto similarity index 100% rename from kotlin/protobuf/src/test/proto/testing/example3.proto rename to kotlin/core/src/test/proto/testing/example3.proto diff --git a/kotlin/protobuf/src/test/proto/testing/java_multiple_files.proto b/kotlin/core/src/test/proto/testing/java_multiple_files.proto similarity index 100% rename from kotlin/protobuf/src/test/proto/testing/java_multiple_files.proto rename to kotlin/core/src/test/proto/testing/java_multiple_files.proto diff --git a/kotlin/protoc-plugin-common/.gitignore b/kotlin/util/.gitignore similarity index 100% rename from kotlin/protoc-plugin-common/.gitignore rename to kotlin/util/.gitignore diff --git a/kotlin/protoc-plugin-common/README.md b/kotlin/util/README.md similarity index 100% rename from kotlin/protoc-plugin-common/README.md rename to kotlin/util/README.md diff --git a/kotlin/protoc-plugin-common/build.gradle b/kotlin/util/build.gradle similarity index 92% rename from kotlin/protoc-plugin-common/build.gradle rename to kotlin/util/build.gradle index 1d4a4a21f45f..527e114ed712 100644 --- a/kotlin/protoc-plugin-common/build.gradle +++ b/kotlin/util/build.gradle @@ -36,8 +36,8 @@ dependencies { publishing { publications { maven(MavenPublication) { - groupId = 'io.grpc' - artifactId = 'protoc-plugin-kotlin' + groupId = 'com.google.protobuf' + artifactId = 'protobuf-kotlin-util' version = '0.1' from components.java diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/common/graph/TopologicalSortGraph.java b/kotlin/util/src/main/java/com/google/common/graph/TopologicalSortGraph.java similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/common/graph/TopologicalSortGraph.java rename to kotlin/util/src/main/java/com/google/common/graph/TopologicalSortGraph.java diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/common/sort/PartialOrdering.java b/kotlin/util/src/main/java/com/google/common/sort/PartialOrdering.java similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/common/sort/PartialOrdering.java rename to kotlin/util/src/main/java/com/google/common/sort/PartialOrdering.java diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/common/sort/TopologicalSort.java b/kotlin/util/src/main/java/com/google/common/sort/TopologicalSort.java similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/common/sort/TopologicalSort.java rename to kotlin/util/src/main/java/com/google/common/sort/TopologicalSort.java diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/AbstractGeneratorRunner.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/AbstractGeneratorRunner.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/AbstractGeneratorRunner.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/AbstractGeneratorRunner.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ClassSimpleName.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/ClassSimpleName.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ClassSimpleName.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/ClassSimpleName.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/CodeGenerators.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/CodeGenerators.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/CodeGenerators.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/CodeGenerators.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ConstantName.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/ConstantName.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ConstantName.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/ConstantName.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/Declarations.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/Declarations.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/Declarations.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/Declarations.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/DescriptorUtil.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/DescriptorUtil.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/DescriptorUtil.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/DescriptorUtil.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/GeneratorConfig.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/GeneratorConfig.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/GeneratorConfig.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/GeneratorConfig.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicy.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicy.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicy.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicy.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/KtPoetUtil.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/KtPoetUtil.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/KtPoetUtil.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/KtPoetUtil.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/MemberSimpleName.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/MemberSimpleName.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/MemberSimpleName.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/MemberSimpleName.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/MessageComponent.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/MessageComponent.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/MessageComponent.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/MessageComponent.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueName.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueName.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueName.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueName.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFieldName.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFieldName.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFieldName.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFieldName.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFileName.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFileName.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFileName.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/ProtoFileName.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoMethodName.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/ProtoMethodName.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoMethodName.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/ProtoMethodName.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoServiceName.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/ProtoServiceName.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoServiceName.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/ProtoServiceName.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleName.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleName.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleName.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleName.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/Scope.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/Scope.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/Scope.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/Scope.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/TypeNames.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/TypeNames.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/TypeNames.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/TypeNames.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubject.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubject.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubject.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubject.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubject.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubject.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubject.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubject.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubject.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubject.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubject.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubject.kt diff --git a/kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubject.kt b/kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubject.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/main/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubject.kt rename to kotlin/util/src/main/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubject.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ClassSimpleNameTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/ClassSimpleNameTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ClassSimpleNameTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/ClassSimpleNameTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ConstantNameTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/ConstantNameTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ConstantNameTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/ConstantNameTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/DeclarationsTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/DeclarationsTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/DeclarationsTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/DeclarationsTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/DescriptorUtilTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/DescriptorUtilTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/DescriptorUtilTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/DescriptorUtilTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/GeneratorConfigTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/GeneratorConfigTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/GeneratorConfigTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/GeneratorConfigTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicyTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicyTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicyTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/JavaPackagePolicyTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/MemberSimpleNameTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/MemberSimpleNameTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/MemberSimpleNameTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/MemberSimpleNameTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/MessageComponentTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/MessageComponentTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/MessageComponentTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/MessageComponentTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueNameTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueNameTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueNameTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/ProtoEnumValueNameTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFieldNameTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFieldNameTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFieldNameTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFieldNameTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFileNameTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFileNameTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFileNameTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/ProtoFileNameTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleNameTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleNameTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleNameTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/ProtoTypeSimpleNameTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ScopeTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/ScopeTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/ScopeTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/ScopeTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubjectTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubjectTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubjectTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/testing/DeclarationsSubjectTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubjectTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubjectTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubjectTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/testing/FileSpecSubjectTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubjectTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubjectTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubjectTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/testing/FunSpecSubjectTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubjectTest.kt b/kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubjectTest.kt similarity index 100% rename from kotlin/protoc-plugin-common/src/test/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubjectTest.kt rename to kotlin/util/src/test/java/com/google/protobuf/kotlin/protoc/testing/TypeSpecSubjectTest.kt diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/example2.proto b/kotlin/util/src/test/proto/testing/example2.proto similarity index 100% rename from kotlin/protoc-plugin-common/src/test/proto/testing/example2.proto rename to kotlin/util/src/test/proto/testing/example2.proto diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/example3.proto b/kotlin/util/src/test/proto/testing/example3.proto similarity index 100% rename from kotlin/protoc-plugin-common/src/test/proto/testing/example3.proto rename to kotlin/util/src/test/proto/testing/example3.proto diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/has_explicit_outer_class_name.proto b/kotlin/util/src/test/proto/testing/has_explicit_outer_class_name.proto similarity index 100% rename from kotlin/protoc-plugin-common/src/test/proto/testing/has_explicit_outer_class_name.proto rename to kotlin/util/src/test/proto/testing/has_explicit_outer_class_name.proto diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/has_nested_class_name_conflict.proto b/kotlin/util/src/test/proto/testing/has_nested_class_name_conflict.proto similarity index 100% rename from kotlin/protoc-plugin-common/src/test/proto/testing/has_nested_class_name_conflict.proto rename to kotlin/util/src/test/proto/testing/has_nested_class_name_conflict.proto diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/has_outer_class_name_conflict.proto b/kotlin/util/src/test/proto/testing/has_outer_class_name_conflict.proto similarity index 100% rename from kotlin/protoc-plugin-common/src/test/proto/testing/has_outer_class_name_conflict.proto rename to kotlin/util/src/test/proto/testing/has_outer_class_name_conflict.proto diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/implicit_java_package.proto b/kotlin/util/src/test/proto/testing/implicit_java_package.proto similarity index 100% rename from kotlin/protoc-plugin-common/src/test/proto/testing/implicit_java_package.proto rename to kotlin/util/src/test/proto/testing/implicit_java_package.proto diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/proto-file-with-hyphen.proto b/kotlin/util/src/test/proto/testing/proto-file-with-hyphen.proto similarity index 100% rename from kotlin/protoc-plugin-common/src/test/proto/testing/proto-file-with-hyphen.proto rename to kotlin/util/src/test/proto/testing/proto-file-with-hyphen.proto diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/proto_api_1_explicit_package.proto b/kotlin/util/src/test/proto/testing/proto_api_1_explicit_package.proto similarity index 100% rename from kotlin/protoc-plugin-common/src/test/proto/testing/proto_api_1_explicit_package.proto rename to kotlin/util/src/test/proto/testing/proto_api_1_explicit_package.proto diff --git a/kotlin/protoc-plugin-common/src/test/proto/testing/proto_api_1_implicit_package.proto b/kotlin/util/src/test/proto/testing/proto_api_1_implicit_package.proto similarity index 100% rename from kotlin/protoc-plugin-common/src/test/proto/testing/proto_api_1_implicit_package.proto rename to kotlin/util/src/test/proto/testing/proto_api_1_implicit_package.proto