Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added additional options for property naming. #1404

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Added additional options for property naming.
  • Loading branch information
mwallace committed Aug 17, 2021
commit 530ce7929bc132bc3c795b4d7529befc9a57e8c4
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public CSharpGeneratorSettings()
EnforceFlagEnums = false;

ValueGenerator = new CSharpValueGenerator(this);
PropertyNameGenerator = new CSharpPropertyNameGenerator();
PropertyNameGenerator = new CSharpPropertyNameGenerator(this);
TemplateFactory = new DefaultTemplateFactory(this, new Assembly[]
{
typeof(CSharpGeneratorSettings).GetTypeInfo().Assembly
Expand All @@ -52,6 +52,8 @@ public CSharpGeneratorSettings()
InlineNamedArrays = false;
InlineNamedDictionaries = false;
InlineNamedTuples = true;

PropertyNamingStyle = CSharpNamingStyle.PascalSnakeCase;
}

/// <summary>Gets or sets the .NET namespace of the generated types (default: MyNamespace).</summary>
Expand Down Expand Up @@ -144,5 +146,8 @@ public CSharpGeneratorSettings()

/// <summary>Gets or sets a value indicating whether to generate Nullable Reference Type annotations (default: false).</summary>
public bool GenerateNullableReferenceTypes { get; set; }

/// <summary>Gets or sets a value indicating what type of naming style to use (default: PascalSnakeCase).</summary>
public CSharpNamingStyle PropertyNamingStyle { get; set; }
}
}
32 changes: 32 additions & 0 deletions src/NJsonSchema.CodeGeneration.CSharp/CSharpNamingStyle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//-----------------------------------------------------------------------
// <copyright file="CSharpClassStyle.cs" company="NJsonSchema">
// Copyright (c) Rico Suter. All rights reserved.
// </copyright>
// <license>https://github.com/RicoSuter/NJsonSchema/blob/master/LICENSE.md</license>
// <author>Rico Suter, [email protected]</author>
//-----------------------------------------------------------------------

