Add option for Dart protobuf to emit Kythe metdata. (#204)

* Make Dart protobuf emite Kythe metdata.

* Fix Travis tests with proper names Set.

* Fix other Travis tests, update CHANGELOG and pubspec.

* Remove unused _offset from IndentingWriter.

* Fix whitespacing in meta goldens.

* Remove default explicit null value in IndentingWriter.

* Reformat test files to change line wrapping for external paths.
diff --git a/protoc_plugin/CHANGELOG.md b/protoc_plugin/CHANGELOG.md
index b626ac3..4093710 100644
--- a/protoc_plugin/CHANGELOG.md
+++ b/protoc_plugin/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 16.1.0
+
+* Add ability to generate Kythe metadata files via the
+  `generate_kythe_info` option.
+
 ## 16.0.0
 
 * Breaking change: Remove the '$checkItem' function from generated message classes and use the new method 'pc' on
diff --git a/protoc_plugin/README.md b/protoc_plugin/README.md
index a46ea53..12efc7f 100644
--- a/protoc_plugin/README.md
+++ b/protoc_plugin/README.md
@@ -64,6 +64,15 @@
 
     --dart_out="<option 1>,<option 2>:."
 
+ ### Generating Code Info
+
+The plugin includes the `generate_kythe_info` option, which, if passed at run
+time, will make the plugin generate metadata files alongside the `.dart` files
+generated for the proto messages and their enums. Pass this along with the other
+dart_out options:
+
+    --dart_out="generate_kythe_info,<other options>:."
+
 Using protocol buffer libraries to build new libraries
 ------------------------------------------------------
 
diff --git a/protoc_plugin/lib/code_generator.dart b/protoc_plugin/lib/code_generator.dart
index d5080c9..bb7a82d 100644
--- a/protoc_plugin/lib/code_generator.dart
+++ b/protoc_plugin/lib/code_generator.dart
@@ -13,6 +13,14 @@
   String get classname;
   String get fullName;
 
+  /// The field path contains the field IDs and indices (for repeated fields)
+  /// that lead to the proto memeber corresponding to a piece of generated code.
+  /// Repeated fields in the descriptor are further identified by the index of
+  /// the message in question.
+  /// For more information see
+  /// https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/descriptor.proto#L728
+  List<int> get fieldPath;
+
   /// The fully qualified name with a leading '.'.
   ///
   /// This exists because names from protoc come like this.
@@ -98,4 +106,5 @@
   String get classname => null;
   String get fullName => '';
   get fileGen => null;
+  List<int> get fieldPath => [];
 }
diff --git a/protoc_plugin/lib/enum_generator.dart b/protoc_plugin/lib/enum_generator.dart
index f441cd6..06958bd 100644
--- a/protoc_plugin/lib/enum_generator.dart
+++ b/protoc_plugin/lib/enum_generator.dart
@@ -17,15 +17,24 @@
   final EnumDescriptorProto _descriptor;
   final List<EnumValueDescriptorProto> _canonicalValues =
       <EnumValueDescriptorProto>[];
+  final List<int> _originalCanonicalIndices = <int>[];
   final List<EnumAlias> _aliases = <EnumAlias>[];
 
   /// Maps the name of an enum value to the Dart name we will use for it.
   final Map<String, String> dartNames = <String, String>{};
+  final List<int> _originalAliasIndices = <int>[];
+  List<int> _fieldPath;
+  final List<int> _fieldPathSegment;
 
-  EnumGenerator(EnumDescriptorProto descriptor, ProtobufContainer parent,
-      Set<String> usedClassNames)
+  /// See [[ProtobufContainer]
+  List<int> get fieldPath =>
+      _fieldPath ??= List.from(_parent.fieldPath)..addAll(_fieldPathSegment);
+
+  EnumGenerator._(EnumDescriptorProto descriptor, ProtobufContainer parent,
+      Set<String> usedClassNames, int repeatedFieldIndex, int fieldIdTag)
       : assert(parent != null),
         _parent = parent,
+        _fieldPathSegment = [fieldIdTag, repeatedFieldIndex],
         classname = messageOrEnumClassName(descriptor.name, usedClassNames,
             parent: parent?.classname ?? ''),
         fullName = parent.fullName == ''
@@ -33,19 +42,38 @@
             : '${parent.fullName}.${descriptor.name}',
         _descriptor = descriptor {
     final usedNames = reservedEnumNames;
-    for (EnumValueDescriptorProto value in descriptor.value) {
+    for (var i = 0; i < descriptor.value.length; i++) {
+      EnumValueDescriptorProto value = descriptor.value[i];
       EnumValueDescriptorProto canonicalValue =
           descriptor.value.firstWhere((v) => v.number == value.number);
       if (value == canonicalValue) {
         _canonicalValues.add(value);
+        _originalCanonicalIndices.add(i);
       } else {
         _aliases.add(new EnumAlias(value, canonicalValue));
+        _originalAliasIndices.add(i);
       }
       dartNames[value.name] = disambiguateName(
           avoidInitialUnderscore(value.name), usedNames, enumSuffixes());
     }
   }
 
+  static const _topLevelFieldTag = 5;
+  static const _nestedFieldTag = 4;
+
+  EnumGenerator.topLevel(
+      EnumDescriptorProto descriptor,
+      ProtobufContainer parent,
+      Set<String> usedClassNames,
+      int repeatedFieldIndex)
+      : this._(descriptor, parent, usedClassNames, repeatedFieldIndex,
+            _topLevelFieldTag);
+
+  EnumGenerator.nested(EnumDescriptorProto descriptor, ProtobufContainer parent,
+      Set<String> usedClassNames, int repeatedFieldIndex)
+      : this._(descriptor, parent, usedClassNames, repeatedFieldIndex,
+            _nestedFieldTag);
+
   String get package => _parent.package;
   FileGenerator get fileGen => _parent.fileGen;
 
@@ -64,23 +92,58 @@
     return "$fileImportPrefix.$name";
   }
 
