protostar 0.1.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +60 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +647 -0
  5. data/THIRD_PARTY_NOTICES.md +61 -0
  6. data/exe/protoc-gen-protostar +7 -0
  7. data/exe/protostar +7 -0
  8. data/lib/protostar/builder/generator.rb +71 -0
  9. data/lib/protostar/builder/sink.rb +35 -0
  10. data/lib/protostar/builder.rb +965 -0
  11. data/lib/protostar/cli.rb +267 -0
  12. data/lib/protostar/codec/decode_generator.rb +199 -0
  13. data/lib/protostar/codec/decoder.rb +296 -0
  14. data/lib/protostar/codec/encode_generator.rb +224 -0
  15. data/lib/protostar/codec/encoder.rb +121 -0
  16. data/lib/protostar/codec/sizer.rb +91 -0
  17. data/lib/protostar/codec.rb +15 -0
  18. data/lib/protostar/compiler/ast.rb +193 -0
  19. data/lib/protostar/compiler/emitters/descriptor_set.rb +57 -0
  20. data/lib/protostar/compiler/emitters/google_ruby.rb +103 -0
  21. data/lib/protostar/compiler/emitters/grpc.rb +175 -0
  22. data/lib/protostar/compiler/emitters/protostar_rbs.rb +252 -0
  23. data/lib/protostar/compiler/emitters/rbs.rb +394 -0
  24. data/lib/protostar/compiler/emitters/ruby.rb +503 -0
  25. data/lib/protostar/compiler/emitters.rb +268 -0
  26. data/lib/protostar/compiler/include/LICENSE +32 -0
  27. data/lib/protostar/compiler/include/google/protobuf/any.proto +106 -0
  28. data/lib/protostar/compiler/include/google/protobuf/api.proto +229 -0
  29. data/lib/protostar/compiler/include/google/protobuf/compiler/plugin.proto +180 -0
  30. data/lib/protostar/compiler/include/google/protobuf/cpp_features.proto +87 -0
  31. data/lib/protostar/compiler/include/google/protobuf/descriptor.proto +1532 -0
  32. data/lib/protostar/compiler/include/google/protobuf/duration.proto +115 -0
  33. data/lib/protostar/compiler/include/google/protobuf/empty.proto +51 -0
  34. data/lib/protostar/compiler/include/google/protobuf/field_mask.proto +243 -0
  35. data/lib/protostar/compiler/include/google/protobuf/java_features.proto +132 -0
  36. data/lib/protostar/compiler/include/google/protobuf/source_context.proto +48 -0
  37. data/lib/protostar/compiler/include/google/protobuf/struct.proto +111 -0
  38. data/lib/protostar/compiler/include/google/protobuf/timestamp.proto +145 -0
  39. data/lib/protostar/compiler/include/google/protobuf/type.proto +217 -0
  40. data/lib/protostar/compiler/include/google/protobuf/wrappers.proto +157 -0
  41. data/lib/protostar/compiler/lexer.rb +659 -0
  42. data/lib/protostar/compiler/linker.rb +894 -0
  43. data/lib/protostar/compiler/option_interpreter.rb +363 -0
  44. data/lib/protostar/compiler/parser.rb +1696 -0
  45. data/lib/protostar/compiler/retention.rb +104 -0
  46. data/lib/protostar/compiler/run.rb +184 -0
  47. data/lib/protostar/compiler/source_info.rb +161 -0
  48. data/lib/protostar/compiler/symbol_table.rb +223 -0
  49. data/lib/protostar/compiler/text_format.rb +340 -0
  50. data/lib/protostar/compiler/validator.rb +944 -0
  51. data/lib/protostar/compiler.rb +304 -0
  52. data/lib/protostar/diagnostics/codes.rb +1307 -0
  53. data/lib/protostar/diagnostics/formats.rb +64 -0
  54. data/lib/protostar/diagnostics/renderer.rb +240 -0
  55. data/lib/protostar/diagnostics/source.rb +153 -0
  56. data/lib/protostar/diagnostics.rb +117 -0
  57. data/lib/protostar/dsl.rb +367 -0
  58. data/lib/protostar/enum.rb +70 -0
  59. data/lib/protostar/errors.rb +23 -0
  60. data/lib/protostar/google/protobuf/any_pb.rb +90 -0
  61. data/lib/protostar/google/protobuf/api_pb.rb +199 -0
  62. data/lib/protostar/google/protobuf/compiler/plugin_pb.rb +168 -0
  63. data/lib/protostar/google/protobuf/descriptor_pb.rb +1239 -0
  64. data/lib/protostar/google/protobuf/duration_pb.rb +99 -0
  65. data/lib/protostar/google/protobuf/empty_pb.rb +36 -0
  66. data/lib/protostar/google/protobuf/field_mask_pb.rb +228 -0
  67. data/lib/protostar/google/protobuf/source_context_pb.rb +33 -0
  68. data/lib/protostar/google/protobuf/struct_pb.rb +91 -0
  69. data/lib/protostar/google/protobuf/timestamp_pb.rb +129 -0
  70. data/lib/protostar/google/protobuf/type_pb.rb +202 -0
  71. data/lib/protostar/google/protobuf/wrappers_pb.rb +125 -0
  72. data/lib/protostar/google_protobuf_shim.rb +24 -0
  73. data/lib/protostar/json/encoder.rb +340 -0
  74. data/lib/protostar/json/errors.rb +34 -0
  75. data/lib/protostar/json/parser.rb +437 -0
  76. data/lib/protostar/json/path.rb +39 -0
  77. data/lib/protostar/json.rb +95 -0
  78. data/lib/protostar/lint/command.rb +197 -0
  79. data/lib/protostar/lint/config.rb +98 -0
  80. data/lib/protostar/lint/context.rb +126 -0
  81. data/lib/protostar/lint/directives.rb +56 -0
  82. data/lib/protostar/lint/fixer.rb +165 -0
  83. data/lib/protostar/lint/naming.rb +54 -0
  84. data/lib/protostar/lint/rule.rb +120 -0
  85. data/lib/protostar/lint/rules/comment_enum.rb +31 -0
  86. data/lib/protostar/lint/rules/comment_enum_value.rb +32 -0
  87. data/lib/protostar/lint/rules/comment_field.rb +33 -0
  88. data/lib/protostar/lint/rules/comment_message.rb +31 -0
  89. data/lib/protostar/lint/rules/comment_rpc.rb +31 -0
  90. data/lib/protostar/lint/rules/comment_service.rb +31 -0
  91. data/lib/protostar/lint/rules/edition_recommended.rb +31 -0
  92. data/lib/protostar/lint/rules/enum_first_value_zero.rb +34 -0
  93. data/lib/protostar/lint/rules/enum_pascal_case.rb +25 -0
  94. data/lib/protostar/lint/rules/enum_value_prefix.rb +38 -0
  95. data/lib/protostar/lint/rules/enum_value_upper_snake_case.rb +28 -0
  96. data/lib/protostar/lint/rules/enum_zero_value_suffix.rb +41 -0
  97. data/lib/protostar/lint/rules/field_lower_snake_case.rb +34 -0
  98. data/lib/protostar/lint/rules/field_not_required.rb +39 -0
  99. data/lib/protostar/lint/rules/field_number_reuse_risk.rb +45 -0
  100. data/lib/protostar/lint/rules/field_numbers_hot_range.rb +44 -0
  101. data/lib/protostar/lint/rules/file_lower_snake_case.rb +27 -0
  102. data/lib/protostar/lint/rules/import_no_public.rb +29 -0
  103. data/lib/protostar/lint/rules/import_no_weak.rb +28 -0
  104. data/lib/protostar/lint/rules/import_used.rb +33 -0
  105. data/lib/protostar/lint/rules/message_pascal_case.rb +27 -0
  106. data/lib/protostar/lint/rules/no_deprecated_features.rb +50 -0
  107. data/lib/protostar/lint/rules/no_groups.rb +34 -0
  108. data/lib/protostar/lint/rules/oneof_lower_snake_case.rb +27 -0
  109. data/lib/protostar/lint/rules/package_defined.rb +28 -0
  110. data/lib/protostar/lint/rules/package_directory_match.rb +33 -0
  111. data/lib/protostar/lint/rules/package_lower_snake_case.rb +28 -0
  112. data/lib/protostar/lint/rules/package_same_directory.rb +39 -0
  113. data/lib/protostar/lint/rules/package_version_suffix.rb +31 -0
  114. data/lib/protostar/lint/rules/reserved_names_with_numbers.rb +44 -0
  115. data/lib/protostar/lint/rules/rpc_pascal_case.rb +25 -0
  116. data/lib/protostar/lint/rules/rpc_request_response_unique.rb +40 -0
  117. data/lib/protostar/lint/rules/rpc_request_standard_name.rb +32 -0
  118. data/lib/protostar/lint/rules/rpc_response_standard_name.rb +31 -0
  119. data/lib/protostar/lint/rules/service_pascal_case.rb +25 -0
  120. data/lib/protostar/lint/rules/service_suffix.rb +28 -0
  121. data/lib/protostar/lint/rules/syntax_specified.rb +35 -0
  122. data/lib/protostar/lint/runner.rb +135 -0
  123. data/lib/protostar/lint.rb +122 -0
  124. data/lib/protostar/message/accessors.rb +86 -0
  125. data/lib/protostar/message/coercion.rb +171 -0
  126. data/lib/protostar/message/field_info.rb +145 -0
  127. data/lib/protostar/message/reflection.rb +149 -0
  128. data/lib/protostar/message.rb +572 -0
  129. data/lib/protostar/plugin.rb +94 -0
  130. data/lib/protostar/schema/default_value.rb +113 -0
  131. data/lib/protostar/schema/descriptor.rb +41 -0
  132. data/lib/protostar/schema/draft/copy.rb +88 -0
  133. data/lib/protostar/schema/draft/resolution.rb +177 -0
  134. data/lib/protostar/schema/draft.rb +779 -0
  135. data/lib/protostar/schema/enum.rb +106 -0
  136. data/lib/protostar/schema/extension_range.rb +37 -0
  137. data/lib/protostar/schema/features.rb +153 -0
  138. data/lib/protostar/schema/field.rb +201 -0
  139. data/lib/protostar/schema/file.rb +217 -0
  140. data/lib/protostar/schema/freezer.rb +61 -0
  141. data/lib/protostar/schema/message.rb +104 -0
  142. data/lib/protostar/schema/oneof.rb +38 -0
  143. data/lib/protostar/schema/options.rb +367 -0
  144. data/lib/protostar/schema/pool.rb +179 -0
  145. data/lib/protostar/schema/proto.rb +576 -0
  146. data/lib/protostar/schema/resolver.rb +118 -0
  147. data/lib/protostar/schema/service.rb +89 -0
  148. data/lib/protostar/schema/source_info.rb +53 -0
  149. data/lib/protostar/schema.rb +65 -0
  150. data/lib/protostar/short_names/any.rb +7 -0
  151. data/lib/protostar/short_names/duration.rb +7 -0
  152. data/lib/protostar/short_names/empty.rb +7 -0
  153. data/lib/protostar/short_names/field_mask.rb +7 -0
  154. data/lib/protostar/short_names/struct.rb +9 -0
  155. data/lib/protostar/short_names/timestamp.rb +7 -0
  156. data/lib/protostar/short_names/wrappers.rb +15 -0
  157. data/lib/protostar/text/extensions.rb +114 -0
  158. data/lib/protostar/text/parser.rb +439 -0
  159. data/lib/protostar/text/printer.rb +304 -0
  160. data/lib/protostar/text/tokenizer.rb +116 -0
  161. data/lib/protostar/text.rb +67 -0
  162. data/lib/protostar/types.rb +152 -0
  163. data/lib/protostar/unknown_field.rb +58 -0
  164. data/lib/protostar/version.rb +5 -0
  165. data/lib/protostar/view/generator.rb +298 -0
  166. data/lib/protostar/view/map.rb +194 -0
  167. data/lib/protostar/view/repeated.rb +143 -0
  168. data/lib/protostar/view/scan.rb +183 -0
  169. data/lib/protostar/view.rb +283 -0
  170. data/lib/protostar/well_known/any.rb +52 -0
  171. data/lib/protostar/well_known/duration.rb +53 -0
  172. data/lib/protostar/well_known/list_value.rb +63 -0
  173. data/lib/protostar/well_known/struct.rb +55 -0
  174. data/lib/protostar/well_known/timestamp.rb +64 -0
  175. data/lib/protostar/well_known/value.rb +60 -0
  176. data/lib/protostar/well_known.rb +38 -0
  177. data/lib/protostar/wire/io_source.rb +40 -0
  178. data/lib/protostar/wire/reader.rb +254 -0
  179. data/lib/protostar/wire/writer.rb +201 -0
  180. data/lib/protostar/wire.rb +96 -0
  181. data/lib/protostar.rb +95 -0
  182. data/shims/google-protobuf/lib/google/protobuf/any_pb.rb +17 -0
  183. data/shims/google-protobuf/lib/google/protobuf/api_pb.rb +22 -0
  184. data/shims/google-protobuf/lib/google/protobuf/compiler/plugin_pb.rb +25 -0
  185. data/shims/google-protobuf/lib/google/protobuf/descriptor_pb.rb +72 -0
  186. data/shims/google-protobuf/lib/google/protobuf/descriptor_pool.rb +164 -0
  187. data/shims/google-protobuf/lib/google/protobuf/descriptors.rb +553 -0
  188. data/shims/google-protobuf/lib/google/protobuf/duration_pb.rb +17 -0
  189. data/shims/google-protobuf/lib/google/protobuf/empty_pb.rb +17 -0
  190. data/shims/google-protobuf/lib/google/protobuf/field_mask_pb.rb +17 -0
  191. data/shims/google-protobuf/lib/google/protobuf/internal/convert.rb +180 -0
  192. data/shims/google-protobuf/lib/google/protobuf/internal/extensions.rb +154 -0
  193. data/shims/google-protobuf/lib/google/protobuf/internal/json.rb +104 -0
  194. data/shims/google-protobuf/lib/google/protobuf/internal/well_known.rb +140 -0
  195. data/shims/google-protobuf/lib/google/protobuf/map.rb +216 -0
  196. data/shims/google-protobuf/lib/google/protobuf/message.rb +544 -0
  197. data/shims/google-protobuf/lib/google/protobuf/message_exts.rb +41 -0
  198. data/shims/google-protobuf/lib/google/protobuf/plugin_pb.rb +4 -0
  199. data/shims/google-protobuf/lib/google/protobuf/repeated_field.rb +366 -0
  200. data/shims/google-protobuf/lib/google/protobuf/source_context_pb.rb +17 -0
  201. data/shims/google-protobuf/lib/google/protobuf/struct_pb.rb +20 -0
  202. data/shims/google-protobuf/lib/google/protobuf/timestamp_pb.rb +17 -0
  203. data/shims/google-protobuf/lib/google/protobuf/type_pb.rb +27 -0
  204. data/shims/google-protobuf/lib/google/protobuf/well_known_types.rb +37 -0
  205. data/shims/google-protobuf/lib/google/protobuf/wrappers_pb.rb +25 -0
  206. data/shims/google-protobuf/lib/google/protobuf.rb +107 -0
  207. data/shims/google-protobuf/lib/google/protobuf_ffi.rb +5 -0
  208. data/shims/google-protobuf/lib/google/protobuf_native.rb +5 -0
  209. data/sig/protostar/google/protobuf/any_pb.rbs +77 -0
  210. data/sig/protostar/google/protobuf/api_pb.rbs +242 -0
  211. data/sig/protostar/google/protobuf/compiler/plugin_pb.rbs +264 -0
  212. data/sig/protostar/google/protobuf/descriptor_pb.rbs +2220 -0
  213. data/sig/protostar/google/protobuf/duration_pb.rbs +82 -0
  214. data/sig/protostar/google/protobuf/empty_pb.rbs +18 -0
  215. data/sig/protostar/google/protobuf/field_mask_pb.rbs +170 -0
  216. data/sig/protostar/google/protobuf/source_context_pb.rbs +24 -0
  217. data/sig/protostar/google/protobuf/struct_pb.rbs +135 -0
  218. data/sig/protostar/google/protobuf/timestamp_pb.rbs +102 -0
  219. data/sig/protostar/google/protobuf/type_pb.rbs +304 -0
  220. data/sig/protostar/google/protobuf/wrappers_pb.rbs +169 -0
  221. data/sig/protostar/lint.rbs +216 -0
  222. data/sig/protostar.rbs +326 -0
  223. metadata +366 -0
