[pigeon] Adds Kotlin documentation comment support (#2665)

diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md
index 8d6aa74..5e32c04 100644
--- a/packages/pigeon/CHANGELOG.md
+++ b/packages/pigeon/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 4.2.1
+
+* Adds documentation comment support for Kotlin.
+
 ## 4.2.0
 
 * Adds experimental support for Kotlin generation.
diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart
index fc32d23..2d8afea 100644
--- a/packages/pigeon/lib/generator_tools.dart
+++ b/packages/pigeon/lib/generator_tools.dart
@@ -9,7 +9,7 @@
 import 'ast.dart';
 
 /// The current version of pigeon. This must match the version in pubspec.yaml.
-const String pigeonVersion = '4.2.0';
+const String pigeonVersion = '4.2.1';
 
 /// Read all the content from [stdin] to a String.
 String readStdin() {
diff --git a/packages/pigeon/lib/kotlin_generator.dart b/packages/pigeon/lib/kotlin_generator.dart
index dc70534..a940d40 100644
--- a/packages/pigeon/lib/kotlin_generator.dart
+++ b/packages/pigeon/lib/kotlin_generator.dart
@@ -7,6 +7,23 @@
 import 'generator_tools.dart';
 import 'pigeon_lib.dart' show TaskQueueType;
 
+/// Documentation open symbol.
+const String _docCommentPrefix = '/**';
+
+/// Documentation continuation symbol.
+const String _docCommentContinuation = ' *';
+
+/// Documentation close symbol.
+const String _docCommentSuffix = ' */';
+
+/// Documentation comment spec.
+const DocumentCommentSpecification _docCommentSpec =
+    DocumentCommentSpecification(
+  _docCommentPrefix,
+  closeCommentToken: _docCommentSuffix,
+  blockContinuationToken: _docCommentContinuation,
+);
+
 /// Options that control how Kotlin code will be generated.
 class KotlinOptions {
   /// Creates a [KotlinOptions] object
@@ -112,8 +129,12 @@
 
   final String apiName = api.name;
 
-  indent.writeln(
-      '/** Generated interface from Pigeon that represents a handler of messages from Flutter. */');
+  const List<String> generatedMessages = <String>[
+    ' Generated interface from Pigeon that represents a handler of messages from Flutter.'
+  ];
+  addDocumentationComments(indent, api.documentationComments, _docCommentSpec,
+      generatorComments: generatedMessages);
+
   indent.write('interface $apiName ');
   indent.scoped('{', '}', () {
     for (final Method method in api.methods) {
@@ -132,6 +153,10 @@
       final String returnType = method.returnType.isVoid
           ? ''
           : _nullsafeKotlinTypeForDartType(method.returnType);
+
+      addDocumentationComments(
+          indent, method.documentationComments, _docCommentSpec);
+
       if (method.isAsynchronous) {
         argSignature.add('callback: ($returnType) -> Unit');
         indent.writeln('fun ${method.name}(${argSignature.join(', ')})');
@@ -256,8 +281,12 @@
 /// }
 void _writeFlutterApi(Indent indent, Api api) {
   assert(api.location == ApiLocation.flutter);
-  indent.writeln(
-      '/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */');
+  const List<String> generatedMessages = <String>[
+    ' Generated class from Pigeon that represents Flutter messages that can be called from Kotlin.'
+  ];
+  addDocumentationComments(indent, api.documentationComments, _docCommentSpec,
+      generatorComments: generatedMessages);
+
   final String apiName = api.name;
   indent.writeln('@Suppress("UNCHECKED_CAST")');
   indent.write('class $apiName(private val binaryMessenger: BinaryMessenger) ');
@@ -277,6 +306,10 @@
           ? ''
           : _nullsafeKotlinTypeForDartType(func.returnType);
       String sendArgument;
+
+      addDocumentationComments(
+          indent, func.documentationComments, _docCommentSpec);
+
       if (func.arguments.isEmpty) {
         indent.write('fun ${func.name}(callback: ($returnType) -> Unit) ');
         sendArgument = 'null';
@@ -430,6 +463,8 @@
   }
 
   void writeEnum(Enum anEnum) {
+    addDocumentationComments(
+        indent, anEnum.documentationComments, _docCommentSpec);
     indent.write('enum class ${anEnum.name}(val raw: Int) ');
     indent.scoped('{', '}', () {
       // We use explicit indexing here as use of the ordinal() method is
@@ -460,6 +495,8 @@
 
   void writeDataClass(Class klass) {
     void writeField(NamedType field) {
+      addDocumentationComments(
+          indent, field.documentationComments, _docCommentSpec);
       indent.write(
           'val ${field.name}: ${_nullsafeKotlinTypeForDartType(field.type)}');
       final String defaultNil = field.type.isNullable ? ' = null' : '';
@@ -566,8 +603,13 @@
       });
     }
 
-    indent.writeln(
-        '/** Generated class from Pigeon that represents data sent in messages. */');
+    const List<String> generatedMessages = <String>[
+      ' Generated class from Pigeon that represents data sent in messages.'
+    ];
+    addDocumentationComments(
+        indent, klass.documentationComments, _docCommentSpec,
+        generatorComments: generatedMessages);
+
     indent.write('data class ${klass.name}(');
     indent.scoped('', '', () {
       for (final NamedType element in klass.fields) {
diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml
index bc7d9d5..cb524d5 100644
--- a/packages/pigeon/pubspec.yaml
+++ b/packages/pigeon/pubspec.yaml
@@ -2,7 +2,7 @@
 description: Code generator tool to make communication between Flutter and the host platform type-safe and easier.
 repository: https://github.com/flutter/packages/tree/main/packages/pigeon
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3Apigeon
-version: 4.2.0 # This must match the version in lib/generator_tools.dart
+version: 4.2.1 # This must match the version in lib/generator_tools.dart
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/pigeon/test/kotlin_generator_test.dart b/packages/pigeon/test/kotlin_generator_test.dart
index d9d5501..35cd887 100644
--- a/packages/pigeon/test/kotlin_generator_test.dart
+++ b/packages/pigeon/test/kotlin_generator_test.dart
@@ -993,4 +993,81 @@
     final String code = sink.toString();
     expect(code, contains('val input: String\n'));
   });
+
+  test('transfers documentation comments', () {
+    final List<String> comments = <String>[
+      ' api comment',
+      ' api method comment',
+      ' class comment',
+      ' class field comment',
+      ' enum comment',
+    ];
+    int count = 0;
+
+    final Root root = Root(
+      apis: <Api>[
+        Api(
+          name: 'api',
+          location: ApiLocation.flutter,
+          documentationComments: <String>[comments[count++]],
+          methods: <Method>[
+            Method(
+              name: 'method',
+              returnType: const TypeDeclaration.voidDeclaration(),
+              documentationComments: <String>[comments[count++]],
+              arguments: <NamedType>[
+                NamedType(
+                  name: 'field',
+                  type: const TypeDeclaration(
+                    baseName: 'int',
+                    isNullable: true,
+                  ),
+                ),
+              ],
+            )
+          ],
+        )
+      ],
+      classes: <Class>[
+        Class(
+          name: 'class',
+          documentationComments: <String>[comments[count++]],
+          fields: <NamedType>[
+            NamedType(
+              documentationComments: <String>[comments[count++]],
+              type: const TypeDeclaration(
+                  baseName: 'Map',
+                  isNullable: true,
+                  typeArguments: <TypeDeclaration>[
+                    TypeDeclaration(baseName: 'String', isNullable: true),
+                    TypeDeclaration(baseName: 'int', isNullable: true),
+                  ]),
+              name: 'field1',
+            ),
+          ],
+        ),
+      ],
+      enums: <Enum>[
+        Enum(
+          name: 'enum',
+          documentationComments: <String>[comments[count++]],
+          members: <String>[
+            'one',
+            'two',
+          ],
+        ),
+      ],
+    );
+    final StringBuffer sink = StringBuffer();
+    const KotlinOptions kotlinOptions = KotlinOptions();
+    generateKotlin(kotlinOptions, root, sink);
+    final String code = sink.toString();
+    for (final String comment in comments) {
+      // This regex finds the comment only between the open and close comment block
+      expect(
+          RegExp(r'(?<=\/\*\*.*?)' + comment + r'(?=.*?\*\/)', dotAll: true)
+              .hasMatch(code),
+          true);
+    }
+  });
 }