+  static const int _enumNameTag = 1;
+  static const int _enumValueTag = 2;
+  static const int _enumValueNameTag = 1;
+
   void generate(IndentingWriter out) {
-    out.addBlock(
+    out.addAnnotatedBlock(
         'class ${classname} extends $_protobufImportPrefix.ProtobufEnum {',
-        '}\n', () {
+        '}\n', [
+      new NamedLocation(
+          name: classname,
+          fieldPathSegment: new List.from(fieldPath)..add(_enumNameTag),
+          start: 'class '.length)
+    ], () {
       // -----------------------------------------------------------------
       // Define enum types.
-      for (EnumValueDescriptorProto val in _canonicalValues) {
+      for (var i = 0; i < _canonicalValues.length; i++) {
+        EnumValueDescriptorProto val = _canonicalValues[i];
         final name = dartNames[val.name];
-        out.println('static const ${classname} $name = '
-            "const ${classname}._(${val.number}, ${singleQuote(name)});");
+        out.printlnAnnotated(
+            'static const ${classname} $name = '
+            "const ${classname}._(${val.number}, ${singleQuote(name)});",
+            [
+              new NamedLocation(
+                  name: name,
+                  fieldPathSegment: new List.from(fieldPath)
+                    ..addAll([
+                      _enumValueTag,
+                      _originalCanonicalIndices[i],
+                      _enumValueNameTag
+                    ]),
+                  start: 'static const ${classname} '.length)
+            ]);
       }
       if (_aliases.isNotEmpty) {
         out.println();
-        for (EnumAlias alias in _aliases) {
+        for (var i = 0; i < _aliases.length; i++) {
+          EnumAlias alias = _aliases[i];
           final name = dartNames[alias.value.name];
-          out.println('static const ${classname} $name ='
-              ' ${dartNames[alias.canonicalValue.name]};');
+          out.printlnAnnotated(
+              'static const ${classname} $name ='
+              ' ${dartNames[alias.canonicalValue.name]};',
+              [
+                new NamedLocation(
+                    name: name,
+                    fieldPathSegment: new List.from(fieldPath)
+                      ..addAll([
+                        _enumValueTag,
+                        _originalAliasIndices[i],
+                        _enumValueNameTag
+                      ]),
+                    start: 'static const ${classname} '.length)
+              ]);
         }
       }
       out.println();
diff --git a/protoc_plugin/lib/extension_generator.dart b/protoc_plugin/lib/extension_generator.dart
index e1b6e02..2a53f44 100644
--- a/protoc_plugin/lib/extension_generator.dart
+++ b/protoc_plugin/lib/extension_generator.dart
@@ -12,9 +12,29 @@
   ProtobufField _field;
   final String _extensionName;
   String _extendedFullName = "";
+  List<int> _fieldPath;
+  final List<int> _fieldPathSegment;
 
-  ExtensionGenerator(this._descriptor, this._parent, Set<String> usedNames)
-      : _extensionName = extensionName(_descriptor, usedNames);
+  /// See [[ProtobufContainer]
+  List<int> get fieldPath =>
+      _fieldPath ??= List.from(_parent.fieldPath)..addAll(_fieldPathSegment);
+
+  ExtensionGenerator._(this._descriptor, this._parent, Set<String> usedNames,
+      int repeatedFieldIndex, int fieldIdTag)
+      : _extensionName = extensionName(_descriptor, usedNames),
+        _fieldPathSegment = [fieldIdTag, repeatedFieldIndex];
+
+  static const _topLevelFieldTag = 7;
+  static const _nestedFieldTag = 6;
+
+  ExtensionGenerator.topLevel(FieldDescriptorProto descriptor,
+      ProtobufContainer parent, Set<String> usedNames, int repeatedFieldIndex)
+      : this._(descriptor, parent, usedNames, repeatedFieldIndex,
+            _topLevelFieldTag);
+  ExtensionGenerator.nested(FieldDescriptorProto descriptor,
+      ProtobufContainer parent, Set<String> usedNames, int repeatedFieldIndex)
+      : this._(
+            descriptor, parent, usedNames, repeatedFieldIndex, _nestedFieldTag);
 
   void resolve(GenerationContext ctx) {
     _field = new ProtobufField.extension(_descriptor, _parent, ctx);
@@ -79,9 +99,16 @@
     var dartType = type.getDartType(fileGen);
 
     if (_field.isRepeated) {
-      out.print('static final $_protobufImportPrefix.Extension $name = '
+      out.printAnnotated(
+          'static final $_protobufImportPrefix.Extension $name = '
           'new $_protobufImportPrefix.Extension<$dartType>.repeated(\'$_extendedFullName\','
-          ' \'$name\', ${_field.number}, ${_field.typeConstant}');
+          ' \'$name\', ${_field.number}, ${_field.typeConstant}',
+          [
+            new NamedLocation(
+                name: name,
+                fieldPathSegment: new List.from(fieldPath)..add(1),
+                start: 'static final $_protobufImportPrefix.Extension '.length)
+          ]);
       if (type.isMessage || type.isGroup) {
         out.println(
             ', $_protobufImportPrefix.getCheckFunction(${_field.typeConstant}), $dartType.create);');
@@ -96,9 +123,16 @@
       return;
     }
 
-    out.print('static final $_protobufImportPrefix.Extension $name = '
+    out.printAnnotated(
+        'static final $_protobufImportPrefix.Extension $name = '
         'new $_protobufImportPrefix.Extension<$dartType>(\'$_extendedFullName\', \'$name\', '
-        '${_field.number}, ${_field.typeConstant}');
+        '${_field.number}, ${_field.typeConstant}',
+        [
+          new NamedLocation(
+              name: name,
+              fieldPathSegment: new List.from(fieldPath)..add(1),
+              start: 'static final $_protobufImportPrefix.Extension '.length)
+        ]);
 
     String initializer = _field.generateDefaultFunction(fileGen);
 
diff --git a/protoc_plugin/lib/file_generator.dart b/protoc_plugin/lib/file_generator.dart
index 845966b..a4225a1 100644
--- a/protoc_plugin/lib/file_generator.dart
+++ b/protoc_plugin/lib/file_generator.dart
@@ -145,16 +145,22 @@
     }
 
     // Load and register all enum and message types.
-    for (EnumDescriptorProto enumType in descriptor.enumType) {
-      enumGenerators.add(new EnumGenerator(enumType, this, usedTopLevelNames));
+    for (var i = 0; i < descriptor.enumType.length; i++) {
+      enumGenerators.add(new EnumGenerator.topLevel(
+          descriptor.enumType[i], this, usedTopLevelNames, i));
     }
-    for (DescriptorProto messageType in descriptor.messageType) {
-      messageGenerators.add(new MessageGenerator(
-          messageType, this, declaredMixins, defaultMixin, usedTopLevelNames));
+    for (var i = 0; i < descriptor.messageType.length; i++) {
+      messageGenerators.add(new MessageGenerator.topLevel(
+          descriptor.messageType[i],
+          this,
+          declaredMixins,
+          defaultMixin,
+          usedTopLevelNames,
+          i));
     }
-    for (FieldDescriptorProto extension in descriptor.extension) {
-      extensionGenerators
-          .add(new ExtensionGenerator(extension, this, usedExtensionNames));
+    for (var i = 0; i < descriptor.extension.length; i++) {
+      extensionGenerators.add(new ExtensionGenerator.topLevel(
+          descriptor.extension[i], this, usedTopLevelNames, i));
     }
     for (ServiceDescriptorProto service in descriptor.service) {
       if (options.useGrpc) {
@@ -188,6 +194,7 @@
   String get classname => '';
   String get fullName => descriptor.package;
   FileGenerator get fileGen => this;
+  List<int> get fieldPath => [];
 
   /// Generates all the Dart files for this .proto file.
   List<CodeGeneratorResponse_File> generateFiles(OutputConfiguration config) {
@@ -201,11 +208,27 @@
         ..content = content;
     }
 
+    IndentingWriter mainWriter = generateMainFile(config);
+    IndentingWriter enumWriter = generateEnumFile(config);
+
     final files = [
-      makeFile(".pb.dart", generateMainFile(config)),
-      makeFile(".pbenum.dart", generateEnumFile(config)),
+      makeFile(".pb.dart", mainWriter.toString()),
+      makeFile(".pbenum.dart", enumWriter.toString()),
       makeFile(".pbjson.dart", generateJsonFile(config)),
     ];
+
+    if (options.generateMetadata) {
+      files.addAll([
+        makeFile(
+            ".pb.dart.meta",
+            new String.fromCharCodes(
+                mainWriter.sourceLocationInfo.writeToBuffer())),
+        makeFile(
+            ".pbenum.dart.meta",
+            new String.fromCharCodes(
+                enumWriter.sourceLocationInfo.writeToBuffer()))
+      ]);
+    }
     if (options.useGrpc) {
       if (grpcGenerators.isNotEmpty) {
         files.add(makeFile(".pbgrpc.dart", generateGrpcFile(config)));
@@ -216,11 +239,15 @@
     return files;
   }
 
+  /// Creates an IndentingWriter with metadata generation enabled or disabled.
+  IndentingWriter makeWriter() => new IndentingWriter(
+      filename: options.generateMetadata ? descriptor.name : null);
+
   /// Returns the contents of the .pb.dart file for this .proto file.
-  String generateMainFile(
+  IndentingWriter generateMainFile(
       [OutputConfiguration config = const DefaultOutputConfiguration()]) {
     if (!_linked) throw new StateError("not linked");
-    IndentingWriter out = new IndentingWriter();
+    IndentingWriter out = makeWriter();
 
     writeMainHeader(out, config);
 
@@ -251,7 +278,7 @@
     for (ClientApiGenerator c in clientApiGenerators) {
       c.generate(out);
     }
-    return out.toString();
+    return out;
   }
 
   /// Writes the header and imports for the .pb.dart file.
@@ -370,11 +397,11 @@
   }
 
   /// Returns the contents of the .pbenum.dart file for this .proto file.
-  String generateEnumFile(
+  IndentingWriter generateEnumFile(
       [OutputConfiguration config = const DefaultOutputConfiguration()]) {
     if (!_linked) throw new StateError("not linked");
 
-    var out = new IndentingWriter();
+    var out = makeWriter();
     _writeHeading(out);
 
     if (enumCount > 0) {
@@ -394,7 +421,7 @@
       m.generateEnums(out);
     }
 
-    return out.toString();
+    return out;
   }
 
   /// Returns the number of enum types generated in the .pbenum.dart file.
@@ -410,7 +437,7 @@
   String generateServerFile(
       [OutputConfiguration config = const DefaultOutputConfiguration()]) {
     if (!_linked) throw new StateError("not linked");
-    var out = new IndentingWriter();
+    var out = makeWriter();
     _writeHeading(out);
 
     if (serviceGenerators.isNotEmpty) {
@@ -450,7 +477,7 @@
   String generateGrpcFile(
       [OutputConfiguration config = const DefaultOutputConfiguration()]) {
     if (!_linked) throw new StateError("not linked");
-    var out = new IndentingWriter();
+    var out = makeWriter();
     _writeHeading(out);
 
     out.println(_asyncImport);
@@ -482,7 +509,7 @@
   String generateJsonFile(
       [OutputConfiguration config = const DefaultOutputConfiguration()]) {
     if (!_linked) throw new StateError("not linked");
-    var out = new IndentingWriter();
+    var out = makeWriter();
     _writeHeading(out);
 
     // Import the .pbjson.dart files we depend on.
diff --git a/protoc_plugin/lib/indenting_writer.dart b/protoc_plugin/lib/indenting_writer.dart
index c71889a..86ae2e8 100644
--- a/protoc_plugin/lib/indenting_writer.dart
+++ b/protoc_plugin/lib/indenting_writer.dart
@@ -4,15 +4,34 @@
 
 library protoc.indenting_writer;
 
+import 'src/descriptor.pb.dart';
+
+/// Specifies code locations where metadata annotations should be attached and
+/// where they should point to in the original proto.
+class NamedLocation {
+  final String name;
+  final List<int> fieldPathSegment;
+  final int start;
+  NamedLocation({this.name, this.fieldPathSegment, this.start});
+}
+
 /// A buffer for writing indented source code.
 class IndentingWriter {
   final StringBuffer _buffer = new StringBuffer();
+  final GeneratedCodeInfo sourceLocationInfo = new GeneratedCodeInfo();
   String _indent = "";
   bool _needIndent = true;
+  // After writing any chunk, _previousOffset is the size of everything that was
+  // written to the buffer before the latest call to print or addBlock.
+  int _previousOffset = 0;
+  final String _sourceFile;
+
+  IndentingWriter({String filename}) : _sourceFile = filename;
 
   /// Appends a string indented to the current level.
   /// (Indentation will be added after newline characters where needed.)
   void print(String text) {
+    _previousOffset = _buffer.length;
     var lastNewline = text.lastIndexOf('\n');
     if (lastNewline == -1) {
       _writeChunk(text);
@@ -32,22 +51,41 @@
     _newline();
   }
 
+  void printAnnotated(String text, List<NamedLocation> namedLocations) {
+    print(text);
+    for (final location in namedLocations) {
+      addAnnotation(location.fieldPathSegment, location.name, location.start);
+    }
+  }
+
+  void printlnAnnotated(String text, List<NamedLocation> namedLocations) {
+    printAnnotated(text, namedLocations);
+    _newline();
+  }
+
   /// Prints a block of text with the body indented one more level.
   void addBlock(String start, String end, void body(),
       {endWithNewline = true}) {
-    _addBlock(start, end, body, endWithNewline, _indent + '  ');
+    println(start);
+    _addBlockBodyAndEnd(end, body, endWithNewline, _indent + '  ');
   }
 
   /// Prints a block of text with an unindented body.
   /// (For example, for triple quotes.)
   void addUnindentedBlock(String start, String end, void body(),
       {endWithNewline = true}) {
-    _addBlock(start, end, body, endWithNewline, '');
+    println(start);
+    _addBlockBodyAndEnd(end, body, endWithNewline, '');
   }
 
-  void _addBlock(
-      String start, String end, void body(), endWithNewline, newIndent) {
-    println(start);
+  void addAnnotatedBlock(
+      String start, String end, List<NamedLocation> namedLocations, void body(),
+      {endWithNewline = true}) {
+    printlnAnnotated(start, namedLocations);
+    _addBlockBodyAndEnd(end, body, endWithNewline, _indent + '  ');
+  }
+
+  void _addBlockBodyAndEnd(String end, void body(), endWithNewline, newIndent) {
     var oldIndent = _indent;
     _indent = newIndent;
     body();
@@ -78,4 +116,20 @@
     _buffer.writeln();
     _needIndent = true;
   }
+
+  /// Creates an annotation, given the starting offset and ending offset.
+  /// [start] should be the location of the identifier as it appears in the
+  /// string that was passed to the previous [print]. Name should be the string
+  /// that was written to file.
+  void addAnnotation(List<int> fieldPath, String name, int start) {
+    if (_sourceFile == null) {
+      return;
+    }
+    var annotation = new GeneratedCodeInfo_Annotation()
+      ..path.addAll(fieldPath)
+      ..sourceFile = _sourceFile
+      ..begin = _previousOffset + start
+      ..end = _previousOffset + start + name.length;
+    sourceLocationInfo.annotation.add(annotation);
+  }
 }
diff --git a/protoc_plugin/lib/message_generator.dart b/protoc_plugin/lib/message_generator.dart
index 7e3f55e..d6d51ae 100644
--- a/protoc_plugin/lib/message_generator.dart
+++ b/protoc_plugin/lib/message_generator.dart
@@ -78,19 +78,29 @@
   final List<List<ProtobufField>> _oneofFields;
   List<OneofNames> _oneofNames;
 
+  List<int> _fieldPath;
+  final List<int> _fieldPathSegment;
+
+  /// See [[ProtobufContainer]
+  List<int> get fieldPath =>
+      _fieldPath ??= List.from(_parent.fieldPath)..addAll(_fieldPathSegment);
+
   // populated by resolve()
   List<ProtobufField> _fieldList;
 
   Set<String> _usedTopLevelNames;
 
-  MessageGenerator(
+  MessageGenerator._(
       DescriptorProto descriptor,
       ProtobufContainer parent,
       Map<String, PbMixin> declaredMixins,
       PbMixin defaultMixin,
-      this._usedTopLevelNames)
+      this._usedTopLevelNames,
+      int repeatedFieldIndex,
+      int fieldIdTag)
       : _descriptor = descriptor,
         _parent = parent,
+        _fieldPathSegment = [fieldIdTag, repeatedFieldIndex],
         classname = messageOrEnumClassName(descriptor.name, _usedTopLevelNames,
             parent: parent?.classname ?? ''),
         assert(parent != null),
@@ -101,21 +111,51 @@
             defaultMixin),
         _oneofFields =
             List.generate(descriptor.oneofDecl.length, (int index) => []) {
-    for (EnumDescriptorProto e in _descriptor.enumType) {
-      _enumGenerators.add(new EnumGenerator(e, this, _usedTopLevelNames));
+    for (var i = 0; i < _descriptor.enumType.length; i++) {
+      EnumDescriptorProto e = _descriptor.enumType[i];
+      _enumGenerators
+          .add(new EnumGenerator.nested(e, this, _usedTopLevelNames, i));
     }
 
-    for (DescriptorProto n in _descriptor.nestedType) {
-      _messageGenerators.add(new MessageGenerator(
-          n, this, declaredMixins, defaultMixin, _usedTopLevelNames));
+    for (var i = 0; i < _descriptor.nestedType.length; i++) {
+      DescriptorProto n = _descriptor.nestedType[i];
+      _messageGenerators.add(new MessageGenerator.nested(
+          n, this, declaredMixins, defaultMixin, _usedTopLevelNames, i));
     }
 
-    for (FieldDescriptorProto x in _descriptor.extension) {
+    // Extensions within messages won't create top-level classes and don't need
+    // to check against / be added to top-level reserved names.
+    final usedExtensionNames = Set<String>()..addAll(forbiddenExtensionNames);
+    for (var i = 0; i < _descriptor.extension.length; i++) {
+      FieldDescriptorProto x = _descriptor.extension[i];
       _extensionGenerators
-          .add(new ExtensionGenerator(x, this, _usedTopLevelNames));
+          .add(new ExtensionGenerator.nested(x, this, usedExtensionNames, i));
     }
   }
 
+  static const _topLevelFieldTag = 4;
+  static const _nestedFieldTag = 3;
+
+  MessageGenerator.topLevel(
+      DescriptorProto descriptor,
+      ProtobufContainer parent,
+      Map<String, PbMixin> declaredMixins,
+      PbMixin defaultMixin,
+      Set<String> usedNames,
+      int repeatedFieldIndex)
+      : this._(descriptor, parent, declaredMixins, defaultMixin, usedNames,
+            repeatedFieldIndex, _topLevelFieldTag);
+
+  MessageGenerator.nested(
+      DescriptorProto descriptor,
+      ProtobufContainer parent,
+      Map<String, PbMixin> declaredMixins,
+      PbMixin defaultMixin,
+      Set<String> usedNames,
+      int repeatedFieldIndex)
+      : this._(descriptor, parent, declaredMixins, defaultMixin, usedNames,
+            repeatedFieldIndex, _nestedFieldTag);
+
   String get package => _parent.package;
 
   /// The generator of the .pb.dart file that will declare this type.
@@ -270,9 +310,14 @@
     String packageClause = package == ''
         ? ''
         : ', package: const $_protobufImportPrefix.PackageName(\'$package\')';
-    out.addBlock(
+    out.addAnnotatedBlock(
         'class ${classname} extends $_protobufImportPrefix.GeneratedMessage${mixinClause} {',
-        '}', () {
+        '}', [
+      new NamedLocation(
+          name: classname,
+          fieldPathSegment: new List.from(fieldPath)..addAll([1]),
+          start: 'class '.length)
+    ], () {
       for (OneofNames oneof in _oneofNames) {
         out.addBlock(
             'static const Map<int, ${oneof.oneofEnumName}> ${oneof.byTagMapName} = {',
diff --git a/protoc_plugin/lib/options.dart b/protoc_plugin/lib/options.dart
index c8fc4a6..9b61b7c 100644
--- a/protoc_plugin/lib/options.dart
+++ b/protoc_plugin/lib/options.dart
@@ -47,8 +47,9 @@
 /// Options expected by the protoc code generation compiler.
 class GenerationOptions {
   final bool useGrpc;
+  final bool generateMetadata;
 
-  GenerationOptions({this.useGrpc = false});
+  GenerationOptions({this.useGrpc = false, this.generateMetadata = false});
 }
 
 /// A parser for a name-value pair option. Options parsed in
@@ -75,6 +76,19 @@
   }
 }
 
+class GenerateMetadataParser implements SingleOptionParser {
+  bool generateKytheInfo = false;
+
+  @override
+  void parse(String name, String value, onError(String details)) {
+    if (value != null) {
+      onError('Invalid metadata option. No Value expected.');
+      return;
+    }
+    generateKytheInfo = true;
+  }
+}
+
 /// Parser used by the compiler, which supports the `rpc` option (see
 /// [RpcOptionParser]) and any additional option added in [parsers]. If
 /// [parsers] has a key for `rpc`, it will be ignored.
@@ -86,9 +100,13 @@
 
   final grpcOptionParser = new GrpcOptionParser();
   newParsers['grpc'] = grpcOptionParser;
+  final generateMetadataParser = new GenerateMetadataParser();
+  newParsers['generate_kythe_info'] = generateMetadataParser;
 
   if (genericOptionsParser(request, response, newParsers)) {
-    return new GenerationOptions(useGrpc: grpcOptionParser.grpcEnabled);
+    return new GenerationOptions(
+        useGrpc: grpcOptionParser.grpcEnabled,
+        generateMetadata: generateMetadataParser.generateKytheInfo);
   }
   return null;
 }
diff --git a/protoc_plugin/lib/protoc.dart b/protoc_plugin/lib/protoc.dart
index 6a1b390..50e004f 100644
--- a/protoc_plugin/lib/protoc.dart
+++ b/protoc_plugin/lib/protoc.dart
@@ -13,7 +13,7 @@
 import 'src/plugin.pb.dart';
 
 import 'const_generator.dart' show writeJsonConst;
-import 'indenting_writer.dart' show IndentingWriter;
+import 'indenting_writer.dart';
 import 'names.dart';
 
 part 'base_type.dart';
diff --git a/protoc_plugin/pubspec.yaml b/protoc_plugin/pubspec.yaml
index 9de08be..28a79e9 100644
--- a/protoc_plugin/pubspec.yaml
+++ b/protoc_plugin/pubspec.yaml
@@ -1,5 +1,5 @@
 name: protoc_plugin
-version: 16.0.0
+version: 16.1.0
 author: Dart Team <misc@dartlang.org>
 description: Protoc compiler plugin to generate Dart code
 homepage: https://github.com/dart-lang/protobuf
diff --git a/protoc_plugin/test/enum_generator_test.dart b/protoc_plugin/test/enum_generator_test.dart
index d1cd2f1..5a1ded3 100755
--- a/protoc_plugin/test/enum_generator_test.dart
+++ b/protoc_plugin/test/enum_generator_test.dart
@@ -30,11 +30,13 @@
           ..name = 'BUSINESS'
           ..number = 2
       ]);
-    IndentingWriter writer = new IndentingWriter();
+    IndentingWriter writer = new IndentingWriter(filename: 'sample.proto');
     FileGenerator fg =
         new FileGenerator(new FileDescriptorProto(), new GenerationOptions());
-    EnumGenerator eg = new EnumGenerator(ed, fg, new Set<String>());
+    EnumGenerator eg = new EnumGenerator.topLevel(ed, fg, new Set<String>(), 0);
     eg.generate(writer);
     expectMatchesGoldenFile(writer.toString(), 'test/goldens/enum');
+    expectMatchesGoldenFile(
+        writer.sourceLocationInfo.toString(), 'test/goldens/enum.meta');
   });
 }
diff --git a/protoc_plugin/test/file_generator_test.dart b/protoc_plugin/test/file_generator_test.dart
index f26d828..80814fc 100644
--- a/protoc_plugin/test/file_generator_test.dart
+++ b/protoc_plugin/test/file_generator_test.dart
@@ -79,7 +79,20 @@
     FileGenerator fg = new FileGenerator(fd, options);
     link(options, [fg]);
     expectMatchesGoldenFile(
-        fg.generateMainFile(), 'test/goldens/oneMessage.pb');
+        fg.generateMainFile().toString(), 'test/goldens/oneMessage.pb');
+  });
+
+  test(
+      'FileGenerator outputs a .pb.dart.meta file for a proto with one message',
+      () {
+    FileDescriptorProto fd = buildFileDescriptor();
+    var options = parseGenerationOptions(
+        new CodeGeneratorRequest()..parameter = 'generate_kythe_info',
+        new CodeGeneratorResponse());
+    FileGenerator fg = new FileGenerator(fd, options);
+    link(options, [fg]);
+    expectMatchesGoldenFile(fg.generateMainFile().sourceLocationInfo.toString(),
+        'test/goldens/oneMessage.pb.meta');
   });
 
   test('FileGenerator outputs a pbjson.dart file for a proto with one message',
@@ -102,9 +115,24 @@
     FileGenerator fg = new FileGenerator(fd, options);
     link(options, [fg]);
     expectMatchesGoldenFile(
-        fg.generateMainFile(), 'test/goldens/topLevelEnum.pb');
+        fg.generateMainFile().toString(), 'test/goldens/topLevelEnum.pb');
     expectMatchesGoldenFile(
-        fg.generateEnumFile(), 'test/goldens/topLevelEnum.pbenum');
+        fg.generateEnumFile().toString(), 'test/goldens/topLevelEnum.pbenum');
+  });
+
+  test('FileGenerator generates metadata files for a top-level enum', () {
+    FileDescriptorProto fd =
+        buildFileDescriptor(phoneNumber: false, topLevelEnum: true);
+    var options = parseGenerationOptions(
+        new CodeGeneratorRequest()..parameter = 'generate_kythe_info',
+        new CodeGeneratorResponse());
+    FileGenerator fg = new FileGenerator(fd, options);
+    link(options, [fg]);
+
+    expectMatchesGoldenFile(fg.generateMainFile().sourceLocationInfo.toString(),
+        'test/goldens/topLevelEnum.pb.meta');
+    expectMatchesGoldenFile(fg.generateEnumFile().sourceLocationInfo.toString(),
+        'test/goldens/topLevelEnum.pbenum.meta');
   });
 
   test('FileGenerator generates a .pbjson.dart file for a top-level enum', () {
@@ -128,7 +156,7 @@
     FileGenerator fg = new FileGenerator(fd, options);
     link(options, [fg]);
 
-    var writer = new IndentingWriter();
+    var writer = new IndentingWriter(filename: '');
     fg.writeMainHeader(writer);
     expectMatchesGoldenFile(
         writer.toString(), 'test/goldens/header_in_package.pb');
@@ -152,7 +180,7 @@
     FileGenerator fg = new FileGenerator(fd, options);
     link(options, [fg]);
 
-    var writer = new IndentingWriter();
+    var writer = new IndentingWriter(filename: '');
     fg.writeMainHeader(writer);
     expectMatchesGoldenFile(
         writer.toString(), 'test/goldens/header_with_fixnum.pb');
@@ -179,9 +207,10 @@
     FileGenerator fg = new FileGenerator(fd, options);
     link(options, [fg]);
 
-    var writer = new IndentingWriter();
+    var writer = new IndentingWriter(filename: '');
     fg.writeMainHeader(writer);
-    expectMatchesGoldenFile(fg.generateMainFile(), 'test/goldens/service.pb');
+    expectMatchesGoldenFile(
+        fg.generateMainFile().toString(), 'test/goldens/service.pb');
     expectMatchesGoldenFile(
         fg.generateServerFile(), 'test/goldens/service.pbserver');
   });
@@ -207,10 +236,10 @@
     FileGenerator fg = new FileGenerator(fd, options);
     link(options, [fg]);
 
-    var writer = new IndentingWriter();
+    var writer = new IndentingWriter(filename: '');
     fg.writeMainHeader(writer);
     expectMatchesGoldenFile(
-        fg.generateMainFile(), 'test/goldens/grpc_service.pb');
+        fg.generateMainFile().toString(), 'test/goldens/grpc_service.pb');
   });
 
   test('FileGenerator outputs gRPC stubs if gRPC is selected', () {
@@ -256,7 +285,7 @@
     FileGenerator fg = new FileGenerator(fd, options);
     link(options, [fg]);
 
-    var writer = new IndentingWriter();
+    var writer = new IndentingWriter(filename: '');
     fg.writeMainHeader(writer);
     expectMatchesGoldenFile(
         fg.generateGrpcFile(), 'test/goldens/grpc_service.pbgrpc');
@@ -362,8 +391,9 @@
     FileGenerator fg = new FileGenerator(fd, options);
     link(options,
         [fg, new FileGenerator(fd1, options), new FileGenerator(fd2, options)]);
-    expectMatchesGoldenFile(fg.generateMainFile(), 'test/goldens/imports.pb');
     expectMatchesGoldenFile(
-        fg.generateEnumFile(), 'test/goldens/imports.pbjson');
+        fg.generateMainFile().toString(), 'test/goldens/imports.pb');
+    expectMatchesGoldenFile(
+        fg.generateEnumFile().toString(), 'test/goldens/imports.pbjson');
   });
 }
diff --git a/protoc_plugin/test/goldens/enum.meta b/protoc_plugin/test/goldens/enum.meta
new file mode 100644
index 0000000..3aa174d
--- /dev/null
+++ b/protoc_plugin/test/goldens/enum.meta
@@ -0,0 +1,48 @@
+annotation: {
+  path: 5
+  path: 0
+  path: 1
+  sourceFile: sample.proto
+  begin: 6
+  end: 15
+}
+annotation: {
+  path: 5
+  path: 0
+  path: 2
+  path: 0
+  path: 1
+  sourceFile: sample.proto
+  begin: 66
+  end: 72
+}
+annotation: {
+  path: 5
+  path: 0
+  path: 2
+  path: 1
+  path: 1
+  sourceFile: sample.proto
+  begin: 132
+  end: 136
+}
+annotation: {
+  path: 5
+  path: 0
+  path: 2
+  path: 2
+  path: 1
+  sourceFile: sample.proto
+  begin: 194
+  end: 198
+}
+annotation: {
+  path: 5
+  path: 0
+  path: 2
+  path: 3
+  path: 1
+  sourceFile: sample.proto
+  begin: 257
+  end: 265
+}
diff --git a/protoc_plugin/test/goldens/messageGenerator.meta b/protoc_plugin/test/goldens/messageGenerator.meta
new file mode 100644
index 0000000..3caf24a
--- /dev/null
+++ b/protoc_plugin/test/goldens/messageGenerator.meta
@@ -0,0 +1,8 @@
+annotation: {
+  path: 4
+  path: 0
+  path: 1
+  sourceFile: 
+  begin: 6
+  end: 17
+}
diff --git a/protoc_plugin/test/goldens/messageGeneratorEnums.meta b/protoc_plugin/test/goldens/messageGeneratorEnums.meta
new file mode 100644
index 0000000..789f5b7
--- /dev/null
+++ b/protoc_plugin/test/goldens/messageGeneratorEnums.meta
@@ -0,0 +1,58 @@
+annotation: {
+  path: 4
+  path: 0
+  path: 4
+  path: 0
+  path: 1
+  sourceFile: 
+  begin: 6
+  end: 27
+}
+annotation: {
+  path: 4
+  path: 0
+  path: 4
+  path: 0
+  path: 2
+  path: 0
+  path: 1
+  sourceFile: 
+  begin: 90
+  end: 96
+}
+annotation: {
+  path: 4
+  path: 0
+  path: 4
+  path: 0
+  path: 2
+  path: 1
+  path: 1
+  sourceFile: 
+  begin: 180
+  end: 184
+}
+annotation: {
+  path: 4
+  path: 0
+  path: 4
+  path: 0
+  path: 2
+  path: 2
+  path: 1
+  sourceFile: 
+  begin: 266
+  end: 270
+}
+annotation: {
+  path: 4
+  path: 0
+  path: 4
+  path: 0
+  path: 2
+  path: 3
+  path: 1
+  sourceFile: 
+  begin: 353
+  end: 361
+}
diff --git a/protoc_plugin/test/goldens/oneMessage.pb.meta b/protoc_plugin/test/goldens/oneMessage.pb.meta
new file mode 100644
index 0000000..b264b07
--- /dev/null
+++ b/protoc_plugin/test/goldens/oneMessage.pb.meta
@@ -0,0 +1,8 @@
+annotation: {
+  path: 4
+  path: 0
+  path: 1
+  sourceFile: test
+  begin: 299
+  end: 310
+}
diff --git a/protoc_plugin/test/goldens/topLevelEnum.pb.meta b/protoc_plugin/test/goldens/topLevelEnum.pb.meta
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/protoc_plugin/test/goldens/topLevelEnum.pb.meta
diff --git a/protoc_plugin/test/goldens/topLevelEnum.pbenum.meta b/protoc_plugin/test/goldens/topLevelEnum.pbenum.meta
new file mode 100644
index 0000000..9fc500e
--- /dev/null
+++ b/protoc_plugin/test/goldens/topLevelEnum.pbenum.meta
@@ -0,0 +1,48 @@
+annotation: {
+  path: 5
+  path: 0
+  path: 1
+  sourceFile: test
+  begin: 313
+  end: 322
+}
+annotation: {
+  path: 5
+  path: 0
+  path: 2
+  path: 0
+  path: 1
+  sourceFile: test
+  begin: 373
+  end: 379
+}
+annotation: {
+  path: 5
+  path: 0
+  path: 2
+  path: 1
+  path: 1
+  sourceFile: test
+  begin: 439
+  end: 443
+}
+annotation: {
+  path: 5
+  path: 0
+  path: 2
+  path: 2
+  path: 1
+  sourceFile: test
+  begin: 501
+  end: 505
+}
+annotation: {
+  path: 5
+  path: 0
+  path: 2
+  path: 3
+  path: 1
+  sourceFile: test
+  begin: 564
+  end: 572
+}
diff --git a/protoc_plugin/test/indenting_writer_test.dart b/protoc_plugin/test/indenting_writer_test.dart
index 61f0427..6c006fc 100755
--- a/protoc_plugin/test/indenting_writer_test.dart
+++ b/protoc_plugin/test/indenting_writer_test.dart
@@ -6,11 +6,12 @@
 library indenting_writer_test;
 
 import 'package:protoc_plugin/indenting_writer.dart';
+import 'package:protoc_plugin/src/descriptor.pb.dart';
 import 'package:test/test.dart';
 
 void main() {
   test('IndentingWriter can indent a block', () {
-    var out = new IndentingWriter();
+    var out = new IndentingWriter(filename: '');
     out.addBlock('class test {', '}', () {
       out.println('first;');
       out.println();
@@ -25,4 +26,45 @@
 }
 ''');
   });
+
+  test('IndentingWriter annotation tracks previous output', () {
+    var out = new IndentingWriter(filename: 'sample.proto');
+    out.print('13 characters');
+    out.print('sample text');
+    out.addAnnotation([1, 2, 3], 'text', 7);
+    GeneratedCodeInfo_Annotation expected = new GeneratedCodeInfo_Annotation()
+      ..path.addAll([1, 2, 3])
+      ..sourceFile = 'sample.proto'
+      ..begin = 20
+      ..end = 24;
+    GeneratedCodeInfo_Annotation annotation =
+        out.sourceLocationInfo.annotation[0];
+    expect(annotation, equals(expected));
+  });
+
+  test('IndentingWriter annotation counts indents correctly', () {
+    var out = new IndentingWriter(filename: '');
+    out.addBlock('34 characters including newline {', '}', () {
+      out.println('sample text');
+      out.addAnnotation([], 'sample', 0);
+    });
+    GeneratedCodeInfo_Annotation annotation =
+        out.sourceLocationInfo.annotation[0];
+    expect(annotation.begin, equals(34));
+    expect(annotation.end, equals(40));
+  });
+
+  test('IndentingWriter annotations counts multiline output correctly', () {
+    var out = new IndentingWriter(filename: '');
+    out.print('20 characters\ntotal\n');
+    out.println('20 characters before this');
+    out.addAnnotation([], 'ch', 3);
+    GeneratedCodeInfo_Annotation annotation =
+        out.sourceLocationInfo.annotation[0];
+    expect(annotation.begin, equals(23));
+    expect(annotation.end, equals(25));
+  });
+
+  test('IndentingWriter does not break when making annotation for null file',
+      () {});
 }
diff --git a/protoc_plugin/test/message_generator_test.dart b/protoc_plugin/test/message_generator_test.dart
index 70b0341..dcc8deb 100755
--- a/protoc_plugin/test/message_generator_test.dart
+++ b/protoc_plugin/test/message_generator_test.dart
@@ -62,19 +62,23 @@
 
     FileGenerator fg = new FileGenerator(fd, options);
     MessageGenerator mg =
-        new MessageGenerator(md, fg, {}, null, new Set<String>());
+        new MessageGenerator.topLevel(md, fg, {}, null, new Set<String>(), 0);
 
     var ctx = new GenerationContext(options);
     mg.register(ctx);
     mg.resolve(ctx);
 
-    var writer = new IndentingWriter();
+    var writer = new IndentingWriter(filename: '');
     mg.generate(writer);
     expectMatchesGoldenFile(writer.toString(), 'test/goldens/messageGenerator');
+    expectMatchesGoldenFile(writer.sourceLocationInfo.toString(),
+        'test/goldens/messageGenerator.meta');
 
-    writer = new IndentingWriter();
+    writer = new IndentingWriter(filename: '');
     mg.generateEnums(writer);
     expectMatchesGoldenFile(
         writer.toString(), 'test/goldens/messageGeneratorEnums');
+    expectMatchesGoldenFile(writer.sourceLocationInfo.toString(),
+        'test/goldens/messageGeneratorEnums.meta');
   });
 }