• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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