@@ -0,0 +1,252 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ class Compiler
5
+ module Emitters
6
+ # Writes `_pb.rbs` signatures for the classes {Ruby} generates:
7
+ # subclasses of `Protostar::Message`, repeated fields as Arrays, maps as
8
+ # Hashes, writers typed as {Message::Coercion} accepts. Signatures for
9
+ # `Protostar::Message` itself are in the gem's `sig/protostar.rbs`.
10
+ # {Rbs} writes protoc's signatures for google-protobuf's classes instead.
11
+ class ProtostarRbs
12
+ INTEGERS = %i[fixed32 fixed64 int32 int64 sfixed32 sfixed64 sint32 sint64 uint32 uint64].freeze
13
+ # The mixins {WellKnown::MIXINS} includes, by full name, as RBS names
14
+ MIXINS = {
15
+ "google.protobuf.Any" => "::Protostar::WellKnown::Any",
16
+ "google.protobuf.Timestamp" => "::Protostar::WellKnown::Timestamp",
17
+ "google.protobuf.Duration" => "::Protostar::WellKnown::Duration",
18
+ "google.protobuf.Struct" => "::Protostar::WellKnown::Struct",
19
+ "google.protobuf.Value" => "::Protostar::WellKnown::Value",
20
+ "google.protobuf.ListValue" => "::Protostar::WellKnown::ListValue"
21
+ }.freeze
22
+ # Ruby values {Message::Coercion} turns into these messages
23
+ COERCED = {
24
+ "google.protobuf.Timestamp" => "::Time",
25
+ "google.protobuf.Duration" => "::Numeric"
26
+ }.freeze
27
+
28
+ # @param namespaces [Hash{String => String}] Ruby module paths by file name, as {Ruby} takes them
29
+ def initialize(namespaces: {})
30
+ @namespaces = namespaces
31
+ @reserved = Message::Accessors.reserved
32
+ end
33
+
34
+ # @param files [Array<Schema::File>]
35
+ # @return [Hash{String => String}] output path to signatures
36
+ def emit(files)
37
+ files.to_h { |f| [Naming.require_name(f.name) + ".rbs", file(f)] }
38
+ end
39
+
40
+ # @param file [Schema::File]
41
+ # @return [String]
42
+ def file(file)
43
+ Naming.check_ruby_package(file)
44
+ @source = file.source_info
45
+ p = Printer.new
46
+ p.print("# Generated by protostar from #{file.name}. Do not edit.\n")
47
+ modules = module_path(file)
48
+ unless file.messages.empty? && file.enums.empty?
49
+ p.print("\n")
50
+ modules.each do |m|
51
+ p.print("module #{m}\n")
52
+ p.indent
53
+ end
54
+ first = [true]
55
+ file.messages.each_with_index { |m, i| message(p, m, [4, i], first) }
56
+ file.enums.each_with_index { |e, i| enum(p, e, [5, i], first) }
57
+ modules.size.times do
58
+ p.outdent
59
+ p.print("end\n")
60
+ end
61
+ end
62
+ p.to_s
63
+ ensure
64
+ @source = nil
65
+ end
66
+
67
+ private
68
+
69
+ def module_path(file)
70
+ namespace = @namespaces[file.name]
71
+ namespace ||= "Protostar::#{Naming.module_path(file).join("::")}" if BOOTSTRAP.include?(file.name)
72
+ Naming.module_path(file, namespace)
73
+ end
74
+
75
+ def constant(descriptor)
76
+ prefix = module_path(descriptor.file).map { |m| "::#{m}" }.join
77
+ "#{prefix}::#{Naming.relative_constant(descriptor)}"
78
+ end
79
+
80
+ def blank(p, first)
81
+ if first[0]
82
+ first[0] = false
83
+ else
84
+ p.print("\n")
85
+ end
86
+ end
87
+
88
+ def comment(p, path)
89
+ text = @source&.location(path)&.leading_comments
90
+ return if text.nil? || text.empty?
91
+ text.split("\n").reject(&:empty?).each { |line| p.print("##{line}\n") }
92
+ end
93
+
94
+ def message?(field)
95
+ field.type == :message || field.type == :group
96
+ end
97
+
98
+ # What the reader returns for one value
99
+ def read_one(field)
100
+ case field.type
101
+ when :double, :float then "::Float"
102
+ when *INTEGERS then "::Integer"
103
+ when :bool then "bool"
104
+ when :string, :bytes then "::String"
105
+ when :enum then "#{constant(field.enum_type)}::names | ::Integer"
106
+ else constant(field.message_type)
107
+ end
108
+ end
109
+
110
+ # What the writer takes for one value
111
+ def write_one(field)
112
+ case field.type
113
+ when :double, :float then "::Float | ::Integer"
114
+ when *INTEGERS then "::Integer"
115
+ when :bool then "bool"
116
+ when :string, :bytes then "::String"
117
+ when :enum then "#{constant(field.enum_type)}::names | ::Integer"
118
+ else
119
+ name = constant(field.message_type)
120
+ [name, "#{name}::init_map", COERCED[field.message_type.full_name]].compact.join(" | ")
121
+ end
122
+ end
123
+
124
+ def read_type(field)
125
+ if field.map?
126
+ "::Hash[#{read_one(field.map_key)}, #{read_one(field.map_value)}]"
127
+ elsif field.repeated?
128
+ "::Array[#{read_one(field)}]"
129
+ elsif message?(field)
130
+ "#{constant(field.message_type)}?"
131
+ else
132
+ read_one(field)
133
+ end
134
+ end
135
+
136
+ def write_type(field)
137
+ inner = if field.map?
138
+ "::Hash[#{write_one(field.map_key)}, #{write_one(field.map_value)}]"
139
+ elsif field.repeated?
140
+ "::Array[#{write_one(field)}]"
141
+ else
142
+ write_one(field)
143
+ end
144
+ inner.include?("|") ? "(#{inner})?" : "#{inner}?"
145
+ end
146
+
147
+ def enum(p, enum, path, first)
148
+ blank(p, first)
149
+ comment(p, path)
150
+ p.print("module #{Naming.rubify_constant(enum.name)}\n")
151
+ p.indent
152
+ p.print("extend ::Protostar::Enum\n\n")
153
+ seen = {}
154
+ enum.values.each do |v|
155
+ const = Protostar::Enum.constant_name(v.name)
156
+ next if const.nil? || seen[const]
157
+ seen[const] = true
158
+ p.print("#{const}: #{v.number}\n")
159
+ end
160
+ p.print("\ntype names = #{enum.values.map { |v| ":#{symbol(v.name)}" }.uniq.join(" | ")}\n")
161
+ p.outdent
162
+ p.print("end\n")
163
+ end
164
+
165
+ # A Symbol literal for a name, quoted when RBS needs it
166
+ def symbol(name)
167
+ /\A[A-Za-z_][A-Za-z0-9_]*\z/.match?(name) ? name : name.inspect
168
+ end
169
+
170
+ def message(p, message, path, first)
171
+ return if message.map_entry?
172
+ blank(p, first)
173
+ comment(p, path)
174
+ p.print("class #{Naming.rubify_constant(message.name)} < ::Protostar::Message\n")
175
+ p.indent
176
+ inner = [true]
177
+ message.nested_messages.each_with_index { |m, i| message(p, m, path + [3, i], inner) }
178
+ message.nested_enums.each_with_index { |e, i| enum(p, e, path + [4, i], inner) }
179
+ blank(p, inner)
180
+ if (mixin = MIXINS[message.full_name])
181
+ p.print("include #{mixin}\nextend #{mixin}::ClassMethods\n\n")
182
+ end
183
+ init_map(p, message)
184
+ constructor(p, message)
185
+ taken = @reserved.dup
186
+ message.fields.each_with_index do |f, i|
187
+ accessors(p, f, path + [2, i], taken)
188
+ end
189
+ message.real_oneofs.each do |o|
190
+ which = :"which_#{o.name}"
191
+ next if taken[which]
192
+ taken[which] = true
193
+ names = o.fields.map { |f| ":#{f.name}" }.uniq.join(" | ")
194
+ p.print("def which_#{o.name}: () -> (#{names})?\n")
195
+ end
196
+ p.outdent
197
+ p.print("end\n")
198
+ end
199
+
200
+ def init_map(p, message)
201
+ if message.fields.empty?
202
+ p.print("type init_map = ::Hash[::Symbol | ::String, untyped]\n")
203
+ return
204
+ end
205
+ p.print("type init_map = {\n")
206
+ p.indent
207
+ message.fields.each_with_index do |f, i|
208
+ comma = (i == message.fields.size - 1) ? "" : ","
209
+ p.print("?#{f.name}: #{write_type(f)}#{comma}\n")
210
+ end
211
+ p.outdent
212
+ p.print("}\n")
213
+ end
214
+
215
+ # `new` takes a Hash, keywords, or both
216
+ def constructor(p, message)
217
+ if message.fields.empty?
218
+ p.print("def initialize: (?init_map? fields, **untyped) -> void\n")
219
+ return
220
+ end
221
+ p.print("def initialize: (\n")
222
+ p.indent
223
+ p.print("?init_map? fields")
224
+ message.fields.each { |f| p.print(",\n?#{f.name}: #{write_type(f)}") }
225
+ p.outdent
226
+ p.print("\n) -> void\n")
227
+ end
228
+
229
+ def accessors(p, field, path, taken)
230
+ name = field.name
231
+ read = read_type(field)
232
+ write = write_type(field)
233
+ reader = !taken[name.to_sym]
234
+ writer = !taken[:"#{name}="]
235
+ taken[name.to_sym] = taken[:"#{name}="] = true
236
+ if reader || writer
237
+ p.print("\n")
238
+ comment(p, path)
239
+ p.print("attr_reader #{name}(): #{read}\n") if reader
240
+ p.print("attr_writer #{name}(): #{write}\n") if writer
241
+ end
242
+ return unless field.presence?
243
+ has = :"has_#{name}?"
244
+ clear = :"clear_#{name}"
245
+ p.print("def #{has}: () -> bool\n") unless taken[has]
246
+ p.print("def #{clear}: () -> void\n") unless taken[clear]
247
+ taken[has] = taken[clear] = true
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,394 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ class Compiler
5
+ module Emitters
6
+ # Writes the `_pb.rbs` signatures protoc's rbs_generator.cc writes.
7
+ # Leading comments come from source info, so compile with it.
8
+ class Rbs
9
+ WRAPPERS = %w[
10
+ google.protobuf.DoubleValue google.protobuf.FloatValue google.protobuf.Int64Value
11
+ google.protobuf.UInt64Value google.protobuf.Int32Value google.protobuf.UInt32Value
12
+ google.protobuf.StringValue google.protobuf.BytesValue google.protobuf.BoolValue
13
+ ].freeze
14
+ INTEGERS = %i[fixed32 fixed64 int32 int64 sfixed32 sfixed64 sint32 sint64 uint32 uint64].freeze
15
+ HEADER = "# Generated by the protocol buffer compiler. DO NOT EDIT!\n" \
16
+ "# This RBS interface is provided for convenience, on a best-effort basis.\n" \
17
+ "# The library is the definitive source for the API contract; if the RBS file\n" \
18
+ "# and the library's behavior differ, the library behavior is authoritative.\n" \
19
+ "# We welcome fixes to change the RBS file to match.\n"
20
+
21
+ # @param files [Array<Schema::File>]
22
+ # @return [Hash{String => String}] output path to signatures
23
+ def emit(files)
24
+ files.to_h { |f| [Naming.require_name(f.name) + ".rbs", file(f)] }
25
+ end
26
+
27
+ # @param file [Schema::File]
28
+ # @return [String]
29
+ def file(file)
30
+ Naming.check_ruby_package(file)
31
+ @source = file.source_info
32
+ p = Printer.new
33
+ p.print("#{HEADER}# source: #{file.name}\n\n")
34
+ initial = [true]
35
+ modules = Naming.module_path(file)
36
+ modules.each do |m|
37
+ p.print("module #{m}\n")
38
+ p.indent
39
+ end
40
+ file.messages.each_with_index do |m, i|
41
+ mid_line(initial, p)
42
+ message(p, m, [4, i])
43
+ end
44
+ file.enums.each_with_index do |e, i|
45
+ mid_line(initial, p)
46
+ enum(p, e, [5, i])
47
+ end
48
+ modules.size.times do
49
+ p.outdent
50
+ p.print("end\n")
51
+ end
52
+ p.print("\n")
53
+ lookup_override(p, file)
54
+ p.to_s
55
+ ensure
56
+ @source = nil
57
+ end
58
+
59
+ private
60
+
61
+ def mid_line(initial, p)
62
+ if initial[0]
63
+ initial[0] = false
64
+ else
65
+ p.print("\n")
66
+ end
67
+ end
68
+
69
+ def separator(initial)
70
+ if initial[0]
71
+ initial[0] = false
72
+ " "
73
+ else
74
+ "| "
75
+ end
76
+ end
77
+
78
+ def wrap_union(type)
79
+ type.include?("|") ? "(#{type})" : type
80
+ end
81
+
82
+ def optional(type)
83
+ type.end_with?("?") ? type : "#{wrap_union(type)}?"
84
+ end
85
+
86
+ def comment(p, path)
87
+ text = @source&.location(path)&.leading_comments
88
+ return if text.nil? || text.empty?
89
+ text.split("\n").reject(&:empty?).each { |line| p.print("##{line}\n") }
90
+ end
91
+
92
+ def deprecation(p, descriptor)
93
+ p.print("# @deprecated\n") if descriptor.options.deprecated?
94
+ end
95
+
96
+ # rbs_generator.cc's ModulePath: the package's modules as "::A::B"
97
+ def module_path(file)
98
+ convert = true
99
+ if (package = file.options.ruby_package)
100
+ convert = false if package.include?("::")
101
+ else
102
+ package = file.package
103
+ end
104
+ package = package.split(".", -1).map { |part| Naming.package_to_module(part) }.join("::") if convert
105
+ package.empty? ? "" : "::#{package}"
106
+ end
107
+
108
+ def message_name(message)
109
+ parent = message.containing_message
110
+ prefix = parent ? message_name(parent) : module_path(message.file)
111
+ "#{prefix}::#{Naming.rubify_constant(message.name)}"
112
+ end
113
+
114
+ def enum_name(enum)
115
+ parent = enum.containing_message
116
+ prefix = parent ? message_name(parent) : module_path(enum.file)
117
+ "#{prefix}::#{Naming.rubify_constant(enum.name)}"
118
+ end
119
+
120
+ def scalar_read(field)
121
+ case field.type
122
+ when :double, :float then "::Float"
123
+ when *INTEGERS then "::Integer"
124
+ when :bool then "bool"
125
+ when :string, :bytes then "::String"
126
+ when :enum then "#{enum_name(field.enum_type)}::names | ::Integer"
127
+ when :message, :group then message_name(field.message_type)
128
+ else "untyped"
129
+ end
130
+ end
131
+
132
+ def scalar_write(field)
133
+ case field.type
134
+ when :double, :float then "::Float | ::Integer"
135
+ when *INTEGERS then "::Integer | ::Float"
136
+ when :bool then "bool"
137
+ when :string then "::String | ::Symbol"
138
+ when :bytes then "::String"
139
+ when :enum
140
+ name = enum_name(field.enum_type)
141
+ "#{name}::names | #{name}::strings | ::Integer | ::Float"
142
+ when :message, :group
143
+ name = message_name(field.message_type)
144
+ case field.message_type.full_name
145
+ when "google.protobuf.Timestamp" then "#{name} | ::Time"
146
+ when "google.protobuf.Duration" then "#{name} | ::int"
147
+ else name
148
+ end
149
+ else "untyped"
150
+ end
151
+ end
152
+
153
+ def message?(field)
154
+ field.type == :message || field.type == :group
155
+ end
156
+
157
+ def read_type(field)
158
+ if field.map?
159
+ key = field.map_key
160
+ value = field.map_value
161
+ "::Google::Protobuf::Map[#{scalar_read(key)}, #{scalar_read(value)}, #{scalar_write(key)}, " \
162
+ "#{scalar_write(value)}]"
163
+ elsif field.repeated?
164
+ "::Google::Protobuf::RepeatedField[#{scalar_read(field)}, #{scalar_write(field)}]"
165
+ elsif message?(field)
166
+ optional(scalar_read(field))
167
+ else
168
+ scalar_read(field)
169
+ end
170
+ end
171
+
172
+ def write_type(field)
173
+ if field.map? || field.repeated?
174
+ read_type(field)
175
+ elsif message?(field) || field.real_oneof
176
+ optional(scalar_write(field))
177
+ else
178
+ scalar_write(field)
179
+ end
180
+ end
181
+
182
+ def init_type(field)
183
+ if field.map?
184
+ "::Hash[#{scalar_write(field.map_key)}, #{scalar_write(field.map_value)}]?"
185
+ elsif field.repeated?
186
+ "::Array[#{scalar_write(field)}]?"
187
+ elsif message?(field)
188
+ name = message_name(field.message_type)
189
+ optional("#{name} | #{name}::init_map")
190
+ else
191
+ optional(scalar_write(field))
192
+ end
193
+ end
194
+
195
+ def enum_value(p, value, path)
196
+ comment(p, path)
197
+ deprecation(p, value)
198
+ name = value.name
199
+ unless name[0].between?("A", "Z")
200
+ if name[0].between?("a", "z")
201
+ name = name[0].upcase + name[1..]
202
+ else
203
+ p.print("# Enum value '#{value.name}' does not start with an uppercase letter as is required for " \
204
+ "Ruby constants.\n# #{value.name}: #{value.number}\n")
205
+ return
206
+ end
207
+ end
208
+ p.print("#{name}: #{value.number}\n")
209
+ end
210
+
211
+ def enum(p, enum, path)
212
+ comment(p, path)
213
+ deprecation(p, enum)
214
+ initial = [true]
215
+ p.print("module #{Naming.rubify_constant(enum.name)}\n")
216
+ p.indent
217
+ mid_line(initial, p)
218
+ p.print("extend ::Google::Protobuf::_EnumModule\n")
219
+ enum.values.each_with_index do |v, i|
220
+ mid_line(initial, p)
221
+ enum_value(p, v, path + [2, i])
222
+ end
223
+ by_number = {}
224
+ enum.values.each { |v| (by_number[v.number] ||= []) << v.name }
225
+ mid_line(initial, p)
226
+ p.print("def self.lookup:\n")
227
+ p.indent
228
+ overload = [true]
229
+ by_number.each do |number, names|
230
+ p.print("#{separator(overload)}(#{number} number) -> #{wrap_union(names.map { |n| ":#{n}" }.join(" | "))}\n")
231
+ end
232
+ p.print("#{separator(overload)}(::int number) -> names?\n")
233
+ p.print("#{separator(overload)}...\n")
234
+ p.outdent
235
+ mid_line(initial, p)
236
+ p.print("def self.resolve:\n")
237
+ p.indent
238
+ overload = [true]
239
+ enum.values.each { |v| p.print("#{separator(overload)}(:#{v.name} name) -> #{v.number}\n") }
240
+ p.print("#{separator(overload)}(::Symbol name) -> numbers?\n")
241
+ p.print("#{separator(overload)}...\n")
242
+ p.outdent
243
+ mid_line(initial, p)
244
+ p.print("type names = #{enum.values.map { |v| ":#{v.name}" }.join(" | ")}\n")
245
+ mid_line(initial, p)
246
+ p.print("type strings = #{enum.values.map { |v| "\"#{v.name}\"" }.join(" | ")}\n")
247
+ mid_line(initial, p)
248
+ p.print("type numbers = #{by_number.keys.join(" | ")}\n")
249
+ p.outdent
250
+ p.print("end\n")
251
+ end
252
+
253
+ def field(p, field, path)
254
+ comment(p, path)
255
+ deprecation(p, field)
256
+ read = read_type(field)
257
+ write = write_type(field)
258
+ if read == write
259
+ p.print("attr_accessor #{field.name}(): #{read}\n")
260
+ else
261
+ p.print("attr_reader #{field.name}(): #{read}\nattr_writer #{field.name}(): #{write}\n")
262
+ end
263
+ if !field.repeated? && field.message_type && WRAPPERS.include?(field.message_type.full_name)
264
+ wrapped = field.message_type.fields_by_number[1]
265
+ if wrapped
266
+ wrapped_read = optional(scalar_read(wrapped))
267
+ wrapped_write = optional(scalar_write(wrapped))
268
+ if wrapped_read == wrapped_write
269
+ p.print("attr_accessor #{field.name}_as_value(): #{wrapped_read}\n")
270
+ else
271
+ p.print("attr_reader #{field.name}_as_value(): #{wrapped_read}\n" \
272
+ "attr_writer #{field.name}_as_value(): #{wrapped_write}\n")
273
+ end
274
+ end
275
+ end
276
+ if field.type == :enum
277
+ if field.repeated?
278
+ p.print("attr_reader #{field.name}_const(): ::Array[::Integer]\n")
279
+ else
280
+ p.print("attr_reader #{field.name}_const(): ::Integer\n")
281
+ end
282
+ end
283
+ p.print("def has_#{field.name}?: () -> bool\n") if field.presence?
284
+ p.print("def clear_#{field.name}: () -> void\n")
285
+ end
286
+
287
+ def oneof(p, oneof, path)
288
+ comment(p, path)
289
+ types = []
290
+ oneof.fields.each do |f|
291
+ t = scalar_read(f)
292
+ types << t unless types.include?(t)
293
+ end
294
+ p.print("attr_reader #{oneof.name}(): #{optional(types.join(" | "))}\n")
295
+ p.print("def has_#{oneof.name}?: () -> bool\n")
296
+ p.print("def clear_#{oneof.name}: () -> void\n")
297
+ end
298
+
299
+ def message(p, message, path)
300
+ comment(p, path)
301
+ deprecation(p, message)
302
+ p.print("class #{Naming.rubify_constant(message.name)} < ::Google::Protobuf::AbstractMessage\n")
303
+ p.indent
304
+ initial = [true]
305
+ message.nested_messages.each_with_index do |m, i|
306
+ next if m.map_entry?
307
+ mid_line(initial, p)
308
+ message(p, m, path + [3, i])
309
+ end
310
+ message.nested_enums.each_with_index do |e, i|
311
+ mid_line(initial, p)
312
+ enum(p, e, path + [4, i])
313
+ end
314
+ mid_line(initial, p)
315
+ p.print("include ::Google::Protobuf::_MessageClass[#{message_name(message)}]\n")
316
+ message.fields.each_with_index do |f, i|
317
+ mid_line(initial, p)
318
+ field(p, f, path + [2, i])
319
+ end
320
+ message.oneofs.each_with_index do |o, i|
321
+ mid_line(initial, p)
322
+ oneof(p, o, path + [8, i])
323
+ end
324
+ mid_line(initial, p)
325
+ p.print("type init_map = {\n")
326
+ p.indent
327
+ message.fields.each do |f|
328
+ type = init_type(f)
329
+ p.print("#{f.name}: #{type},\n\"#{f.name}\" => #{type},\n")
330
+ end
331
+ p.outdent
332
+ p.print("}\n")
333
+ mid_line(initial, p)
334
+ p.print("def initialize: (?init_map initial_value) -> void\n")
335
+ full = message.full_name
336
+ if !message.fields.empty? && full != "google.protobuf.ListValue" && full != "google.protobuf.Struct"
337
+ mid_line(initial, p)
338
+ p.print("def []:\n")
339
+ p.indent
340
+ overload = [true]
341
+ message.fields.each do |f|
342
+ p.print("#{separator(overload)}(\"#{f.name}\" name) -> #{wrap_union(read_type(f))}\n")
343
+ end
344
+ p.outdent
345
+ mid_line(initial, p)
346
+ p.print("def []=:\n")
347
+ p.indent
348
+ overload = [true]
349
+ message.fields.each do |f|
350
+ p.print("#{separator(overload)}(\"#{f.name}\" name, #{wrap_union(write_type(f))} value) -> void\n")
351
+ end
352
+ p.outdent
353
+ end
354
+ p.outdent
355
+ p.print("end\n")
356
+ end
357
+
358
+ def message_lookup(p, message, overload)
359
+ p.print("#{separator(overload)}(\"#{message.full_name}\" name) -> (::Google::Protobuf::Descriptor & " \
360
+ "::Google::Protobuf::_SpecificDescriptor[singleton(#{message_name(message)})])\n")
361
+ message.nested_messages.each { |m| message_lookup(p, m, overload) unless m.map_entry? }
362
+ message.nested_enums.each { |e| enum_lookup(p, e, overload) }
363
+ end
364
+
365
+ def enum_lookup(p, enum, overload)
366
+ p.print("#{separator(overload)}(\"#{enum.full_name}\" name) -> (::Google::Protobuf::EnumDescriptor & " \
367
+ "::Google::Protobuf::_SpecificEnumDescriptor[singleton(#{enum_name(enum)})])\n")
368
+ end
369
+
370
+ def lookup_override(p, file)
371
+ p.print("module Google\n")
372
+ p.indent
373
+ p.print("module Protobuf\n")
374
+ p.indent
375
+ p.print("class DescriptorPool\n")
376
+ p.indent
377
+ p.print("def lookup:\n")
378
+ p.indent
379
+ overload = [true]
380
+ file.messages.each { |m| message_lookup(p, m, overload) }
381
+ file.enums.each { |e| enum_lookup(p, e, overload) }
382
+ p.print("#{separator(overload)}...\n")
383
+ p.outdent
384
+ p.outdent
385
+ p.print("end\n")
386
+ p.outdent
387
+ p.print("end\n")
388
+ p.outdent
389
+ p.print("end\n")
390
+ end
391
+ end
392
+ end
393
+ end
394
+ end