namespace NJsonSchema.CodeGeneration.CSharp
{
/// <summary>The CSharp naming styles.</summary>
public enum CSharpNamingStyle
{
/// <summary>Generate Names with flat case (twowords).</summary>
FlatCase,

/// <summary>Generate Names with upper flat case (TWOWORDS).</summary>
UpperFlatCase,

/// <summary>Generate Names with camel case (twoWords).</summary>
CamelCase,

/// <summary>Generate Names with pascal case (TwoWords).</summary>
PascalCase,

/// <summary>Generate Names with snake case (two_words).</summary>
SnakeCase,

/// <summary>Generate Names with pascal snake case (Two_Words).</summary>
PascalSnakeCase
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,53 @@
// <author>Rico Suter, [email protected]</author>
//-----------------------------------------------------------------------

using System;

namespace NJsonSchema.CodeGeneration.CSharp
{
/// <summary>Generates the property name for a given CSharp <see cref="JsonSchemaProperty"/>.</summary>
public class CSharpPropertyNameGenerator : IPropertyNameGenerator
{
private readonly Func<string, string> _propertyNameGenerator;

/// <summary>Initializes a new instance of the <see cref="CSharpPropertyNameGenerator" /> class.</summary>
/// <param name="settings">The settings.</param>
public CSharpPropertyNameGenerator(CSharpGeneratorSettings settings)
{
_propertyNameGenerator = GetPropertyNameGenerator(settings);
}

private static Func<string, string> GetPropertyNameGenerator(CSharpGeneratorSettings settings)
{
switch (settings.PropertyNamingStyle)
{
case CSharpNamingStyle.FlatCase:
return ConversionUtilities.ConvertNameToFlatCase;

case CSharpNamingStyle.UpperFlatCase:
return ConversionUtilities.ConvertNameToUpperFlatCase;

case CSharpNamingStyle.CamelCase:
return ConversionUtilities.ConvertNameToCamelCase;

case CSharpNamingStyle.PascalCase:
return ConversionUtilities.ConvertNameToPascalCase;

case CSharpNamingStyle.SnakeCase:
return ConversionUtilities.ConvertNameToSnakeCase;

case CSharpNamingStyle.PascalSnakeCase:
return ConversionUtilities.ConvertNameToPascalSnakeCase;

default:
throw new NotImplementedException();
}
}

/// <summary>Generates the property name.</summary>
/// <param name="property">The property.</param>
/// <returns>The new name.</returns>
public virtual string Generate(JsonSchemaProperty property)
{
return ConversionUtilities.ConvertToUpperCamelCase(property.Name
.Replace("\"", string.Empty)
.Replace("@", string.Empty)
.Replace("?", string.Empty)
.Replace("$", string.Empty)
.Replace("[", string.Empty)
.Replace("]", string.Empty)
.Replace("(", "_")
.Replace(")", string.Empty)
.Replace(".", "-")
.Replace("=", "-")
.Replace("+", "plus"), true)
.Replace("*", "Star")
.Replace(":", "_")
.Replace("-", "_")
.Replace("#", "_");
}
=> _propertyNameGenerator(property.Name);
}
}
122 changes: 122 additions & 0 deletions src/NJsonSchema.Tests/ConversionUtilities/ConversionUtilitiesTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;

namespace NJsonSchema.Tests.ConversionUtilities
{
public class ConversionUtilitiesTests
{
[Theory]
[MemberData(nameof(GetTestCaseData))]
public async Task Flat_Case_Data_Should_Match(TestCaseData data)
{
//// Act
var actual = NJsonSchema.ConversionUtilities.ConvertNameToFlatCase(data.Input);

//// Assert
Assert.Equal(data.FlatCase, actual);
}

[Theory]
[MemberData(nameof(GetTestCaseData))]
public async Task Upper_Flat_Case_Data_Should_Match(TestCaseData data)
{
//// Act
var actual = NJsonSchema.ConversionUtilities.ConvertNameToUpperFlatCase(data.Input);

//// Assert
Assert.Equal(data.UpperFlatCase, actual);
}

[Theory]
[MemberData(nameof(GetTestCaseData))]
public async Task Camel_Case_Data_Should_Match(TestCaseData data)
{
//// Act
var actual = NJsonSchema.ConversionUtilities.ConvertNameToCamelCase(data.Input);

//// Assert
Assert.Equal(data.CamelCase, actual);
}

[Theory]
[MemberData(nameof(GetTestCaseData))]
public async Task Pascal_Case_Data_Should_Match(TestCaseData data)
{
//// Act
var actual = NJsonSchema.ConversionUtilities.ConvertNameToPascalCase(data.Input);

//// Assert
Assert.Equal(data.PascalCase, actual);
}

[Theory]
[MemberData(nameof(GetTestCaseData))]
public async Task Snake_Case_Data_Should_Match(TestCaseData data)
{
//// Act
var actual = NJsonSchema.ConversionUtilities.ConvertNameToSnakeCase(data.Input);

//// Assert
Assert.Equal(data.SnakeCase, actual);
}

[Theory]
[MemberData(nameof(GetTestCaseData))]
public async Task Pascal_Snake_Case_Data_Should_Match(TestCaseData data)
{
//// Act
var actual = NJsonSchema.ConversionUtilities.ConvertNameToPascalSnakeCase(data.Input);

//// Assert
Assert.Equal(data.PascalSnakeCase, actual);
}

public static IEnumerable<object[]> GetTestCaseData()
{
yield return new[] { new TestCaseData("TwoWords", "twowords", "TWOWORDS", "twoWords", "TwoWords", "two_words", "Two_Words") };

yield return new[] { new TestCaseData("Two_Words", "twowords", "TWOWORDS", "twoWords", "TwoWords", "two_words", "Two_Words") };

yield return new[] { new TestCaseData("twoWords", "twowords", "TWOWORDS", "twoWords", "TwoWords", "two_words", "Two_Words") };

yield return new[] { new TestCaseData("two_words", "twowords", "TWOWORDS", "twoWords", "TwoWords", "two_words", "Two_Words") };

yield return new[] { new TestCaseData("two_words", "twowords", "TWOWORDS", "twoWords", "TwoWords", "two_words", "Two_Words") };

yield return new[] { new TestCaseData("class", "@class", "CLASS", "@class", "Class", "@class", "Class") };

yield return new[] { new TestCaseData("%yield", "yield", "YIELD", "yield", "Yield", "yield", "Yield") };

yield return new[] { new TestCaseData("2+", "_2plus", "_2PLUS", "_2plus", "_2plus", "_2plus", "_2plus") };
}

public class TestCaseData
{
public string Input { get; private set; }

public string FlatCase { get; private set; }

public string UpperFlatCase { get; private set; }

public string CamelCase { get; private set; }

public string PascalCase { get; private set; }

public string SnakeCase { get; private set; }

public string PascalSnakeCase { get; private set; }

public TestCaseData(string input, string flatCase, string upperFlatCase, string camelCase, string pascalCase, string snakeCase, string pascalSnakeCase)
{
Input = input;
FlatCase = flatCase;
UpperFlatCase = upperFlatCase;
CamelCase = camelCase;
PascalCase = pascalCase;
SnakeCase = snakeCase;
PascalSnakeCase = pascalSnakeCase;
}
}
}
}
128 changes: 128 additions & 0 deletions src/NJsonSchema/ConversionUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
// <author>Rico Suter, [email protected]</author>
//-----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -179,6 +181,132 @@ public static string ConvertCSharpDocs(string input, int tabCount)
return Regex.Replace(xml, @"^( *)/// ", m => m.Groups[1] + "/// <br/>", RegexOptions.Multiline);
}

private static Dictionary<char, string> _wordReplacements = new Dictionary<char, string>
{
{ '+', "plus" },
{ '*', "star" }
};

private static HashSet<string> _reservedKeywords = new HashSet<string>
{
"abstract", "as", "base", "bool", "break",
"byte", "case", "catch", "char", "checked",
"class", "const", "continue", "decimal", "default",
"delegate", "do", "double", "else", "enum",
"event", "explicit", "extern", "false", "finally",
"fixed", "float", "for", "foreach","goto",
"if", "implicit", "in", "int", "interface",
"internal", "is", "lock", "long", "namespace",
"new", "null", "object", "operator", "out",
"override", "params", "private", "protected", "public",
"readonly", "ref", "return", "sbyte",
"sealed", "short", "sizeof", "stackalloc", "static", "string",
"struct", "switch", "this", "throw", "true",
"try", "typeof", "uint", "ulong", "unchecked",
"unsafe", "ushort", "using", "virtual", "void",
"volatile", "while"
};

private static IEnumerable<string> SplitWords(string input)
{
var sb = new StringBuilder();

foreach (var c in input)
{
var isLetterOrDigit = char.IsLetterOrDigit(c);
var isUpper = char.IsUpper(c);
var isReplaceable = _wordReplacements.ContainsKey(c);

if (!isLetterOrDigit || isUpper || isReplaceable)
{
if (isReplaceable)
sb.Append(_wordReplacements[c]);

if (sb.Length > 0)
{
yield return sb.ToString();
sb.Clear();
}

if (!isUpper)
continue;
}

sb.Append(c);
}

if (sb.Length > 0)
yield return sb.ToString();
}

private static string JoinWords(IEnumerable<string> inputs, string join, bool pascal)
{
var adjustedInputs = inputs.Select(x => x.ToLower())
.Select((x, i) => pascal || i > 0 ? char.ToUpper(x[0]) + x.Substring(1) : x);

// Join our Inputs by the join identifier
var output = string.Join(join, adjustedInputs);

// If our first character is a number, then we need to prepend it with an underscore
if (char.IsNumber(output[0]))
output = $"_{output}";

return output;
}

private static string SplitAndJoin(string input, string join, bool pascal, Func<string, string> postprocess = null)
{
var split = SplitWords(input);

var joined = JoinWords(split, join, pascal);

var output = postprocess != null
? postprocess(joined)
: joined;

// If we got a C# reserved keyword, then we need to prepend it with an amperstand
if (_reservedKeywords.Contains(output))
output = $"@{output}";

return output;
}

/// <summary>Converts the given input to flat case (twowords).</summary>
/// <param name="input">The input.</param>
/// <returns>The output.</returns>
public static string ConvertNameToFlatCase(string input)
=> SplitAndJoin(input, string.Empty, false, postprocess: (x) => x.ToLower());

/// <summary>Converts the given input to upper flat case (TWOWORDS).</summary>
/// <param name="input">The input.</param>
/// <returns>The output.</returns>
public static string ConvertNameToUpperFlatCase(string input)
=> SplitAndJoin(input, string.Empty, false, postprocess: (x) => x.ToUpper());

/// <summary>Converts the given input to camel case (twoWords).</summary>
/// <param name="input">The input.</param>
/// <returns>The output.</returns>
public static string ConvertNameToCamelCase(string input)
=> SplitAndJoin(input, string.Empty, false);

/// <summary>Converts the given input to pascal case (TwoWords).</summary>
/// <param name="input">The input.</param>
/// <returns>The output.</returns>
public static string ConvertNameToPascalCase(string input)
=> SplitAndJoin(input, string.Empty, true);

/// <summary>Converts the given input to snake case (two_words).</summary>
/// <param name="input">The input.</param>
/// <returns>The output.</returns>
public static string ConvertNameToSnakeCase(string input)
=> SplitAndJoin(input, "_", false, postprocess: (x) => x.ToLower());

/// <summary>Converts the given input to pascal snake case (Two_Words).</summary>
/// <param name="input">The input.</param>
/// <returns>The output.</returns>
public static string ConvertNameToPascalSnakeCase(string input)
=> SplitAndJoin(input, "_", true);

private static string ConvertDashesToCamelCase(string input)
{
var sb = new StringBuilder();
Expand Down