C# tools: support generated filename corner cases

protoc and gRPC codegens differently treat non-ASCII letter
characters and symbols other than underscores when constructing
their respective output filenames for generated .cs files.
This change reproduces their respective behaviors exactly.

Fixes #17661
pull/18470/head
kkm 6 years ago
parent 224ac2f3ad
commit cbb70f534b
  1. 14
      src/csharp/Grpc.Tools.Tests/CSharpGeneratorTest.cs
  2. 45
      src/csharp/Grpc.Tools/GeneratorServices.cs

@ -33,12 +33,14 @@ namespace Grpc.Tools.Tests
[TestCase("foo.proto", "Foo.cs", "FooGrpc.cs")]
[TestCase("sub/foo.proto", "Foo.cs", "FooGrpc.cs")]
[TestCase("one_two.proto", "OneTwo.cs", "OneTwoGrpc.cs")]
[TestCase("__one_two!.proto", "OneTwo!.cs", "OneTwo!Grpc.cs")]
[TestCase("one(two).proto", "One(two).cs", "One(two)Grpc.cs")]
[TestCase("one_(two).proto", "One(two).cs", "One(two)Grpc.cs")]
[TestCase("one two.proto", "One two.cs", "One twoGrpc.cs")]
[TestCase("one_ two.proto", "One two.cs", "One twoGrpc.cs")]
[TestCase("one .proto", "One .cs", "One Grpc.cs")]
[TestCase("ONE_TWO.proto", "ONETWO.cs", "ONETWOGrpc.cs")]
[TestCase("one.two.proto", "OneTwo.cs", "One.twoGrpc.cs")]
[TestCase("__one_two!.proto", "OneTwo.cs", "OneTwo!Grpc.cs")]
[TestCase("one(two).proto", "OneTwo.cs", "One(two)Grpc.cs")]
[TestCase("one_(two).proto", "OneTwo.cs", "One(two)Grpc.cs")]
[TestCase("one two.proto", "OneTwo.cs", "One twoGrpc.cs")]
[TestCase("one_ two.proto", "OneTwo.cs", "One twoGrpc.cs")]
[TestCase("one .proto", "One.cs", "One Grpc.cs")]
public void NameMangling(string proto, string expectCs, string expectGrpcCs)
{
var poss = _generator.GetPossibleOutputs(Utils.MakeItem(proto, "grpcservices", "both"));

@ -66,29 +66,28 @@ namespace Grpc.Tools
public override string[] GetPossibleOutputs(ITaskItem protoItem)
{
bool doGrpc = GrpcOutputPossible(protoItem);
string filename = LowerUnderscoreToUpperCamel(
Path.GetFileNameWithoutExtension(protoItem.ItemSpec));
var outputs = new string[doGrpc ? 2 : 1];
string basename = Path.GetFileNameWithoutExtension(protoItem.ItemSpec);
string outdir = protoItem.GetMetadata(Metadata.OutputDir);
string fileStem = Path.Combine(outdir, filename);
outputs[0] = fileStem + ".cs";
string filename = LowerUnderscoreToUpperCamelProtocWay(basename);
outputs[0] = Path.Combine(outdir, filename) + ".cs";
if (doGrpc)
{
// Override outdir if kGrpcOutputDir present, default to proto output.
outdir = protoItem.GetMetadata(Metadata.GrpcOutputDir);
if (outdir != "")
{
fileStem = Path.Combine(outdir, filename);
}
outputs[1] = fileStem + "Grpc.cs";
string grpcdir = protoItem.GetMetadata(Metadata.GrpcOutputDir);
filename = LowerUnderscoreToUpperCamelGrpcWay(basename);
outputs[1] = Path.Combine(
grpcdir != "" ? grpcdir : outdir, filename) + "Grpc.cs";
}
return outputs;
}
string LowerUnderscoreToUpperCamel(string str)
// This is how the gRPC codegen currently construct its output filename.
// See src/compiler/generator_helpers.h:118.
string LowerUnderscoreToUpperCamelGrpcWay(string str)
{
// See src/compiler/generator_helpers.h:118
var result = new StringBuilder(str.Length, str.Length);
bool cap = true;
foreach (char c in str)
@ -109,6 +108,26 @@ namespace Grpc.Tools
}
return result.ToString();
}
// This is how the protoc codegen constructs its output filename.
// See protobuf/compiler/csharp/csharp_helpers.cc:356.
// Note that protoc explicitly discards non-ASCII letters.
string LowerUnderscoreToUpperCamelProtocWay(string str)
{
var result = new StringBuilder(str.Length, str.Length);
bool cap = true;
foreach (char c in str)
{
char upperC = char.ToUpperInvariant(c);
bool isAsciiLetter = 'A' <= upperC && upperC <= 'Z';
if (isAsciiLetter || ('0' <= c && c <= '9'))
{
result.Append(cap ? upperC : c);
}
cap = !isAsciiLetter;
}
return result.ToString();
}
};
// C++ generator services.

Loading…
Cancel
Save