diff --git a/CHANGELOG.md b/CHANGELOG.md index e84163c..cba1479 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +* Parameters of generated record methods use camel-casing, as opposed to + Pascal-casing previously, to align with naming conventions in .NET. +* The name of the parameter to `With*` methods now always reads `value` + as opposed to being named after the property verbatim. + ### Added * [`Object.ToString`][objtostr] override for records that follows the same diff --git a/src/Amadevus.RecordGenerator.Generators/CSharpKeyword.cs b/src/Amadevus.RecordGenerator.Generators/CSharpKeyword.cs new file mode 100644 index 0000000..b94b651 --- /dev/null +++ b/src/Amadevus.RecordGenerator.Generators/CSharpKeyword.cs @@ -0,0 +1,95 @@ +namespace Amadevus.RecordGenerator.Generators +{ + static class CSharpKeyword + { + // The following method was derived from: + // Source https://github.com/dotnet/roslyn/blob/9d80dea7fe1b14043b9b2ac4d0b59ed26f508742/src/Tools/Source/CompilerGeneratorTools/Source/BoundTreeGenerator/BoundNodeClassWriter.cs#L1821-L1905 + // + // Copyright (c) Microsoft. All Rights Reserved. + // Licensed under the Apache License, Version 2.0: + // https://www.apache.org/licenses/LICENSE-2.0 + + public static bool Is(string name) + { + switch (name) + { + case "bool": + case "byte": + case "sbyte": + case "short": + case "ushort": + case "int": + case "uint": + case "long": + case "ulong": + case "double": + case "float": + case "decimal": + case "string": + case "char": + case "object": + case "typeof": + case "sizeof": + case "null": + case "true": + case "false": + case "if": + case "else": + case "while": + case "for": + case "foreach": + case "do": + case "switch": + case "case": + case "default": + case "lock": + case "try": + case "throw": + case "catch": + case "finally": + case "goto": + case "break": + case "continue": + case "return": + case "public": + case "private": + case "internal": + case "protected": + case "static": + case "readonly": + case "sealed": + case "const": + case "new": + case "override": + case "abstract": + case "virtual": + case "partial": + case "ref": + case "out": + case "in": + case "where": + case "params": + case "this": + case "base": + case "namespace": + case "using": + case "class": + case "struct": + case "interface": + case "delegate": + case "checked": + case "operator": + case "implicit": + case "explicit": + case "fixed": + case "extern": + case "event": + case "enum": + case "unsafe": + return true; + default: + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Amadevus.RecordGenerator.Generators/DeconstructPartialGenerator.cs b/src/Amadevus.RecordGenerator.Generators/DeconstructPartialGenerator.cs index 1ecf83e..44538e4 100644 --- a/src/Amadevus.RecordGenerator.Generators/DeconstructPartialGenerator.cs +++ b/src/Amadevus.RecordGenerator.Generators/DeconstructPartialGenerator.cs @@ -37,7 +37,7 @@ private MemberDeclarationSyntax GenerateDeconstruct() ParameterSyntax CreateParameter(RecordDescriptor.Entry entry) { return - Parameter(entry.Identifier) + Parameter(entry.IdentifierInCamelCase) .WithType(entry.Type) .AddModifiers(Token(SyntaxKind.OutKeyword)); } @@ -47,7 +47,7 @@ StatementSyntax CreateAssignment(RecordDescriptor.Entry entry) ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, - IdentifierName(entry.Identifier), + IdentifierName(entry.IdentifierInCamelCase), MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), diff --git a/src/Amadevus.RecordGenerator.Generators/Names.cs b/src/Amadevus.RecordGenerator.Generators/Names.cs index 7ebc1a1..47ec1b6 100644 --- a/src/Amadevus.RecordGenerator.Generators/Names.cs +++ b/src/Amadevus.RecordGenerator.Generators/Names.cs @@ -16,6 +16,8 @@ internal static class Names public const string Validate = "Validate"; public new const string ToString = nameof(object.ToString); + public const string Value = "value"; + public const string ToolName = "Amadevus.RecordGenerator"; public const string GeneratedCodeAttribute = "System.CodeDom.Compiler.GeneratedCodeAttribute"; } diff --git a/src/Amadevus.RecordGenerator.Generators/RecordDescriptor.cs b/src/Amadevus.RecordGenerator.Generators/RecordDescriptor.cs index af6e4d7..4b81d67 100644 --- a/src/Amadevus.RecordGenerator.Generators/RecordDescriptor.cs +++ b/src/Amadevus.RecordGenerator.Generators/RecordDescriptor.cs @@ -1,4 +1,5 @@ using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Immutable; @@ -29,10 +30,15 @@ public Entry(SyntaxToken Identifier, TypeSyntax Type, PropertyDeclarationSyntax this.Identifier = Identifier; this.Type = Type; this.PropertySyntax = PropertySyntax; + var id = (string)Identifier.Value; + var camelized = char.ToLowerInvariant(id[0]) + id.Substring(1); + IdentifierInCamelCase = SyntaxFactory.Identifier(CSharpKeyword.Is(camelized) ? "@" + camelized : camelized); } public SyntaxToken Identifier { get; } + public SyntaxToken IdentifierInCamelCase { get; } + public TypeSyntax Type { get; } public PropertyDeclarationSyntax PropertySyntax { get; } diff --git a/src/Amadevus.RecordGenerator.Generators/RecordPartialGenerator.cs b/src/Amadevus.RecordGenerator.Generators/RecordPartialGenerator.cs index 0adb51a..274af83 100644 --- a/src/Amadevus.RecordGenerator.Generators/RecordPartialGenerator.cs +++ b/src/Amadevus.RecordGenerator.Generators/RecordPartialGenerator.cs @@ -55,7 +55,7 @@ StatementSyntax CreateCtorAssignment(RecordDescriptor.Entry entry) SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName(entry.Identifier)), - IdentifierName(entry.Identifier))); + IdentifierName(entry.IdentifierInCamelCase))); } StatementSyntax CreateValidateInvocation() { @@ -70,7 +70,7 @@ ArgumentSyntax CreateValidateArgument(RecordDescriptor.Entry entry) { return Argument( - IdentifierName(entry.Identifier)) + IdentifierName(entry.IdentifierInCamelCase)) .WithRefKindKeyword(Token(SyntaxKind.RefKeyword)); } } @@ -80,7 +80,7 @@ private MethodDeclarationSyntax GenerateUpdateMethod() var arguments = Descriptor.Entries.Select(x => { return Argument( - IdentifierName(x.Identifier)); + IdentifierName(x.IdentifierInCamelCase)); }); return MethodDeclaration(Descriptor.Type, Names.Update) .AddModifiers(SyntaxKind.PublicKeyword) @@ -100,11 +100,14 @@ private IEnumerable GenerateMutators() return Descriptor.Entries.Select(CreateRecordMutator); MethodDeclarationSyntax CreateRecordMutator(RecordDescriptor.Entry entry) { + var valueIdentifier = Identifier(Names.Value); + var arguments = Descriptor.Entries.Select(x => { return Argument( - IdentifierName(x.Identifier)); + IdentifierName(x == entry ? valueIdentifier : x.Identifier)); }); + var mutator = MethodDeclaration( Descriptor.Type, @@ -112,7 +115,7 @@ MethodDeclarationSyntax CreateRecordMutator(RecordDescriptor.Entry entry) .AddModifiers(SyntaxKind.PublicKeyword) .WithParameters( Parameter( - entry.Identifier) + valueIdentifier) .WithType(entry.Type)) .WithBodyStatements( ReturnStatement( @@ -160,7 +163,7 @@ private MemberDeclarationSyntax GenerateValidatePartialMethod() ParameterSyntax CreateValidateParameter(RecordDescriptor.Entry entry) { return - Parameter(entry.Identifier) + Parameter(entry.IdentifierInCamelCase) .WithType(entry.Type) .AddModifiers(Token(SyntaxKind.RefKeyword)); } @@ -169,7 +172,7 @@ ParameterSyntax CreateValidateParameter(RecordDescriptor.Entry entry) private static ParameterSyntax CreateParameter(RecordDescriptor.Entry property) { return Parameter( - property.Identifier) + property.IdentifierInCamelCase) .WithType(property.Type); } diff --git a/test/Amadevus.RecordGenerator.Test/GeneratedTypesNamingConventionsTests.cs b/test/Amadevus.RecordGenerator.Test/GeneratedTypesNamingConventionsTests.cs new file mode 100644 index 0000000..80d1ceb --- /dev/null +++ b/test/Amadevus.RecordGenerator.Test/GeneratedTypesNamingConventionsTests.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Xunit; +using Amadevus.RecordGenerator.TestsBase; + +namespace Amadevus.RecordGenerator.Test +{ + public class GeneratedTypesNamingConventionsTests : RecordTestsBase + { + public static readonly IEnumerable ParameterDataSource = + from t in new[] + { + typeof(Item) , typeof(Item.Builder), + typeof(Container) , typeof(Container.Builder), + typeof(GenericRecord<>), typeof(GenericRecord<>.Builder), + } + from m in t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public + | BindingFlags.Instance + | BindingFlags.Static) + from p in m.GetParameters() + select new[] + { + // Trim namespace if present + t.Namespace is string ns + ? t.FullName.Substring(ns.Length + 1 /* dot */) + : t.FullName, + m.Name, + p.Name + }; + + [Theory] + [MemberData(nameof(ParameterDataSource))] + public void Parameter_Name_Uses_Camel_Case( + #pragma warning disable xUnit1026 + string type, string method, + #pragma warning restore xUnit1026 + string name) + { + Assert.Matches(@"^[a-z]", name); + } + } +} \ No newline at end of file diff --git a/test/Amadevus.RecordGenerator.Test/RecordConstructorTests.cs b/test/Amadevus.RecordGenerator.Test/RecordConstructorTests.cs index 77cf533..32cb67e 100644 --- a/test/Amadevus.RecordGenerator.Test/RecordConstructorTests.cs +++ b/test/Amadevus.RecordGenerator.Test/RecordConstructorTests.cs @@ -9,7 +9,7 @@ public class RecordConstructorTests : RecordTestsBase [Fact] public void Ctor_HasParametersNamedLikeProperties() { - new Item(Id: ItemId, Name: ItemName); + new Item(id: ItemId, name: ItemName); } [Fact] diff --git a/test/Amadevus.RecordGenerator.Test/RecordFormatterTests.cs b/test/Amadevus.RecordGenerator.Test/RecordFormatterTests.cs index de05dd4..58d1756 100644 --- a/test/Amadevus.RecordGenerator.Test/RecordFormatterTests.cs +++ b/test/Amadevus.RecordGenerator.Test/RecordFormatterTests.cs @@ -1,11 +1,10 @@ -using System.Collections.Immutable; +using System.Collections.Generic; +using System.Collections.Immutable; using Xunit; +using Amadevus.RecordGenerator.TestsBase; namespace Amadevus.RecordGenerator.Test { - using System.Collections.Generic; - using TestsBase; - public class RecordFormatterTests : RecordTestsBase { [Fact]