1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc. All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7
8 // Author: kenton@google.com (Kenton Varda)
9 // Based on original Protocol Buffers design by
10 // Sanjay Ghemawat, Jeff Dean, and others.
11
12 #include "google/protobuf/compiler/csharp/names.h"
13
14 #include <string>
15
16 #include "absl/strings/match.h"
17 #include "absl/strings/str_replace.h"
18 #include "absl/strings/string_view.h"
19 #include "absl/strings/strip.h"
20 #include "google/protobuf/compiler/csharp/names.h"
21 #include "google/protobuf/descriptor.pb.h"
22
23 // Must be last.
24 #include "google/protobuf/port_def.inc"
25
26 namespace google {
27 namespace protobuf {
28 namespace compiler {
29 namespace csharp {
30
31 namespace {
32
StripDotProto(absl::string_view proto_file)33 absl::string_view StripDotProto(absl::string_view proto_file) {
34 int lastindex = proto_file.find_last_of('.');
35 return proto_file.substr(0, lastindex);
36 }
37
38 // Returns the Pascal-cased last part of the proto file. For example,
39 // input of "google/protobuf/foo_bar.proto" would result in "FooBar".
GetFileNameBase(const FileDescriptor * descriptor)40 std::string GetFileNameBase(const FileDescriptor* descriptor) {
41 const absl::string_view proto_file = descriptor->name();
42 int lastslash = proto_file.find_last_of('/');
43 const absl::string_view base = proto_file.substr(lastslash + 1);
44 return UnderscoresToPascalCase(StripDotProto(base));
45 }
46
ToCSharpName(absl::string_view name,const FileDescriptor * file)47 std::string ToCSharpName(absl::string_view name, const FileDescriptor* file) {
48 std::string result = GetFileNamespace(file);
49 if (!result.empty()) {
50 result += '.';
51 }
52 absl::string_view classname;
53 if (file->package().empty()) {
54 classname = name;
55 } else {
56 // Strip the proto package from full_name since we've replaced it with
57 // the C# namespace.
58 classname = name.substr(file->package().size() + 1);
59 }
60 return absl::StrCat("global::", result,
61 absl::StrReplaceAll(classname, {{".", ".Types."}}));
62 }
63
64 } // namespace
65
GetFileNamespace(const FileDescriptor * descriptor)66 std::string GetFileNamespace(const FileDescriptor* descriptor) {
67 if (descriptor->options().has_csharp_namespace()) {
68 return descriptor->options().csharp_namespace();
69 }
70 return UnderscoresToCamelCase(descriptor->package(), true, true);
71 }
72
GetClassName(const Descriptor * descriptor)73 std::string GetClassName(const Descriptor* descriptor) {
74 return ToCSharpName(descriptor->full_name(), descriptor->file());
75 }
76
GetClassName(const EnumDescriptor * descriptor)77 std::string GetClassName(const EnumDescriptor* descriptor) {
78 return ToCSharpName(descriptor->full_name(), descriptor->file());
79 }
80
GetReflectionClassUnqualifiedName(const FileDescriptor * descriptor)81 std::string GetReflectionClassUnqualifiedName(const FileDescriptor* descriptor) {
82 // TODO: Detect collisions with existing messages,
83 // and append an underscore if necessary.
84 return absl::StrCat(GetFileNameBase(descriptor), "Reflection");
85 }
86
GetReflectionClassName(const FileDescriptor * descriptor)87 std::string GetReflectionClassName(const FileDescriptor* descriptor) {
88 std::string result = GetFileNamespace(descriptor);
89 if (!result.empty()) {
90 result += '.';
91 }
92 return absl::StrCat("global::", result,
93 GetReflectionClassUnqualifiedName(descriptor));
94 }
95
GetExtensionClassUnqualifiedName(const FileDescriptor * descriptor)96 std::string GetExtensionClassUnqualifiedName(const FileDescriptor* descriptor) {
97 // TODO: Detect collisions with existing messages,
98 // and append an underscore if necessary.
99 return absl::StrCat(GetFileNameBase(descriptor), "Extensions");
100 }
101
GetOutputFile(const FileDescriptor * descriptor,absl::string_view file_extension,bool generate_directories,absl::string_view base_namespace,std::string * error)102 std::string GetOutputFile(const FileDescriptor* descriptor,
103 absl::string_view file_extension,
104 bool generate_directories,
105 absl::string_view base_namespace,
106 std::string* error) {
107 std::string relative_filename =
108 absl::StrCat(GetFileNameBase(descriptor), file_extension);
109 if (!generate_directories) {
110 return relative_filename;
111 }
112 std::string ns = GetFileNamespace(descriptor);
113 absl::string_view namespace_suffix = ns;
114 if (!base_namespace.empty()) {
115 // Check that the base_namespace is either equal to or a leading part of
116 // the file namespace. This isn't just a simple prefix; "Foo.B" shouldn't
117 // be regarded as a prefix of "Foo.Bar". The simplest option is to add "."
118 // to both.
119 if (!absl::ConsumePrefix(&namespace_suffix, base_namespace) ||
120 (!namespace_suffix.empty() &&
121 !absl::ConsumePrefix(&namespace_suffix, "."))) {
122 *error = absl::StrCat("Namespace ", ns,
123 " is not a prefix namespace of base namespace ",
124 base_namespace);
125 return ""; // This will be ignored, because we've set an error.
126 }
127 }
128
129 return absl::StrCat(absl::StrReplaceAll(namespace_suffix, {{".", "/"}}),
130 namespace_suffix.empty() ? "" : "/", relative_filename);
131 }
132
UnderscoresToPascalCase(absl::string_view input)133 std::string UnderscoresToPascalCase(absl::string_view input) {
134 return UnderscoresToCamelCase(input, true);
135 }
136
137 // TODO: can we reuse a utility function?
UnderscoresToCamelCase(absl::string_view input,bool cap_next_letter,bool preserve_period)138 std::string UnderscoresToCamelCase(absl::string_view input,
139 bool cap_next_letter, bool preserve_period) {
140 std::string result;
141
142 // Note: I distrust ctype.h due to locales.
143 for (int i = 0; i < input.size(); i++) {
144 if ('a' <= input[i] && input[i] <= 'z') {
145 if (cap_next_letter) {
146 result += input[i] + ('A' - 'a');
147 } else {
148 result += input[i];
149 }
150 cap_next_letter = false;
151 } else if ('A' <= input[i] && input[i] <= 'Z') {
152 if (i == 0 && !cap_next_letter) {
153 // Force first letter to lower-case unless explicitly told to
154 // capitalize it.
155 result += input[i] + ('a' - 'A');
156 } else {
157 // Capital letters after the first are left as-is.
158 result += input[i];
159 }
160 cap_next_letter = false;
161 } else if ('0' <= input[i] && input[i] <= '9') {
162 result += input[i];
163 cap_next_letter = true;
164 } else {
165 cap_next_letter = true;
166 if (input[i] == '.' && preserve_period) {
167 result += '.';
168 }
169 }
170 }
171 // Add a trailing "_" if the name should be altered.
172 if (input.size() > 0 && input[input.size() - 1] == '#') {
173 result += '_';
174 }
175
176 // https://github.com/protocolbuffers/protobuf/issues/8101
177 // To avoid generating invalid identifiers - if the input string
178 // starts with _<digit> (or multiple underscores then digit) then
179 // we need to preserve the underscore as an identifier cannot start
180 // with a digit.
181 // This check is being done after the loop rather than before
182 // to handle the case where there are multiple underscores before the
183 // first digit. We let them all be consumed so we can see if we would
184 // start with a digit.
185 // Note: not preserving leading underscores for all otherwise valid identifiers
186 // so as to not break anything that relies on the existing behaviour
187 if (result.size() > 0 && ('0' <= result[0] && result[0] <= '9')
188 && input.size() > 0 && input[0] == '_')
189 {
190 result.insert(0, 1, '_');
191 }
192 return result;
193 }
194
195 } // namespace csharp
196 } // namespace compiler
197 } // namespace protobuf
198 } // namespace google
199
200 #include "google/protobuf/port_undef.inc"
201