From 8d1c32d5ac2926033e03b145add5ab9a687fed14 Mon Sep 17 00:00:00 2001 From: Ernest Micklei Date: Wed, 12 Apr 2017 22:27:24 +0200 Subject: [PATCH] assigning comment to ast nodes (visitee). Issue #5 --- cmd/protofmt/comments.proto | 40 +++++ cmd/protofmt/main.go | 8 +- cmd/protofmt/unittest_proto2_formatted.proto | 90 +++-------- .../unittest_proto3_arena_formatted.proto | 19 +-- cmd/protofmt/unittest_proto3_formatted.proto | 50 +------ comment.go | 130 ++++++++++++++++ comment_test.go | 81 ++++++++++ enum.go | 48 ++++-- enum_test.go | 7 + extensions.go | 7 +- extensions_test.go | 7 +- field.go | 34 +++-- formatter.go | 99 +++++++------ formatter_test.go | 140 ++++++++++-------- formatter_utils.go | 94 ++++++++---- group.go | 21 ++- group_test.go | 15 +- import.go | 18 ++- message.go | 35 ++++- message_test.go | 2 +- oneof.go | 20 ++- option.go | 42 +++--- option_test.go | 32 ++++ package.go | 24 +-- parser.go | 5 - parser_test.go | 24 ++- proto.go | 69 +++------ reserved.go | 9 +- scanner_test.go | 9 +- service.go | 34 ++++- service_test.go | 27 ++-- syntax.go | 22 ++- syntax_test.go | 26 +--- visitor.go | 13 +- 34 files changed, 832 insertions(+), 469 deletions(-) create mode 100644 cmd/protofmt/comments.proto create mode 100644 comment.go create mode 100644 comment_test.go diff --git a/cmd/protofmt/comments.proto b/cmd/protofmt/comments.proto new file mode 100644 index 0000000..b5d0715 --- /dev/null +++ b/cmd/protofmt/comments.proto @@ -0,0 +1,40 @@ +/* + begin +*/ + +// comment 1 +// comment 2 +syntax = "proto"; // inline 1 + +// comment 3 +// comment 4 +package test; // inline 2 + +// comment 5 +// comment 6 +message Test { + // comment 7 + // comment 8 + int64 i = 1; // inline 3 + /* + cstyle + */ +} + +// comment 9 +enum Codes { + // comment 10 + Unknown = 0; // inline 4 +} + +// comment 11 +service API { + // comment 12 + rpc doit (In) returns (Out); // inline 5 +} + +// comment 13 +import "some.proto"; // inline 6 + +// comment 14 +option (test.option) = 42; // inline 7 \ No newline at end of file diff --git a/cmd/protofmt/main.go b/cmd/protofmt/main.go index 82e3fc0..793275d 100644 --- a/cmd/protofmt/main.go +++ b/cmd/protofmt/main.go @@ -36,7 +36,8 @@ import ( ) var ( - overwrite = flag.Bool("w", false, "write result to (source) file instead of stdout") + oOverwrite = flag.Bool("w", false, "write result to (source) file instead of stdout") + oDebug = flag.Bool("d", false, "dump the AST for debugging") ) // go run *.go unformatted.proto @@ -64,7 +65,7 @@ func readFormatWrite(filename string) error { if err := format(file, buf); err != nil { return err } - if *overwrite { + if *oOverwrite { // write back to input if err := ioutil.WriteFile(filename, buf.Bytes(), os.ModePerm); err != nil { return err @@ -84,6 +85,9 @@ func format(input io.Reader, output io.Writer) error { if err != nil { return err } + // if *oDebug { + // spew.Dump(def) + // } proto.NewFormatter(output, " ").Format(def) // 2 spaces return nil } diff --git a/cmd/protofmt/unittest_proto2_formatted.proto b/cmd/protofmt/unittest_proto2_formatted.proto index 9f552c1..607087b 100644 --- a/cmd/protofmt/unittest_proto2_formatted.proto +++ b/cmd/protofmt/unittest_proto2_formatted.proto @@ -1,4 +1,3 @@ - // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ @@ -28,6 +27,7 @@ // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + // Author: kenton@google.com (Kenton Varda) // Based on original Protocol Buffers design by // Sanjay Ghemawat, Jeff Dean, and others. @@ -37,29 +37,26 @@ syntax = "proto2"; // Some generic_services option(s) added automatically. // See: http://go/proto2-generic-services-default -option cc_generic_services = true; // auto-added +option cc_generic_services = true; // auto-added option java_generic_services = true; // auto-added -option py_generic_services = true; // auto-added -option cc_enable_arenas = true; - -import "google/protobuf/unittest_import.proto"; +option py_generic_services = true; // auto-added +option cc_enable_arenas = true; +import "google/protobuf/unittest_import.proto"; // We don't put this in a package within proto2 because we need to make sure // that the generated code doesn't depend on being in the proto2 namespace. // In test_util.h we do "using namespace unittest = protobuf_unittest". package protobuf_unittest; - // Protos optimized for SPEED use a strict superset of the generated code // of equivalent ones optimized for CODE_SIZE, so we should optimize all our // tests for speed unless explicitly testing code size optimization. -option optimize_for = SPEED; +option optimize_for = SPEED; option java_outer_classname = "UnittestProto"; // This proto includes every type of field in both singular and repeated // forms. message TestAllTypes { message NestedMessage { - // The field name "b" fails to compile in proto1 because it conflicts with // a local variable named "b" in one of the generated methods. Doh. // This file needs to compile in proto1 to test backwards-compatibility. @@ -71,7 +68,6 @@ message TestAllTypes { BAZ = 3; NEG = -1; // Intentionally negative. } - // Singular optional int32 optional_int32 = 1; optional int64 optional_int64 = 2; @@ -88,6 +84,7 @@ message TestAllTypes { optional bool optional_bool = 13; optional string optional_string = 14; optional bytes optional_bytes = 15; + optional group OptionalGroup = 16 { optional int32 a = 17; } @@ -99,11 +96,9 @@ message TestAllTypes { optional protobuf_unittest_import.ImportEnum optional_import_enum = 23; optional string optional_string_piece = 24 [ctype = STRING_PIECE]; optional string optional_cord = 25 [ctype = CORD ]; - // Defined in unittest_import_public.proto optional protobuf_unittest_import.PublicImportMessage optional_public_import_message = 26; optional NestedMessage optional_lazy_message = 27 [lazy = true]; - // Repeated repeated int32 repeated_int32 = 31; repeated int64 repeated_int64 = 32; @@ -132,7 +127,6 @@ message TestAllTypes { repeated string repeated_string_piece = 54 [ctype = STRING_PIECE]; repeated string repeated_cord = 55 [ctype = CORD ]; repeated NestedMessage repeated_lazy_message = 57 [lazy = true ]; - // Singular with defaults optional int32 default_int32 = 61 [default = 41 ] ; optional int64 default_int64 = 62 [default = 42 ] ; @@ -155,7 +149,6 @@ message TestAllTypes { optional string default_string_piece = 84 [ctype = STRING_PIECE, default = "abc"]; optional string default_cord = 85 [ctype = CORD , default = "123"]; - // For oneof test oneof oneof_field { uint32 oneof_uint32 = 111; NestedMessage oneof_nested_message = 112; @@ -163,7 +156,6 @@ message TestAllTypes { bytes oneof_bytes = 114; } } - // This proto includes a recusively nested message. message NestedTestAllTypes { optional NestedTestAllTypes child = 1; @@ -173,20 +165,17 @@ message NestedTestAllTypes { message TestDeprecatedFields { optional int32 deprecated_int32 = 1 [deprecated = true]; } - // Define these after TestAllTypes to make sure the compiler can handle // that. message ForeignMessage { optional int32 c = 1; optional int32 d = 2; } - enum ForeignEnum { FOREIGN_FOO = 4; FOREIGN_BAR = 5; FOREIGN_BAZ = 6; } - message TestReservedFields { reserved 2, 15, 9 to 11; reserved "bar", "baz"; @@ -195,7 +184,6 @@ message TestAllExtensions { extensions 1 to max; } extend TestAllExtensions { - // Singular optional int32 optional_int32_extension = 1; optional int64 optional_int64_extension = 2; @@ -212,6 +200,7 @@ extend TestAllExtensions { optional bool optional_bool_extension = 13; optional string optional_string_extension = 14; optional bytes optional_bytes_extension = 15; + optional group OptionalGroup_extension = 16 { optional int32 a = 17; } @@ -225,7 +214,6 @@ extend TestAllExtensions { optional string optional_cord_extension = 25 [ctype = CORD ]; optional protobuf_unittest_import.PublicImportMessage optional_public_import_message_extension = 26; optional TestAllTypes.NestedMessage optional_lazy_message_extension = 27 [lazy = true ]; - // Repeated repeated int32 repeated_int32_extension = 31; repeated int64 repeated_int64_extension = 32; @@ -254,7 +242,6 @@ extend TestAllExtensions { repeated string repeated_string_piece_extension = 54 [ctype = STRING_PIECE]; repeated string repeated_cord_extension = 55 [ctype = CORD ]; repeated TestAllTypes.NestedMessage repeated_lazy_message_extension = 57 [lazy = true ]; - // Singular with defaults optional int32 default_int32_extension = 61 [default = 41 ] ; optional int64 default_int64_extension = 62 [default = 42 ] ; @@ -276,7 +263,6 @@ extend TestAllExtensions { optional protobuf_unittest_import.ImportEnum default_import_enum_extension = 83 [default = IMPORT_BAR ] ; optional string default_string_piece_extension = 84 [ctype = STRING_PIECE, default = "abc"]; optional string default_cord_extension = 85 [ctype = CORD , default = "123"]; - // For oneof test optional uint32 oneof_uint32_extension = 111; optional TestAllTypes.NestedMessage oneof_nested_message_extension = 112; @@ -285,17 +271,14 @@ extend TestAllExtensions { } message TestNestedExtension { extend TestAllExtensions { - // Check for bug where string extensions declared in tested scope did not // compile. optional string test = 1002 [default = "test"]; - // Used to test if generated extension name is correct when there are // underscores. optional string nested_string_extension = 1003; } } - // We have separate messages for testing required fields because it's // annoying to have to fill in required fields in TestProto in order to // do anything with it. Note that we don't need to test every type of @@ -309,7 +292,6 @@ message TestRequired { optional TestRequired single = 1000; repeated TestRequired multi = 1001; } - // Pad the field count to 32 so that we can test that IsInitialized() // properly checks multiple elements of has_bits_. optional int32 dummy4 = 4; @@ -348,15 +330,12 @@ message TestRequiredForeign { repeated TestRequired repeated_message = 2; optional int32 dummy = 3; } - // Test that we can use NestedMessage from outside TestAllTypes. message TestForeignNested { optional TestAllTypes.NestedMessage foreign_nested = 1; } - // TestEmptyMessage is used to test unknown field support. message TestEmptyMessage {} - // Like above, but declare all field numbers as potential extensions. No // actual extensions should ever be defined for this type. message TestEmptyMessageWithExtensions { @@ -367,10 +346,8 @@ message TestMultipleExtensionRanges { extensions 4143 to 4243; extensions 65536 to max; } - // Test that really large tag numbers don't break anything. message TestReallyLargeTagNumber { - // The largest possible tag number is 2^28 - 1, since the wire format uses // three bits to communicate wire type. optional int32 a = 1; @@ -380,7 +357,6 @@ message TestRecursiveMessage { optional TestRecursiveMessage a = 1; optional int32 i = 2; } - // Test that mutual recursion works. message TestMutualRecursionA { optional TestMutualRecursionB bb = 1; @@ -389,28 +365,26 @@ message TestMutualRecursionB { optional TestMutualRecursionA a = 1; optional int32 optional_int32 = 2; } - // Test that groups have disjoint field numbers from their siblings and // parents. This is NOT possible in proto1; only google.protobuf. When attempting // to compile with proto1, this will emit an error; so we only include it // in protobuf_unittest_proto. message TestDupFieldNumber { - // NO_PROTO1 optional int32 a = 1; // NO_PROTO1 + optional group Foo = 2 { optional int32 a = 1; } - // NO_PROTO1 - optional group Bar = 3 { +optional group Bar = 3 { optional int32 a = 1; } +// NO_PROTO1 - // NO_PROTO1 } - // NO_PROTO1 + // Additional messages for testing lazy fields. message TestEagerMessage { optional TestAllTypes sub_message = 1 [lazy = false]; @@ -418,7 +392,6 @@ message TestEagerMessage { message TestLazyMessage { optional TestAllTypes sub_message = 1 [lazy = true]; } - // Needed for a Python test. message TestNestedMessageHasBits { message NestedMessage { @@ -427,7 +400,6 @@ message TestNestedMessageHasBits { } optional NestedMessage optional_nested_message = 1; } - // Test an enum that has multiple values with the same number. enum TestEnumWithDupValue { option allow_alias = true; @@ -437,7 +409,6 @@ enum TestEnumWithDupValue { FOO2 = 1; BAR2 = 2; } - // Test an enum with large, unordered values. enum TestSparseEnum { SPARSE_A = 123; @@ -448,7 +419,6 @@ enum TestSparseEnum { SPARSE_F = 0; SPARSE_G = 2; } - // Test message with CamelCase field names. This violates Protocol Buffer // standard style. message TestCamelCaseFieldNames { @@ -465,18 +435,18 @@ message TestCamelCaseFieldNames { repeated string RepeatedStringPieceField = 11 [ctype = STRING_PIECE]; repeated string RepeatedCordField = 12 [ctype = CORD ]; } - // We list fields out of order, to ensure that we're using field number and not // field index to determine serialization order. message TestFieldOrderings { optional string my_string = 11; + extensions 2 to 10; optional int64 my_int = 1; extensions 12 to 100; optional float my_float = 101; + message NestedMessage { optional int64 oo = 2; - // The field name "b" fails to compile in proto1 because it conflicts with // a local variable named "b" in one of the generated methods. Doh. // This file needs to compile in proto1 to test backwards-compatibility. @@ -496,23 +466,19 @@ message TestExtremeDefaultValues { optional int64 small_int64 = 5 [default = -0x7FFFFFFFFFFFFFFF ]; optional int32 really_small_int32 = 21 [default = -0x80000000 ]; optional int64 really_small_int64 = 22 [default = -0x8000000000000000 ]; - // The default value here is UTF-8 for "\u1234". (We could also just type // the UTF-8 text directly into this text file rather than escape it, but // lots of people use editors that would be confused by this.) optional string utf8_string = 6 [default = "\341\210\264"]; - // Tests for single-precision floating-point values. optional float zero_float = 7 [default = 0 ]; optional float one_float = 8 [default = 1 ]; optional float small_float = 9 [default = 1.5 ]; optional float negative_one_float = 10 [default = -1 ]; optional float negative_float = 11 [default = -1.5]; - // Using exponents optional float large_float = 12 [default = 2E8 ]; optional float small_negative_float = 13 [default = -8e-28]; - // Text for nonfinite floating-point values. optional double inf_double = 14 [default = inf ]; optional double neg_inf_double = 15 [default = -inf]; @@ -520,14 +486,12 @@ message TestExtremeDefaultValues { optional float inf_float = 17 [default = inf ]; optional float neg_inf_float = 18 [default = -inf]; optional float nan_float = 19 [default = nan ]; - // Tests for C++ trigraphs. // Trigraphs should be escaped in C++ generated files, but they should not be // escaped for other languages. // Note that in .proto file, "\?" is a valid way to escape ? in string // literals. optional string cpp_trigraph = 20 [default = "? \? ?? \?? \??? ??/ ?\?-"]; - // String defaults containing the character '\000' optional string string_with_zero = 23 [default = "hel\000lo" ] ; optional bytes bytes_with_zero = 24 [default = "wor\000ld" ] ; @@ -538,7 +502,6 @@ message TestExtremeDefaultValues { message SparseEnumMessage { optional TestSparseEnum sparse_enum = 1; } - // Test String and Bytes: string is for valid UTF-8 strings message OneString { optional string data = 1; @@ -552,7 +515,6 @@ message OneBytes { message MoreBytes { repeated bytes data = 1; } - // Test int32, uint32, int64, uint64, and bool are all compatible message Int32Message { optional int32 data = 1; @@ -569,13 +531,13 @@ message Uint64Message { message BoolMessage { optional bool data = 1; } - // Test oneofs. message TestOneof { oneof foo { int32 foo_int = 1; string foo_string = 2; TestAllTypes foo_message = 3; + group FooGroup = 4 { optional int32 a = 5; optional string b = 6; @@ -586,6 +548,7 @@ message TestOneofBackwardsCompatible { optional int32 foo_int = 1; optional string foo_string = 2; optional TestAllTypes foo_message = 3; + optional group FooGroup = 4 { optional int32 a = 5; optional string b = 6; @@ -600,6 +563,7 @@ message TestOneof2 { bytes foo_bytes = 5; NestedEnum foo_enum = 6; NestedMessage foo_message = 7; + group FooGroup = 8 { optional int32 a = 9; optional string b = 10; @@ -616,6 +580,7 @@ message TestOneof2 { } optional int32 baz_int = 18; optional string baz_string = 19 [default = "BAZ"]; + message NestedMessage { optional int64 qux_int = 1; repeated int32 corge_int = 2; @@ -636,7 +601,6 @@ message TestRequiredOneof { double required_double = 1; } } - // Test messages for packed fields message TestPackedTypes { repeated int32 packed_int32 = 90 [packed = true]; @@ -654,7 +618,6 @@ message TestPackedTypes { repeated bool packed_bool = 102 [packed = true]; repeated ForeignEnum packed_enum = 103 [packed = true]; } - // A message with the same fields as TestPackedTypes, but without packing. Used // to test packed <-> unpacked wire compatibility. message TestUnpackedTypes { @@ -711,7 +674,6 @@ extend TestUnpackedExtensions { repeated bool unpacked_bool_extension = 102 [packed = false]; repeated ForeignEnum unpacked_enum_extension = 103 [packed = false]; } - // Used by ExtensionSetTest/DynamicExtensions. The test actually builds // a set of extensions to TestAllExtensions dynamically, based on the fields // of this message type. @@ -733,37 +695,32 @@ message TestDynamicExtensions { repeated sint32 packed_extension = 2006 [packed = true]; } message TestRepeatedScalarDifferentTagSizes { - // Parsing repeated fixed size values used to fail. This message needs to be // used in order to get a tag of the right size; all of the repeated fields // in TestAllTypes didn't trigger the check. repeated fixed32 repeated_fixed32 = 12; - // Check for a varint type, just for good measure. repeated int32 repeated_int32 = 13; - // These have two-byte tags. repeated fixed64 repeated_fixed64 = 2046; repeated int64 repeated_int64 = 2047; - // Three byte tags. repeated float repeated_float = 262142; repeated uint64 repeated_uint64 = 262143; } - // Test that if an optional or required message/group field appears multiple // times in the input, they need to be merged. message TestParsingMerge { - // RepeatedFieldsGenerator defines matching field types as TestParsingMerge, // except that all fields are repeated. In the tests, we will serialize the // RepeatedFieldsGenerator to bytes, and parse the bytes to TestParsingMerge. // Repeated fields in RepeatedFieldsGenerator are expected to be merged into // the corresponding required/optional fields in TestParsingMerge. - message RepeatedFieldsGenerator { +message RepeatedFieldsGenerator { repeated TestAllTypes field1 = 1; repeated TestAllTypes field2 = 2; repeated TestAllTypes field3 = 3; + group Group1 = 10 { optional TestAllTypes field1 = 11; } @@ -776,6 +733,7 @@ message TestParsingMerge { TestAllTypes required_all_types = 1; optional TestAllTypes optional_all_types = 2; repeated TestAllTypes repeated_all_types = 3; + optional group OptionalGroup = 10 { optional TestAllTypes optional_group_all_types = 11; } @@ -783,17 +741,16 @@ message TestParsingMerge { optional TestAllTypes repeated_group_all_types = 21; } extensions 1000 to max; + extend TestParsingMerge { optional TestAllTypes optional_ext = 1000; repeated TestAllTypes repeated_ext = 1001; } } message TestCommentInjectionMessage { - // */ <- This should not close the generated doc comment optional string a = 1 [default = "*/ <- Neither should this."]; } - // Test that RPC services work. message FooRequest {} message FooResponse {} @@ -804,7 +761,6 @@ service TestService { rpc Foo (FooRequest) returns (FooResponse); rpc Bar (BarRequest) returns (BarResponse); } - message BarRequest {} message BarResponse {} message TestJsonName { @@ -824,10 +780,12 @@ message TestHugeFieldNumbers { optional string optional_string = 536870005; optional bytes optional_bytes = 536870006; optional ForeignMessage optional_message = 536870007; + optional group OptionalGroup = 536870008 { optional int32 group_a = 536870009; } map string_string_map = 536870010; + oneof oneof_field { uint32 oneof_uint32 = 536870011; TestAllTypes oneof_test_all_types = 536870012; diff --git a/cmd/protofmt/unittest_proto3_arena_formatted.proto b/cmd/protofmt/unittest_proto3_arena_formatted.proto index 161f578..097c250 100644 --- a/cmd/protofmt/unittest_proto3_arena_formatted.proto +++ b/cmd/protofmt/unittest_proto3_arena_formatted.proto @@ -1,4 +1,3 @@ - // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ @@ -31,8 +30,7 @@ syntax = "proto"; option cc_enable_arenas = true; - -import "google/protobuf/unittest_import.proto"; +import "google/protobuf/unittest_import.proto"; package proto_arena_unittest; @@ -40,7 +38,6 @@ package proto_arena_unittest; // forms. message TestAllTypes { message NestedMessage { - // The field name "b" fails to compile in proto1 because it conflicts with // a local variable named "b" in one of the generated methods. Doh. // This file needs to compile in proto1 to test backwards-compatibility. @@ -53,7 +50,6 @@ message TestAllTypes { BAZ = 3; NEG = -1; // Intentionally negative. } - // Singular int32 optional_int32 = 1; int64 optional_int64 = 2; @@ -70,7 +66,6 @@ message TestAllTypes { bool optional_bool = 13; string optional_string = 14; bytes optional_bytes = 15; - // Groups are not allowed in proto. // optional group OptionalGroup = 16 { // optional int32 a = 17; @@ -80,18 +75,15 @@ message TestAllTypes { protobuf_unittest_import.ImportMessage optional_import_message = 20; NestedEnum optional_nested_enum = 21; ForeignEnum optional_foreign_enum = 22; - // Omitted (compared to unittest.proto) because proto2 enums are not allowed // inside proto2 messages. // // optional protobuf_unittest_import.ImportEnum optional_import_enum = 23; string optional_string_piece = 24 [ctype = STRING_PIECE]; string optional_cord = 25 [ctype = CORD ]; - // Defined in unittest_import_public.proto protobuf_unittest_import.PublicImportMessage optional_public_import_message = 26; NestedMessage optional_lazy_message = 27 [lazy = true]; - // Repeated repeated int32 repeated_int32 = 31; repeated int64 repeated_int64 = 32; @@ -108,7 +100,6 @@ message TestAllTypes { repeated bool repeated_bool = 43; repeated string repeated_string = 44; repeated bytes repeated_bytes = 45; - // Groups are not allowed in proto. // repeated group RepeatedGroup = 46 { // optional int32 a = 47; @@ -118,7 +109,6 @@ message TestAllTypes { repeated protobuf_unittest_import.ImportMessage repeated_import_message = 50; repeated NestedEnum repeated_nested_enum = 51; repeated ForeignEnum repeated_foreign_enum = 52; - // Omitted (compared to unittest.proto) because proto2 enums are not allowed // inside proto2 messages. // @@ -126,6 +116,7 @@ message TestAllTypes { repeated string repeated_string_piece = 54 [ctype = STRING_PIECE]; repeated string repeated_cord = 55 [ctype = CORD ]; repeated NestedMessage repeated_lazy_message = 57 [lazy = true ]; + oneof oneof_field { uint32 oneof_uint32 = 111; NestedMessage oneof_nested_message = 112; @@ -133,7 +124,6 @@ message TestAllTypes { bytes oneof_bytes = 114; } } - // Test messages for packed fields message TestPackedTypes { repeated int32 packed_int32 = 90 [packed = true]; @@ -151,7 +141,6 @@ message TestPackedTypes { repeated bool packed_bool = 102 [packed = true]; repeated ForeignEnum packed_enum = 103 [packed = true]; } - // Explicitly set packed to false message TestUnpackedTypes { repeated int32 repeated_int32 = 1 [packed = false]; @@ -169,25 +158,21 @@ message TestUnpackedTypes { repeated bool repeated_bool = 13 [packed = false]; repeated TestAllTypes.NestedEnum repeated_nested_enum = 14 [packed = false]; } - // This proto includes a recusively nested message. message NestedTestAllTypes { NestedTestAllTypes child = 1; TestAllTypes payload = 2; } - // Define these after TestAllTypes to make sure the compiler can handle // that. message ForeignMessage { int32 c = 1; } - enum ForeignEnum { FOREIGN_ZERO = 0; FOREIGN_FOO = 4; FOREIGN_BAR = 5; FOREIGN_BAZ = 6; } - // TestEmptyMessage is used to test behavior of unknown fields. message TestEmptyMessage {} diff --git a/cmd/protofmt/unittest_proto3_formatted.proto b/cmd/protofmt/unittest_proto3_formatted.proto index a21aa83..e33378c 100644 --- a/cmd/protofmt/unittest_proto3_formatted.proto +++ b/cmd/protofmt/unittest_proto3_formatted.proto @@ -1,4 +1,3 @@ - // Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ @@ -28,6 +27,7 @@ // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + // Author: kenton@google.com (Kenton Varda) // Based on original Protocol Buffers design by // Sanjay Ghemawat, Jeff Dean, and others. @@ -37,30 +37,27 @@ syntax = "proto"; // Some generic_services option(s) added automatically. // See: http://go/proto2-generic-services-default -option cc_generic_services = true; // auto-added -option java_generic_services = true; // auto-added -option py_generic_services = true; // auto-added -option cc_enable_arenas = true; -option csharp_namespace = "Google.Protobuf.TestProtos"; - -import "google/protobuf/unittest_import_proto.proto"; +option cc_generic_services = true; // auto-added +option java_generic_services = true; // auto-added +option py_generic_services = true; // auto-added +option cc_enable_arenas = true; +option csharp_namespace = "Google.Protobuf.TestProtos"; +import "google/protobuf/unittest_import_proto.proto"; // We don't put this in a package within proto2 because we need to make sure // that the generated code doesn't depend on being in the proto2 namespace. // In test_util.h we do "using namespace unittest = protobuf_unittest". package protobuf_unittest; - // Protos optimized for SPEED use a strict superset of the generated code // of equivalent ones optimized for CODE_SIZE, so we should optimize all our // tests for speed unless explicitly testing code size optimization. -option optimize_for = SPEED; +option optimize_for = SPEED; option java_outer_classname = "UnittestProto"; // This proto includes every type of field in both singular and repeated // forms. message TestAllTypes { message NestedMessage { - // The field name "b" fails to compile in proto1 because it conflicts with // a local variable named "b" in one of the generated methods. Doh. // This file needs to compile in proto1 to test backwards-compatibility. @@ -73,7 +70,6 @@ message TestAllTypes { BAZ = 3; NEG = -1; // Intentionally negative. } - // Singular int32 single_int32 = 1; int64 single_int64 = 2; @@ -96,10 +92,8 @@ message TestAllTypes { NestedEnum single_nested_enum = 21; ForeignEnum single_foreign_enum = 22; protobuf_unittest_import.ImportEnum single_import_enum = 23; - // Defined in unittest_import_public.proto protobuf_unittest_import.PublicImportMessage single_public_import_message = 26; - // Repeated repeated int32 repeated_int32 = 31; repeated int64 repeated_int64 = 32; @@ -122,11 +116,9 @@ message TestAllTypes { repeated NestedEnum repeated_nested_enum = 51; repeated ForeignEnum repeated_foreign_enum = 52; repeated protobuf_unittest_import.ImportEnum repeated_import_enum = 53; - // Defined in unittest_import_public.proto repeated protobuf_unittest_import.PublicImportMessage repeated_public_import_message = 54; - // For oneof test oneof oneof_field { uint32 oneof_uint32 = 111; NestedMessage oneof_nested_message = 112; @@ -134,7 +126,6 @@ message TestAllTypes { bytes oneof_bytes = 114; } } - // This proto includes a recusively nested message. message NestedTestAllTypes { NestedTestAllTypes child = 1; @@ -144,33 +135,27 @@ message NestedTestAllTypes { message TestDeprecatedFields { int32 deprecated_int32 = 1 [deprecated = true]; } - // Define these after TestAllTypes to make sure the compiler can handle // that. message ForeignMessage { int32 c = 1; } - enum ForeignEnum { FOREIGN_UNSPECIFIED = 0; FOREIGN_FOO = 4; FOREIGN_BAR = 5; FOREIGN_BAZ = 6; } - message TestReservedFields { reserved 2, 15, 9 to 11; reserved "bar", "baz"; } - // Test that we can use NestedMessage from outside TestAllTypes. message TestForeignNested { TestAllTypes.NestedMessage foreign_nested = 1; } - // Test that really large tag numbers don't break anything. message TestReallyLargeTagNumber { - // The largest possible tag number is 2^28 - 1, since the wire format uses // three bits to communicate wire type. int32 a = 1; @@ -180,7 +165,6 @@ message TestRecursiveMessage { TestRecursiveMessage a = 1; int32 i = 2; } - // Test that mutual recursion works. message TestMutualRecursionA { TestMutualRecursionB bb = 1; @@ -189,7 +173,6 @@ message TestMutualRecursionB { TestMutualRecursionA a = 1; int32 optional_int32 = 2; } - // Test an enum that has multiple values with the same number. enum TestEnumWithDupValue { TEST_ENUM_WITH_DUP_VALUE_UNSPECIFIED = 0; @@ -200,7 +183,6 @@ enum TestEnumWithDupValue { FOO2 = 1; BAR2 = 2; } - // Test an enum with large, unordered values. enum TestSparseEnum { TEST_SPARSE_ENUM_UNSPECIFIED = 0; @@ -209,12 +191,10 @@ enum TestSparseEnum { SPARSE_C = 12589234; SPARSE_D = -15; SPARSE_E = -53452; - // In proto, value 0 must be the first one specified // SPARSE_F = 0; SPARSE_G = 2; } - // Test message with CamelCase field names. This violates Protocol Buffer // standard style. message TestCamelCaseFieldNames { @@ -227,7 +207,6 @@ message TestCamelCaseFieldNames { repeated ForeignEnum RepeatedEnumField = 9; repeated ForeignMessage RepeatedMessageField = 10; } - // We list fields out of order, to ensure that we're using field number and not // field index to determine serialization order. message TestFieldOrderings { @@ -236,7 +215,6 @@ message TestFieldOrderings { float my_float = 101; message NestedMessage { int64 oo = 2; - // The field name "b" fails to compile in proto1 because it conflicts with // a local variable named "b" in one of the generated methods. Doh. // This file needs to compile in proto1 to test backwards-compatibility. @@ -247,7 +225,6 @@ message TestFieldOrderings { message SparseEnumMessage { TestSparseEnum sparse_enum = 1; } - // Test String and Bytes: string is for valid UTF-8 strings message OneString { string data = 1; @@ -261,7 +238,6 @@ message OneBytes { message MoreBytes { bytes data = 1; } - // Test int32, uint32, int64, uint64, and bool are all compatible message Int32Message { int32 data = 1; @@ -278,7 +254,6 @@ message Uint64Message { message BoolMessage { bool data = 1; } - // Test oneofs. message TestOneof { oneof foo { @@ -287,7 +262,6 @@ message TestOneof { TestAllTypes foo_message = 3; } } - // Test messages for packed fields message TestPackedTypes { repeated int32 packed_int32 = 90 [packed = true]; @@ -305,7 +279,6 @@ message TestPackedTypes { repeated bool packed_bool = 102 [packed = true]; repeated ForeignEnum packed_enum = 103 [packed = true]; } - // A message with the same fields as TestPackedTypes, but without packing. Used // to test packed <-> unpacked wire compatibility. message TestUnpackedTypes { @@ -325,29 +298,23 @@ message TestUnpackedTypes { repeated ForeignEnum unpacked_enum = 103 [packed = false]; } message TestRepeatedScalarDifferentTagSizes { - // Parsing repeated fixed size values used to fail. This message needs to be // used in order to get a tag of the right size; all of the repeated fields // in TestAllTypes didn't trigger the check. repeated fixed32 repeated_fixed32 = 12; - // Check for a varint type, just for good measure. repeated int32 repeated_int32 = 13; - // These have two-byte tags. repeated fixed64 repeated_fixed64 = 2046; repeated int64 repeated_int64 = 2047; - // Three byte tags. repeated float repeated_float = 262142; repeated uint64 repeated_uint64 = 262143; } message TestCommentInjectionMessage { - // */ <- This should not close the generated doc comment string a = 1; } - // Test that RPC services work. message FooRequest {} message FooResponse {} @@ -358,6 +325,5 @@ service TestService { rpc Foo (FooRequest) returns (FooResponse); rpc Bar (BarRequest) returns (BarResponse); } - message BarRequest {} message BarResponse {} diff --git a/comment.go b/comment.go new file mode 100644 index 0000000..6b83b84 --- /dev/null +++ b/comment.go @@ -0,0 +1,130 @@ +// Copyright (c) 2017 Ernest Micklei +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package proto + +import "strings" + +// Comment holds a message. +type Comment struct { + lineNumber int + Lines []string + Cstyle bool // refers to /* ... */, C++ style is using // +} + +// newComment returns a comment. +func newComment(lit string) *Comment { + nonEmpty := []string{} + lines := strings.Split(lit, "\n") + for _, each := range lines { + nonEmpty = append(nonEmpty, each) + } + return &Comment{Lines: nonEmpty, Cstyle: len(lines) > 1} +} + +// columns is part of columnsPrintable +func (c *Comment) columnsPrintables() (list []columnsPrintable) { + for _, each := range c.Lines { + list = append(list, inlineComment{each}) + } + return +} + +type inlineComment struct { + line string +} + +func (i inlineComment) columns() (list []aligned) { + return append(list, notAligned("//"+i.line)) +} + +// Accept dispatches the call to the visitor. +func (c *Comment) Accept(v Visitor) { + v.VisitComment(c) +} + +// Merge appends all lines from the argument comment. +func (c *Comment) Merge(other *Comment) { + c.Lines = append(c.Lines, other.Lines...) + c.Cstyle = c.Cstyle || other.Cstyle +} + +// Message returns the first line or empty if no lines. +func (c Comment) Message() string { + if len(c.Lines) == 0 { + return "" + } + return c.Lines[0] +} + +// commentInliner is for types that can have an inline comment. +type commentInliner interface { + inlineComment(c *Comment) +} + +// maybeScanInlineComment tries to scan comment on the current line ; if present then set it for the last element added. +func maybeScanInlineComment(p *Parser, c elementContainer) { + currentLine := p.s.line + // see if there is an inline Comment + tok, lit := p.scanIgnoreWhitespace() + esize := len(c.elements()) + // seen comment and on same line and elements have been added + if tCOMMENT == tok && p.s.line <= currentLine+1 && esize > 0 { + // if the last added element can have an inline comment then set it + last := c.elements()[esize-1] + if inliner, ok := last.(commentInliner); ok { + // TODO skip multiline? + inliner.inlineComment(newComment(lit)) + } + } else { + p.unscan() + } +} + +// takeLastComment removes and returns the last element of the list if it is a Comment. +func takeLastComment(list []Visitee) (*Comment, []Visitee) { + if len(list) == 0 { + return nil, list + } + if last, ok := list[len(list)-1].(*Comment); ok { + return last, list[:len(list)-1] + } + return nil, list +} + +// mergeOrReturnComment creates a new comment and tries to merge it with the last element (if is a comment and is on the next line). +func mergeOrReturnComment(elements []Visitee, lit string, lineNumber int) *Comment { + com := newComment(lit) + com.lineNumber = lineNumber + // last element must be a comment to merge + + // do not merge c-style comments + + // last comment line was on previous line + if esize := len(elements); esize > 0 { + if last, ok := elements[esize-1].(*Comment); ok && !last.Cstyle && lineNumber <= last.lineNumber+len(last.Lines) { // less than because last line of file could be inline comment + last.Merge(com) + // mark as merged + com = nil + } + } + return com +} diff --git a/comment_test.go b/comment_test.go new file mode 100644 index 0000000..c9a4770 --- /dev/null +++ b/comment_test.go @@ -0,0 +1,81 @@ +// Copyright (c) 2017 Ernest Micklei +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package proto + +import "testing" + +func TestCreateComment(t *testing.T) { + c0 := newComment("") + if got, want := len(c0.Lines), 1; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + c1 := newComment(`hello +world`) + if got, want := len(c1.Lines), 2; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + if got, want := c1.Lines[0], "hello"; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + if got, want := c1.Lines[1], "world"; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + if got, want := c1.Cstyle, true; got != want { + t.Errorf("got [%v] want [%v]", c1, want) + } +} + +func TestTakeLastComment(t *testing.T) { + c0 := newComment("hi") + c1 := newComment("there") + _, l := takeLastComment([]Visitee{c0, c1}) + if got, want := len(l), 1; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } + if got, want := l[0], c0; got != want { + t.Errorf("got [%v] want [%v]", c1, want) + } +} + +func TestParseCommentWithEmptyLines(t *testing.T) { + proto := ` +// comment 1 +// comment 2 +// +// comment 3 +// comment 4` + p := newParserOn(proto) + def, err := p.Parse() + if err != nil { + t.Fatal(err) + } + //spew.Dump(def) + if got, want := len(def.Elements), 1; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } + + if got, want := len(def.Elements[0].(*Comment).Lines), 5; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } +} diff --git a/enum.go b/enum.go index 2ba6077..0426bae 100644 --- a/enum.go +++ b/enum.go @@ -1,7 +1,7 @@ // Copyright (c) 2017 Ernest Micklei -// +// // MIT License -// +// // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: -// +// // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -27,7 +27,7 @@ import "strconv" // Enum definition consists of a name and an enum body. type Enum struct { - Line int + Comment *Comment Name string Elements []Visitee } @@ -37,6 +37,11 @@ func (e *Enum) Accept(v Visitor) { v.VisitEnum(e) } +// Doc is part of Documented +func (e *Enum) Doc() *Comment { + return e.Comment +} + // addElement is part of elementContainer func (e *Enum) addElement(v Visitee) { e.Elements = append(e.Elements, v) @@ -47,6 +52,13 @@ func (e *Enum) elements() []Visitee { return e.Elements } +// takeLastComment is part of elementContainer +// removes and returns the last element of the list if it is a Comment. +func (e *Enum) takeLastComment() (last *Comment) { + last, e.Elements = takeLastComment(e.Elements) + return +} + func (e *Enum) parse(p *Parser) error { tok, lit := p.scanIgnoreWhitespace() if tok != tIDENT { @@ -63,9 +75,12 @@ func (e *Enum) parse(p *Parser) error { tok, lit = p.scanIgnoreWhitespace() switch tok { case tCOMMENT: - e.Elements = append(e.Elements, p.newComment(lit)) + if com := mergeOrReturnComment(e.elements(), lit, p.s.line); com != nil { // not merged? + e.Elements = append(e.Elements, com) + } case tOPTION: v := new(Option) + v.Comment = e.takeLastComment() err := v.parse(p) if err != nil { return err @@ -78,6 +93,7 @@ func (e *Enum) parse(p *Parser) error { default: p.unscan() f := new(EnumField) + f.Comment = e.takeLastComment() err := f.parse(p) if err != nil { return err @@ -94,10 +110,11 @@ done: // EnumField is part of the body of an Enum. type EnumField struct { - Name string - Integer int - ValueOption *Option - Comment *Comment + Comment *Comment + Name string + Integer int + ValueOption *Option + InlineComment *Comment } // Accept dispatches the call to the visitor. @@ -107,7 +124,12 @@ func (f *EnumField) Accept(v Visitor) { // inlineComment is part of commentInliner. func (f *EnumField) inlineComment(c *Comment) { - f.Comment = c + f.InlineComment = c +} + +// Doc is part of Documented +func (f *EnumField) Doc() *Comment { + return f.Comment } // columns returns printable source tokens @@ -117,8 +139,8 @@ func (f EnumField) columns() (cols []aligned) { cols = append(cols, f.ValueOption.columns()...) } cols = append(cols, alignedSemicolon) - if f.Comment != nil { - cols = append(cols, notAligned(" //"), notAligned(f.Comment.Message)) + if f.InlineComment != nil { + cols = append(cols, notAligned(" //"), notAligned(f.InlineComment.Message())) } return } diff --git a/enum_test.go b/enum_test.go index be37ff2..a15c983 100644 --- a/enum_test.go +++ b/enum_test.go @@ -27,6 +27,7 @@ import "testing" func TestEnum(t *testing.T) { proto := ` +// enum enum EnumAllowingAlias { option allow_alias = true; UNKNOWN = 0; @@ -45,6 +46,12 @@ enum EnumAllowingAlias { if got, want := len(enums[0].Elements), 4; got != want { t.Errorf("got [%v] want [%v]", got, want) } + if got, want := enums[0].Comment != nil, true; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } + if got, want := enums[0].Comment.Message(), " enum"; got != want { + t.Errorf("got [%v] want [%v]", enums[0].Comment, want) + } ef1 := enums[0].Elements[1].(*EnumField) if got, want := ef1.Integer, 0; got != want { t.Errorf("got [%v] want [%v]", got, want) diff --git a/extensions.go b/extensions.go index 5a11e7e..6b1f635 100644 --- a/extensions.go +++ b/extensions.go @@ -26,13 +26,14 @@ package proto // Extensions declare that a range of field numbers in a message are available for third-party extensions. // proto2 only type Extensions struct { - Ranges []Range - Comment *Comment + Comment *Comment + Ranges []Range + InlineComment *Comment } // inlineComment is part of commentInliner. func (e *Extensions) inlineComment(c *Comment) { - e.Comment = c + e.InlineComment = c } // Accept dispatches the call to the visitor. diff --git a/extensions_test.go b/extensions_test.go index 04cd866..d499da4 100644 --- a/extensions_test.go +++ b/extensions_test.go @@ -27,6 +27,7 @@ import "testing" func TestExtensions(t *testing.T) { proto := `message M { + // extensions extensions 4, 20 to max; // max }` p := newParserOn(proto) @@ -36,8 +37,8 @@ func TestExtensions(t *testing.T) { if err != nil { t.Fatal(err) } - if len(m.Elements) == 0 { - t.Fatal("extensions expected") + if len(m.Elements) != 1 { + t.Fatal("1 extension expected, got", len(m.Elements)) } f := m.Elements[0].(*Extensions) if got, want := len(f.Ranges), 2; got != want { @@ -49,7 +50,7 @@ func TestExtensions(t *testing.T) { if f.Comment == nil { t.Fatal("comment expected") } - if got, want := f.Comment.Message, " max"; got != want { + if got, want := f.InlineComment.Message(), " max"; got != want { t.Errorf("got [%s] want [%s]", got, want) } } diff --git a/field.go b/field.go index aca86f5..5017e37 100644 --- a/field.go +++ b/field.go @@ -1,7 +1,7 @@ // Copyright (c) 2017 Ernest Micklei -// +// // MIT License -// +// // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: -// +// // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -27,16 +27,17 @@ import "strconv" // Field is an abstract message field. type Field struct { - Name string - Type string - Sequence int - Options []*Option - Comment *Comment + Comment *Comment + Name string + Type string + Sequence int + Options []*Option + InlineComment *Comment } // inlineComment is part of commentInliner. func (f *Field) inlineComment(c *Comment) { - f.Comment = c + f.InlineComment = c } // NormalField represents a field in a Message. @@ -54,6 +55,11 @@ func (f *NormalField) Accept(v Visitor) { v.VisitNormalField(f) } +// Doc is part of Documented +func (f *NormalField) Doc() *Comment { + return f.Comment +} + // columns returns printable source tokens func (f *NormalField) columns() (cols []aligned) { if f.Repeated { @@ -78,8 +84,8 @@ func (f *NormalField) columns() (cols []aligned) { cols = append(cols, leftAligned("]")) } cols = append(cols, alignedSemicolon) - if f.Comment != nil { - cols = append(cols, notAligned(" //"), notAligned(f.Comment.Message)) + if f.InlineComment != nil { + cols = append(cols, notAligned(" //"), notAligned(f.InlineComment.Message())) } return } @@ -188,8 +194,8 @@ func (f *MapField) columns() (cols []aligned) { cols = append(cols, leftAligned("]")) } cols = append(cols, alignedSemicolon) - if f.Comment != nil { - cols = append(cols, notAligned(" //"), notAligned(f.Comment.Message)) + if f.InlineComment != nil { + cols = append(cols, notAligned(" //"), notAligned(f.InlineComment.Message())) } return } diff --git a/formatter.go b/formatter.go index 5772086..8340803 100644 --- a/formatter.go +++ b/formatter.go @@ -26,15 +26,15 @@ package proto import ( "fmt" "io" - "strings" ) // Formatter visits a Proto and writes formatted source. type Formatter struct { w io.Writer + indentSeparator string indentLevel int lastStmt string - indentSeparator string + lastLevel int } // NewFormatter returns a new Formatter. Only the indentation separator is configurable. @@ -44,40 +44,24 @@ func NewFormatter(writer io.Writer, indentSeparator string) *Formatter { // Format visits all proto elements and writes formatted source. func (f *Formatter) Format(p *Proto) { - f.printAsGroups(p.Elements) + for _, each := range p.Elements { + each.Accept(f) + } } -// VisitComment formats a Comment. +// VisitComment formats a Comment and writes a newline. func (f *Formatter) VisitComment(c *Comment) { - f.begin("comment") - if c.IsMultiline() { - fmt.Fprintln(f.w, "/*") - lines := strings.Split(c.Message, "\n") - for i, each := range lines { - // leading no tab or space - leftAligned := strings.TrimLeft(each, "\t ") - // only skip first and last empty lines - skip := (i == 0 && len(leftAligned) == 0) || - (i == len(lines)-1 && len(leftAligned) == 0) - if !skip { - f.indent(0) - fmt.Fprintf(f.w, " %s\n", leftAligned) - } - } - f.indent(0) - fmt.Fprintf(f.w, " */\n") - } else { - fmt.Fprintf(f.w, "//%s\n", c.Message) - } + f.printComment(c) + f.nl() } // VisitEnum formats a Enum. func (f *Formatter) VisitEnum(e *Enum) { - f.begin("enum") + f.begin("enum", e) fmt.Fprintf(f.w, "enum %s {", e.Name) if len(e.Elements) > 0 { f.nl() - f.indentLevel++ + f.level(1) f.printAsGroups(e.Elements) f.indent(-1) } @@ -86,14 +70,18 @@ func (f *Formatter) VisitEnum(e *Enum) { } // VisitEnumField formats a EnumField. -func (f *Formatter) VisitEnumField(e *EnumField) {} +func (f *Formatter) VisitEnumField(e *EnumField) { + f.printAsGroups([]Visitee{e}) +} // VisitImport formats a Import. -func (f *Formatter) VisitImport(i *Import) {} +func (f *Formatter) VisitImport(i *Import) { + f.printAsGroups([]Visitee{i}) +} // VisitMessage formats a Message. func (f *Formatter) VisitMessage(m *Message) { - f.begin("message") + f.begin("message", m) if m.IsExtend { fmt.Fprintf(f.w, "extend ") } else { @@ -102,7 +90,7 @@ func (f *Formatter) VisitMessage(m *Message) { fmt.Fprintf(f.w, "%s {", m.Name) if len(m.Elements) > 0 { f.nl() - f.indentLevel++ + f.level(1) f.printAsGroups(m.Elements) f.indent(-1) } @@ -112,10 +100,11 @@ func (f *Formatter) VisitMessage(m *Message) { // VisitOption formats a Option. func (f *Formatter) VisitOption(o *Option) { + f.begin("option", o) fmt.Fprintf(f.w, "option %s = ", o.Name) if o.AggregatedConstants != nil { fmt.Fprintf(f.w, "{\n") - f.indentLevel++ + f.level(1) for _, each := range o.AggregatedConstants { f.indent(0) fmt.Fprintf(f.w, "%s: %s\n", each.Name, each.Literal.String()) @@ -123,24 +112,29 @@ func (f *Formatter) VisitOption(o *Option) { f.indent(-1) fmt.Fprintf(f.w, "}") } else { + // TODO printAs groups with fixed length fmt.Fprintf(f.w, o.Constant.String()) } fmt.Fprintf(f.w, ";") - if o.Comment != nil { - o.Comment.Accept(f) + if o.InlineComment != nil { + fmt.Fprintf(f.w, " //%s", o.InlineComment.Message()) } + f.nl() } // VisitPackage formats a Package. -func (f *Formatter) VisitPackage(p *Package) {} +func (f *Formatter) VisitPackage(p *Package) { + f.nl() + f.printAsGroups([]Visitee{p}) +} // VisitService formats a Service. func (f *Formatter) VisitService(s *Service) { - f.begin("service") + f.begin("service", s) fmt.Fprintf(f.w, "service %s {", s.Name) if len(s.Elements) > 0 { f.nl() - f.indentLevel++ + f.level(1) f.printAsGroups(s.Elements) f.indent(-1) } @@ -150,17 +144,18 @@ func (f *Formatter) VisitService(s *Service) { // VisitSyntax formats a Syntax. func (f *Formatter) VisitSyntax(s *Syntax) { - f.begin("syntax") - fmt.Fprintf(f.w, "syntax = %q;\n", s.Value) + f.begin("syntax", s) + fmt.Fprintf(f.w, "syntax = %q", s.Value) + f.endWithComment(s.InlineComment) } // VisitOneof formats a Oneof. func (f *Formatter) VisitOneof(o *Oneof) { - f.begin("oneof") + f.begin("oneof", o) fmt.Fprintf(f.w, "oneof %s {", o.Name) if len(o.Elements) > 0 { f.nl() - f.indentLevel++ + f.level(1) f.printAsGroups(o.Elements) f.indent(-1) } @@ -169,11 +164,13 @@ func (f *Formatter) VisitOneof(o *Oneof) { } // VisitOneofField formats a OneofField. -func (f *Formatter) VisitOneofField(o *OneOfField) {} +func (f *Formatter) VisitOneofField(o *OneOfField) { + f.printAsGroups([]Visitee{o}) +} // VisitReserved formats a Reserved. func (f *Formatter) VisitReserved(r *Reserved) { - f.begin("reserved") + f.begin("reserved", r) io.WriteString(f.w, "reserved ") if len(r.Ranges) > 0 { for i, each := range r.Ranges { @@ -190,7 +187,7 @@ func (f *Formatter) VisitReserved(r *Reserved) { fmt.Fprintf(f.w, "%q", each) } } - f.endWithComment(r.Comment) + f.endWithComment(r.InlineComment) } // VisitRPC formats a RPC. @@ -199,21 +196,25 @@ func (f *Formatter) VisitRPC(r *RPC) { } // VisitMapField formats a MapField. -func (f *Formatter) VisitMapField(m *MapField) {} +func (f *Formatter) VisitMapField(m *MapField) { + f.printAsGroups([]Visitee{m}) +} // VisitNormalField formats a NormalField. -func (f *Formatter) VisitNormalField(f1 *NormalField) {} +func (f *Formatter) VisitNormalField(f1 *NormalField) { + f.printAsGroups([]Visitee{f1}) +} // VisitGroup formats a proto2 Group. func (f *Formatter) VisitGroup(g *Group) { - f.begin("group") + f.begin("group", g) if g.Optional { io.WriteString(f.w, "optional ") } fmt.Fprintf(f.w, "group %s = %d {", g.Name, g.Sequence) if len(g.Elements) > 0 { f.nl() - f.indentLevel++ + f.level(1) f.printAsGroups(g.Elements) f.indent(-1) } @@ -223,7 +224,7 @@ func (f *Formatter) VisitGroup(g *Group) { // VisitExtensions formats a proto2 Extensions. func (f *Formatter) VisitExtensions(e *Extensions) { - f.begin("extensions") + f.begin("extensions", e) io.WriteString(f.w, "extensions ") for i, each := range e.Ranges { if i > 0 { @@ -231,5 +232,5 @@ func (f *Formatter) VisitExtensions(e *Extensions) { } fmt.Fprintf(f.w, "%s", each.String()) } - f.endWithComment(e.Comment) + f.endWithComment(e.InlineComment) } diff --git a/formatter_test.go b/formatter_test.go index 37777bf..85fd455 100644 --- a/formatter_test.go +++ b/formatter_test.go @@ -52,9 +52,8 @@ func TestPrintListOfColumns(t *testing.T) { list := []columnsPrintable{e0, e1} b := new(bytes.Buffer) f := NewFormatter(b, " ") - f.printListOfColumns(list, "enum") - formatted := ` -A = 1 [a = 1234]; + f.printListOfColumns(list) + formatted := `A = 1 [a = 1234]; ABC = 12 [ab = 1234]; ` if got, want := b.String(), formatted; got != want { @@ -62,72 +61,55 @@ ABC = 12 [ab = 1234]; } } -func TestFormatComment(t *testing.T) { - proto := ` -/* +func TestFormatCStyleComment(t *testing.T) { + t.Skip() + proto := `/* * Hello * World */ - ` - def, _ := NewParser(strings.NewReader(proto)).Parse() - b := new(bytes.Buffer) - f := NewFormatter(b, " ") - f.Format(def) - if got, want := strings.TrimSpace(b.String()), strings.TrimSpace(proto); got != want { - t.Errorf("got [%s] want [%s]", got, want) - } -} - -func TestFormatInlineComment(t *testing.T) { - proto := ` -message ConnectRequest { - string clientID = 1; // Client name/identifier. - string heartbeatInbox = 2; // Inbox for server initiated heartbeats. -} - ` +` def, _ := NewParser(strings.NewReader(proto)).Parse() b := new(bytes.Buffer) f := NewFormatter(b, " ") f.Format(def) - if got, want := strings.TrimSpace(b.String()), strings.TrimSpace(proto); got != want { - t.Errorf("got [%s] want [%s]", got, want) + if got, want := proto, formatted(def.Elements[0]); got != want { + println(diff(got, want)) + t.Fail() } } -func formatted(t *testing.T, v Visitee) string { - b := new(bytes.Buffer) - f := NewFormatter(b, " ") // 2 spaces - v.Accept(f) - return b.String() +func TestFormatExtendMessage(t *testing.T) { + proto := `// extend +extend google.protobuf.MessageOptions { + // my_option + optional string my_option = 51234; // mynumber } - -func TestExtendMessage(t *testing.T) { - proto := `extend google.protobuf.MessageOptions { optional string my_option = 51234; }` +` p := newParserOn(proto) - p.scanIgnoreWhitespace() // consume first token - m := new(Message) - m.IsExtend = true - err := m.parse(p) + pp, err := p.Parse() if err != nil { t.Fatal(err) } - if got, want := formatted(t, m), ` -extend google.protobuf.MessageOptions { - optional string my_option = 51234; -} -`; got != want { - fmt.Println(diff(t, got, want)) + m, ok := pp.Elements[0].(*Message) + if !ok { + t.Fatal("message expected") + } + if got, want := formatted(m), proto; got != want { + fmt.Println(diff(got, want)) t.Fail() } } -func TestAggregatedOptionSyntax(t *testing.T) { - proto := `rpc Find ( Finder ) returns ( stream Result ) { - option (google.api.http) = { - post: "/v1/finders/1" - body: "*" - }; - }` +func TestFormatAggregatedOptionSyntax(t *testing.T) { + // TODO format not that nice + proto := `rpc Find (Finder) returns (stream Result) { + option (google.api.http) = { + post: "/v1/finders/1" + body: "*" + }; + +} +` p := newParserOn(proto) r := new(RPC) p.scanIgnoreWhitespace() // consumer rpc @@ -135,15 +117,8 @@ func TestAggregatedOptionSyntax(t *testing.T) { if err != nil { t.Fatal(err) } - if got, want := formatted(t, r), ` -rpc Find (Finder) returns (stream Result) { - option (google.api.http) = { - post: "/v1/finders/1" - body: "*" - }; -} -`; got != want { - fmt.Println(diff(t, got, want)) + if got, want := formatted(r), proto; got != want { + fmt.Println(diff(got, want)) fmt.Println("---") fmt.Println(got) fmt.Println("---") @@ -152,7 +127,52 @@ rpc Find (Finder) returns (stream Result) { } } -func diff(t *testing.T, left, right string) string { +func TestFormatCommentSample(t *testing.T) { + proto := ` +/* + begin +*/ + +// comment 1 +// comment 2 +syntax = "proto"; // inline 1 + +// comment 3 +// comment 4 +package test; // inline 2 + +// comment 5 +// comment 6 +message Test { + // comment 7 + // comment 8 + int64 i = 1; // inline 3 +} +` + p := newParserOn(proto) + def, err := p.Parse() + if err != nil { + t.Fatal(err) + } + if got, want := len(def.Elements), 4; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } + b := new(bytes.Buffer) + f := NewFormatter(b, " ") // 2 spaces + f.Format(def) + //println(b.String()) + //spew.Dump(def) +} + +/// testing utils +func formatted(v Visitee) string { + b := new(bytes.Buffer) + f := NewFormatter(b, " ") // 2 spaces + v.Accept(f) + return b.String() +} + +func diff(left, right string) string { b := new(bytes.Buffer) w := func(char rune) { if '\n' == char { diff --git a/formatter_utils.go b/formatter_utils.go index 7233923..100225e 100644 --- a/formatter_utils.go +++ b/formatter_utils.go @@ -23,25 +23,56 @@ package proto -import "io" +import ( + "fmt" + "io" +) -// begin write indentation after a newline depending on whether the last element was a comment. -func (f *Formatter) begin(stmt string) { - if "comment" == stmt && f.lastStmt == "comment" { - f.indent(0) - return +// printDoc writes documentation is available +func (f *Formatter) printDoc(v Visitee) { + if hasDoc, ok := v.(Documented); ok { + if doc := hasDoc.Doc(); doc != nil { + f.printComment(doc) + } } - if "comment" == stmt && f.lastStmt != "comment" { - io.WriteString(f.w, "\n") - f.indent(0) - f.lastStmt = stmt - return +} + +// printComment formats a Comment. +func (f *Formatter) printComment(c *Comment) { + if c.Cstyle { + fmt.Fprintln(f.w, "/*") + } + for i, each := range c.Lines { + // first indent is already done; additional lines need to be indented too. + if i > 0 { + f.indent(0) + } + if c.Cstyle { + // only skip first and last empty lines + skip := (i == 0 && len(each) == 0) || + (i == len(c.Lines)-1 && len(each) == 0) + if !skip { + fmt.Fprintf(f.w, "%s\n", each) + } + } else { + fmt.Fprintf(f.w, "//%s\n", each) + } } - if "comment" != f.lastStmt && f.lastStmt != stmt && f.indentLevel == 0 { - io.WriteString(f.w, "\n") + if c.Cstyle { + fmt.Fprintf(f.w, " */\n") + } +} + +// begin writes a newline if the last statement kind is different. always indents. +// if the Visitee has comment then print it. +func (f *Formatter) begin(stmt string, v Visitee) { + // if not the first statement and different from last and on same indent level. + if len(f.lastStmt) > 0 && f.lastStmt != stmt && f.lastLevel == f.indentLevel { + f.nl() } f.indent(0) f.lastStmt = stmt + f.printDoc(v) } func (f *Formatter) end(stmt string) { @@ -50,7 +81,7 @@ func (f *Formatter) end(stmt string) { // indent changes the indent level and writes indentation. func (f *Formatter) indent(diff int) { - f.indentLevel += diff + f.level(diff) for i := 0; i < f.indentLevel; i++ { io.WriteString(f.w, f.indentSeparator) } @@ -59,13 +90,13 @@ func (f *Formatter) indent(diff int) { // columnsPrintable is for elements that can be printed in aligned columns. type columnsPrintable interface { columns() (cols []aligned) + //doc() *Comment } -func (f *Formatter) printListOfColumns(list []columnsPrintable, groupName string) { +func (f *Formatter) printListOfColumns(list []columnsPrintable) { if len(list) == 0 { return } - f.begin(groupName) // collect all column values values := [][]aligned{} widths := map[int]int{} @@ -86,11 +117,8 @@ func (f *Formatter) printListOfColumns(list []columnsPrintable, groupName string } } // now print all values - for i, each := range values { - if i > 0 { - f.nl() - f.indent(0) - } + for _, each := range values { + f.indent(0) for c := 0; c < len(widths); c++ { pw := widths[c] // only print if there is a value @@ -99,8 +127,8 @@ func (f *Formatter) printListOfColumns(list []columnsPrintable, groupName string io.WriteString(f.w, each[c].formatted(f.indentSeparator, f.indentLevel, pw)) } } + f.nl() } - f.nl() } // nl writes a newline. @@ -108,6 +136,12 @@ func (f *Formatter) nl() { io.WriteString(f.w, "\n") } +// level changes the current indentLevel +func (f *Formatter) level(diff int) { + f.lastLevel = f.indentLevel + f.indentLevel += diff +} + // printAsGroups prints the list in groups of the same element type. func (f *Formatter) printAsGroups(list []Visitee) { group := []columnsPrintable{} @@ -120,18 +154,26 @@ func (f *Formatter) printAsGroups(list []Visitee) { lastGroupName = groupName // print current group if len(group) > 0 { - f.printListOfColumns(group, groupName) + f.printListOfColumns(group) // begin new group group = []columnsPrintable{} } } + // comment as a group entity + if hasDoc, ok := each.(Documented); ok { + if doc := hasDoc.Doc(); doc != nil { + f.printListOfColumns(group) + // begin new group + group = append([]columnsPrintable{}, doc.columnsPrintables()...) + } + } group = append(group, printable) } else { // not printable in group lastGroupName = groupName // print current group if len(group) > 0 { - f.printListOfColumns(group, groupName) + f.printListOfColumns(group) // begin new group group = []columnsPrintable{} } @@ -139,7 +181,7 @@ func (f *Formatter) printAsGroups(list []Visitee) { } } // print last group - f.printListOfColumns(group, lastGroupName) + f.printListOfColumns(group) } // endWithComment writes a statement end (;) followed by inline comment if present. @@ -147,7 +189,7 @@ func (f *Formatter) endWithComment(commentOrNil *Comment) { io.WriteString(f.w, ";") if commentOrNil != nil { io.WriteString(f.w, " //") - io.WriteString(f.w, commentOrNil.Message) + io.WriteString(f.w, commentOrNil.Message()) } io.WriteString(f.w, "\n") } diff --git a/group.go b/group.go index 93e9cd5..7a23f82 100644 --- a/group.go +++ b/group.go @@ -1,7 +1,7 @@ // Copyright (c) 2017 Ernest Micklei -// +// // MIT License -// +// // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: -// +// // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -26,6 +26,7 @@ package proto // Group represents a (proto2 only) group. // https://developers.google.com/protocol-buffers/docs/reference/proto2-spec#group_field type Group struct { + Comment *Comment Name string Optional bool Sequence int @@ -47,6 +48,18 @@ func (g *Group) elements() []Visitee { return g.Elements } +// Doc is part of Documented +func (g *Group) Doc() *Comment { + return g.Comment +} + +// takeLastComment is part of elementContainer +// removes and returns the last element of the list if it is a Comment. +func (g *Group) takeLastComment() (last *Comment) { + last, g.Elements = takeLastComment(g.Elements) + return +} + // parse expects: // groupName "=" fieldNumber { messageBody } func (g *Group) parse(p *Parser) error { diff --git a/group_test.go b/group_test.go index 414fd45..61b7c2d 100644 --- a/group_test.go +++ b/group_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2017 Ernest Micklei -// +// // MIT License -// +// // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: -// +// // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -27,7 +27,9 @@ import "testing" func TestGroup(t *testing.T) { oto := `message M { + // group optional group OptionalGroup = 16 { + // field optional int32 a = 17; } }` @@ -39,10 +41,13 @@ func TestGroup(t *testing.T) { t.Error(err) } if got, want := len(m.Elements), 1; got != want { - t.Errorf("got [%v] want [%v]", got, want) + t.Fatalf("got [%v] want [%v]", got, want) } g := m.Elements[0].(*Group) if got, want := len(g.Elements), 1; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } + if got, want := g.Comment != nil, true; got != want { t.Errorf("got [%v] want [%v]", got, want) } f := g.Elements[0].(*NormalField) diff --git a/import.go b/import.go index bc87740..86ff7a5 100644 --- a/import.go +++ b/import.go @@ -27,9 +27,10 @@ import "fmt" // Import holds a filename to another .proto definition. type Import struct { - Filename string - Kind string // weak, public, - Comment *Comment + Comment *Comment + Filename string + Kind string // weak, public, + InlineComment *Comment } func (i *Import) parse(p *Parser) error { @@ -58,7 +59,12 @@ func (i *Import) Accept(v Visitor) { // inlineComment is part of commentInliner. func (i *Import) inlineComment(c *Comment) { - i.Comment = c + i.InlineComment = c +} + +// Doc is part of Documented +func (i *Import) Doc() *Comment { + return i.Comment } // columns returns printable source tokens @@ -68,8 +74,8 @@ func (i *Import) columns() (cols []aligned) { cols = append(cols, leftAligned(i.Kind), alignedSpace) } cols = append(cols, notAligned(fmt.Sprintf("%q", i.Filename)), alignedSemicolon) - if i.Comment != nil { - cols = append(cols, notAligned(" //"), notAligned(i.Comment.Message)) + if i.InlineComment != nil { + cols = append(cols, notAligned(" //"), notAligned(i.InlineComment.Message())) } return } diff --git a/message.go b/message.go index 61dd29c..b9aa00a 100644 --- a/message.go +++ b/message.go @@ -1,7 +1,7 @@ // Copyright (c) 2017 Ernest Micklei -// +// // MIT License -// +// // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: -// +// // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -25,6 +25,7 @@ package proto // Message consists of a message name and a message body. type Message struct { + Comment *Comment Name string IsExtend bool Elements []Visitee @@ -63,39 +64,47 @@ func parseMessageBody(p *Parser, c elementContainer) error { tok, lit = p.scanIgnoreWhitespace() switch tok { case tCOMMENT: - c.addElement(p.newComment(lit)) + if com := mergeOrReturnComment(c.elements(), lit, p.s.line); com != nil { // not merged? + c.addElement(com) + } case tENUM: e := new(Enum) + e.Comment = c.takeLastComment() if err := e.parse(p); err != nil { return err } c.addElement(e) case tMESSAGE: msg := new(Message) + msg.Comment = c.takeLastComment() if err := msg.parse(p); err != nil { return err } c.addElement(msg) case tOPTION: o := new(Option) + o.Comment = c.takeLastComment() if err := o.parse(p); err != nil { return err } c.addElement(o) case tONEOF: o := new(Oneof) + o.Comment = c.takeLastComment() if err := o.parse(p); err != nil { return err } c.addElement(o) case tMAP: f := newMapField() + f.Comment = c.takeLastComment() if err := f.parse(p); err != nil { return err } c.addElement(f) case tRESERVED: r := new(Reserved) + r.Comment = c.takeLastComment() if err := r.parse(p); err != nil { return err } @@ -107,6 +116,7 @@ func parseMessageBody(p *Parser, c elementContainer) error { tok, lit = p.scanIgnoreWhitespace() if tGROUP == tok { g := new(Group) + g.Comment = c.takeLastComment() g.Optional = prevTok == tOPTIONAL if err := g.parse(p); err != nil { return err @@ -116,6 +126,7 @@ func parseMessageBody(p *Parser, c elementContainer) error { // not a group, will be tFIELD p.unscan() f := newNormalField() + f.Comment = c.takeLastComment() f.Optional = prevTok == tOPTIONAL f.Repeated = prevTok == tREPEATED f.Required = prevTok == tREQUIRED @@ -126,18 +137,21 @@ func parseMessageBody(p *Parser, c elementContainer) error { } case tGROUP: g := new(Group) + g.Comment = c.takeLastComment() if err := g.parse(p); err != nil { return err } c.addElement(g) case tEXTENSIONS: e := new(Extensions) + e.Comment = c.takeLastComment() if err := e.parse(p); err != nil { return err } c.addElement(e) case tEXTEND: e := new(Message) + e.Comment = c.takeLastComment() e.IsExtend = true if err := e.parse(p); err != nil { return err @@ -153,6 +167,7 @@ func parseMessageBody(p *Parser, c elementContainer) error { // tFIELD p.unscan() f := newNormalField() + f.Comment = c.takeLastComment() if err := f.parse(p); err != nil { return err } @@ -180,3 +195,13 @@ func (m *Message) addElement(v Visitee) { func (m *Message) elements() []Visitee { return m.Elements } + +func (m *Message) takeLastComment() (last *Comment) { + last, m.Elements = takeLastComment(m.Elements) + return +} + +// Doc is part of Documented +func (m *Message) Doc() *Comment { + return m.Comment +} diff --git a/message_test.go b/message_test.go index abb7e40..d0afc45 100644 --- a/message_test.go +++ b/message_test.go @@ -53,7 +53,7 @@ func TestMessage(t *testing.T) { if got, want := m.Name, "Out"; got != want { t.Errorf("got [%v] want [%v]", got, want) } - if got, want := len(m.Elements), 8; got != want { + if got, want := len(m.Elements), 6; got != want { t.Errorf("got [%v] want [%v]", got, want) } } diff --git a/oneof.go b/oneof.go index 88de183..d795b75 100644 --- a/oneof.go +++ b/oneof.go @@ -1,7 +1,7 @@ // Copyright (c) 2017 Ernest Micklei -// +// // MIT License -// +// // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: -// +// // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -27,6 +27,7 @@ import "strconv" // Oneof is a field alternate. type Oneof struct { + Comment *Comment Name string Elements []Visitee } @@ -41,6 +42,13 @@ func (o *Oneof) elements() []Visitee { return o.Elements } +// takeLastComment is part of elementContainer +// removes and returns the last element of the list if it is a Comment. +func (o *Oneof) takeLastComment() (last *Comment) { + last, o.Elements = takeLastComment(o.Elements) + return last +} + // parse expects: // oneofName "{" { oneofField | emptyStatement } "}" func (o *Oneof) parse(p *Parser) error { @@ -121,8 +129,8 @@ func (o *OneOfField) columns() (cols []aligned) { cols = append(cols, leftAligned("]")) } cols = append(cols, alignedSemicolon) - if o.Comment != nil { - cols = append(cols, notAligned(" //"), notAligned(o.Comment.Message)) + if o.InlineComment != nil { + cols = append(cols, notAligned(" //"), notAligned(o.InlineComment.Message())) } return } diff --git a/option.go b/option.go index 4df23dd..578aecb 100644 --- a/option.go +++ b/option.go @@ -28,21 +28,12 @@ import "bytes" // Option is a protoc compiler option type Option struct { + Comment *Comment Name string Constant Literal IsEmbedded bool - Comment *Comment AggregatedConstants []*NamedLiteral -} - -// inlineComment is part of commentInliner. -func (o *Option) inlineComment(c *Comment) { - o.Comment = c -} - -// Accept dispatches the call to the visitor. -func (o *Option) Accept(v Visitor) { - v.VisitOption(o) + InlineComment *Comment } // columns returns printable source tokens @@ -58,8 +49,8 @@ func (o *Option) columns() (cols []aligned) { } if !o.IsEmbedded { cols = append(cols, alignedSemicolon) - if o.Comment != nil { - cols = append(cols, notAligned(" //"), notAligned(o.Comment.Message)) + if o.InlineComment != nil { + cols = append(cols, notAligned(" //"), notAligned(o.InlineComment.Message())) } } return @@ -127,11 +118,25 @@ func (o *Option) parse(p *Parser) error { return nil } +// inlineComment is part of commentInliner. +func (o *Option) inlineComment(c *Comment) { + o.InlineComment = c +} + +// Accept dispatches the call to the visitor. +func (o *Option) Accept(v Visitor) { + v.VisitOption(o) +} + +// Doc is part of Documented +func (o *Option) Doc() *Comment { + return o.Comment +} + // Literal represents intLit,floatLit,strLit or boolLit type Literal struct { Source string IsString bool - Comment string } // String returns the source (if quoted then use double quote). @@ -144,21 +149,12 @@ func (l Literal) String() string { if l.IsString { buf.WriteRune('"') } - if len(l.Comment) > 0 { - buf.WriteString(" //") - buf.WriteString(l.Comment) - } return buf.String() } // parse expects to read a literal constant after =. func (l *Literal) parse(p *Parser) error { l.Source, l.IsString = p.s.scanLiteral() - p.s.skipWhitespace() - if p.s.peek('/') { - p.s.read() // consume first slash - l.Comment = p.s.scanComment() - } return nil } diff --git a/option_test.go b/option_test.go index 7cc3395..51968d6 100644 --- a/option_test.go +++ b/option_test.go @@ -51,6 +51,11 @@ func TestOptionCases(t *testing.T) { "(foo_options)", "", "", + }, { + `option optimize_for = SPEED;`, + "optimize_for", + "", + "SPEED", }} { p := newParserOn(each.proto) pr, err := p.Parse() @@ -94,3 +99,30 @@ func TestLiteralString(t *testing.T) { t.Errorf("got [%v] want [%v]", got, want) } } + +func TestOptionComments(t *testing.T) { + proto := ` +// comment +option Help = "me"; // inline` + p := newParserOn(proto) + pr, err := p.Parse() + if err != nil { + t.Fatal(err) + } + o := pr.Elements[0].(*Option) + if got, want := o.IsEmbedded, false; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + if got, want := o.Comment != nil, true; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } + if got, want := o.Comment.Lines[0], " comment"; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } + if got, want := o.InlineComment != nil, true; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } + if got, want := o.InlineComment.Lines[0], " inline"; got != want { + t.Fatalf("got [%v] want [%v]", got, want) + } +} diff --git a/package.go b/package.go index c5f0b5a..b58879d 100644 --- a/package.go +++ b/package.go @@ -1,7 +1,7 @@ // Copyright (c) 2017 Ernest Micklei -// +// // MIT License -// +// // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: -// +// // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -25,8 +25,14 @@ package proto // Package specifies the namespace for all proto elements. type Package struct { - Name string - Comment *Comment + Comment *Comment + Name string + InlineComment *Comment +} + +// Doc is part of Documented +func (p *Package) Doc() *Comment { + return p.Comment } func (p *Package) parse(pr *Parser) error { @@ -47,14 +53,14 @@ func (p *Package) Accept(v Visitor) { // inlineComment is part of commentInliner. func (p *Package) inlineComment(c *Comment) { - p.Comment = c + p.InlineComment = c } // columns returns printable source tokens func (p *Package) columns() (cols []aligned) { cols = append(cols, notAligned("package "), notAligned(p.Name), alignedSemicolon) - if p.Comment != nil { - cols = append(cols, notAligned(" //"), notAligned(p.Comment.Message)) + if p.InlineComment != nil { + cols = append(cols, notAligned(" //"), notAligned(p.InlineComment.Message())) } return } diff --git a/parser.go b/parser.go index 1deb3ea..9229d64 100644 --- a/parser.go +++ b/parser.go @@ -81,11 +81,6 @@ func (p *Parser) scanIgnoreWhitespace() (tok token, lit string) { // unscan pushes the previously read token back onto the buffer. func (p *Parser) unscan() { p.buf.n = 1 } -// newComment returns a comment with line indication. -func (p *Parser) newComment(lit string) *Comment { - return &Comment{Message: lit} -} - func (p *Parser) unexpected(found, expected string, obj interface{}) error { debug := "" if p.debug { diff --git a/parser_test.go b/parser_test.go index 4101842..98f1238 100644 --- a/parser_test.go +++ b/parser_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2017 Ernest Micklei -// +// // MIT License -// +// // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: -// +// // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -30,15 +30,25 @@ import ( func TestParseComment(t *testing.T) { proto := ` - // single + // first + // second + /* - multi* - */` + ctyle + multi + line + */ + + // cpp style single line // + + message test{} + ` p := newParserOn(proto) pr, err := p.Parse() if err != nil { t.Fatal(err) } + if got, want := len(collect(pr).Comments()), 2; got != want { t.Errorf("got [%v] want [%v]", got, want) } diff --git a/proto.go b/proto.go index 397e291..d4c9b11 100644 --- a/proto.go +++ b/proto.go @@ -1,7 +1,7 @@ // Copyright (c) 2017 Ernest Micklei -// +// // MIT License -// +// // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: -// +// // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -23,8 +23,6 @@ package proto -import "strings" - // Proto represents a .proto definition type Proto struct { Elements []Visitee @@ -40,39 +38,53 @@ func (proto *Proto) elements() []Visitee { return proto.Elements } +// takeLastComment is part of elementContainer +// removes and returns the last element of the list if it is a Comment. +func (proto *Proto) takeLastComment() (last *Comment) { + last, proto.Elements = takeLastComment(proto.Elements) + return +} + // parse parsers a complete .proto definition source. func (proto *Proto) parse(p *Parser) error { for { tok, lit := p.scanIgnoreWhitespace() switch tok { case tCOMMENT: - proto.Elements = append(proto.Elements, p.newComment(lit)) + if com := mergeOrReturnComment(proto.Elements, lit, p.s.line); com != nil { // not merged? + proto.Elements = append(proto.Elements, com) + } case tOPTION: o := new(Option) + o.Comment, proto.Elements = takeLastComment(proto.Elements) if err := o.parse(p); err != nil { return err } proto.Elements = append(proto.Elements, o) case tSYNTAX: s := new(Syntax) + s.Comment, proto.Elements = takeLastComment(proto.Elements) if err := s.parse(p); err != nil { return err } proto.Elements = append(proto.Elements, s) case tIMPORT: im := new(Import) + im.Comment, proto.Elements = takeLastComment(proto.Elements) if err := im.parse(p); err != nil { return err } proto.Elements = append(proto.Elements, im) case tENUM: enum := new(Enum) + enum.Comment, proto.Elements = takeLastComment(proto.Elements) if err := enum.parse(p); err != nil { return err } proto.Elements = append(proto.Elements, enum) case tSERVICE: service := new(Service) + service.Comment, proto.Elements = takeLastComment(proto.Elements) err := service.parse(p) if err != nil { return err @@ -80,12 +92,14 @@ func (proto *Proto) parse(p *Parser) error { proto.Elements = append(proto.Elements, service) case tPACKAGE: pkg := new(Package) + pkg.Comment, proto.Elements = takeLastComment(proto.Elements) if err := pkg.parse(p); err != nil { return err } proto.Elements = append(proto.Elements, pkg) case tMESSAGE: msg := new(Message) + msg.Comment, proto.Elements = takeLastComment(proto.Elements) if err := msg.parse(p); err != nil { return err } @@ -93,6 +107,7 @@ func (proto *Proto) parse(p *Parser) error { // BEGIN proto2 case tEXTEND: msg := new(Message) + msg.Comment, proto.Elements = takeLastComment(proto.Elements) msg.IsExtend = true if err := msg.parse(p); err != nil { return err @@ -112,47 +127,9 @@ done: return nil } -// Comment holds a message and line number. -type Comment struct { - Message string -} - -// Accept dispatches the call to the visitor. -func (c *Comment) Accept(v Visitor) { - v.VisitComment(c) -} - -// IsMultiline returns whether its message has one or more lineends. -func (c Comment) IsMultiline() bool { - return strings.Contains(c.Message, "\n") -} - -// commentInliner is for types that can have an inline comment. -type commentInliner interface { - inlineComment(c *Comment) -} - // elementContainer unifies types that have elements. type elementContainer interface { addElement(v Visitee) elements() []Visitee -} - -// maybeScanInlineComment tries to scan comment on the current line ; if present then set it for the last element added. -func maybeScanInlineComment(p *Parser, c elementContainer) { - currentLine := p.s.line - // see if there is an inline Comment - tok, lit := p.scanIgnoreWhitespace() - esize := len(c.elements()) - // seen comment and on same line and elements have been added - if tCOMMENT == tok && p.s.line == currentLine+1 && esize > 0 { - // if the last added element can have an inline comment then set it - last := c.elements()[esize-1] - if inliner, ok := last.(commentInliner); ok { - // TODO skip multiline? - inliner.inlineComment(p.newComment(lit)) - } - } else { - p.unscan() - } + takeLastComment() *Comment } diff --git a/reserved.go b/reserved.go index 1210255..656eeb8 100644 --- a/reserved.go +++ b/reserved.go @@ -25,14 +25,15 @@ package proto // Reserved statements declare a range of field numbers or field names that cannot be used in a message. type Reserved struct { - Ranges []Range - FieldNames []string - Comment *Comment + Comment *Comment + Ranges []Range + FieldNames []string + InlineComment *Comment } // inlineComment is part of commentInliner. func (r *Reserved) inlineComment(c *Comment) { - r.Comment = c + r.InlineComment = c } // Accept dispatches the call to the visitor. diff --git a/scanner_test.go b/scanner_test.go index 8de6a50..548c64f 100644 --- a/scanner_test.go +++ b/scanner_test.go @@ -58,11 +58,16 @@ func TestScanMultilineComment(t *testing.T) { func TestScanSingleLineComment(t *testing.T) { r := strings.NewReader(` - // dreadful // + // include this // + // but not this `) s := newScanner(r) s.scanUntil('/') // consume COMMENT token - if got, want := s.scanComment(), ` dreadful //`; got != want { + if got, want := s.scanComment(), ` include this //`; got != want { + t.Errorf("got [%v] want [%v]", got, want) + } + s.scanUntil('/') // consume COMMENT token + if got, want := s.scanComment(), ` but not this`; got != want { t.Errorf("got [%v] want [%v]", got, want) } } diff --git a/service.go b/service.go index 4ae4614..133e746 100644 --- a/service.go +++ b/service.go @@ -30,6 +30,7 @@ import ( // Service defines a set of RPC calls. type Service struct { + Comment *Comment Name string Elements []Visitee } @@ -39,6 +40,11 @@ func (s *Service) Accept(v Visitor) { v.VisitService(s) } +// Doc is part of Documented +func (s *Service) Doc() *Comment { + return s.Comment +} + // addElement is part of elementContainer func (s *Service) addElement(v Visitee) { s.Elements = append(s.Elements, v) @@ -49,6 +55,13 @@ func (s *Service) elements() []Visitee { return s.Elements } +// takeLastComment is part of elementContainer +// removes and returns the last elements of the list if it is a Comment. +func (s *Service) takeLastComment() (last *Comment) { + last, s.Elements = takeLastComment(s.Elements) + return +} + // parse continues after reading "service" func (s *Service) parse(p *Parser) error { tok, lit := p.scanIgnoreWhitespace() @@ -66,9 +79,12 @@ func (s *Service) parse(p *Parser) error { tok, lit = p.scanIgnoreWhitespace() switch tok { case tCOMMENT: - s.Elements = append(s.Elements, p.newComment(lit)) + if com := mergeOrReturnComment(s.Elements, lit, p.s.line); com != nil { // not merged? + s.Elements = append(s.Elements, com) + } case tRPC: rpc := new(RPC) + rpc.Comment, s.Elements = takeLastComment(s.Elements) err := rpc.parse(p) if err != nil { return err @@ -88,13 +104,14 @@ done: // RPC represents an rpc entry in a message. type RPC struct { + Comment *Comment Name string RequestType string StreamsRequest bool ReturnsType string StreamsReturns bool - Comment *Comment Options []*Option + InlineComment *Comment } // Accept dispatches the call to the visitor. @@ -102,9 +119,14 @@ func (r *RPC) Accept(v Visitor) { v.VisitRPC(r) } +// Doc is part of Documented +func (r *RPC) Doc() *Comment { + return r.Comment +} + // inlineComment is part of commentInliner. func (r *RPC) inlineComment(c *Comment) { - r.Comment = c + r.InlineComment = c } // columns returns printable source tokens @@ -135,7 +157,7 @@ func (r *RPC) columns() (cols []aligned) { buf := new(bytes.Buffer) io.WriteString(buf, " {\n") f := NewFormatter(buf, " ") // TODO get separator, now 2 spaces - f.indent(1) + f.level(1) for _, each := range r.Options { each.Accept(f) io.WriteString(buf, "\n") @@ -146,8 +168,8 @@ func (r *RPC) columns() (cols []aligned) { } else { cols = append(cols, alignedSemicolon) } - if r.Comment != nil { - cols = append(cols, notAligned(" //"), notAligned(r.Comment.Message)) + if r.InlineComment != nil { + cols = append(cols, notAligned(" //"), notAligned(r.InlineComment.Message())) } return cols } diff --git a/service_test.go b/service_test.go index e18bf14..6dd7d53 100644 --- a/service_test.go +++ b/service_test.go @@ -36,14 +36,17 @@ func TestService(t *testing.T) { t.Fatal(err) } srv := collect(pr).Services()[0] - if got, want := len(srv.Elements), 3; got != want { - t.Errorf("got [%v] want [%v]", got, want) + if got, want := len(srv.Elements), 2; got != want { + t.Fatalf("got [%v] want [%v]", got, want) } - rpc1 := srv.Elements[1].(*RPC) + rpc1 := srv.Elements[0].(*RPC) if got, want := rpc1.Name, "CreateAccount"; got != want { - t.Errorf("got [%v] want [%v]", got, want) + t.Fatalf("got [%v] want [%v]", got, want) + } + if got, want := rpc1.Doc().Message(), " comment"; got != want { + t.Fatalf("got [%v] want [%v]", got, want) } - rpc2 := srv.Elements[2].(*RPC) + rpc2 := srv.Elements[1].(*RPC) if got, want := rpc2.Name, "GetAccounts"; got != want { t.Errorf("got [%v] want [%v]", got, want) } @@ -51,10 +54,11 @@ func TestService(t *testing.T) { func TestRPCWithOptionAggregateSyntax(t *testing.T) { proto := `service AccountService { + // CreateAccount rpc CreateAccount (CreateAccount) returns (ServiceFault){ option (test_ident) = { - test: "test" // test - test2:"test2" // test2 + test: "test" + test2:"test2" }; } }` @@ -64,7 +68,7 @@ func TestRPCWithOptionAggregateSyntax(t *testing.T) { } srv := collect(pr).Services()[0] if got, want := len(srv.Elements), 1; got != want { - t.Errorf("got [%v] want [%v]", got, want) + t.Fatalf("got [%v] want [%v]", got, want) } rpc1 := srv.Elements[0].(*RPC) if got, want := len(rpc1.Options), 1; got != want { @@ -80,13 +84,8 @@ func TestRPCWithOptionAggregateSyntax(t *testing.T) { if got, want := opt.AggregatedConstants[0].Source, "test"; got != want { t.Errorf("got [%v] want [%v]", got, want) } - if got, want := opt.AggregatedConstants[0].Comment, " test"; got != want { - t.Errorf("got [%v] want [%v]", got, want) - } if got, want := opt.AggregatedConstants[1].Source, "test2"; got != want { t.Errorf("got [%v] want [%v]", got, want) } - if got, want := opt.AggregatedConstants[1].Comment, " test2"; got != want { - t.Errorf("got [%v] want [%v]", got, want) - } + t.Log(formatted(srv)) } diff --git a/syntax.go b/syntax.go index 0342327..f479552 100644 --- a/syntax.go +++ b/syntax.go @@ -1,7 +1,7 @@ // Copyright (c) 2017 Ernest Micklei -// +// // MIT License -// +// // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: -// +// // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -25,7 +25,9 @@ package proto // Syntax should have value "proto" type Syntax struct { - Value string + Comment *Comment + Value string + InlineComment *Comment } func (s *Syntax) parse(p *Parser) error { @@ -44,3 +46,13 @@ func (s *Syntax) parse(p *Parser) error { func (s *Syntax) Accept(v Visitor) { v.VisitSyntax(s) } + +// Doc is part of Documented +func (s *Syntax) Doc() *Comment { + return s.Comment +} + +// inlineComment is part of commentInliner. +func (s *Syntax) inlineComment(c *Comment) { + s.InlineComment = c +} diff --git a/syntax_test.go b/syntax_test.go index 52228a0..8c19af1 100644 --- a/syntax_test.go +++ b/syntax_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2017 Ernest Micklei -// +// // MIT License -// +// // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: -// +// // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -38,21 +38,3 @@ func TestSyntax(t *testing.T) { t.Errorf("got [%v] want [%v]", got, want) } } - -func TestCommentAroundSyntax(t *testing.T) { - proto := ` - // comment1 - // comment2 - syntax = 'proto'; // comment3 - // comment4 -` - p := newParserOn(proto) - r, err := p.Parse() - if err != nil { - t.Fatal(err) - } - comments := collect(r).Comments() - if got, want := len(comments), 3; got != want { - t.Fatalf("got [%v] want [%v]", got, want) - } -} diff --git a/visitor.go b/visitor.go index 9ba6bf6..73c10b8 100644 --- a/visitor.go +++ b/visitor.go @@ -1,7 +1,7 @@ // Copyright (c) 2017 Ernest Micklei -// +// // MIT License -// +// // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including @@ -9,10 +9,10 @@ // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: -// +// // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. -// +// // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -50,6 +50,11 @@ type Visitee interface { Accept(v Visitor) } +// Documented is for types that may have an associated comment (not inlined). +type Documented interface { + Doc() *Comment +} + // reflector is a Visitor that can tell the short type name of a Visitee. type reflector struct { name string