From cf0371c8f038026fed78e4c113987d886b37bb81 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sun, 29 Jun 2025 04:45:11 +0200 Subject: [PATCH 01/61] test: CommentedClass --- .../CommentedClass/CreateStudent.expected.txt | 0 .../CommentedClass/CreateStudent.g.cs | 0 .../Abstract/CommentedClass/Student.cs | 37 +++++++++++++++++++ .../CodeGeneration/TestDataProvider.cs | 1 + 4 files changed, 38 insertions(+) create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/CreateStudent.expected.txt create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/CreateStudent.g.cs create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/Student.cs diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/CreateStudent.expected.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/CreateStudent.g.cs new file mode 100644 index 0000000..e69de29 diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/Student.cs new file mode 100644 index 0000000..b2fe948 --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/Student.cs @@ -0,0 +1,37 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using System; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.CommentedClass; + +[FluentApi] +public class Student +{ + //// + //// Sets the first and last name of the student. + //// + //// The student's first name. + [FluentMember(0, "WithName")] + public string FirstName { get; set; } + + //// The student's last name. + [FluentMember(0, "WithName")] + public string LastName{ get; set; } + + //// + //// Sets the student's date of birth. + //// + //// The student's date of birth. + [FluentMember(1, "BornOn")] + public DateOnly DateOfBirth{ get; set; } + + //// + //// Sets the current semester the student is enrolled in. + //// + //// The current semester number. + [FluentMember(2, "InSemester")] + public int Semester { get; set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs index 9ad6a26..c63626b 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs @@ -15,6 +15,7 @@ internal class TestDataProvider : IEnumerable new object[] { "Abstract", "CollectionMemberClass", "Student" }, new object[] { "Abstract", "CollectionMemberClassWithSuppression", "Student" }, new object[] { "Abstract", "CollectionNullableArrayClass", "Student" }, + new object[] { "Abstract", "CommentedClass", "Student" }, new object[] { "Abstract", "ContinueWithAfterCompoundClass", "Student" }, new object[] { "Abstract", "ContinueWithInForkClass", "Student" }, new object[] { "Abstract", "ContinueWithOfOverloadedMethodClass", "Student" }, From 1a5f98e2bf10ae127cb8e5d04db5148263496739 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Mon, 30 Jun 2025 14:55:29 +0200 Subject: [PATCH 02/61] feat: get comments from symbol info --- .../CodeBoardElements/FluentApiSymbolInfo.cs | 12 ++++-- .../CodeBoardElements/MemberSymbolInfo.cs | 5 ++- .../CodeBoardElements/MethodSymbolInfo.cs | 5 ++- .../SourceGenerators/SymbolInfoCreator.cs | 38 +++++++++++++++++-- 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiSymbolInfo.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiSymbolInfo.cs index 4902a9b..341ecc4 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiSymbolInfo.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiSymbolInfo.cs @@ -9,13 +9,15 @@ internal FluentApiSymbolInfo( string name, string declaringClassNameWithTypeParameters, Accessibility accessibility, - bool requiresReflection) + bool requiresReflection, + IReadOnlyCollection comments) { Name = name; NameInCamelCase = Name.TrimStart('_').FirstCharToLower(); DeclaringClassNameWithTypeParameters = declaringClassNameWithTypeParameters; Accessibility = accessibility; RequiresReflection = requiresReflection; + Comments = comments; } internal string Name { get; } @@ -23,13 +25,15 @@ internal FluentApiSymbolInfo( internal string DeclaringClassNameWithTypeParameters { get; } internal Accessibility Accessibility { get; } internal bool RequiresReflection { get; } + internal IReadOnlyCollection Comments { get; } protected bool Equals(FluentApiSymbolInfo other) { return Name == other.Name && DeclaringClassNameWithTypeParameters == other.DeclaringClassNameWithTypeParameters && Accessibility == other.Accessibility && - RequiresReflection == other.RequiresReflection; + RequiresReflection == other.RequiresReflection && + Comments.SequenceEqual(other.Comments); } public override bool Equals(object? obj) @@ -42,6 +46,8 @@ public override bool Equals(object? obj) public override int GetHashCode() { - return new HashCode().Add(Name, DeclaringClassNameWithTypeParameters, Accessibility, RequiresReflection); + return new HashCode() + .Add(Name, DeclaringClassNameWithTypeParameters, Accessibility, RequiresReflection) + .AddSequence(Comments); } } \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MemberSymbolInfo.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MemberSymbolInfo.cs index 8fc7fa3..aaa96e0 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MemberSymbolInfo.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MemberSymbolInfo.cs @@ -15,8 +15,9 @@ internal MemberSymbolInfo( string typeForCodeGeneration, bool isNullable, bool isProperty, - CollectionType? collectionType) - : base(name, declaringClassNameWithTypeParameters, accessibility, requiresReflection) + CollectionType? collectionType, + IReadOnlyCollection comments) + : base(name, declaringClassNameWithTypeParameters, accessibility, requiresReflection, comments) { Type = type; TypeForCodeGeneration = typeForCodeGeneration; diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MethodSymbolInfo.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MethodSymbolInfo.cs index 8a534d4..508d378 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MethodSymbolInfo.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MethodSymbolInfo.cs @@ -13,8 +13,9 @@ internal MethodSymbolInfo( bool requiresReflection, GenericInfo? genericInfo, IReadOnlyCollection parameterInfos, - string returnType) - : base(name, declaringClassNameWithTypeParameters, accessibility, requiresReflection) + string returnType, + IReadOnlyCollection comments) + : base(name, declaringClassNameWithTypeParameters, accessibility, requiresReflection, comments) { GenericInfo = genericInfo; ParameterInfos = parameterInfos; diff --git a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs index 36a689c..c13b71e 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs @@ -4,6 +4,7 @@ using M31.FluentApi.Generator.SourceGenerators.Collections; using M31.FluentApi.Generator.SourceGenerators.Generics; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; namespace M31.FluentApi.Generator.SourceGenerators; @@ -39,7 +40,8 @@ private static MemberSymbolInfo CreateMemberSymbolInfo( CodeTypeExtractor.GetTypeForCodeGeneration(fieldSymbol.Type), fieldSymbol.NullableAnnotation == NullableAnnotation.Annotated, false, - CollectionInference.InferCollectionType(fieldSymbol.Type)); + CollectionInference.InferCollectionType(fieldSymbol.Type), + GetFluentSymbolComments(fieldSymbol)); } private static MemberSymbolInfo CreateMemberSymbolInfo( @@ -55,7 +57,8 @@ private static MemberSymbolInfo CreateMemberSymbolInfo( CodeTypeExtractor.GetTypeForCodeGeneration(propertySymbol.Type), propertySymbol.NullableAnnotation == NullableAnnotation.Annotated, true, - CollectionInference.InferCollectionType(propertySymbol.Type)); + CollectionInference.InferCollectionType(propertySymbol.Type), + GetFluentSymbolComments(propertySymbol)); } private static MethodSymbolInfo CreateMethodSymbolInfo( @@ -78,7 +81,8 @@ private static MethodSymbolInfo CreateMethodSymbolInfo( RequiresReflection(methodSymbol), genericInfo, parameterInfos, - CodeTypeExtractor.GetTypeForCodeGeneration(methodSymbol.ReturnType)); + CodeTypeExtractor.GetTypeForCodeGeneration(methodSymbol.ReturnType), + GetFluentSymbolComments(methodSymbol)); } private static GenericInfo? GetGenericInfo(IMethodSymbol methodSymbol) @@ -193,4 +197,32 @@ private static ParameterKinds GetParameterKinds(IParameterSymbol parameterSymbol return parameterKinds; } + + private static IReadOnlyCollection GetFluentSymbolComments(ISymbol symbol) + { + SyntaxReference? syntaxRef = symbol.DeclaringSyntaxReferences.FirstOrDefault(); + if (syntaxRef == null) + { + return Array.Empty(); + } + + SyntaxNode syntaxNode = syntaxRef.GetSyntax(); + SyntaxTriviaList leadingTrivia = syntaxNode.GetLeadingTrivia(); + List comments = new List(leadingTrivia.Count); + + + foreach (SyntaxTrivia trivia in leadingTrivia) + { + if (trivia.IsKind(SyntaxKind.SingleLineCommentTrivia)) + { + string text = trivia.ToFullString(); + if (text.StartsWith("////") && !text.StartsWith("/////")) + { + comments.Add(text); + } + } + } + + return comments; + } } \ No newline at end of file From c6dee5068a2760e5c98c82115217b6e7c133424a Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sun, 6 Jul 2025 10:25:43 +0200 Subject: [PATCH 03/61] wip: comments --- .../CodeBuilding/Method.cs | 7 +++ .../CodeBuilding/MethodComments.cs | 25 +++++++++++ .../BuilderStepMethod.cs | 16 ++++++- .../CodeBoardActors/Commons/BuilderMethod.cs | 6 ++- .../DocumentationComments/Comment.cs | 18 ++++++++ .../DocumentationComments/CommentAttribute.cs | 15 +++++++ .../DocumentationComments/Comments.cs | 8 ++++ .../SourceGenerators/SymbolInfoCreator.cs | 30 +++---------- .../CodeGeneration/CodeGenerationTests.cs | 2 +- .../Abstract/CommentedClass/Student.cs | 44 ++++++++++++------- .../CodeGeneration/TestDataProvider.cs | 2 +- 11 files changed, 128 insertions(+), 45 deletions(-) create mode 100644 src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs create mode 100644 src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comment.cs create mode 100644 src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/CommentAttribute.cs create mode 100644 src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comments.cs diff --git a/src/M31.FluentApi.Generator/CodeBuilding/Method.cs b/src/M31.FluentApi.Generator/CodeBuilding/Method.cs index 119bf73..08892b9 100644 --- a/src/M31.FluentApi.Generator/CodeBuilding/Method.cs +++ b/src/M31.FluentApi.Generator/CodeBuilding/Method.cs @@ -4,13 +4,20 @@ internal class Method : ICode { internal Method(MethodSignature methodSignature) { + MethodComments = new MethodComments(); MethodSignature = methodSignature; MethodBody = new MethodBody(); } + internal MethodComments MethodComments { get; } internal MethodSignature MethodSignature { get; } internal MethodBody MethodBody { get; } + internal void AddCommentLine(string commentLine) + { + MethodComments.AddCommentLine(commentLine); + } + internal void AppendBodyLine(string line) { MethodBody.AppendLine(line); diff --git a/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs b/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs new file mode 100644 index 0000000..e6efbf6 --- /dev/null +++ b/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs @@ -0,0 +1,25 @@ +namespace M31.FluentApi.Generator.CodeBuilding; + +internal class MethodComments : ICode +{ + private readonly List comments; + + internal MethodComments() + { + comments = new List(); + } + + internal IReadOnlyCollection Comments => comments; + + internal void AddCommentLine(string commentLine) + { + comments.Add(commentLine); + } + + public CodeBuilder AppendCode(CodeBuilder codeBuilder) + { + return codeBuilder + .StartLine() + .AppendLines(comments); + } +} diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethod.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethod.cs index 6a3ca8f..5f01051 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethod.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethod.cs @@ -17,7 +17,13 @@ protected BuilderStepMethod(BuilderMethod builderMethod) protected Method CreateMethod(string defaultReturnType, params string[] modifiers) { MethodSignature methodSignature = CreateMethodSignature(defaultReturnType, null, modifiers); - return new Method(methodSignature); + Method method = new Method(methodSignature); + foreach (string comment in Comments) + { + method.AddCommentLine(comment); + } + + return method; } protected Method CreateInterfaceMethod( @@ -26,7 +32,13 @@ protected Method CreateInterfaceMethod( params string[] modifiers) { MethodSignature methodSignature = CreateMethodSignature(defaultReturnType, interfaceName, modifiers); - return new Method(methodSignature); + Method method = new Method(methodSignature); + foreach (string comment in Comments) + { + method.AddCommentLine(comment); + } + + return method; } private MethodSignature CreateMethodSignature( diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethod.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethod.cs index 4c1fa65..6977a49 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethod.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethod.cs @@ -10,19 +10,22 @@ internal class BuilderMethod internal IReadOnlyCollection Parameters { get; } internal string? ReturnTypeToRespect { get; } internal BuildBodyCode BuildBodyCode { get; } + internal IReadOnlyCollection Comments { get; } internal BuilderMethod( string methodName, GenericInfo? genericInfo, IReadOnlyCollection parameters, string? returnTypeToRespect, - BuildBodyCode buildBodyCode) + BuildBodyCode buildBodyCode, + IReadOnlyCollection comments = null) // todo remove default { MethodName = methodName; GenericInfo = genericInfo; Parameters = parameters; ReturnTypeToRespect = returnTypeToRespect; BuildBodyCode = buildBodyCode; + Comments = new List(); // todo set comments } internal BuilderMethod(BuilderMethod builderMethod) @@ -32,5 +35,6 @@ internal BuilderMethod(BuilderMethod builderMethod) Parameters = builderMethod.Parameters; ReturnTypeToRespect = builderMethod.ReturnTypeToRespect; BuildBodyCode = builderMethod.BuildBodyCode; + Comments = builderMethod.Comments; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comment.cs b/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comment.cs new file mode 100644 index 0000000..92e6d30 --- /dev/null +++ b/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comment.cs @@ -0,0 +1,18 @@ +using System.Text.RegularExpressions; + +namespace M31.FluentApi.Generator.SourceGenerators.DocumentationComments; + +internal class Comment +{ + private static readonly Regex attributeRegex = new Regex(@"(?\w+)\s*=\s*""(?[^""]*)""", RegexOptions.Compiled | RegexOptions.Singleline); + + internal string Tag { get; } + internal IReadOnlyCollection Attributes { get; } + internal string Content { get; } + + internal static IReadOnlyCollection ParseCommentAttributes(string commentAttributes) + { + MatchCollection matches = attributeRegex.Matches(commentAttributes); + return matches.Cast().Select(m => new CommentAttribute(m.Groups["key"].Value, m.Groups["value"].Value)).ToArray(); + } +} diff --git a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/CommentAttribute.cs b/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/CommentAttribute.cs new file mode 100644 index 0000000..08a54bb --- /dev/null +++ b/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/CommentAttribute.cs @@ -0,0 +1,15 @@ +namespace M31.FluentApi.Generator.SourceGenerators.DocumentationComments; + +internal class CommentAttribute +{ + internal CommentAttribute(string key, string value) + { + Key = key; + Value = value; + } + + internal string Key { get; } + internal string Value { get; } + + // todo: equality +} diff --git a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comments.cs b/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comments.cs new file mode 100644 index 0000000..c84c83d --- /dev/null +++ b/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comments.cs @@ -0,0 +1,8 @@ +using System.Text.RegularExpressions; + +namespace M31.FluentApi.Generator.SourceGenerators.DocumentationComments; + +internal class Comments +{ + private static readonly Regex commentRegex = new Regex(@"<(?fluent\w+)\s+(?[^>]+)>\s*(?.*?)\s*>", RegexOptions.Compiled | RegexOptions.Singleline); +} diff --git a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs index c13b71e..8fce63d 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs @@ -1,10 +1,10 @@ +using System.Text.RegularExpressions; using M31.FluentApi.Generator.CodeBuilding; using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements; using M31.FluentApi.Generator.Commons; using M31.FluentApi.Generator.SourceGenerators.Collections; using M31.FluentApi.Generator.SourceGenerators.Generics; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; namespace M31.FluentApi.Generator.SourceGenerators; @@ -200,29 +200,9 @@ private static ParameterKinds GetParameterKinds(IParameterSymbol parameterSymbol private static IReadOnlyCollection GetFluentSymbolComments(ISymbol symbol) { - SyntaxReference? syntaxRef = symbol.DeclaringSyntaxReferences.FirstOrDefault(); - if (syntaxRef == null) - { - return Array.Empty(); - } - - SyntaxNode syntaxNode = syntaxRef.GetSyntax(); - SyntaxTriviaList leadingTrivia = syntaxNode.GetLeadingTrivia(); - List comments = new List(leadingTrivia.Count); - - - foreach (SyntaxTrivia trivia in leadingTrivia) - { - if (trivia.IsKind(SyntaxKind.SingleLineCommentTrivia)) - { - string text = trivia.ToFullString(); - if (text.StartsWith("////") && !text.StartsWith("/////")) - { - comments.Add(text); - } - } - } - - return comments; + // todo: pass cancellation token. + string? commentXml = symbol.GetDocumentationCommentXml(); + Console.WriteLine(commentXml); + return Array.Empty(); } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/CodeGenerationTests.cs b/src/M31.FluentApi.Tests/CodeGeneration/CodeGenerationTests.cs index b7b05bd..59d8c26 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/CodeGenerationTests.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/CodeGenerationTests.cs @@ -16,7 +16,7 @@ public void CanGenerateBuilderForAbstractTestClasses(params string[] testClassPa TestClassCodeGenerator testClassCodeGenerator = TestClassCodeGenerator.Create(testClassPathAndName); GeneratorOutputs generatorOutputs = testClassCodeGenerator.RunGenerators(); Assert.NotNull(generatorOutputs.MainOutput); - + foreach (GeneratorOutput generatorOutput in generatorOutputs.Outputs) { testClassCodeGenerator.WriteGeneratedCodeIfChanged(generatorOutput); diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/Student.cs index b2fe948..90c8323 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/Student.cs @@ -10,28 +10,42 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.CommentedClass [FluentApi] public class Student { - //// - //// Sets the first and last name of the student. - //// - //// The student's first name. + /// + /// Sets the first and last name of the student. + /// And other stuff. + /// + /// + /// Some irrelevant summary. + /// + /** + * Hello + * World + * + */ [FluentMember(0, "WithName")] public string FirstName { get; set; } - //// The student's last name. [FluentMember(0, "WithName")] public string LastName{ get; set; } - //// - //// Sets the student's date of birth. - //// - //// The student's date of birth. - [FluentMember(1, "BornOn")] - public DateOnly DateOfBirth{ get; set; } + [FluentMember(1, "OfAge")] + public int Age { get; private set; } - //// - //// Sets the current semester the student is enrolled in. - //// - //// The current semester number. + /// + /// Calculates and sets the student's age based on the provided date of birth. + /// + [FluentMethod(1)] + private void BornOn(DateOnly dateOfBirth) + { + DateOnly today = new DateOnly(2024, 9, 26); + int age = today.Year - dateOfBirth.Year; + if (dateOfBirth > today.AddYears(-age)) age--; + Age = age; + } + + /// + /// Sets the current semester the student is enrolled in. + /// [FluentMember(2, "InSemester")] public int Semester { get; set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs index c63626b..98f67dd 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs @@ -7,7 +7,7 @@ namespace M31.FluentApi.Tests.CodeGeneration; internal class TestDataProvider : IEnumerable { private readonly List testClasses = - Filter(new string[] { }, + Filter(new string[] { "CommentedClass" }, new List { new object[] { "Abstract", "AliasNamespaceClass", "Student" }, From eba6155e06260071024b86f46c3b0d7701cf76ff Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sun, 6 Jul 2025 10:26:05 +0200 Subject: [PATCH 04/61] wip: comments --- .../SourceGenerators/SymbolInfoCreator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs index 8fce63d..a76a9af 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs @@ -1,4 +1,3 @@ -using System.Text.RegularExpressions; using M31.FluentApi.Generator.CodeBuilding; using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements; using M31.FluentApi.Generator.Commons; From e9ec5c5748765f46558e0e3ee17754732a1e7d5a Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Mon, 7 Jul 2025 07:27:56 +0200 Subject: [PATCH 05/61] feat: comment equality and hash methods --- .../CodeBoardElements/FluentApiSymbolInfo.cs | 10 ++-- .../CodeBoardElements/MemberSymbolInfo.cs | 3 +- .../CodeBoardElements/MethodSymbolInfo.cs | 3 +- .../DocumentationComments/Comment.cs | 35 +++++++++--- .../DocumentationComments/CommentAttribute.cs | 22 +++++++- .../DocumentationComments/Comments.cs | 55 +++++++++++++++++++ .../SourceGenerators/SymbolInfoCreator.cs | 6 +- 7 files changed, 115 insertions(+), 19 deletions(-) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiSymbolInfo.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiSymbolInfo.cs index 341ecc4..b864fbe 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiSymbolInfo.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiSymbolInfo.cs @@ -1,4 +1,5 @@ using M31.FluentApi.Generator.Commons; +using M31.FluentApi.Generator.SourceGenerators.DocumentationComments; using Microsoft.CodeAnalysis; namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements; @@ -10,7 +11,7 @@ internal FluentApiSymbolInfo( string declaringClassNameWithTypeParameters, Accessibility accessibility, bool requiresReflection, - IReadOnlyCollection comments) + Comments comments) { Name = name; NameInCamelCase = Name.TrimStart('_').FirstCharToLower(); @@ -25,7 +26,7 @@ internal FluentApiSymbolInfo( internal string DeclaringClassNameWithTypeParameters { get; } internal Accessibility Accessibility { get; } internal bool RequiresReflection { get; } - internal IReadOnlyCollection Comments { get; } + internal Comments Comments { get; } protected bool Equals(FluentApiSymbolInfo other) { @@ -33,7 +34,7 @@ protected bool Equals(FluentApiSymbolInfo other) DeclaringClassNameWithTypeParameters == other.DeclaringClassNameWithTypeParameters && Accessibility == other.Accessibility && RequiresReflection == other.RequiresReflection && - Comments.SequenceEqual(other.Comments); + Comments.Equals(other.Comments); } public override bool Equals(object? obj) @@ -47,7 +48,6 @@ public override bool Equals(object? obj) public override int GetHashCode() { return new HashCode() - .Add(Name, DeclaringClassNameWithTypeParameters, Accessibility, RequiresReflection) - .AddSequence(Comments); + .Add(Name, DeclaringClassNameWithTypeParameters, Accessibility, RequiresReflection, Comments); } } \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MemberSymbolInfo.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MemberSymbolInfo.cs index aaa96e0..ff9fdbf 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MemberSymbolInfo.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MemberSymbolInfo.cs @@ -1,5 +1,6 @@ using M31.FluentApi.Generator.Commons; using M31.FluentApi.Generator.SourceGenerators.Collections; +using M31.FluentApi.Generator.SourceGenerators.DocumentationComments; using Microsoft.CodeAnalysis; namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements; @@ -16,7 +17,7 @@ internal MemberSymbolInfo( bool isNullable, bool isProperty, CollectionType? collectionType, - IReadOnlyCollection comments) + Comments comments) : base(name, declaringClassNameWithTypeParameters, accessibility, requiresReflection, comments) { Type = type; diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MethodSymbolInfo.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MethodSymbolInfo.cs index 508d378..740a85d 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MethodSymbolInfo.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MethodSymbolInfo.cs @@ -1,4 +1,5 @@ using M31.FluentApi.Generator.Commons; +using M31.FluentApi.Generator.SourceGenerators.DocumentationComments; using M31.FluentApi.Generator.SourceGenerators.Generics; using Microsoft.CodeAnalysis; @@ -14,7 +15,7 @@ internal MethodSymbolInfo( GenericInfo? genericInfo, IReadOnlyCollection parameterInfos, string returnType, - IReadOnlyCollection comments) + Comments comments) : base(name, declaringClassNameWithTypeParameters, accessibility, requiresReflection, comments) { GenericInfo = genericInfo; diff --git a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comment.cs b/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comment.cs index 92e6d30..1c0aa36 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comment.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comment.cs @@ -1,18 +1,39 @@ -using System.Text.RegularExpressions; +using M31.FluentApi.Generator.Commons; namespace M31.FluentApi.Generator.SourceGenerators.DocumentationComments; - internal class Comment { - private static readonly Regex attributeRegex = new Regex(@"(?\w+)\s*=\s*""(?[^""]*)""", RegexOptions.Compiled | RegexOptions.Singleline); - internal string Tag { get; } internal IReadOnlyCollection Attributes { get; } internal string Content { get; } - internal static IReadOnlyCollection ParseCommentAttributes(string commentAttributes) + internal Comment(string tag, IReadOnlyCollection attributes, string content) + { + Tag = tag; + Attributes = attributes; + Content = content; + } + + protected bool Equals(Comment other) + { + return Tag == other.Tag && + Attributes.SequenceEqual(other.Attributes) && + Content == other.Content; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Comment)obj); + } + + public override int GetHashCode() { - MatchCollection matches = attributeRegex.Matches(commentAttributes); - return matches.Cast().Select(m => new CommentAttribute(m.Groups["key"].Value, m.Groups["value"].Value)).ToArray(); + return new HashCode() + .Add(Tag) + .AddSequence(Attributes) + .Add(Content); } } diff --git a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/CommentAttribute.cs b/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/CommentAttribute.cs index 08a54bb..f94cf0e 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/CommentAttribute.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/CommentAttribute.cs @@ -1,5 +1,6 @@ -namespace M31.FluentApi.Generator.SourceGenerators.DocumentationComments; +using M31.FluentApi.Generator.Commons; +namespace M31.FluentApi.Generator.SourceGenerators.DocumentationComments; internal class CommentAttribute { internal CommentAttribute(string key, string value) @@ -11,5 +12,22 @@ internal CommentAttribute(string key, string value) internal string Key { get; } internal string Value { get; } - // todo: equality + protected bool Equals(CommentAttribute other) + { + return Key == other.Key && + Value == other.Value; + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((CommentAttribute)obj); + } + + public override int GetHashCode() + { + return new HashCode().Add(Key, Value); + } } diff --git a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comments.cs b/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comments.cs index c84c83d..07e7478 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comments.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comments.cs @@ -1,8 +1,63 @@ using System.Text.RegularExpressions; +using M31.FluentApi.Generator.Commons; namespace M31.FluentApi.Generator.SourceGenerators.DocumentationComments; internal class Comments { private static readonly Regex commentRegex = new Regex(@"<(?fluent\w+)\s+(?[^>]+)>\s*(?.*?)\s*>", RegexOptions.Compiled | RegexOptions.Singleline); + private static readonly Regex attributeRegex = new Regex(@"(?\w+)\s*=\s*""(?[^""]*)""", RegexOptions.Compiled | RegexOptions.Singleline); + + private Comments(IReadOnlyCollection comments) + { + List = comments; + } + + public IReadOnlyCollection List { get; } + + internal static Comments Parse(string? comments) + { + if (comments == null) + { + return new Comments(Array.Empty()); + } + + List commentList = new List(); + MatchCollection matches = commentRegex.Matches(comments); + + foreach (Match match in matches) + { + string tag = match.Groups["tag"].Value; + string attributes = match.Groups["attrs"].Value; + string content = match.Groups["content"].Value; + Comment comment = new Comment(tag, ParseCommentAttributes(attributes), content); + commentList.Add(comment); + } + + return new Comments(commentList); + } + + private static IReadOnlyCollection ParseCommentAttributes(string commentAttributes) + { + MatchCollection matches = attributeRegex.Matches(commentAttributes); + return matches.Cast().Select(m => new CommentAttribute(m.Groups["key"].Value, m.Groups["value"].Value)).ToArray(); + } + + protected bool Equals(Comments other) + { + return List.SequenceEqual(other.List); + } + + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Comments)obj); + } + + public override int GetHashCode() + { + return new HashCode().AddSequence(List); + } } diff --git a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs index a76a9af..4fa4b70 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs @@ -2,6 +2,7 @@ using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements; using M31.FluentApi.Generator.Commons; using M31.FluentApi.Generator.SourceGenerators.Collections; +using M31.FluentApi.Generator.SourceGenerators.DocumentationComments; using M31.FluentApi.Generator.SourceGenerators.Generics; using Microsoft.CodeAnalysis; @@ -197,11 +198,10 @@ private static ParameterKinds GetParameterKinds(IParameterSymbol parameterSymbol return parameterKinds; } - private static IReadOnlyCollection GetFluentSymbolComments(ISymbol symbol) + private static Comments GetFluentSymbolComments(ISymbol symbol) { // todo: pass cancellation token. string? commentXml = symbol.GetDocumentationCommentXml(); - Console.WriteLine(commentXml); - return Array.Empty(); + return Comments.Parse(commentXml); } } \ No newline at end of file From 3922883694cfce6fce4ab2fde2f2ef5fefbb789f Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Tue, 8 Jul 2025 04:40:43 +0200 Subject: [PATCH 06/61] Test: DocumentationComments (wip) --- .../DocumentationComments/Comment.cs | 4 +- .../DocumentationComments/Comments.cs | 20 +++---- .../Components/DocumentationCommentsTests.cs | 57 +++++++++++++++++++ 3 files changed, 69 insertions(+), 12 deletions(-) create mode 100644 src/M31.FluentApi.Tests/Components/DocumentationCommentsTests.cs diff --git a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comment.cs b/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comment.cs index 1c0aa36..cc93780 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comment.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comment.cs @@ -4,10 +4,10 @@ namespace M31.FluentApi.Generator.SourceGenerators.DocumentationComments; internal class Comment { internal string Tag { get; } - internal IReadOnlyCollection Attributes { get; } + internal IReadOnlyList Attributes { get; } internal string Content { get; } - internal Comment(string tag, IReadOnlyCollection attributes, string content) + internal Comment(string tag, IReadOnlyList attributes, string content) { Tag = tag; Attributes = attributes; diff --git a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comments.cs b/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comments.cs index 07e7478..e62b518 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comments.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comments.cs @@ -5,25 +5,25 @@ namespace M31.FluentApi.Generator.SourceGenerators.DocumentationComments; internal class Comments { - private static readonly Regex commentRegex = new Regex(@"<(?fluent\w+)\s+(?[^>]+)>\s*(?.*?)\s*>", RegexOptions.Compiled | RegexOptions.Singleline); + private static readonly Regex commentRegex = new Regex(@"<(?fluent\w+)(\s+(?[^>]+))?>\s*(?.*?)\s*>", RegexOptions.Compiled | RegexOptions.Singleline); private static readonly Regex attributeRegex = new Regex(@"(?\w+)\s*=\s*""(?[^""]*)""", RegexOptions.Compiled | RegexOptions.Singleline); - private Comments(IReadOnlyCollection comments) + private Comments(IReadOnlyList comments) { List = comments; } - public IReadOnlyCollection List { get; } + public IReadOnlyList List { get; } - internal static Comments Parse(string? comments) + internal static Comments Parse(string? commentXml) { - if (comments == null) + if (commentXml == null) { return new Comments(Array.Empty()); } - List commentList = new List(); - MatchCollection matches = commentRegex.Matches(comments); + List comments = new List(); + MatchCollection matches = commentRegex.Matches(commentXml); foreach (Match match in matches) { @@ -31,13 +31,13 @@ internal static Comments Parse(string? comments) string attributes = match.Groups["attrs"].Value; string content = match.Groups["content"].Value; Comment comment = new Comment(tag, ParseCommentAttributes(attributes), content); - commentList.Add(comment); + comments.Add(comment); } - return new Comments(commentList); + return new Comments(comments); } - private static IReadOnlyCollection ParseCommentAttributes(string commentAttributes) + private static IReadOnlyList ParseCommentAttributes(string commentAttributes) { MatchCollection matches = attributeRegex.Matches(commentAttributes); return matches.Cast().Select(m => new CommentAttribute(m.Groups["key"].Value, m.Groups["value"].Value)).ToArray(); diff --git a/src/M31.FluentApi.Tests/Components/DocumentationCommentsTests.cs b/src/M31.FluentApi.Tests/Components/DocumentationCommentsTests.cs new file mode 100644 index 0000000..2dca487 --- /dev/null +++ b/src/M31.FluentApi.Tests/Components/DocumentationCommentsTests.cs @@ -0,0 +1,57 @@ +using M31.FluentApi.Generator.SourceGenerators.DocumentationComments; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Linq; +using Xunit; + +namespace M31.FluentApi.Tests.Components; + +public class DocumentationCommentsTests +{ + + public string Property1 { get; private set; } + + [Fact] + public void CanParseCommentsTest1() + { + string sourceCode = @" + namespace M31.FluentApi.Tests.Components + { + public class DocumentationCommentsTests + { + /// + /// Sets Property1. + /// + public string Property1 { get; private set; } + } + }"; + + IPropertySymbol propertySymbol = GetPropertySymbol(sourceCode, "Property1"); + string commentXml = propertySymbol.GetDocumentationCommentXml()!; + Comments comments = Comments.Parse(commentXml); + Assert.Equal(1, comments.List.Count); + Comment comment = comments.List[0]; + Assert.Equal("fluentSummary", comment.Tag); + Assert.Equal(0, comment.Attributes.Count); + Assert.Equal("Sets Property1.", comment.Content); + } + + private static IPropertySymbol GetPropertySymbol(string code, string propertyName) + { + SyntaxTree tree = CSharpSyntaxTree.ParseText(code); + CSharpCompilation compilation = CSharpCompilation.Create( + "TestCompilation", + syntaxTrees: new[] { tree }); + + SemanticModel semanticModel = compilation.GetSemanticModel(tree); + SyntaxNode root = tree.GetRoot(); + + PropertyDeclarationSyntax propertyDecl = root + .DescendantNodes() + .OfType() + .First(p => p.Identifier.Text == propertyName); + + return semanticModel.GetDeclaredSymbol(propertyDecl)!; + } +} \ No newline at end of file From b2d0ec9cfc727e6a1a6f8d8a1a0ff5312a26ea09 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sat, 12 Jul 2025 04:51:52 +0200 Subject: [PATCH 07/61] test: DocumentationComments --- .../Components/DocumentationCommentsTests.cs | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/src/M31.FluentApi.Tests/Components/DocumentationCommentsTests.cs b/src/M31.FluentApi.Tests/Components/DocumentationCommentsTests.cs index 2dca487..891d699 100644 --- a/src/M31.FluentApi.Tests/Components/DocumentationCommentsTests.cs +++ b/src/M31.FluentApi.Tests/Components/DocumentationCommentsTests.cs @@ -10,10 +10,16 @@ namespace M31.FluentApi.Tests.Components; public class DocumentationCommentsTests { + /// + /// Sets Property1. + /// + /// + /// The new value of Property1. + /// public string Property1 { get; private set; } [Fact] - public void CanParseCommentsTest1() + public void CanParseSingleTag() { string sourceCode = @" namespace M31.FluentApi.Tests.Components @@ -37,6 +43,74 @@ public class DocumentationCommentsTests Assert.Equal("Sets Property1.", comment.Content); } + [Fact] + public void CanParseMultipleTagsAndAttributes() + { + string sourceCode = @" + namespace M31.FluentApi.Tests.Components + { + public class DocumentationCommentsTests + { + /// + /// Sets Property1. + /// + /// + /// The new value of Property1. + /// + public string Property1 { get; private set; } + } + }"; + + IPropertySymbol propertySymbol = GetPropertySymbol(sourceCode, "Property1"); + string commentXml = propertySymbol.GetDocumentationCommentXml()!; + Comments comments = Comments.Parse(commentXml); + Assert.Equal(2, comments.List.Count); + + Comment comment1 = comments.List[0]; + Assert.Equal("fluentSummary", comment1.Tag); + Assert.Equal(1, comment1.Attributes.Count); + Assert.Equal("methodName", comment1.Attributes[0].Key); + Assert.Equal("WithProperty1", comment1.Attributes[0].Value); + Assert.Equal("Sets Property1.", comment1.Content); + + Comment comment2 = comments.List[1]; + Assert.Equal("fluentParam", comment2.Tag); + Assert.Equal(2, comment2.Attributes.Count); + Assert.Equal("methodName", comment2.Attributes[0].Key); + Assert.Equal("WithProperty1", comment2.Attributes[0].Value); + Assert.Equal("name", comment2.Attributes[1].Key); + Assert.Equal("property1", comment2.Attributes[1].Value); + Assert.Equal("The new value of Property1.", comment2.Content); + } + + [Fact] + public void CanIgnoreNonFluentTags() + { + string sourceCode = @" + namespace M31.FluentApi.Tests.Components + { + public class DocumentationCommentsTests + { + /// + /// Gets or sets Property1. // todo check + /// + /// + /// Sets Property1. + /// + public string Property1 { get; private set; } + } + }"; + + IPropertySymbol propertySymbol = GetPropertySymbol(sourceCode, "Property1"); + string commentXml = propertySymbol.GetDocumentationCommentXml()!; + Comments comments = Comments.Parse(commentXml); + Assert.Equal(1, comments.List.Count); + Comment comment = comments.List[0]; + Assert.Equal("fluentSummary", comment.Tag); + Assert.Equal(0, comment.Attributes.Count); + Assert.Equal("Sets Property1.", comment.Content); + } + private static IPropertySymbol GetPropertySymbol(string code, string propertyName) { SyntaxTree tree = CSharpSyntaxTree.ParseText(code); From 6383760b410aa22fd620ec84c7aa61f60766a5bb Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sun, 13 Jul 2025 05:43:53 +0200 Subject: [PATCH 08/61] test: Comments.Parse and Comment.GetLine --- .../BuilderStepMethod.cs | 4 +-- .../CodeBoardActors/Commons/BuilderMethod.cs | 7 +++-- .../Commons/BuilderMethodFactory.cs | 19 +++++++++--- .../MethodCreation/Forks/ForkCreator.cs | 2 +- .../CodeBoardElements/CodeBoard.cs | 3 ++ .../DocumentationComments/Comment.cs | 7 ++++- .../DocumentationComments/CommentAttribute.cs | 7 ++++- .../DocumentationComments/Comments.cs | 7 ++++- .../DocumentationComments/FluentComments.cs | 23 ++++++++++++++ .../CodeBoardElements/FluentApiSymbolInfo.cs | 2 +- .../CodeBoardElements/MemberSymbolInfo.cs | 2 +- .../CodeBoardElements/MethodSymbolInfo.cs | 2 +- .../SourceGenerators/SymbolInfoCreator.cs | 2 +- .../CommentGetLineTests.cs | 31 +++++++++++++++++++ .../CommentsParseTests.cs} | 19 +++--------- 15 files changed, 105 insertions(+), 32 deletions(-) rename src/M31.FluentApi.Generator/{SourceGenerators => CodeGeneration/CodeBoardElements}/DocumentationComments/Comment.cs (80%) rename src/M31.FluentApi.Generator/{SourceGenerators => CodeGeneration/CodeBoardElements}/DocumentationComments/CommentAttribute.cs (80%) rename src/M31.FluentApi.Generator/{SourceGenerators => CodeGeneration/CodeBoardElements}/DocumentationComments/Comments.cs (91%) create mode 100644 src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentComments.cs create mode 100644 src/M31.FluentApi.Tests/Components/DocumentationComments/CommentGetLineTests.cs rename src/M31.FluentApi.Tests/Components/{DocumentationCommentsTests.cs => DocumentationComments/CommentsParseTests.cs} (90%) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethod.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethod.cs index 5f01051..076647c 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethod.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethod.cs @@ -18,7 +18,7 @@ protected Method CreateMethod(string defaultReturnType, params string[] modifier { MethodSignature methodSignature = CreateMethodSignature(defaultReturnType, null, modifiers); Method method = new Method(methodSignature); - foreach (string comment in Comments) + foreach (string comment in Comments.GetLines()) { method.AddCommentLine(comment); } @@ -33,7 +33,7 @@ protected Method CreateInterfaceMethod( { MethodSignature methodSignature = CreateMethodSignature(defaultReturnType, interfaceName, modifiers); Method method = new Method(methodSignature); - foreach (string comment in Comments) + foreach (string comment in Comments.GetLines()) { method.AddCommentLine(comment); } diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethod.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethod.cs index 6977a49..f360993 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethod.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethod.cs @@ -1,4 +1,5 @@ using M31.FluentApi.Generator.CodeBuilding; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; using M31.FluentApi.Generator.SourceGenerators.Generics; namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.Commons; @@ -10,7 +11,7 @@ internal class BuilderMethod internal IReadOnlyCollection Parameters { get; } internal string? ReturnTypeToRespect { get; } internal BuildBodyCode BuildBodyCode { get; } - internal IReadOnlyCollection Comments { get; } + internal Comments Comments { get; } internal BuilderMethod( string methodName, @@ -18,14 +19,14 @@ internal BuilderMethod( IReadOnlyCollection parameters, string? returnTypeToRespect, BuildBodyCode buildBodyCode, - IReadOnlyCollection comments = null) // todo remove default + Comments comments) { MethodName = methodName; GenericInfo = genericInfo; Parameters = parameters; ReturnTypeToRespect = returnTypeToRespect; BuildBodyCode = buildBodyCode; - Comments = new List(); // todo set comments + Comments = comments; } internal BuilderMethod(BuilderMethod builderMethod) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs index be71dbf..d69b3ed 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs @@ -1,20 +1,24 @@ using M31.FluentApi.Generator.CodeBuilding; using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.Commons; internal class BuilderMethodFactory { private readonly InnerBodyCreationDelegates innerBodyCreationDelegates; + private readonly FluentComments fluentComments; - internal BuilderMethodFactory(InnerBodyCreationDelegates innerBodyCreationDelegates) + internal BuilderMethodFactory(InnerBodyCreationDelegates innerBodyCreationDelegates, FluentComments fluentComments) { this.innerBodyCreationDelegates = innerBodyCreationDelegates; + this.fluentComments = fluentComments; } internal BuilderMethod CreateBuilderMethod(string methodName) { - return new BuilderMethod(methodName, null, new List(), null, (_, _, _) => new List()); + Comments comments = Comments.Parse(null); // todo + return new BuilderMethod(methodName, null, new List(), null, (_, _, _) => new List(), comments); } internal BuilderMethod CreateBuilderMethod(string methodName, ComputeValueCode computeValue) @@ -35,7 +39,8 @@ List BuildBodyCode( }; } - return new BuilderMethod(methodName, null, parameters, null, BuildBodyCode); + Comments comments = Comments.Parse(null); // todo + return new BuilderMethod(methodName, null, parameters, null, BuildBodyCode, comments); } internal BuilderMethod CreateBuilderMethod(string methodName, List computeValues) @@ -53,7 +58,8 @@ List BuildBodyCode( .ToList(); } - return new BuilderMethod(methodName, null, parameters, null, BuildBodyCode); + Comments comments = Comments.Parse(null); // todo + return new BuilderMethod(methodName, null, parameters, null, BuildBodyCode, comments); } internal BuilderMethod CreateBuilderMethod( @@ -81,11 +87,14 @@ List BuildBodyCode( .BuildCode(instancePrefix, parameters, reservedVariableNames, returnType); } + Comments comments = fluentComments.GetMethodComments(methodSymbolInfo); + return new BuilderMethod( methodName, methodSymbolInfo.GenericInfo, parameters, returnTypeToRespect, - BuildBodyCode); + BuildBodyCode, + comments); } } \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Forks/ForkCreator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Forks/ForkCreator.cs index 2c06f78..8851bfd 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Forks/ForkCreator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Forks/ForkCreator.cs @@ -15,7 +15,7 @@ internal ForkCreator() public void Modify(CodeBoard codeBoard) { - BuilderMethodFactory builderMethodFactory = new BuilderMethodFactory(codeBoard.InnerBodyCreationDelegates); + BuilderMethodFactory builderMethodFactory = new BuilderMethodFactory(codeBoard.InnerBodyCreationDelegates, codeBoard.FluentComments); MethodCreator methodCreator = new MethodCreator(builderMethodFactory); CreateForks(methodCreator, codeBoard); codeBoard.Forks = GetForks(); diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/CodeBoard.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/CodeBoard.cs index e3b05ce..7f4e349 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/CodeBoard.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/CodeBoard.cs @@ -1,6 +1,7 @@ using M31.FluentApi.Generator.CodeBuilding; using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.Commons; using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.MethodCreation.Forks; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; using M31.FluentApi.Generator.SourceAnalyzers; using M31.FluentApi.Generator.SourceGenerators; using M31.FluentApi.Generator.SourceGenerators.AttributeElements; @@ -32,6 +33,7 @@ private CodeBoard( StaticConstructor = null; InnerBodyCreationDelegates = new InnerBodyCreationDelegates(); BuilderMethodToAttributeData = new Dictionary(); + FluentComments = new FluentComments(); Forks = new List(); ReservedVariableNames = new ReservedVariableNames(); diagnostics = new List(); @@ -47,6 +49,7 @@ private CodeBoard( internal Method? Constructor { get; set; } internal Method? StaticConstructor { get; set; } internal InnerBodyCreationDelegates InnerBodyCreationDelegates { get; } + internal FluentComments FluentComments { get; } internal Dictionary BuilderMethodToAttributeData { get; } internal IReadOnlyList Forks { get; set; } internal ReservedVariableNames ReservedVariableNames { get; } diff --git a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comment.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comment.cs similarity index 80% rename from src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comment.cs rename to src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comment.cs index cc93780..136f7d3 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comment.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comment.cs @@ -1,6 +1,6 @@ using M31.FluentApi.Generator.Commons; -namespace M31.FluentApi.Generator.SourceGenerators.DocumentationComments; +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; internal class Comment { internal string Tag { get; } @@ -14,6 +14,11 @@ internal Comment(string tag, IReadOnlyList attributes, string Content = content; } + internal string GetLine() + { + return $"/// <{Tag}{string.Join(string.Empty, Attributes.Select(a => $" {a}"))}>{Content}"; + } + protected bool Equals(Comment other) { return Tag == other.Tag && diff --git a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/CommentAttribute.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/CommentAttribute.cs similarity index 80% rename from src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/CommentAttribute.cs rename to src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/CommentAttribute.cs index f94cf0e..10c73ca 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/CommentAttribute.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/CommentAttribute.cs @@ -1,6 +1,6 @@ using M31.FluentApi.Generator.Commons; -namespace M31.FluentApi.Generator.SourceGenerators.DocumentationComments; +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; internal class CommentAttribute { internal CommentAttribute(string key, string value) @@ -30,4 +30,9 @@ public override int GetHashCode() { return new HashCode().Add(Key, Value); } + + public override string ToString() + { + return @$"{Key}=""{Value}"""; + } } diff --git a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comments.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs similarity index 91% rename from src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comments.cs rename to src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs index e62b518..824274c 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/DocumentationComments/Comments.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs @@ -1,7 +1,7 @@ using System.Text.RegularExpressions; using M31.FluentApi.Generator.Commons; -namespace M31.FluentApi.Generator.SourceGenerators.DocumentationComments; +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; internal class Comments { @@ -43,6 +43,11 @@ private static IReadOnlyList ParseCommentAttributes(string com return matches.Cast().Select(m => new CommentAttribute(m.Groups["key"].Value, m.Groups["value"].Value)).ToArray(); } + internal IReadOnlyCollection GetLines() + { + return Array.Empty(); // todo + } + protected bool Equals(Comments other) { return List.SequenceEqual(other.List); diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentComments.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentComments.cs new file mode 100644 index 0000000..0583b82 --- /dev/null +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentComments.cs @@ -0,0 +1,23 @@ +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; + +internal class FluentComments +{ + private readonly Dictionary memberComments; + private readonly Dictionary methodComments; + + internal FluentComments() + { + memberComments = new Dictionary(); + methodComments = new Dictionary(); + } + + internal Comments GetMemberComments(string memberName) + { + return memberComments[memberName]; + } + + internal Comments GetMethodComments(MethodSymbolInfo methodSymbolInfo) + { + return methodComments[methodSymbolInfo]; + } +} diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiSymbolInfo.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiSymbolInfo.cs index b864fbe..e3ec41e 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiSymbolInfo.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiSymbolInfo.cs @@ -1,5 +1,5 @@ +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; using M31.FluentApi.Generator.Commons; -using M31.FluentApi.Generator.SourceGenerators.DocumentationComments; using Microsoft.CodeAnalysis; namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements; diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MemberSymbolInfo.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MemberSymbolInfo.cs index ff9fdbf..db4ec9d 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MemberSymbolInfo.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MemberSymbolInfo.cs @@ -1,6 +1,6 @@ +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; using M31.FluentApi.Generator.Commons; using M31.FluentApi.Generator.SourceGenerators.Collections; -using M31.FluentApi.Generator.SourceGenerators.DocumentationComments; using Microsoft.CodeAnalysis; namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements; diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MethodSymbolInfo.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MethodSymbolInfo.cs index 740a85d..ae5fd20 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MethodSymbolInfo.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MethodSymbolInfo.cs @@ -1,5 +1,5 @@ +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; using M31.FluentApi.Generator.Commons; -using M31.FluentApi.Generator.SourceGenerators.DocumentationComments; using M31.FluentApi.Generator.SourceGenerators.Generics; using Microsoft.CodeAnalysis; diff --git a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs index 4fa4b70..95cf6da 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs @@ -1,8 +1,8 @@ using M31.FluentApi.Generator.CodeBuilding; using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; using M31.FluentApi.Generator.Commons; using M31.FluentApi.Generator.SourceGenerators.Collections; -using M31.FluentApi.Generator.SourceGenerators.DocumentationComments; using M31.FluentApi.Generator.SourceGenerators.Generics; using Microsoft.CodeAnalysis; diff --git a/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentGetLineTests.cs b/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentGetLineTests.cs new file mode 100644 index 0000000..18231f2 --- /dev/null +++ b/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentGetLineTests.cs @@ -0,0 +1,31 @@ +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +using System; +using Xunit; + +namespace M31.FluentApi.Tests.Components.DocumentationComments; + +public class CommentGetLineTests +{ + [Fact] + public void CanGetLineOfSimpleComment() + { + Comment comment = new Comment("fluentSummary", Array.Empty(), "Sets Property1."); + string line = comment.GetLine(); + Assert.Equal("/// Sets Property1.", line); + } + + [Fact] + public void CanGetLineOfCommentWithAttributes() + { + CommentAttribute[] attributes = new CommentAttribute[] + { + new CommentAttribute("methodName", "method1"), + new CommentAttribute("name", "parameter1") + }; + Comment comment = new Comment("fluentParam", attributes, "The parameter1."); + string line = comment.GetLine(); + Assert.Equal(@"/// The parameter1.", line); + } + + +} diff --git a/src/M31.FluentApi.Tests/Components/DocumentationCommentsTests.cs b/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsParseTests.cs similarity index 90% rename from src/M31.FluentApi.Tests/Components/DocumentationCommentsTests.cs rename to src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsParseTests.cs index 891d699..92bb9f4 100644 --- a/src/M31.FluentApi.Tests/Components/DocumentationCommentsTests.cs +++ b/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsParseTests.cs @@ -1,23 +1,14 @@ -using M31.FluentApi.Generator.SourceGenerators.DocumentationComments; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Linq; using Xunit; -namespace M31.FluentApi.Tests.Components; +namespace M31.FluentApi.Tests.Components.DocumentationComments; -public class DocumentationCommentsTests +public class CommentsParseTests { - - /// - /// Sets Property1. - /// - /// - /// The new value of Property1. - /// - public string Property1 { get; private set; } - [Fact] public void CanParseSingleTag() { @@ -84,7 +75,7 @@ public class DocumentationCommentsTests } [Fact] - public void CanIgnoreNonFluentTags() + public void CanIgnoreNonFluentTagsInParsing() { string sourceCode = @" namespace M31.FluentApi.Tests.Components @@ -92,7 +83,7 @@ namespace M31.FluentApi.Tests.Components public class DocumentationCommentsTests { /// - /// Gets or sets Property1. // todo check + /// Gets or sets Property1. /// /// /// Sets Property1. From a500988466aa0ce086a9cc632c96f406a9555f93 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sun, 13 Jul 2025 14:57:16 +0200 Subject: [PATCH 09/61] feat: comment transformer (wip) --- .../Commons/BuilderMethodFactory.cs | 8 ++--- .../CommentsTransformer.cs | 30 +++++++++++++++++++ .../MethodCreation/Forks/ForkCreator.cs | 2 +- .../CodeBoardElements/CodeBoard.cs | 4 +-- .../DocumentationComments/Comments.cs | 4 +-- ...uentComments.cs => TransformedComments.cs} | 4 +-- .../CommentGetLineTests.cs | 4 +-- .../CommentsParseTests.cs | 8 ++--- 8 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsTransformer.cs rename src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/{FluentComments.cs => TransformedComments.cs} (90%) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs index d69b3ed..e9680ff 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs @@ -7,12 +7,12 @@ namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.Commons; internal class BuilderMethodFactory { private readonly InnerBodyCreationDelegates innerBodyCreationDelegates; - private readonly FluentComments fluentComments; + private readonly TransformedComments transformedComments; - internal BuilderMethodFactory(InnerBodyCreationDelegates innerBodyCreationDelegates, FluentComments fluentComments) + internal BuilderMethodFactory(InnerBodyCreationDelegates innerBodyCreationDelegates, TransformedComments transformedComments) { this.innerBodyCreationDelegates = innerBodyCreationDelegates; - this.fluentComments = fluentComments; + this.transformedComments = transformedComments; } internal BuilderMethod CreateBuilderMethod(string methodName) @@ -87,7 +87,7 @@ List BuildBodyCode( .BuildCode(instancePrefix, parameters, reservedVariableNames, returnType); } - Comments comments = fluentComments.GetMethodComments(methodSymbolInfo); + Comments comments = transformedComments.GetMethodComments(methodSymbolInfo); return new BuilderMethod( methodName, diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsTransformer.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsTransformer.cs new file mode 100644 index 0000000..8ce6364 --- /dev/null +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsTransformer.cs @@ -0,0 +1,30 @@ +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; + +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.DocumentationGeneration; + +internal static class CommentsTransformer +{ + internal static Comments TransformComments(Comments comments) + { + return new Comments(comments.List.Select(TransformComment).ToArray()); + } + + internal static Comment TransformComment(Comment comment) + { + string transformedTag = TransformTag(comment.Tag); + IReadOnlyList transformedAttributes = TransformAttributes(comment.Attributes); + return new Comment(transformedTag, transformedAttributes, comment.Content); + } + + internal static string TransformTag(string tag) + { + return tag; // todo + } + + internal static IReadOnlyList TransformAttributes(IReadOnlyList attributes) + { + // todo: write unit test + CommentAttribute? firstMethodAttribute = attributes.FirstOrDefault(a => a.Key == "method"); + return firstMethodAttribute == null ? attributes : attributes.Where(a => a != firstMethodAttribute).ToArray(); + } +} diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Forks/ForkCreator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Forks/ForkCreator.cs index 8851bfd..cfb7c1d 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Forks/ForkCreator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Forks/ForkCreator.cs @@ -15,7 +15,7 @@ internal ForkCreator() public void Modify(CodeBoard codeBoard) { - BuilderMethodFactory builderMethodFactory = new BuilderMethodFactory(codeBoard.InnerBodyCreationDelegates, codeBoard.FluentComments); + BuilderMethodFactory builderMethodFactory = new BuilderMethodFactory(codeBoard.InnerBodyCreationDelegates, codeBoard.TransformedComments); MethodCreator methodCreator = new MethodCreator(builderMethodFactory); CreateForks(methodCreator, codeBoard); codeBoard.Forks = GetForks(); diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/CodeBoard.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/CodeBoard.cs index 7f4e349..1b1127a 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/CodeBoard.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/CodeBoard.cs @@ -33,7 +33,7 @@ private CodeBoard( StaticConstructor = null; InnerBodyCreationDelegates = new InnerBodyCreationDelegates(); BuilderMethodToAttributeData = new Dictionary(); - FluentComments = new FluentComments(); + TransformedComments = new TransformedComments(); Forks = new List(); ReservedVariableNames = new ReservedVariableNames(); diagnostics = new List(); @@ -49,7 +49,7 @@ private CodeBoard( internal Method? Constructor { get; set; } internal Method? StaticConstructor { get; set; } internal InnerBodyCreationDelegates InnerBodyCreationDelegates { get; } - internal FluentComments FluentComments { get; } + internal TransformedComments TransformedComments { get; } internal Dictionary BuilderMethodToAttributeData { get; } internal IReadOnlyList Forks { get; set; } internal ReservedVariableNames ReservedVariableNames { get; } diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs index 824274c..f15901a 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs @@ -8,7 +8,7 @@ internal class Comments private static readonly Regex commentRegex = new Regex(@"<(?fluent\w+)(\s+(?[^>]+))?>\s*(?.*?)\s*>", RegexOptions.Compiled | RegexOptions.Singleline); private static readonly Regex attributeRegex = new Regex(@"(?\w+)\s*=\s*""(?[^""]*)""", RegexOptions.Compiled | RegexOptions.Singleline); - private Comments(IReadOnlyList comments) + internal Comments(IReadOnlyList comments) { List = comments; } @@ -45,7 +45,7 @@ private static IReadOnlyList ParseCommentAttributes(string com internal IReadOnlyCollection GetLines() { - return Array.Empty(); // todo + return List.Select(c => c.ToString()).ToArray(); } protected bool Equals(Comments other) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentComments.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs similarity index 90% rename from src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentComments.cs rename to src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs index 0583b82..59f4890 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentComments.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs @@ -1,11 +1,11 @@ namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; -internal class FluentComments +internal class TransformedComments { private readonly Dictionary memberComments; private readonly Dictionary methodComments; - internal FluentComments() + internal TransformedComments() { memberComments = new Dictionary(); methodComments = new Dictionary(); diff --git a/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentGetLineTests.cs b/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentGetLineTests.cs index 18231f2..6dccbcd 100644 --- a/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentGetLineTests.cs +++ b/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentGetLineTests.cs @@ -19,12 +19,12 @@ public void CanGetLineOfCommentWithAttributes() { CommentAttribute[] attributes = new CommentAttribute[] { - new CommentAttribute("methodName", "method1"), + new CommentAttribute("method", "method1"), new CommentAttribute("name", "parameter1") }; Comment comment = new Comment("fluentParam", attributes, "The parameter1."); string line = comment.GetLine(); - Assert.Equal(@"/// The parameter1.", line); + Assert.Equal(@"/// The parameter1.", line); } diff --git a/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsParseTests.cs b/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsParseTests.cs index 92bb9f4..ac75be3 100644 --- a/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsParseTests.cs +++ b/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsParseTests.cs @@ -42,10 +42,10 @@ namespace M31.FluentApi.Tests.Components { public class DocumentationCommentsTests { - /// + /// /// Sets Property1. /// - /// + /// /// The new value of Property1. /// public string Property1 { get; private set; } @@ -60,14 +60,14 @@ public class DocumentationCommentsTests Comment comment1 = comments.List[0]; Assert.Equal("fluentSummary", comment1.Tag); Assert.Equal(1, comment1.Attributes.Count); - Assert.Equal("methodName", comment1.Attributes[0].Key); + Assert.Equal("method", comment1.Attributes[0].Key); Assert.Equal("WithProperty1", comment1.Attributes[0].Value); Assert.Equal("Sets Property1.", comment1.Content); Comment comment2 = comments.List[1]; Assert.Equal("fluentParam", comment2.Tag); Assert.Equal(2, comment2.Attributes.Count); - Assert.Equal("methodName", comment2.Attributes[0].Key); + Assert.Equal("method", comment2.Attributes[0].Key); Assert.Equal("WithProperty1", comment2.Attributes[0].Value); Assert.Equal("name", comment2.Attributes[1].Key); Assert.Equal("property1", comment2.Attributes[1].Value); From cb8ff087eeaa0b368691a6bb29635deb2e892023 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Tue, 15 Jul 2025 05:29:54 +0200 Subject: [PATCH 10/61] test: CommentsTransformer --- .../CommentsTransformer.cs | 24 +++++-- .../CommentsTransformerTests.cs | 68 +++++++++++++++++++ 2 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsTransformerTests.cs diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsTransformer.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsTransformer.cs index 8ce6364..2e68c3f 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsTransformer.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsTransformer.cs @@ -1,4 +1,5 @@ using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +using M31.FluentApi.Generator.Commons; namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.DocumentationGeneration; @@ -6,24 +7,33 @@ internal static class CommentsTransformer { internal static Comments TransformComments(Comments comments) { - return new Comments(comments.List.Select(TransformComment).ToArray()); + return new Comments(comments.List.Select(TransformComment).OfType().ToArray()); } - internal static Comment TransformComment(Comment comment) + internal static Comment? TransformComment(Comment comment) { - string transformedTag = TransformTag(comment.Tag); + string? transformedTag = TransformTag(comment.Tag); + if (transformedTag == null) + { + return null; + } + IReadOnlyList transformedAttributes = TransformAttributes(comment.Attributes); return new Comment(transformedTag, transformedAttributes, comment.Content); } - internal static string TransformTag(string tag) + private static string? TransformTag(string tag) { - return tag; // todo + if (!tag.StartsWith("fluent")) + { + return null; + } + + return tag.Substring("fluent".Length).FirstCharToLower(); } - internal static IReadOnlyList TransformAttributes(IReadOnlyList attributes) + private static IReadOnlyList TransformAttributes(IReadOnlyList attributes) { - // todo: write unit test CommentAttribute? firstMethodAttribute = attributes.FirstOrDefault(a => a.Key == "method"); return firstMethodAttribute == null ? attributes : attributes.Where(a => a != firstMethodAttribute).ToArray(); } diff --git a/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsTransformerTests.cs b/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsTransformerTests.cs new file mode 100644 index 0000000..93056a9 --- /dev/null +++ b/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsTransformerTests.cs @@ -0,0 +1,68 @@ +using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.DocumentationGeneration; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +using System; +using Xunit; + +namespace M31.FluentApi.Tests.Components.DocumentationComments; + +public class CommentsTransformerTests +{ + [Fact] + public void CanTransformSimpleFluentComment() + { + Comment comment = new Comment("fluentSummary", Array.Empty(), "Sets Property1."); + Comment? transformed = CommentsTransformer.TransformComment(comment); + Assert.NotNull(transformed); + Assert.Equal("/// Sets Property1.", transformed!.GetLine()); + } + + [Fact] + public void NonFluentCommentsAreNotTransformed() + { + Comment comment = new Comment("Summary", Array.Empty(), "Sets Property1."); + Comment? transformed = CommentsTransformer.TransformComment(comment); + Assert.Null(transformed); + } + + [Fact] + public void CanTransformFluentCommentWithAttribute() + { + CommentAttribute[] attributes = new CommentAttribute[] + { + new CommentAttribute("name", "property1") + }; + Comment comment = new Comment("fluentParam", attributes, "The new value of Property1."); + Comment? transformed = CommentsTransformer.TransformComment(comment); + Assert.NotNull(transformed); + Assert.Equal(@"/// The new value of Property1.", transformed!.GetLine()); + } + + [Fact] + public void MethodAttributeIsRemoved() + { + CommentAttribute[] attributes = new CommentAttribute[] + { + new CommentAttribute("method", "WithProperty1"), + new CommentAttribute("name", "property1") + }; + Comment comment = new Comment("fluentParam", attributes, "The new value of Property1."); + Comment? transformed = CommentsTransformer.TransformComment(comment); + Assert.NotNull(transformed); + Assert.Equal(@"/// The new value of Property1.", transformed!.GetLine()); + } + + [Fact] + public void FurtherMethodAttributesAreRetained() + { + CommentAttribute[] attributes = new CommentAttribute[] + { + new CommentAttribute("method", "WithProperty1"), + new CommentAttribute("method", "static"), // Attribute unrelated to the FluentAPI. + new CommentAttribute("name", "property1") + }; + Comment comment = new Comment("fluentParam", attributes, "The new value of Property1."); + Comment? transformed = CommentsTransformer.TransformComment(comment); + Assert.NotNull(transformed); + Assert.Equal(@"/// The new value of Property1.", transformed!.GetLine()); + } +} \ No newline at end of file From 77707424095fdd70cd4fbf20b894897afd4c39f3 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Tue, 15 Jul 2025 06:01:37 +0200 Subject: [PATCH 11/61] refactor: add FluentCommentsParser class --- .../Commons/BuilderMethodFactory.cs | 6 +-- .../DocumentationComments/Comments.cs | 34 +---------------- .../FluentCommentsParser.cs | 37 +++++++++++++++++++ .../SourceGenerators/SymbolInfoCreator.cs | 2 +- ...eTests.cs => FluentCommentsParserTests.cs} | 8 ++-- 5 files changed, 46 insertions(+), 41 deletions(-) create mode 100644 src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentCommentsParser.cs rename src/M31.FluentApi.Tests/Components/DocumentationComments/{CommentsParseTests.cs => FluentCommentsParserTests.cs} (94%) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs index e9680ff..0bce5f0 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs @@ -17,7 +17,7 @@ internal BuilderMethodFactory(InnerBodyCreationDelegates innerBodyCreationDelega internal BuilderMethod CreateBuilderMethod(string methodName) { - Comments comments = Comments.Parse(null); // todo + Comments comments = FluentCommentsParser.Parse(null); // todo return new BuilderMethod(methodName, null, new List(), null, (_, _, _) => new List(), comments); } @@ -39,7 +39,7 @@ List BuildBodyCode( }; } - Comments comments = Comments.Parse(null); // todo + Comments comments = FluentCommentsParser.Parse(null); // todo return new BuilderMethod(methodName, null, parameters, null, BuildBodyCode, comments); } @@ -58,7 +58,7 @@ List BuildBodyCode( .ToList(); } - Comments comments = Comments.Parse(null); // todo + Comments comments = FluentCommentsParser.Parse(null); // todo return new BuilderMethod(methodName, null, parameters, null, BuildBodyCode, comments); } diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs index f15901a..10f3bc0 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs @@ -1,13 +1,9 @@ -using System.Text.RegularExpressions; -using M31.FluentApi.Generator.Commons; +using M31.FluentApi.Generator.Commons; namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; internal class Comments { - private static readonly Regex commentRegex = new Regex(@"<(?fluent\w+)(\s+(?[^>]+))?>\s*(?.*?)\s*>", RegexOptions.Compiled | RegexOptions.Singleline); - private static readonly Regex attributeRegex = new Regex(@"(?\w+)\s*=\s*""(?[^""]*)""", RegexOptions.Compiled | RegexOptions.Singleline); - internal Comments(IReadOnlyList comments) { List = comments; @@ -15,34 +11,6 @@ internal Comments(IReadOnlyList comments) public IReadOnlyList List { get; } - internal static Comments Parse(string? commentXml) - { - if (commentXml == null) - { - return new Comments(Array.Empty()); - } - - List comments = new List(); - MatchCollection matches = commentRegex.Matches(commentXml); - - foreach (Match match in matches) - { - string tag = match.Groups["tag"].Value; - string attributes = match.Groups["attrs"].Value; - string content = match.Groups["content"].Value; - Comment comment = new Comment(tag, ParseCommentAttributes(attributes), content); - comments.Add(comment); - } - - return new Comments(comments); - } - - private static IReadOnlyList ParseCommentAttributes(string commentAttributes) - { - MatchCollection matches = attributeRegex.Matches(commentAttributes); - return matches.Cast().Select(m => new CommentAttribute(m.Groups["key"].Value, m.Groups["value"].Value)).ToArray(); - } - internal IReadOnlyCollection GetLines() { return List.Select(c => c.ToString()).ToArray(); diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentCommentsParser.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentCommentsParser.cs new file mode 100644 index 0000000..b78843e --- /dev/null +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentCommentsParser.cs @@ -0,0 +1,37 @@ +using System.Text.RegularExpressions; + +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; + +internal static class FluentCommentsParser +{ + private static readonly Regex commentRegex = new Regex(@"<(?fluent\w+)(\s+(?[^>]+))?>\s*(?.*?)\s*>", RegexOptions.Compiled | RegexOptions.Singleline); + private static readonly Regex attributeRegex = new Regex(@"(?\w+)\s*=\s*""(?[^""]*)""", RegexOptions.Compiled | RegexOptions.Singleline); + + internal static Comments Parse(string? commentXml) + { + if (commentXml == null) + { + return new Comments(Array.Empty()); + } + + List comments = new List(); + MatchCollection matches = commentRegex.Matches(commentXml); + + foreach (Match match in matches) + { + string tag = match.Groups["tag"].Value; + string attributes = match.Groups["attrs"].Value; + string content = match.Groups["content"].Value; + Comment comment = new Comment(tag, ParseCommentAttributes(attributes), content); + comments.Add(comment); + } + + return new Comments(comments); + } + + private static IReadOnlyList ParseCommentAttributes(string commentAttributes) + { + MatchCollection matches = attributeRegex.Matches(commentAttributes); + return matches.Cast().Select(m => new CommentAttribute(m.Groups["key"].Value, m.Groups["value"].Value)).ToArray(); + } +} diff --git a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs index 95cf6da..dfd825f 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs @@ -202,6 +202,6 @@ private static Comments GetFluentSymbolComments(ISymbol symbol) { // todo: pass cancellation token. string? commentXml = symbol.GetDocumentationCommentXml(); - return Comments.Parse(commentXml); + return FluentCommentsParser.Parse(commentXml); } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsParseTests.cs b/src/M31.FluentApi.Tests/Components/DocumentationComments/FluentCommentsParserTests.cs similarity index 94% rename from src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsParseTests.cs rename to src/M31.FluentApi.Tests/Components/DocumentationComments/FluentCommentsParserTests.cs index ac75be3..59b2157 100644 --- a/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsParseTests.cs +++ b/src/M31.FluentApi.Tests/Components/DocumentationComments/FluentCommentsParserTests.cs @@ -7,7 +7,7 @@ namespace M31.FluentApi.Tests.Components.DocumentationComments; -public class CommentsParseTests +public class FluentCommentsParserTests { [Fact] public void CanParseSingleTag() @@ -26,7 +26,7 @@ public class DocumentationCommentsTests IPropertySymbol propertySymbol = GetPropertySymbol(sourceCode, "Property1"); string commentXml = propertySymbol.GetDocumentationCommentXml()!; - Comments comments = Comments.Parse(commentXml); + Comments comments = FluentCommentsParser.Parse(commentXml); Assert.Equal(1, comments.List.Count); Comment comment = comments.List[0]; Assert.Equal("fluentSummary", comment.Tag); @@ -54,7 +54,7 @@ public class DocumentationCommentsTests IPropertySymbol propertySymbol = GetPropertySymbol(sourceCode, "Property1"); string commentXml = propertySymbol.GetDocumentationCommentXml()!; - Comments comments = Comments.Parse(commentXml); + Comments comments = FluentCommentsParser.Parse(commentXml); Assert.Equal(2, comments.List.Count); Comment comment1 = comments.List[0]; @@ -94,7 +94,7 @@ public class DocumentationCommentsTests IPropertySymbol propertySymbol = GetPropertySymbol(sourceCode, "Property1"); string commentXml = propertySymbol.GetDocumentationCommentXml()!; - Comments comments = Comments.Parse(commentXml); + Comments comments = FluentCommentsParser.Parse(commentXml); Assert.Equal(1, comments.List.Count); Comment comment = comments.List[0]; Assert.Equal("fluentSummary", comment.Tag); From f6b079e2e11e3091d2bf5d0909f9282a8e7d5c4a Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Tue, 15 Jul 2025 09:35:12 +0200 Subject: [PATCH 12/61] feat(CommentsGenerator): HandleMethodSymbolInfo --- .../CommentsGenerator.cs | 40 +++++++++++++++++++ .../TransformedComments.cs | 26 +++++++++++- .../InnerBodyCreationDelegates.cs | 4 +- .../CodeGeneration/CodeGenerator.cs | 2 + .../Commons/GenerationException.cs | 2 +- 5 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs new file mode 100644 index 0000000..19fc375 --- /dev/null +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs @@ -0,0 +1,40 @@ +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +using M31.FluentApi.Generator.SourceGenerators; + +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.DocumentationGeneration; + +internal class CommentsGenerator : ICodeBoardActor +{ + // todo: cancellation + public void Modify(CodeBoard codeBoard) + { + foreach (FluentApiInfo fluentApiInfo in codeBoard.FluentApiInfos) + { + switch (fluentApiInfo.SymbolInfo) + { + case MemberSymbolInfo memberInfo: + HandleMemberSymbolInfo(memberInfo, codeBoard); + break; + + case MethodSymbolInfo methodSymbolInfo: + HandleMethodSymbolInfo(methodSymbolInfo, codeBoard); + break; + + default: + throw new ArgumentException($"Unknown symbol info type: {fluentApiInfo.SymbolInfo.GetType()}"); + } + } + } + + private void HandleMemberSymbolInfo(MemberSymbolInfo memberInfo, CodeBoard codeBoard) + { + return; // todo + } + + private void HandleMethodSymbolInfo(MethodSymbolInfo methodInfo, CodeBoard codeBoard) + { + Comments transformedComments = CommentsTransformer.TransformComments(methodInfo.Comments); + codeBoard.TransformedComments.AssignMethodComments(methodInfo, transformedComments); + } +} diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs index 59f4890..a3b9f91 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs @@ -1,4 +1,6 @@ -namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +using M31.FluentApi.Generator.Commons; + +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; internal class TransformedComments { @@ -11,11 +13,33 @@ internal TransformedComments() methodComments = new Dictionary(); } + internal void AssignMemberComments(string memberName, Comments comments) + { + if (memberComments.ContainsKey(memberName)) + { + throw new InvalidOperationException( + $"{nameof(Comments)} for member {memberName} has already been assigned."); + } + + memberComments[memberName] = comments; + } + internal Comments GetMemberComments(string memberName) { return memberComments[memberName]; } + internal void AssignMethodComments(MethodSymbolInfo methodSymbolInfo, Comments comments) + { + if (methodComments.ContainsKey(methodSymbolInfo)) + { + throw new InvalidOperationException( + $"{nameof(Comments)} for method {methodSymbolInfo.Name} has already been assigned."); + } + + methodComments[methodSymbolInfo] = comments; + } + internal Comments GetMethodComments(MethodSymbolInfo methodSymbolInfo) { return methodComments[methodSymbolInfo]; diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/InnerBodyCreationDelegates.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/InnerBodyCreationDelegates.cs index 3feeb62..c8b8425 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/InnerBodyCreationDelegates.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/InnerBodyCreationDelegates.cs @@ -17,7 +17,7 @@ internal void AssignSetMemberCode(string memberName, SetMemberCode setMemberCode { if (memberToSetMemberCode.ContainsKey(memberName)) { - throw new GenerationException( + throw new InvalidOperationException( $"{nameof(SetMemberCode)} for member {memberName} has already been assigned."); } @@ -33,7 +33,7 @@ internal void AssignCallMethodCode(MethodSymbolInfo methodSymbolInfo, CallMethod { if (methodToCallMethodCode.ContainsKey(methodSymbolInfo)) { - throw new GenerationException( + throw new InvalidOperationException( $"{nameof(CallMethodCode)} for method {methodSymbolInfo.Name} has already been assigned."); } diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeGenerator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeGenerator.cs index 51b7e22..4ee0370 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeGenerator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeGenerator.cs @@ -1,5 +1,6 @@ using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors; using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.BuilderMethodsGeneration; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.DocumentationGeneration; using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.DuplicateMethodsChecking; using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.InnerBodyGeneration; using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.MethodCreation.Forks; @@ -32,6 +33,7 @@ internal static CodeGeneratorResult GenerateCode(FluentApiClassInfo classInfo, C List actors = new List() { + new CommentsGenerator(), new EntityFieldGenerator(), new ConstructorGenerator(), new InnerBodyCreator(), diff --git a/src/M31.FluentApi.Generator/Commons/GenerationException.cs b/src/M31.FluentApi.Generator/Commons/GenerationException.cs index 879d0a4..bb97a9e 100644 --- a/src/M31.FluentApi.Generator/Commons/GenerationException.cs +++ b/src/M31.FluentApi.Generator/Commons/GenerationException.cs @@ -3,7 +3,7 @@ namespace M31.FluentApi.Generator.Commons; internal class GenerationException : Exception { /// - /// Initializes a new instance of the class. Throw this exception only for errors + /// Initializes a new instance of the class. Throw this exception only for usage errors /// that should not occur because they were already handled with diagnostics. Therefore, if this exception is /// actually thrown, the diagnostics need to be improved. /// From 6a72d824db1bf98050e30d12478da0ee2afc3b4f Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Thu, 17 Jul 2025 09:58:56 +0200 Subject: [PATCH 13/61] test(CommentedMethodClass): wip --- .../CodeBuilding/Method.cs | 1 + .../CodeBuilding/MethodComments.cs | 1 - .../DocumentationComments/Comments.cs | 2 +- .../CreateStudent.expected.txt | 0 .../CommentedMethodClass}/CreateStudent.g.cs | 0 .../CommentedMethodClass}/Student.cs | 22 +++++-------------- .../CodeGeneration/TestDataProvider.cs | 6 ++--- 7 files changed, 11 insertions(+), 21 deletions(-) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{CommentedClass => DocumentationComments/CommentedMethodClass}/CreateStudent.expected.txt (100%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{CommentedClass => DocumentationComments/CommentedMethodClass}/CreateStudent.g.cs (100%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{CommentedClass => DocumentationComments/CommentedMethodClass}/Student.cs (72%) diff --git a/src/M31.FluentApi.Generator/CodeBuilding/Method.cs b/src/M31.FluentApi.Generator/CodeBuilding/Method.cs index 08892b9..7278372 100644 --- a/src/M31.FluentApi.Generator/CodeBuilding/Method.cs +++ b/src/M31.FluentApi.Generator/CodeBuilding/Method.cs @@ -26,6 +26,7 @@ internal void AppendBodyLine(string line) public CodeBuilder AppendCode(CodeBuilder codeBuilder) { return codeBuilder + .Append(MethodComments) .Append(MethodSignature) .Append(MethodBody); } diff --git a/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs b/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs index e6efbf6..a7a2863 100644 --- a/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs +++ b/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs @@ -19,7 +19,6 @@ internal void AddCommentLine(string commentLine) public CodeBuilder AppendCode(CodeBuilder codeBuilder) { return codeBuilder - .StartLine() .AppendLines(comments); } } diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs index 10f3bc0..07c88d6 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs @@ -13,7 +13,7 @@ internal Comments(IReadOnlyList comments) internal IReadOnlyCollection GetLines() { - return List.Select(c => c.ToString()).ToArray(); + return List.Select(c => c.GetLine()).ToArray(); } protected bool Equals(Comments other) diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/CreateStudent.expected.txt similarity index 100% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/CreateStudent.expected.txt rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/CreateStudent.expected.txt diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/CreateStudent.g.cs similarity index 100% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/CreateStudent.g.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/CreateStudent.g.cs diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/Student.cs similarity index 72% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/Student.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/Student.cs index 90c8323..6d0c08c 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/CommentedClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/Student.cs @@ -5,23 +5,11 @@ using System; using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.CommentedClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedMethodClass; [FluentApi] public class Student { - /// - /// Sets the first and last name of the student. - /// And other stuff. - /// - /// - /// Some irrelevant summary. - /// - /** - * Hello - * World - * - */ [FluentMember(0, "WithName")] public string FirstName { get; set; } @@ -31,9 +19,14 @@ public class Student [FluentMember(1, "OfAge")] public int Age { get; private set; } + /// + /// This summary will not be taken into account. + /// + /// This parameter documentation will not be taken into account. /// /// Calculates and sets the student's age based on the provided date of birth. /// + /// The student's date of birth. [FluentMethod(1)] private void BornOn(DateOnly dateOfBirth) { @@ -43,9 +36,6 @@ private void BornOn(DateOnly dateOfBirth) Age = age; } - /// - /// Sets the current semester the student is enrolled in. - /// [FluentMember(2, "InSemester")] public int Semester { get; set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs index 98f67dd..60550f9 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs @@ -7,7 +7,7 @@ namespace M31.FluentApi.Tests.CodeGeneration; internal class TestDataProvider : IEnumerable { private readonly List testClasses = - Filter(new string[] { "CommentedClass" }, + Filter(new string[] { "CommentedMethodClass" }, new List { new object[] { "Abstract", "AliasNamespaceClass", "Student" }, @@ -15,13 +15,13 @@ internal class TestDataProvider : IEnumerable new object[] { "Abstract", "CollectionMemberClass", "Student" }, new object[] { "Abstract", "CollectionMemberClassWithSuppression", "Student" }, new object[] { "Abstract", "CollectionNullableArrayClass", "Student" }, - new object[] { "Abstract", "CommentedClass", "Student" }, new object[] { "Abstract", "ContinueWithAfterCompoundClass", "Student" }, new object[] { "Abstract", "ContinueWithInForkClass", "Student" }, new object[] { "Abstract", "ContinueWithOfOverloadedMethodClass", "Student" }, new object[] { "Abstract", "ContinueWithSelfClass", "Student" }, new object[] { "Abstract", "CustomFluentMethodNameClass", "Student" }, new object[] { "Abstract", "DefaultFluentMethodNameClass", "Student" }, + new object[] { "Abstract", "DocumentationComments", "CommentedMethodClass", "Student" }, new object[] { "Abstract", "EmptyClass", "Student" }, new object[] { "Abstract", "FluentDefaultMemberClass", "Student" }, new object[] { "Abstract", "FluentLambdaClass", "Student|Address" }, @@ -111,6 +111,6 @@ internal class TestDataProvider : IEnumerable private static List Filter(string[] testsToFilter, List tests) { - return testsToFilter.Length == 0 ? tests : tests.Where(t => testsToFilter.Contains((string)t[1])).ToList(); + return testsToFilter.Length == 0 ? tests : tests.Where(t => testsToFilter.Contains((string)t[^2])).ToList(); } } \ No newline at end of file From 6eb5950d1497e23323bb422b413716e25e0e15d9 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Fri, 18 Jul 2025 06:41:19 +0200 Subject: [PATCH 14/61] feat: CommentedMethodSignature --- .../CodeBuilding/CommentedMethodSignature.cs | 25 +++++++++++++++++++ .../CodeBuilding/Interface.cs | 10 ++++---- .../CodeBuilding/Method.cs | 2 +- .../CodeBuilding/MethodComments.cs | 7 +++++- .../BuilderGenerator.cs | 5 +++- .../DocumentationComments/Comments.cs | 4 +-- 6 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 src/M31.FluentApi.Generator/CodeBuilding/CommentedMethodSignature.cs diff --git a/src/M31.FluentApi.Generator/CodeBuilding/CommentedMethodSignature.cs b/src/M31.FluentApi.Generator/CodeBuilding/CommentedMethodSignature.cs new file mode 100644 index 0000000..19968c6 --- /dev/null +++ b/src/M31.FluentApi.Generator/CodeBuilding/CommentedMethodSignature.cs @@ -0,0 +1,25 @@ +namespace M31.FluentApi.Generator.CodeBuilding; + +internal class CommentedMethodSignature : ICode +{ + internal MethodSignature MethodSignature { get; } + internal MethodComments MethodComments { get; } + + internal CommentedMethodSignature(MethodSignature methodSignature, MethodComments methodComments) + { + MethodSignature = methodSignature; + MethodComments = methodComments; + } + + internal CommentedMethodSignature TransformSignature(Func transform) + { + return new CommentedMethodSignature(transform(MethodSignature), MethodComments); + } + + public CodeBuilder AppendCode(CodeBuilder codeBuilder) + { + return codeBuilder + .Append(MethodComments) + .Append(MethodSignature); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/CodeBuilding/Interface.cs b/src/M31.FluentApi.Generator/CodeBuilding/Interface.cs index 31296dc..5b916e3 100644 --- a/src/M31.FluentApi.Generator/CodeBuilding/Interface.cs +++ b/src/M31.FluentApi.Generator/CodeBuilding/Interface.cs @@ -4,25 +4,25 @@ namespace M31.FluentApi.Generator.CodeBuilding; internal class Interface : ICode { - private readonly List methodSignatures; + private readonly List methodSignatures; private readonly List baseInterfaces; internal Interface(string accessModifier, string name) { AccessModifier = accessModifier; Name = name; - methodSignatures = new List(); + methodSignatures = new List(); baseInterfaces = new List(); } internal string AccessModifier { get; } internal string Name { get; } - internal IReadOnlyCollection MethodSignatures => methodSignatures; + internal IReadOnlyCollection MethodSignatures => methodSignatures; internal IReadOnlyCollection BaseInterfaces => baseInterfaces; - internal void AddMethodSignature(MethodSignature methodSignature) + internal void AddMethodSignature(CommentedMethodSignature methodSignature) { - if (!methodSignature.IsSignatureForInterface) + if (!methodSignature.MethodSignature.IsSignatureForInterface) { throw new ArgumentException("Expected a stand-alone method signature."); } diff --git a/src/M31.FluentApi.Generator/CodeBuilding/Method.cs b/src/M31.FluentApi.Generator/CodeBuilding/Method.cs index 7278372..b7cbcb1 100644 --- a/src/M31.FluentApi.Generator/CodeBuilding/Method.cs +++ b/src/M31.FluentApi.Generator/CodeBuilding/Method.cs @@ -15,7 +15,7 @@ internal Method(MethodSignature methodSignature) internal void AddCommentLine(string commentLine) { - MethodComments.AddCommentLine(commentLine); + MethodComments.AddLine(commentLine); } internal void AppendBodyLine(string line) diff --git a/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs b/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs index a7a2863..87c49f3 100644 --- a/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs +++ b/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs @@ -9,9 +9,14 @@ internal MethodComments() comments = new List(); } + internal MethodComments(List comments) + { + this.comments = comments; + } + internal IReadOnlyCollection Comments => comments; - internal void AddCommentLine(string commentLine) + internal void AddLine(string commentLine) { comments.Add(commentLine); } diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderGenerator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderGenerator.cs index 0800ff7..566573e 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderGenerator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderGenerator.cs @@ -38,7 +38,10 @@ public void Modify(CodeBoard codeBoard) { Method method = CreateMethod(interfaceMethod, codeBoard); codeBoard.BuilderClass.AddMethod(method); - @interface.AddMethodSignature(method.MethodSignature.ToSignatureForInterface()); + CommentedMethodSignature methodSignature = new CommentedMethodSignature( + method.MethodSignature.ToSignatureForInterface(), + method.MethodComments); + @interface.AddMethodSignature(methodSignature); } @interface.AddBaseInterfaces(builderInterface.BaseInterfaces); diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs index 07c88d6..b7e9bfc 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs @@ -11,9 +11,9 @@ internal Comments(IReadOnlyList comments) public IReadOnlyList List { get; } - internal IReadOnlyCollection GetLines() + internal List GetLines() { - return List.Select(c => c.GetLine()).ToArray(); + return List.Select(c => c.GetLine()).ToList(); } protected bool Equals(Comments other) From e6db2b51f1c897e11b40fc281b2f34a5dbd25bca Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Fri, 18 Jul 2025 08:17:22 +0200 Subject: [PATCH 15/61] feat: inheritdoc --- .../CodeBuilding/Method.cs | 7 ++ .../CodeBuilding/MethodComments.cs | 1 + .../BuilderGenerator.cs | 10 +- .../DocumentationComments/Comments.cs | 1 + .../CommentedMethodClass/CreateStudent.g.cs | 102 ++++++++++++++++++ .../CommentedMethodClass/Student.cs | 29 +++-- .../CodeBuilding/CodeBuildingTests.cs | 4 +- 7 files changed, 140 insertions(+), 14 deletions(-) diff --git a/src/M31.FluentApi.Generator/CodeBuilding/Method.cs b/src/M31.FluentApi.Generator/CodeBuilding/Method.cs index b7cbcb1..4bc2c5f 100644 --- a/src/M31.FluentApi.Generator/CodeBuilding/Method.cs +++ b/src/M31.FluentApi.Generator/CodeBuilding/Method.cs @@ -9,6 +9,13 @@ internal Method(MethodSignature methodSignature) MethodBody = new MethodBody(); } + internal Method(MethodComments methodComments, MethodSignature methodSignature, MethodBody methodBody) + { + MethodComments = methodComments; + MethodSignature = methodSignature; + MethodBody = methodBody; + } + internal MethodComments MethodComments { get; } internal MethodSignature MethodSignature { get; } internal MethodBody MethodBody { get; } diff --git a/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs b/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs index 87c49f3..4bb1f8c 100644 --- a/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs +++ b/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs @@ -15,6 +15,7 @@ internal MethodComments(List comments) } internal IReadOnlyCollection Comments => comments; + internal bool Any => Comments.Count > 0; internal void AddLine(string commentLine) { diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderGenerator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderGenerator.cs index 566573e..1509b18 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderGenerator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderGenerator.cs @@ -37,7 +37,7 @@ public void Modify(CodeBoard codeBoard) foreach (InterfaceBuilderMethod interfaceMethod in builderInterface.Methods) { Method method = CreateMethod(interfaceMethod, codeBoard); - codeBoard.BuilderClass.AddMethod(method); + codeBoard.BuilderClass.AddMethod(CreateMethodWithInheritedDocs(method)); CommentedMethodSignature methodSignature = new CommentedMethodSignature( method.MethodSignature.ToSignatureForInterface(), method.MethodComments); @@ -55,6 +55,14 @@ public void Modify(CodeBoard codeBoard) AddInterfaceDefinitionsToBuilderClass(interfaces, codeBoard.BuilderClass); } + private Method CreateMethodWithInheritedDocs(Method method) + { + return new Method( + new MethodComments(method.MethodComments.Any ? new List() { "/// " } : new List()), + method.MethodSignature, + method.MethodBody); + } + private Method CreateMethod(BuilderStepMethod builderStepMethod, CodeBoard codeBoard) { ReservedVariableNames reservedVariableNames = codeBoard.ReservedVariableNames.NewLocalScope(); diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs index b7e9bfc..53e741c 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs @@ -10,6 +10,7 @@ internal Comments(IReadOnlyList comments) } public IReadOnlyList List { get; } + internal bool Any => List.Count > 0; internal List GetLines() { diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/CreateStudent.g.cs index e69de29..f0b7750 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/CreateStudent.g.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/CreateStudent.g.cs @@ -0,0 +1,102 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +using System; +using System.Reflection; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedMethodClass; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.IWithName, + CreateStudent.IOfAgeBornOn +{ + private readonly Student student; + private static readonly MethodInfo withNameMethodInfo; + private static readonly PropertyInfo agePropertyInfo; + private static readonly MethodInfo bornOnMethodInfo; + + static CreateStudent() + { + withNameMethodInfo = typeof(Student).GetMethod( + "WithName", + 0, + BindingFlags.Instance | BindingFlags.NonPublic, + null, + new Type[] { typeof(string), typeof(string) }, + null)!; + agePropertyInfo = typeof(Student).GetProperty("Age", BindingFlags.Instance | BindingFlags.Public)!; + bornOnMethodInfo = typeof(Student).GetMethod( + "BornOn", + 0, + BindingFlags.Instance | BindingFlags.NonPublic, + null, + new Type[] { typeof(System.DateOnly) }, + null)!; + } + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + /// Sets the student's first and last name. + /// The student's first name. + /// The student's last name. + public static IOfAgeBornOn WithName(string firstName, string lastName) + { + CreateStudent createStudent = new CreateStudent(); + CreateStudent.withNameMethodInfo.Invoke(createStudent.student, new object?[] { firstName, lastName }); + return createStudent; + } + + /// + IOfAgeBornOn IWithName.WithName(string firstName, string lastName) + { + CreateStudent.withNameMethodInfo.Invoke(student, new object?[] { firstName, lastName }); + return this; + } + + Student IOfAgeBornOn.OfAge(int age) + { + CreateStudent.agePropertyInfo.SetValue(student, age); + return student; + } + + /// + Student IOfAgeBornOn.BornOn(System.DateOnly dateOfBirth) + { + CreateStudent.bornOnMethodInfo.Invoke(student, new object?[] { dateOfBirth }); + return student; + } + + public interface ICreateStudent : IWithName + { + } + + public interface IWithName + { + /// Sets the student's first and last name. + /// The student's first name. + /// The student's last name. + IOfAgeBornOn WithName(string firstName, string lastName); + } + + public interface IOfAgeBornOn + { + Student OfAge(int age); + + /// Calculates and sets the student's age based on the provided date of birth. + /// The student's date of birth. + Student BornOn(System.DateOnly dateOfBirth); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/Student.cs index 6d0c08c..bc3cd0d 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/Student.cs @@ -10,19 +10,29 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationC [FluentApi] public class Student { - [FluentMember(0, "WithName")] - public string FirstName { get; set; } + public string FirstName { get; private set; } + public string LastName { get; private set; } - [FluentMember(0, "WithName")] - public string LastName{ get; set; } + /// + /// This summary will not be taken into account. + /// + /// This parameter documentation will not be taken into account. + /// This parameter documentation will not be taken into account. + /// + /// Sets the student's first and last name. + /// + /// The student's first name. + /// The student's last name. + [FluentMethod(0)] + private void WithName(string firstName, string lastName) + { + FirstName = firstName; + LastName = lastName; + } [FluentMember(1, "OfAge")] public int Age { get; private set; } - /// - /// This summary will not be taken into account. - /// - /// This parameter documentation will not be taken into account. /// /// Calculates and sets the student's age based on the provided date of birth. /// @@ -35,7 +45,4 @@ private void BornOn(DateOnly dateOfBirth) if (dateOfBirth > today.AddYears(-age)) age--; Age = age; } - - [FluentMember(2, "InSemester")] - public int Semester { get; set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/Components/CodeBuilding/CodeBuildingTests.cs b/src/M31.FluentApi.Tests/Components/CodeBuilding/CodeBuildingTests.cs index de49f9a..9b95221 100644 --- a/src/M31.FluentApi.Tests/Components/CodeBuilding/CodeBuildingTests.cs +++ b/src/M31.FluentApi.Tests/Components/CodeBuilding/CodeBuildingTests.cs @@ -34,7 +34,7 @@ static Interface CreateIMoveInterface() MethodSignature moveSignature = MethodSignature.Create("void", "Move", null, true); moveSignature.AddParameter("double", "deltaX"); moveSignature.AddParameter("double", "deltaY"); - @interface.AddMethodSignature(moveSignature); + @interface.AddMethodSignature(new CommentedMethodSignature(moveSignature, new MethodComments())); return @interface; } @@ -45,7 +45,7 @@ static Class CreateRobotClass(Interface moveInterface) robot.AddInterface(moveInterface.Name); Method constructor = CreateConstructor(); robot.AddMethod(constructor); - Method moveMethod = CreateMoveMethod(moveInterface.MethodSignatures.First()); + Method moveMethod = CreateMoveMethod(moveInterface.MethodSignatures.First().MethodSignature); robot.AddMethod(moveMethod); Method assignTaskMethod = CreateAssignTaskMethod(); robot.AddMethod(assignTaskMethod); From 2da15caf19b8b612ad080a949b2965cd7e81fa95 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sat, 19 Jul 2025 07:55:28 +0200 Subject: [PATCH 16/61] test: CommentedPropertiesClass --- .../Commons/BuilderMethodFactory.cs | 7 +- .../CommentsGenerator.cs | 9 +- .../MethodCreation/DefaultMethod.cs | 4 +- .../MethodCreation/MethodCreator.cs | 4 +- .../CreateStudent.expected.txt | 0 .../CreateStudent.expected.txt | 102 ++++++++++++++ .../CreateStudent.g.cs | 2 +- .../Student.cs | 2 +- .../CreateStudent.expected.txt | 124 ++++++++++++++++++ .../CreateStudent.g.cs | 124 ++++++++++++++++++ .../CommentedPropertiesClass/Student.cs | 53 ++++++++ 11 files changed, 421 insertions(+), 10 deletions(-) delete mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/CreateStudent.expected.txt create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/CreateStudent.expected.txt rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/{CommentedMethodClass => CommentedMethodsClass}/CreateStudent.g.cs (98%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/{CommentedMethodClass => CommentedMethodsClass}/Student.cs (97%) create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/CreateStudent.expected.txt create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/CreateStudent.g.cs create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/Student.cs diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs index 0bce5f0..556c9ae 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs @@ -15,9 +15,9 @@ internal BuilderMethodFactory(InnerBodyCreationDelegates innerBodyCreationDelega this.transformedComments = transformedComments; } - internal BuilderMethod CreateBuilderMethod(string methodName) + internal BuilderMethod CreateEmptyBuilderMethod(MemberSymbolInfo memberInfo, string methodName) { - Comments comments = FluentCommentsParser.Parse(null); // todo + Comments comments = transformedComments.GetMemberComments(memberInfo.Name); return new BuilderMethod(methodName, null, new List(), null, (_, _, _) => new List(), comments); } @@ -39,7 +39,7 @@ List BuildBodyCode( }; } - Comments comments = FluentCommentsParser.Parse(null); // todo + Comments comments = transformedComments.GetMemberComments(computeValue.TargetMember); return new BuilderMethod(methodName, null, parameters, null, BuildBodyCode, comments); } @@ -59,6 +59,7 @@ List BuildBodyCode( } Comments comments = FluentCommentsParser.Parse(null); // todo + // Comments comments = transformedComments.GetMemberComments(computeValue.TargetMember); return new BuilderMethod(methodName, null, parameters, null, BuildBodyCode, comments); } diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs index 19fc375..e491c94 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs @@ -6,11 +6,15 @@ namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.DocumentationGe internal class CommentsGenerator : ICodeBoardActor { - // todo: cancellation public void Modify(CodeBoard codeBoard) { foreach (FluentApiInfo fluentApiInfo in codeBoard.FluentApiInfos) { + if (codeBoard.CancellationRequested) + { + return; + } + switch (fluentApiInfo.SymbolInfo) { case MemberSymbolInfo memberInfo: @@ -29,7 +33,8 @@ public void Modify(CodeBoard codeBoard) private void HandleMemberSymbolInfo(MemberSymbolInfo memberInfo, CodeBoard codeBoard) { - return; // todo + Comments transformedComments = CommentsTransformer.TransformComments(memberInfo.Comments); + codeBoard.TransformedComments.AssignMemberComments(memberInfo.Name, transformedComments); } private void HandleMethodSymbolInfo(MethodSymbolInfo methodInfo, CodeBoard codeBoard) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/DefaultMethod.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/DefaultMethod.cs index c7a8093..61151a3 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/DefaultMethod.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/DefaultMethod.cs @@ -16,6 +16,8 @@ internal DefaultMethod(MemberSymbolInfo symbolInfo, FluentDefaultAttributeInfo d public BuilderMethods CreateBuilderMethods(MethodCreator methodCreator) { - return new BuilderMethods(methodCreator.CreateMethodThatDoesNothing(DefaultAttributeInfo.Method)); + return new BuilderMethods(methodCreator.CreateMethodThatDoesNothing( + SymbolInfo, + DefaultAttributeInfo.Method)); } } \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/MethodCreator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/MethodCreator.cs index fb68027..d6b1406 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/MethodCreator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/MethodCreator.cs @@ -45,10 +45,10 @@ internal BuilderMethod CreateMethodWithComputedValue( .CreateBuilderMethod(methodName, ComputeValueCode.Create(symbolInfo.Name, parameter, buildCodeWithParameter)); } - internal BuilderMethod CreateMethodThatDoesNothing(string methodName) + internal BuilderMethod CreateMethodThatDoesNothing(MemberSymbolInfo memberSymbolInfo, string methodName) { return BuilderMethodFactory - .CreateBuilderMethod(methodName); + .CreateEmptyBuilderMethod(memberSymbolInfo, methodName); } private Parameter GetStandardParameter(MemberSymbolInfo symbolInfo, string? defaultValue = null) diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/CreateStudent.expected.txt deleted file mode 100644 index e69de29..0000000 diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/CreateStudent.expected.txt new file mode 100644 index 0000000..c624978 --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/CreateStudent.expected.txt @@ -0,0 +1,102 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +using System; +using System.Reflection; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedMethodsClass; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.IWithName, + CreateStudent.IOfAgeBornOn +{ + private readonly Student student; + private static readonly MethodInfo withNameMethodInfo; + private static readonly PropertyInfo agePropertyInfo; + private static readonly MethodInfo bornOnMethodInfo; + + static CreateStudent() + { + withNameMethodInfo = typeof(Student).GetMethod( + "WithName", + 0, + BindingFlags.Instance | BindingFlags.NonPublic, + null, + new Type[] { typeof(string), typeof(string) }, + null)!; + agePropertyInfo = typeof(Student).GetProperty("Age", BindingFlags.Instance | BindingFlags.Public)!; + bornOnMethodInfo = typeof(Student).GetMethod( + "BornOn", + 0, + BindingFlags.Instance | BindingFlags.NonPublic, + null, + new Type[] { typeof(System.DateOnly) }, + null)!; + } + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + /// Sets the student's first and last name. + /// The student's first name. + /// The student's last name. + public static IOfAgeBornOn WithName(string firstName, string lastName) + { + CreateStudent createStudent = new CreateStudent(); + CreateStudent.withNameMethodInfo.Invoke(createStudent.student, new object?[] { firstName, lastName }); + return createStudent; + } + + /// + IOfAgeBornOn IWithName.WithName(string firstName, string lastName) + { + CreateStudent.withNameMethodInfo.Invoke(student, new object?[] { firstName, lastName }); + return this; + } + + Student IOfAgeBornOn.OfAge(int age) + { + CreateStudent.agePropertyInfo.SetValue(student, age); + return student; + } + + /// + Student IOfAgeBornOn.BornOn(System.DateOnly dateOfBirth) + { + CreateStudent.bornOnMethodInfo.Invoke(student, new object?[] { dateOfBirth }); + return student; + } + + public interface ICreateStudent : IWithName + { + } + + public interface IWithName + { + /// Sets the student's first and last name. + /// The student's first name. + /// The student's last name. + IOfAgeBornOn WithName(string firstName, string lastName); + } + + public interface IOfAgeBornOn + { + Student OfAge(int age); + + /// Calculates and sets the student's age based on the provided date of birth. + /// The student's date of birth. + Student BornOn(System.DateOnly dateOfBirth); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/CreateStudent.g.cs similarity index 98% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/CreateStudent.g.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/CreateStudent.g.cs index f0b7750..c624978 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/CreateStudent.g.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/CreateStudent.g.cs @@ -8,7 +8,7 @@ using System; using System.Reflection; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedMethodClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedMethodsClass; public class CreateStudent : CreateStudent.ICreateStudent, diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/Student.cs similarity index 97% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/Student.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/Student.cs index bc3cd0d..458e4ec 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/Student.cs @@ -5,7 +5,7 @@ using System; using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedMethodClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedMethodsClass; [FluentApi] public class Student diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/CreateStudent.expected.txt new file mode 100644 index 0000000..c64238d --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/CreateStudent.expected.txt @@ -0,0 +1,124 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedPropertiesClass; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.IWithGivenNameWithFirstName, + CreateStudent.IWithLastName, + CreateStudent.IOfAge, + CreateStudent.IInSemester, + CreateStudent.ILivingIn +{ + private readonly Student student; + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + public static IWithLastName WithGivenName(string givenName) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.GivenName = givenName; + return createStudent; + } + + /// Sets the student's first name. + /// The student's first name. + public static IWithLastName WithFirstName(string firstName) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.FirstName = firstName; + return createStudent; + } + + IWithLastName IWithGivenNameWithFirstName.WithGivenName(string givenName) + { + student.GivenName = givenName; + return this; + } + + /// + IWithLastName IWithGivenNameWithFirstName.WithFirstName(string firstName) + { + student.FirstName = firstName; + return this; + } + + /// + IOfAge IWithLastName.WithLastName(string lastName) + { + student.LastName = lastName; + return this; + } + + /// + IInSemester IOfAge.OfAge(int age) + { + student.Age = age; + return this; + } + + /// + ILivingIn IInSemester.InSemester(int semester) + { + student.Semester = semester; + return this; + } + + Student ILivingIn.LivingIn(string city) + { + student.City = city; + return student; + } + + public interface ICreateStudent : IWithGivenNameWithFirstName + { + } + + public interface IWithGivenNameWithFirstName + { + IWithLastName WithGivenName(string givenName); + + /// Sets the student's first name. + /// The student's first name. + IWithLastName WithFirstName(string firstName); + } + + public interface IWithLastName + { + /// Sets the student's last name. + /// The student's last name. + IOfAge WithLastName(string lastName); + } + + public interface IOfAge + { + /// Sets the student's age. + /// The student's age. + IInSemester OfAge(int age); + } + + public interface IInSemester + { + /// Sets the student's semester. + /// The student's semester. + ILivingIn InSemester(int semester); + } + + public interface ILivingIn + { + Student LivingIn(string city); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/CreateStudent.g.cs new file mode 100644 index 0000000..c64238d --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/CreateStudent.g.cs @@ -0,0 +1,124 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedPropertiesClass; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.IWithGivenNameWithFirstName, + CreateStudent.IWithLastName, + CreateStudent.IOfAge, + CreateStudent.IInSemester, + CreateStudent.ILivingIn +{ + private readonly Student student; + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + public static IWithLastName WithGivenName(string givenName) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.GivenName = givenName; + return createStudent; + } + + /// Sets the student's first name. + /// The student's first name. + public static IWithLastName WithFirstName(string firstName) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.FirstName = firstName; + return createStudent; + } + + IWithLastName IWithGivenNameWithFirstName.WithGivenName(string givenName) + { + student.GivenName = givenName; + return this; + } + + /// + IWithLastName IWithGivenNameWithFirstName.WithFirstName(string firstName) + { + student.FirstName = firstName; + return this; + } + + /// + IOfAge IWithLastName.WithLastName(string lastName) + { + student.LastName = lastName; + return this; + } + + /// + IInSemester IOfAge.OfAge(int age) + { + student.Age = age; + return this; + } + + /// + ILivingIn IInSemester.InSemester(int semester) + { + student.Semester = semester; + return this; + } + + Student ILivingIn.LivingIn(string city) + { + student.City = city; + return student; + } + + public interface ICreateStudent : IWithGivenNameWithFirstName + { + } + + public interface IWithGivenNameWithFirstName + { + IWithLastName WithGivenName(string givenName); + + /// Sets the student's first name. + /// The student's first name. + IWithLastName WithFirstName(string firstName); + } + + public interface IWithLastName + { + /// Sets the student's last name. + /// The student's last name. + IOfAge WithLastName(string lastName); + } + + public interface IOfAge + { + /// Sets the student's age. + /// The student's age. + IInSemester OfAge(int age); + } + + public interface IInSemester + { + /// Sets the student's semester. + /// The student's semester. + ILivingIn InSemester(int semester); + } + + public interface ILivingIn + { + Student LivingIn(string city); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/Student.cs new file mode 100644 index 0000000..6f55bbc --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/Student.cs @@ -0,0 +1,53 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedPropertiesClass; + +[FluentApi] +public class Student +{ + /// + /// This summary will not be taken into account. + /// + /// This parameter will not be taken into account. + [FluentMember(0)] + public string GivenName { get; set; } + + /// + /// Sets the student's first name. + /// + /// The student's first name. + [FluentMember(0)] + public string FirstName { get; set; } + + /// + /// Sets the student's last name. + /// + /// The student's last name. + [FluentMember(1)] + public string LastName { get; set; } + + /// + /// Sets the student's age. + /// + /// The student's age. + [FluentMember(2, "OfAge")] + public int Age { get; set; } + + /// + /// Sets the student's semester. + /// + /// The student's semester. + [FluentMember(3, "InSemester")] + public int Semester { get; set; } + + /// + /// This summary will not be taken into account. + /// + /// This parameter will not be taken into account. + [FluentMember(4, "LivingIn")] + public string City { get; set; } +} \ No newline at end of file From a428cc28f24ef01c6f66e4ed5e98d5d7d3a42ea4 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sun, 20 Jul 2025 05:49:36 +0200 Subject: [PATCH 17/61] test: CommentedDefaultNullablyPropertyClass (wip) --- .../Commons/BuilderMethodFactory.cs | 6 +- .../CommentsGenerator.cs | 22 ++- .../DocumentationComments/MemberCommentKey.cs | 18 +++ .../TransformedComments.cs | 21 ++- .../CreateStudent.expected.txt | 0 .../CreateStudent.g.cs | 137 ++++++++++++++++++ .../Student.cs | 33 +++++ 7 files changed, 225 insertions(+), 12 deletions(-) create mode 100644 src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/MemberCommentKey.cs create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/CreateStudent.expected.txt create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/CreateStudent.g.cs create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/Student.cs diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs index 556c9ae..e349322 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs @@ -17,7 +17,8 @@ internal BuilderMethodFactory(InnerBodyCreationDelegates innerBodyCreationDelega internal BuilderMethod CreateEmptyBuilderMethod(MemberSymbolInfo memberInfo, string methodName) { - Comments comments = transformedComments.GetMemberComments(memberInfo.Name); + MemberCommentKey key = new MemberCommentKey(memberInfo.Name, methodName); + Comments comments = transformedComments.GetMemberComments(key); return new BuilderMethod(methodName, null, new List(), null, (_, _, _) => new List(), comments); } @@ -39,7 +40,8 @@ List BuildBodyCode( }; } - Comments comments = transformedComments.GetMemberComments(computeValue.TargetMember); + MemberCommentKey key = new MemberCommentKey(computeValue.TargetMember, methodName); + Comments comments = transformedComments.GetMemberComments(key); return new BuilderMethod(methodName, null, parameters, null, BuildBodyCode, comments); } diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs index e491c94..694ee23 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs @@ -33,8 +33,26 @@ public void Modify(CodeBoard codeBoard) private void HandleMemberSymbolInfo(MemberSymbolInfo memberInfo, CodeBoard codeBoard) { - Comments transformedComments = CommentsTransformer.TransformComments(memberInfo.Comments); - codeBoard.TransformedComments.AssignMemberComments(memberInfo.Name, transformedComments); + IGrouping[] groups = GroupByMethodName(memberInfo.Comments); + + foreach (var group in groups) + { + MemberCommentKey key = new MemberCommentKey(memberInfo.Name, group.Key); + Comments comments = new Comments(group.ToArray()); + Comments transformedComments = CommentsTransformer.TransformComments(comments); + codeBoard.TransformedComments.AssignMemberComments(key, transformedComments); + } + } + + private IGrouping[] GroupByMethodName(Comments transformedComments) + { + List<(string, Comments)> methodComments = new List<(string, Comments)>(); + return transformedComments.List.GroupBy(GetMethodName).ToArray(); + + static string GetMethodName(Comment comment) + { + return comment.Attributes.FirstOrDefault(a => a.Key == "method")?.Value ?? string.Empty; + } } private void HandleMethodSymbolInfo(MethodSymbolInfo methodInfo, CodeBoard codeBoard) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/MemberCommentKey.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/MemberCommentKey.cs new file mode 100644 index 0000000..d184441 --- /dev/null +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/MemberCommentKey.cs @@ -0,0 +1,18 @@ +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; + +internal record MemberCommentKey +{ + internal MemberCommentKey(string memberName, string method) + { + MemberName = memberName; + Method = method; + } + + internal string MemberName { get; } + internal string Method { get; } + + public override string ToString() + { + return $"{MemberName}-{Method}"; + } +} diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs index a3b9f91..ae30d14 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs @@ -4,29 +4,34 @@ namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.Documentation internal class TransformedComments { - private readonly Dictionary memberComments; + private readonly Dictionary memberComments; private readonly Dictionary methodComments; internal TransformedComments() { - memberComments = new Dictionary(); + memberComments = new Dictionary(); methodComments = new Dictionary(); } - internal void AssignMemberComments(string memberName, Comments comments) + internal void AssignMemberComments(MemberCommentKey memberCommentKey, Comments comments) { - if (memberComments.ContainsKey(memberName)) + if (memberComments.ContainsKey(memberCommentKey)) { throw new InvalidOperationException( - $"{nameof(Comments)} for member {memberName} has already been assigned."); + $"{nameof(Comments)} for key {memberCommentKey} has already been assigned."); } - memberComments[memberName] = comments; + memberComments[memberCommentKey] = comments; } - internal Comments GetMemberComments(string memberName) + internal Comments GetMemberComments(MemberCommentKey memberCommentKey) { - return memberComments[memberName]; + if (memberComments.TryGetValue(memberCommentKey, out Comments comments)) + { + return comments; + } + + return new Comments(Array.Empty()); } internal void AssignMethodComments(MethodSymbolInfo methodSymbolInfo, Comments comments) diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/CreateStudent.expected.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/CreateStudent.g.cs new file mode 100644 index 0000000..8132057 --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/CreateStudent.g.cs @@ -0,0 +1,137 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +using System.Reflection; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedDefaultNullablePropertyClass; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.IWithNameLivingIn, + CreateStudent.IWhoIsHappy +{ + private readonly Student student; + private static readonly PropertyInfo isHappyPropertyInfo; + + static CreateStudent() + { + isHappyPropertyInfo = typeof(Student).GetProperty("IsHappy", BindingFlags.Instance | BindingFlags.Public)!; + } + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + public static IWhoIsHappy WithName(string name) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.Name = name; + return createStudent; + } + + /// Sets the student's city. + /// The student's city. + public static IWhoIsHappy LivingIn(string? city) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.City = city; + return createStudent; + } + + /// Set's the student's city to Boston. + public static IWhoIsHappy LivingInBoston() + { + CreateStudent createStudent = new CreateStudent(); + return createStudent; + } + + /// Set's the student's city to null. + public static IWhoIsHappy InUnknownCity() + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.City = null; + return createStudent; + } + + IWhoIsHappy IWithNameLivingIn.WithName(string name) + { + student.Name = name; + return this; + } + + /// + IWhoIsHappy IWithNameLivingIn.LivingIn(string? city) + { + student.City = city; + return this; + } + + /// + IWhoIsHappy IWithNameLivingIn.LivingInBoston() + { + return this; + } + + /// + IWhoIsHappy IWithNameLivingIn.InUnknownCity() + { + student.City = null; + return this; + } + + Student IWhoIsHappy.WhoIsHappy(bool? isHappy) + { + CreateStudent.isHappyPropertyInfo.SetValue(student, isHappy); + return student; + } + + Student IWhoIsHappy.WhoIsSad() + { + CreateStudent.isHappyPropertyInfo.SetValue(student, false); + return student; + } + + Student IWhoIsHappy.WithUnknownMood() + { + CreateStudent.isHappyPropertyInfo.SetValue(student, null); + return student; + } + + public interface ICreateStudent : IWithNameLivingIn + { + } + + public interface IWithNameLivingIn + { + IWhoIsHappy WithName(string name); + + /// Sets the student's city. + /// The student's city. + IWhoIsHappy LivingIn(string? city); + + /// Set's the student's city to Boston. + IWhoIsHappy LivingInBoston(); + + /// Set's the student's city to null. + IWhoIsHappy InUnknownCity(); + } + + public interface IWhoIsHappy + { + Student WhoIsHappy(bool? isHappy = true); + + Student WhoIsSad(); + + Student WithUnknownMood(); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/Student.cs new file mode 100644 index 0000000..9206fbc --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/Student.cs @@ -0,0 +1,33 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedDefaultNullablePropertyClass; + +[FluentApi] +public class Student +{ + [FluentMember(0)] + public string Name { get; set; } + + /// + /// Sets the student's city. + /// + /// The student's city. + /// + /// Set's the student's city to Boston. + /// + /// + /// Set's the student's city to null. + /// + [FluentMember(0, "LivingIn")] + [FluentDefault("LivingInBoston")] + [FluentNullable("InUnknownCity")] + public string? City { get; set; } = "Boston"; + + [FluentPredicate(1, "WhoIsHappy", "WhoIsSad")] + [FluentNullable("WithUnknownMood")] + public bool? IsHappy { get; private set; } +} \ No newline at end of file From 96aa75c43ddddb1db6e1ede8761e329c8b5eec93 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Tue, 22 Jul 2025 11:10:21 +0200 Subject: [PATCH 18/61] test: CommentedPropertiesClassAdvanced (wip) --- .../CreateStudent.expected.txt | 0 .../CreateStudent.g.cs | 56 ++++++++----------- .../Student.cs | 13 ++++- .../CodeGeneration/TestDataProvider.cs | 6 +- 4 files changed, 38 insertions(+), 37 deletions(-) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/{CommentedDefaultNullablePropertyClass => CommentedPropertiesClassAdvanced}/CreateStudent.expected.txt (100%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/{CommentedDefaultNullablePropertyClass => CommentedPropertiesClassAdvanced}/CreateStudent.g.cs (65%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/{CommentedDefaultNullablePropertyClass => CommentedPropertiesClassAdvanced}/Student.cs (72%) diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.expected.txt similarity index 100% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/CreateStudent.expected.txt rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.expected.txt diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.g.cs similarity index 65% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/CreateStudent.g.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.g.cs index 8132057..dbf56e2 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/CreateStudent.g.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.g.cs @@ -7,11 +7,13 @@ using System.Reflection; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedDefaultNullablePropertyClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedPropertiesClassAdvanced; public class CreateStudent : CreateStudent.ICreateStudent, - CreateStudent.IWithNameLivingIn, + CreateStudent.IWithName, + CreateStudent.IWithAge, + CreateStudent.ILivingIn, CreateStudent.IWhoIsHappy { private readonly Student student; @@ -32,58 +34,40 @@ public static ICreateStudent InitialStep() return new CreateStudent(); } - public static IWhoIsHappy WithName(string name) + public static IWithAge WithName(string name) { CreateStudent createStudent = new CreateStudent(); createStudent.student.Name = name; return createStudent; } - /// Sets the student's city. - /// The student's city. - public static IWhoIsHappy LivingIn(string? city) + IWithAge IWithName.WithName(string name) { - CreateStudent createStudent = new CreateStudent(); - createStudent.student.City = city; - return createStudent; - } - - /// Set's the student's city to Boston. - public static IWhoIsHappy LivingInBoston() - { - CreateStudent createStudent = new CreateStudent(); - return createStudent; - } - - /// Set's the student's city to null. - public static IWhoIsHappy InUnknownCity() - { - CreateStudent createStudent = new CreateStudent(); - createStudent.student.City = null; - return createStudent; + student.Name = name; + return this; } - IWhoIsHappy IWithNameLivingIn.WithName(string name) + ILivingIn IWithAge.WithAge(int age) { - student.Name = name; + student.Age = age; return this; } /// - IWhoIsHappy IWithNameLivingIn.LivingIn(string? city) + IWhoIsHappy ILivingIn.LivingIn(string? city) { student.City = city; return this; } /// - IWhoIsHappy IWithNameLivingIn.LivingInBoston() + IWhoIsHappy ILivingIn.LivingInBoston() { return this; } /// - IWhoIsHappy IWithNameLivingIn.InUnknownCity() + IWhoIsHappy ILivingIn.InUnknownCity() { student.City = null; return this; @@ -107,14 +91,22 @@ Student IWhoIsHappy.WithUnknownMood() return student; } - public interface ICreateStudent : IWithNameLivingIn + public interface ICreateStudent : IWithName { } - public interface IWithNameLivingIn + public interface IWithName { - IWhoIsHappy WithName(string name); + IWithAge WithName(string name); + } + public interface IWithAge + { + ILivingIn WithAge(int age); + } + + public interface ILivingIn + { /// Sets the student's city. /// The student's city. IWhoIsHappy LivingIn(string? city); diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/Student.cs similarity index 72% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/Student.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/Student.cs index 9206fbc..3c5b0c6 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedDefaultNullablePropertyClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/Student.cs @@ -4,7 +4,7 @@ using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedDefaultNullablePropertyClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedPropertiesClassAdvanced; [FluentApi] public class Student @@ -12,6 +12,13 @@ public class Student [FluentMember(0)] public string Name { get; set; } + /// + /// Sets the students's age. + /// + /// The student's age. + [FluentMember(1)] + public int Age { get; set; } + /// /// Sets the student's city. /// @@ -22,12 +29,12 @@ public class Student /// /// Set's the student's city to null. /// - [FluentMember(0, "LivingIn")] + [FluentMember(2, "LivingIn")] [FluentDefault("LivingInBoston")] [FluentNullable("InUnknownCity")] public string? City { get; set; } = "Boston"; - [FluentPredicate(1, "WhoIsHappy", "WhoIsSad")] + [FluentPredicate(3, "WhoIsHappy", "WhoIsSad")] [FluentNullable("WithUnknownMood")] public bool? IsHappy { get; private set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs index 60550f9..97f78f3 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs @@ -7,7 +7,7 @@ namespace M31.FluentApi.Tests.CodeGeneration; internal class TestDataProvider : IEnumerable { private readonly List testClasses = - Filter(new string[] { "CommentedMethodClass" }, + Filter(new string[] { "CommentedPropertiesClassAdvanced" }, new List { new object[] { "Abstract", "AliasNamespaceClass", "Student" }, @@ -21,7 +21,9 @@ internal class TestDataProvider : IEnumerable new object[] { "Abstract", "ContinueWithSelfClass", "Student" }, new object[] { "Abstract", "CustomFluentMethodNameClass", "Student" }, new object[] { "Abstract", "DefaultFluentMethodNameClass", "Student" }, - new object[] { "Abstract", "DocumentationComments", "CommentedMethodClass", "Student" }, + new object[] { "Abstract", "DocumentationComments", "CommentedMethodsClass", "Student" }, + new object[] { "Abstract", "DocumentationComments", "CommentedPropertiesClass", "Student" }, + new object[] { "Abstract", "DocumentationComments", "CommentedPropertiesClassAdvanced", "Student" }, new object[] { "Abstract", "EmptyClass", "Student" }, new object[] { "Abstract", "FluentDefaultMemberClass", "Student" }, new object[] { "Abstract", "FluentLambdaClass", "Student|Address" }, From 43ec06de2d2aa304b36a1b8f0aa25123533379a4 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Tue, 22 Jul 2025 14:33:18 +0200 Subject: [PATCH 19/61] test: CommentedPropertiesClassAdvanced --- .../CommentsGenerator.cs | 22 ++- .../AttributeInfo/AttributeInfoBase.cs | 3 +- .../FluentCollectionAttributeInfo.cs | 2 + .../FluentDefaultAttributeInfo.cs | 5 +- .../FluentLambdaAttributeInfo.cs | 1 + .../FluentMemberAttributeInfo.cs | 1 + .../FluentMethodAttributeInfo.cs | 1 + .../FluentNullableAttributeInfo.cs | 6 +- .../FluentPredicateAttributeInfo.cs | 1 + .../OrthogonalAttributeInfoBase.cs | 2 +- .../CreateStudent.expected.txt | 132 ++++++++++++++++++ .../CreateStudent.g.cs | 17 ++- 12 files changed, 168 insertions(+), 25 deletions(-) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs index 694ee23..980859e 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs @@ -18,7 +18,7 @@ public void Modify(CodeBoard codeBoard) switch (fluentApiInfo.SymbolInfo) { case MemberSymbolInfo memberInfo: - HandleMemberSymbolInfo(memberInfo, codeBoard); + HandleMemberSymbolInfo(memberInfo, fluentApiInfo, codeBoard); break; case MethodSymbolInfo methodSymbolInfo: @@ -31,9 +31,10 @@ public void Modify(CodeBoard codeBoard) } } - private void HandleMemberSymbolInfo(MemberSymbolInfo memberInfo, CodeBoard codeBoard) + private void HandleMemberSymbolInfo(MemberSymbolInfo memberInfo, FluentApiInfo fluentApiInfo, CodeBoard codeBoard) { - IGrouping[] groups = GroupByMethodName(memberInfo.Comments); + string? singleMethodName = TryGetSingleMethodName(fluentApiInfo); + IGrouping[] groups = GroupByMethodName(memberInfo.Comments, singleMethodName); foreach (var group in groups) { @@ -44,14 +45,21 @@ private void HandleMemberSymbolInfo(MemberSymbolInfo memberInfo, CodeBoard codeB } } - private IGrouping[] GroupByMethodName(Comments transformedComments) + private string? TryGetSingleMethodName(FluentApiInfo fluentApiInfo) + { + string[] fluentMethodNames = fluentApiInfo.AttributeInfo.FluentMethodNames + .Concat(fluentApiInfo.OrthogonalAttributeInfos.SelectMany(o => o.FluentMethodNames)).ToArray(); + return fluentMethodNames.Length != 1 ? null : fluentMethodNames[0]; + } + + private IGrouping[] GroupByMethodName(Comments transformedComments, string? fallbackMethodName) { List<(string, Comments)> methodComments = new List<(string, Comments)>(); - return transformedComments.List.GroupBy(GetMethodName).ToArray(); + return transformedComments.List.GroupBy(GetMethodName).Where(g => g.Key != string.Empty).ToArray(); - static string GetMethodName(Comment comment) + string GetMethodName(Comment comment) { - return comment.Attributes.FirstOrDefault(a => a.Key == "method")?.Value ?? string.Empty; + return comment.Attributes.FirstOrDefault(a => a.Key == "method")?.Value ?? fallbackMethodName ?? string.Empty; } } diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/AttributeInfoBase.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/AttributeInfoBase.cs index 8b31708..08b5c3f 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/AttributeInfoBase.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/AttributeInfoBase.cs @@ -8,5 +8,6 @@ protected AttributeInfoBase(int builderStep) } internal int BuilderStep { get; } - internal abstract string FluentMethodName { get; } + internal abstract string FluentMethodName { get; } // todo: rename or remove + internal abstract IReadOnlyCollection FluentMethodNames { get; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentCollectionAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentCollectionAttributeInfo.cs index 85816ad..91671ae 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentCollectionAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentCollectionAttributeInfo.cs @@ -30,6 +30,8 @@ private FluentCollectionAttributeInfo( internal string? WithZeroItems { get; } internal LambdaBuilderInfo? LambdaBuilderInfo { get; } internal override string FluentMethodName => WithItems; + internal override IReadOnlyCollection FluentMethodNames => + new string?[] { WithItems, WithItem, WithZeroItems }.OfType().ToArray(); internal static FluentCollectionAttributeInfo Create( AttributeData attributeData, diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentDefaultAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentDefaultAttributeInfo.cs index 2ffa8dc..b967d86 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentDefaultAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentDefaultAttributeInfo.cs @@ -12,10 +12,7 @@ private FluentDefaultAttributeInfo(string method) internal string Method { get; } - internal override IReadOnlyCollection MethodNames() - { - return new string[] { Method }; - } + internal override IReadOnlyCollection FluentMethodNames => new string[] { Method }; internal static FluentDefaultAttributeInfo Create(AttributeData attributeData, string memberName) { diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentLambdaAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentLambdaAttributeInfo.cs index 03398d6..0daa1da 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentLambdaAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentLambdaAttributeInfo.cs @@ -15,6 +15,7 @@ private FluentLambdaAttributeInfo(int builderStep, string method, LambdaBuilderI internal string Method { get; } internal LambdaBuilderInfo BuilderInfo { get; } internal override string FluentMethodName => Method; + internal override IReadOnlyCollection FluentMethodNames => new string[] { Method }; internal static FluentLambdaAttributeInfo Create( AttributeData attributeData, diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs index ac8bf61..bebfb6d 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs @@ -20,6 +20,7 @@ private FluentMemberAttributeInfo( internal string Method { get; } internal int ParameterPosition { get; } internal override string FluentMethodName => Method; + internal override IReadOnlyCollection FluentMethodNames => new string[] { Method }; internal LambdaBuilderInfo? LambdaBuilderInfo { get; } internal static FluentMemberAttributeInfo Create( diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMethodAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMethodAttributeInfo.cs index 0e6d561..c17eaf3 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMethodAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMethodAttributeInfo.cs @@ -13,6 +13,7 @@ private FluentMethodAttributeInfo(int builderStep, string method) internal string Method { get; } internal override string FluentMethodName => Method; + internal override IReadOnlyCollection FluentMethodNames => new string[] { Method }; internal static FluentMethodAttributeInfo Create(AttributeData attributeData, string memberName) { diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentNullableAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentNullableAttributeInfo.cs index 1c6d1eb..8955dac 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentNullableAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentNullableAttributeInfo.cs @@ -11,11 +11,7 @@ private FluentNullableAttributeInfo(string method) } internal string Method { get; } - - internal override IReadOnlyCollection MethodNames() - { - return new string[] { Method }; - } + internal override IReadOnlyCollection FluentMethodNames => new string[] { Method }; internal static FluentNullableAttributeInfo Create(AttributeData attributeData, string memberName) { diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentPredicateAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentPredicateAttributeInfo.cs index f84a0cd..beaee00 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentPredicateAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentPredicateAttributeInfo.cs @@ -15,6 +15,7 @@ private FluentPredicateAttributeInfo(int builderStep, string method, string nega internal string Method { get; } internal string NegatedMethod { get; } internal override string FluentMethodName => Method; + internal override IReadOnlyCollection FluentMethodNames => new string[] { Method, NegatedMethod }; internal static FluentPredicateAttributeInfo Create(AttributeData attributeData, string memberName) { diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/OrthogonalAttributeInfoBase.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/OrthogonalAttributeInfoBase.cs index 91eaf2e..784250c 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/OrthogonalAttributeInfoBase.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/OrthogonalAttributeInfoBase.cs @@ -2,5 +2,5 @@ namespace M31.FluentApi.Generator.SourceGenerators.AttributeInfo; internal abstract record OrthogonalAttributeInfoBase { - internal abstract IReadOnlyCollection MethodNames(); + internal abstract IReadOnlyCollection FluentMethodNames { get; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.expected.txt index e69de29..f60870f 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.expected.txt +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.expected.txt @@ -0,0 +1,132 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +using System.Reflection; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedPropertiesClassAdvanced; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.IWithName, + CreateStudent.IOfAge, + CreateStudent.ILivingIn, + CreateStudent.IWhoIsHappy +{ + private readonly Student student; + private static readonly PropertyInfo isHappyPropertyInfo; + + static CreateStudent() + { + isHappyPropertyInfo = typeof(Student).GetProperty("IsHappy", BindingFlags.Instance | BindingFlags.Public)!; + } + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + public static IOfAge WithName(string name) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.Name = name; + return createStudent; + } + + IOfAge IWithName.WithName(string name) + { + student.Name = name; + return this; + } + + /// + ILivingIn IOfAge.OfAge(int age) + { + student.Age = age; + return this; + } + + /// + IWhoIsHappy ILivingIn.LivingIn(string? city) + { + student.City = city; + return this; + } + + /// + IWhoIsHappy ILivingIn.LivingInBoston() + { + return this; + } + + /// + IWhoIsHappy ILivingIn.InUnknownCity() + { + student.City = null; + return this; + } + + Student IWhoIsHappy.WhoIsHappy(bool? isHappy) + { + CreateStudent.isHappyPropertyInfo.SetValue(student, isHappy); + return student; + } + + Student IWhoIsHappy.WhoIsSad() + { + CreateStudent.isHappyPropertyInfo.SetValue(student, false); + return student; + } + + Student IWhoIsHappy.WithUnknownMood() + { + CreateStudent.isHappyPropertyInfo.SetValue(student, null); + return student; + } + + public interface ICreateStudent : IWithName + { + } + + public interface IWithName + { + IOfAge WithName(string name); + } + + public interface IOfAge + { + /// Sets the students's age. + /// The student's age. + ILivingIn OfAge(int age); + } + + public interface ILivingIn + { + /// Sets the student's city. + /// The student's city. + IWhoIsHappy LivingIn(string? city); + + /// Set's the student's city to Boston. + IWhoIsHappy LivingInBoston(); + + /// Set's the student's city to null. + IWhoIsHappy InUnknownCity(); + } + + public interface IWhoIsHappy + { + Student WhoIsHappy(bool? isHappy = true); + + Student WhoIsSad(); + + Student WithUnknownMood(); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.g.cs index dbf56e2..f60870f 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.g.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.g.cs @@ -12,7 +12,7 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationC public class CreateStudent : CreateStudent.ICreateStudent, CreateStudent.IWithName, - CreateStudent.IWithAge, + CreateStudent.IOfAge, CreateStudent.ILivingIn, CreateStudent.IWhoIsHappy { @@ -34,20 +34,21 @@ public static ICreateStudent InitialStep() return new CreateStudent(); } - public static IWithAge WithName(string name) + public static IOfAge WithName(string name) { CreateStudent createStudent = new CreateStudent(); createStudent.student.Name = name; return createStudent; } - IWithAge IWithName.WithName(string name) + IOfAge IWithName.WithName(string name) { student.Name = name; return this; } - ILivingIn IWithAge.WithAge(int age) + /// + ILivingIn IOfAge.OfAge(int age) { student.Age = age; return this; @@ -97,12 +98,14 @@ public interface ICreateStudent : IWithName public interface IWithName { - IWithAge WithName(string name); + IOfAge WithName(string name); } - public interface IWithAge + public interface IOfAge { - ILivingIn WithAge(int age); + /// Sets the students's age. + /// The student's age. + ILivingIn OfAge(int age); } public interface ILivingIn From 0dcbcaa2427eb03f7d9d52639a28a161991b9f4b Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Wed, 23 Jul 2025 06:07:30 +0200 Subject: [PATCH 20/61] refactor: remove FluentMethod property --- .../CodeBoardActors/MethodCreation/FluentMethods.cs | 2 +- .../CodeBoardActors/MethodCreation/LambdaMethod.cs | 2 +- .../CodeBoardActors/MethodCreation/MemberMethod.cs | 2 +- .../SourceGenerators/AttributeInfo/AttributeInfoBase.cs | 3 +-- .../AttributeInfo/FluentCollectionAttributeInfo.cs | 3 +-- .../AttributeInfo/FluentDefaultAttributeInfo.cs | 2 +- .../AttributeInfo/FluentLambdaAttributeInfo.cs | 3 +-- .../AttributeInfo/FluentMemberAttributeInfo.cs | 3 +-- .../AttributeInfo/FluentMethodAttributeInfo.cs | 3 +-- .../AttributeInfo/FluentNullableAttributeInfo.cs | 2 +- .../AttributeInfo/FluentPredicateAttributeInfo.cs | 3 +-- .../AttributeInfo/OrthogonalAttributeInfoBase.cs | 2 +- .../SourceGenerators/FluentApiInfo.cs | 1 - .../SourceGenerators/FluentApiInfoGroupCreator.cs | 6 +++--- .../CommentedPropertiesClassAdvanced/Student.cs | 2 +- src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs | 2 +- 16 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/FluentMethods.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/FluentMethods.cs index 109d34b..2dcf218 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/FluentMethods.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/FluentMethods.cs @@ -25,7 +25,7 @@ public BuilderMethods CreateBuilderMethods(MethodCreator methodCreator) BuilderMethod builderMethod = methodCreator.BuilderMethodFactory.CreateBuilderMethod( SymbolInfo, - MethodAttributeInfo.FluentMethodName, + MethodAttributeInfo.Method, ReturnAttributeInfo != null); return new BuilderMethods(builderMethod); } diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/LambdaMethod.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/LambdaMethod.cs index 3946116..ffff921 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/LambdaMethod.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/LambdaMethod.cs @@ -22,7 +22,7 @@ internal LambdaMethod( public BuilderMethods CreateBuilderMethods(MethodCreator methodCreator) { BuilderMethod memberBuilderMethod = - methodCreator.CreateMethod(SymbolInfo, LambdaAttributeInfo.FluentMethodName); + methodCreator.CreateMethod(SymbolInfo, LambdaAttributeInfo.Method); BuilderMethod lambdaBuilderMethod = CreateLambdaBuilderMethod( methodCreator, LambdaAttributeInfo.Method, SymbolInfo, LambdaAttributeInfo.BuilderInfo); diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/MemberMethod.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/MemberMethod.cs index c7644bd..8e0dcd1 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/MemberMethod.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/MemberMethod.cs @@ -20,7 +20,7 @@ public BuilderMethods CreateBuilderMethods(MethodCreator methodCreator) List builderMethods = new List(); HashSet requiredUsings = new HashSet(); - BuilderMethod builderMethod = methodCreator.CreateMethod(SymbolInfo, MemberAttributeInfo.FluentMethodName); + BuilderMethod builderMethod = methodCreator.CreateMethod(SymbolInfo, MemberAttributeInfo.Method); builderMethods.Add(builderMethod); if (MemberAttributeInfo.LambdaBuilderInfo != null) diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/AttributeInfoBase.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/AttributeInfoBase.cs index 08b5c3f..a68a948 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/AttributeInfoBase.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/AttributeInfoBase.cs @@ -8,6 +8,5 @@ protected AttributeInfoBase(int builderStep) } internal int BuilderStep { get; } - internal abstract string FluentMethodName { get; } // todo: rename or remove - internal abstract IReadOnlyCollection FluentMethodNames { get; } + internal abstract IReadOnlyList FluentMethodNames { get; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentCollectionAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentCollectionAttributeInfo.cs index 91671ae..24935e0 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentCollectionAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentCollectionAttributeInfo.cs @@ -29,8 +29,7 @@ private FluentCollectionAttributeInfo( internal string? WithItem { get; } internal string? WithZeroItems { get; } internal LambdaBuilderInfo? LambdaBuilderInfo { get; } - internal override string FluentMethodName => WithItems; - internal override IReadOnlyCollection FluentMethodNames => + internal override IReadOnlyList FluentMethodNames => new string?[] { WithItems, WithItem, WithZeroItems }.OfType().ToArray(); internal static FluentCollectionAttributeInfo Create( diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentDefaultAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentDefaultAttributeInfo.cs index b967d86..59e9e59 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentDefaultAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentDefaultAttributeInfo.cs @@ -12,7 +12,7 @@ private FluentDefaultAttributeInfo(string method) internal string Method { get; } - internal override IReadOnlyCollection FluentMethodNames => new string[] { Method }; + internal override IReadOnlyList FluentMethodNames => new string[] { Method }; internal static FluentDefaultAttributeInfo Create(AttributeData attributeData, string memberName) { diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentLambdaAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentLambdaAttributeInfo.cs index 0daa1da..e810909 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentLambdaAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentLambdaAttributeInfo.cs @@ -14,8 +14,7 @@ private FluentLambdaAttributeInfo(int builderStep, string method, LambdaBuilderI internal string Method { get; } internal LambdaBuilderInfo BuilderInfo { get; } - internal override string FluentMethodName => Method; - internal override IReadOnlyCollection FluentMethodNames => new string[] { Method }; + internal override IReadOnlyList FluentMethodNames => new string[] { Method }; internal static FluentLambdaAttributeInfo Create( AttributeData attributeData, diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs index bebfb6d..0b9fa8c 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs @@ -19,8 +19,7 @@ private FluentMemberAttributeInfo( internal string Method { get; } internal int ParameterPosition { get; } - internal override string FluentMethodName => Method; - internal override IReadOnlyCollection FluentMethodNames => new string[] { Method }; + internal override IReadOnlyList FluentMethodNames => new string[] { Method }; internal LambdaBuilderInfo? LambdaBuilderInfo { get; } internal static FluentMemberAttributeInfo Create( diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMethodAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMethodAttributeInfo.cs index c17eaf3..b2d76fd 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMethodAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMethodAttributeInfo.cs @@ -12,8 +12,7 @@ private FluentMethodAttributeInfo(int builderStep, string method) } internal string Method { get; } - internal override string FluentMethodName => Method; - internal override IReadOnlyCollection FluentMethodNames => new string[] { Method }; + internal override IReadOnlyList FluentMethodNames => new string[] { Method }; internal static FluentMethodAttributeInfo Create(AttributeData attributeData, string memberName) { diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentNullableAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentNullableAttributeInfo.cs index 8955dac..a0a1343 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentNullableAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentNullableAttributeInfo.cs @@ -11,7 +11,7 @@ private FluentNullableAttributeInfo(string method) } internal string Method { get; } - internal override IReadOnlyCollection FluentMethodNames => new string[] { Method }; + internal override IReadOnlyList FluentMethodNames => new string[] { Method }; internal static FluentNullableAttributeInfo Create(AttributeData attributeData, string memberName) { diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentPredicateAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentPredicateAttributeInfo.cs index beaee00..44ee898 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentPredicateAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentPredicateAttributeInfo.cs @@ -14,8 +14,7 @@ private FluentPredicateAttributeInfo(int builderStep, string method, string nega internal string Method { get; } internal string NegatedMethod { get; } - internal override string FluentMethodName => Method; - internal override IReadOnlyCollection FluentMethodNames => new string[] { Method, NegatedMethod }; + internal override IReadOnlyList FluentMethodNames => new string[] { Method, NegatedMethod }; internal static FluentPredicateAttributeInfo Create(AttributeData attributeData, string memberName) { diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/OrthogonalAttributeInfoBase.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/OrthogonalAttributeInfoBase.cs index 784250c..4e46706 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/OrthogonalAttributeInfoBase.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/OrthogonalAttributeInfoBase.cs @@ -2,5 +2,5 @@ namespace M31.FluentApi.Generator.SourceGenerators.AttributeInfo; internal abstract record OrthogonalAttributeInfoBase { - internal abstract IReadOnlyCollection FluentMethodNames { get; } + internal abstract IReadOnlyList FluentMethodNames { get; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfo.cs index 7267b5e..dda0b71 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfo.cs @@ -31,7 +31,6 @@ internal FluentApiInfo( internal IReadOnlyCollection OrthogonalAttributeInfos { get; } internal IReadOnlyCollection ControlAttributeInfos { get; } internal FluentApiAdditionalInfo AdditionalInfo { get; } - internal string FluentMethodName => AttributeInfo.FluentMethodName; protected bool Equals(FluentApiInfo other) { diff --git a/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoGroupCreator.cs b/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoGroupCreator.cs index 4f35b5f..479786b 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoGroupCreator.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoGroupCreator.cs @@ -30,11 +30,11 @@ private IReadOnlyCollection CreateGroups(IReadOnlyCollection // Group FluentApiInfos that have the same builder step, the same fluent method name, and represent // FluentMembers (compounds). (int builderStep, string fluentMethodName, Type type, FluentApiInfo[] infoArray)[] grouping = infos - .GroupBy(i => (i.AttributeInfo.BuilderStep, i.FluentMethodName, i.AttributeInfo.GetType())) + .GroupBy(i => (i.AttributeInfo.BuilderStep, i.AttributeInfo.FluentMethodNames[0], i.AttributeInfo.GetType())) .SelectMany(g => g.First().AttributeInfo.GetType() == typeof(FluentMemberAttributeInfo) - ? new[] { (g.Key.BuilderStep, g.Key.FluentMethodName, g.Key.GetType(), g.ToArray()) } - : g.Select(g2 => (g.Key.BuilderStep, g.Key.FluentMethodName, g.Key.GetType(), new[] { g2 }))) + ? new[] { (g.Key.BuilderStep, g.Key.Item2, g.Key.Item3, g.ToArray()) } + : g.Select(g2 => (g.Key.BuilderStep, g.Key.Item2, g.Key.Item3, new[] { g2 }))) .OrderBy(g => g.BuilderStep) .ToArray(); diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/Student.cs index 3c5b0c6..34855f2 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/Student.cs @@ -16,7 +16,7 @@ public class Student /// Sets the students's age. /// /// The student's age. - [FluentMember(1)] + [FluentMember(1, "OfAge")] public int Age { get; set; } /// diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs index 97f78f3..fc85766 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs @@ -7,7 +7,7 @@ namespace M31.FluentApi.Tests.CodeGeneration; internal class TestDataProvider : IEnumerable { private readonly List testClasses = - Filter(new string[] { "CommentedPropertiesClassAdvanced" }, + Filter(new string[] { }, new List { new object[] { "Abstract", "AliasNamespaceClass", "Student" }, From 71601a4e9ba69809111cc7641a8d4e3e822e44ac Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sat, 26 Jul 2025 08:29:44 +0200 Subject: [PATCH 21/61] test: CommentedCompoundClass --- .../Commons/BuilderMethodFactory.cs | 10 +++- .../CreateStudent.expected.txt | 0 .../CommentedCompoundClass/CreateStudent.g.cs | 56 +++++++++++++++++++ .../CommentedCompoundClass/Student.cs | 22 ++++++++ .../CodeGeneration/TestDataProvider.cs | 1 + 5 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.expected.txt create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.g.cs create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/Student.cs diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs index e349322..1ac08d6 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs @@ -60,11 +60,17 @@ List BuildBodyCode( .ToList(); } - Comments comments = FluentCommentsParser.Parse(null); // todo - // Comments comments = transformedComments.GetMemberComments(computeValue.TargetMember); + Comments comments = GetCompoundComments(methodName, computeValues.Select(v => v.TargetMember).ToArray()); return new BuilderMethod(methodName, null, parameters, null, BuildBodyCode, comments); } + private Comments GetCompoundComments(string methodName, IReadOnlyCollection memberNames) + { + return new Comments(memberNames + .SelectMany(n => transformedComments.GetMemberComments(new MemberCommentKey(n, methodName)).List) + .ToArray()); + } + internal BuilderMethod CreateBuilderMethod( MethodSymbolInfo methodSymbolInfo, string methodName, diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.expected.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.g.cs new file mode 100644 index 0000000..5f340b9 --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.g.cs @@ -0,0 +1,56 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedCompoundClass; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.IWithName +{ + private readonly Student student; + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + /// Sets the student's name. + /// The student's first name. + /// The student's last name. + public static Student WithName(string firstName, string lastName) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.FirstName = firstName; + createStudent.student.LastName = lastName; + return createStudent.student; + } + + /// + Student IWithName.WithName(string firstName, string lastName) + { + student.FirstName = firstName; + student.LastName = lastName; + return student; + } + + public interface ICreateStudent : IWithName + { + } + + public interface IWithName + { + /// Sets the student's name. + /// The student's first name. + /// The student's last name. + Student WithName(string firstName, string lastName); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/Student.cs new file mode 100644 index 0000000..7bdf7c0 --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/Student.cs @@ -0,0 +1,22 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedCompoundClass; + +[FluentApi] +public class Student +{ + /// + /// Sets the student's name. + /// + /// The student's first name. + [FluentMember(0, "WithName")] + public string FirstName { get; set; } + + /// The student's last name. + [FluentMember(0, "WithName")] + public string LastName { get; set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs index fc85766..954f69c 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs @@ -21,6 +21,7 @@ internal class TestDataProvider : IEnumerable new object[] { "Abstract", "ContinueWithSelfClass", "Student" }, new object[] { "Abstract", "CustomFluentMethodNameClass", "Student" }, new object[] { "Abstract", "DefaultFluentMethodNameClass", "Student" }, + new object[] { "Abstract", "DocumentationComments", "CommentedCompoundClass", "Student" }, new object[] { "Abstract", "DocumentationComments", "CommentedMethodsClass", "Student" }, new object[] { "Abstract", "DocumentationComments", "CommentedPropertiesClass", "Student" }, new object[] { "Abstract", "DocumentationComments", "CommentedPropertiesClassAdvanced", "Student" }, From 00b08798a28902ab83eaaa4c9386f06d85db2d1b Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sun, 27 Jul 2025 16:22:15 +0200 Subject: [PATCH 22/61] feat: FluentApiCommentsProvider (wip) --- src/ExampleProject/MyPerson.cs | 42 ++++++++++++ src/ExampleProject/Program.cs | 6 ++ .../FluentApiCommentsProvider.cs | 65 +++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 src/ExampleProject/MyPerson.cs create mode 100644 src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiCommentsProvider.cs diff --git a/src/ExampleProject/MyPerson.cs b/src/ExampleProject/MyPerson.cs new file mode 100644 index 0000000..17e085c --- /dev/null +++ b/src/ExampleProject/MyPerson.cs @@ -0,0 +1,42 @@ +using M31.FluentApi.Attributes; + +namespace ExampleProject; + +[FluentApi] +class MyPerson +{ + /// + /// Sets the student's first name. + /// + /// The student's first name. + [FluentMember(0)] + public string FirstName { get; set; } + + /// + /// Sets the student's last name. + /// + /// The student's last name. + [FluentMember(1)] + public string LastName { get; set; } + + /// + /// Sets the student's age. + /// + /// The student's age. + [FluentMember(2, "OfAge")] + public int Age { get; set; } + + /// + /// Sets the student's semester. + /// + /// The student's semester. + [FluentMember(3, "InSemester")] + public int Semester { get; set; } + + /// + /// This summary will not be taken into account. + /// + /// This parameter will not be taken into account. + [FluentMember(4, "LivingIn")] + public string City { get; set; } +} diff --git a/src/ExampleProject/Program.cs b/src/ExampleProject/Program.cs index 50ddc6e..ba3b1ec 100644 --- a/src/ExampleProject/Program.cs +++ b/src/ExampleProject/Program.cs @@ -3,6 +3,12 @@ #pragma warning disable CS8321 // Local function is declared but never used +// MyPerson +// + +MyPerson mp = CreateMyPerson.WithFirstName("Kevin").WithLastName("Schaal") + .OfAge(22).InSemester(4).LivingIn("Tübingen"); + // Student // diff --git a/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiCommentsProvider.cs b/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiCommentsProvider.cs new file mode 100644 index 0000000..78a1f4b --- /dev/null +++ b/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiCommentsProvider.cs @@ -0,0 +1,65 @@ +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace M31.FluentApi.Generator.SourceAnalyzers; + +[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(AddFluentSummaryRefactoringProvider)), Shared] +public class AddFluentSummaryRefactoringProvider : CodeRefactoringProvider +{ + public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + //var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + //if (root == null) + // return; + + //var span = context.Span; + //var token = root.FindToken(span.Start); + + //// Check if the user is on a method identifier + //var methodDeclaration = token.Parent?.AncestorsAndSelf().OfType().FirstOrDefault(); + //if (methodDeclaration == null) + // return; + + //// Only offer if no existing doc comment + //if (methodDeclaration.GetLeadingTrivia().Any(t => t.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia))) + // return; + + // Register code action + var action = CodeAction.Create("Add doc comment", + c => AddFluentSummaryAsync(context.Document, null!, c), + nameof(AddFluentSummaryRefactoringProvider)); + + context.RegisterRefactoring(action); + } + + private async Task AddFluentSummaryAsync(Document document, MethodDeclarationSyntax methodDecl, CancellationToken cancellationToken) + { + return document; + var leadingTrivia = methodDecl.GetLeadingTrivia(); + + var xmlComment = SyntaxFactory.TriviaList( + SyntaxFactory.Comment("/// "), + SyntaxFactory.CarriageReturnLineFeed, + SyntaxFactory.Comment("/// ..."), + SyntaxFactory.CarriageReturnLineFeed, + SyntaxFactory.Comment("/// "), + SyntaxFactory.CarriageReturnLineFeed + ); + + var newMethod = methodDecl.WithLeadingTrivia(xmlComment.AddRange(leadingTrivia)); + + var root = await document.GetSyntaxRootAsync(cancellationToken); + if (root == null) + return document; + + var newRoot = root.ReplaceNode(methodDecl, newMethod); + return document.WithSyntaxRoot(newRoot); + } +} From 1f9afd8ea79844a874988137564e0a7f048efc9f Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sat, 2 Aug 2025 11:45:39 +0200 Subject: [PATCH 23/61] feat: FluentApiCommentsProvider (wip) --- .../FluentApiCommentsProvider.cs | 72 +++++++++++++------ .../FluentApiCommentsProviderTests.cs | 30 ++++++++ .../Helpers/AnalyzerAndCodeFixVerifier.cs | 2 +- .../Helpers/SourceExtensions.cs | 27 +++++++ .../UncommentedPropertyClass/Student.cs | 14 ++++ .../Student.fixed.txt | 18 +++++ 6 files changed, 141 insertions(+), 22 deletions(-) create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/SourceExtensions.cs create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/UncommentedPropertyClass/Student.cs create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/UncommentedPropertyClass/Student.fixed.txt diff --git a/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiCommentsProvider.cs b/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiCommentsProvider.cs index 78a1f4b..98f1b7f 100644 --- a/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiCommentsProvider.cs +++ b/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiCommentsProvider.cs @@ -1,6 +1,5 @@ using System.Composition; -using System.Threading; -using System.Threading.Tasks; +using M31.FluentApi.Generator.Commons; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeRefactorings; @@ -10,36 +9,67 @@ namespace M31.FluentApi.Generator.SourceAnalyzers; -[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(AddFluentSummaryRefactoringProvider)), Shared] -public class AddFluentSummaryRefactoringProvider : CodeRefactoringProvider +[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(FluentApiCommentsProvider))] +[Shared] +internal class FluentApiCommentsProvider : CodeRefactoringProvider { public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { - //var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - //if (root == null) - // return; + Document document = context.Document; - //var span = context.Span; - //var token = root.FindToken(span.Start); + SyntaxNode? root = await document.GetSyntaxRootAsync(context.CancellationToken); + if (root == null) + { + return; + } + + // SemanticModel? semanticModel = await document.GetSemanticModelAsync(context.CancellationToken); + // if (semanticModel == null) + // { + // return; + // } + + SyntaxNode node = root.FindNode(context.Span); + + // Check for property, field, or method declaration + if (node is not MemberDeclarationSyntax memberSyntax) + { + return; + } + + if (!memberSyntax.Parent.IsClassStructOrRecordSyntax(out TypeDeclarationSyntax typeSyntax)) + { + return; + } - //// Check if the user is on a method identifier - //var methodDeclaration = token.Parent?.AncestorsAndSelf().OfType().FirstOrDefault(); - //if (methodDeclaration == null) - // return; + if (!typeSyntax.AttributeLists + .SelectMany(list => list.Attributes) + .Any(attr => attr.IsFluentApiAttributeSyntax())) + { + return; + } - //// Only offer if no existing doc comment - //if (methodDeclaration.GetLeadingTrivia().Any(t => t.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia))) - // return; + // // Only offer refactoring if member has [FluentMember] or no comment yet + // var memberSymbol = semanticModel.GetDeclaredSymbol(memberSyntax, context.CancellationToken); + // if (memberSymbol == null) + // return; + // + // bool hasFluentMember = memberSymbol.GetAttributes().Any(attr => + // attr.AttributeClass?.Name == "FluentMemberAttribute" || + // attr.AttributeClass?.ToDisplayString() == "FluentMemberAttribute"); + // + // if (!hasFluentMember) + // return; - // Register code action - var action = CodeAction.Create("Add doc comment", + CodeAction action = CodeAction.Create("Add doc comment b", c => AddFluentSummaryAsync(context.Document, null!, c), - nameof(AddFluentSummaryRefactoringProvider)); + nameof(FluentApiCommentsProvider)); context.RegisterRefactoring(action); } - private async Task AddFluentSummaryAsync(Document document, MethodDeclarationSyntax methodDecl, CancellationToken cancellationToken) + private async Task AddFluentSummaryAsync(Document document, MethodDeclarationSyntax methodDecl, + CancellationToken cancellationToken) { return document; var leadingTrivia = methodDecl.GetLeadingTrivia(); @@ -62,4 +92,4 @@ private async Task AddFluentSummaryAsync(Document document, MethodDecl var newRoot = root.ReplaceNode(methodDecl, newMethod); return document.WithSyntaxRoot(newRoot); } -} +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs new file mode 100644 index 0000000..226871d --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs @@ -0,0 +1,30 @@ +using System.IO; +using System.Threading.Tasks; +using M31.FluentApi.Attributes; +using M31.FluentApi.Generator.SourceAnalyzers; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Xunit; +using M31.FluentApi.Tests.AnalyzerAndCodeFixes.Helpers; +using static M31.FluentApi.Tests.AnalyzerAndCodeFixes.Helpers.TestSourceCodeReader; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes; + +public class MyRefactoringProviderTests +{ + [Theory] + [InlineData("UncommentedPropertyClass", "FirstName")] + public async Task CanProvideFluentApiComments(string commentTestClass, string selectedSpan) + { + SourceWithFix source = ReadSource(Path.Join("FluentApiComments", commentTestClass), "Student"); + await new CSharpCodeRefactoringTest + { + TestCode = source.Source.SelectSpan(selectedSpan), + FixedCode = source.FixedSource!, + TestState = + { + AdditionalReferences = { typeof(FluentApiAttribute).Assembly } + } + }.RunAsync(); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/AnalyzerAndCodeFixVerifier.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/AnalyzerAndCodeFixVerifier.cs index cc8c7d9..296830a 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/AnalyzerAndCodeFixVerifier.cs +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/AnalyzerAndCodeFixVerifier.cs @@ -63,7 +63,7 @@ internal CodeFixTest( Path.Combine("ref", "net6.0")); #else ReferenceAssemblies = ReferenceAssemblies.Net.Net50; -#endif +#endif // todo: remove TestState.AdditionalReferences.Add(typeof(FluentApiAttribute).Assembly); } diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/SourceExtensions.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/SourceExtensions.cs new file mode 100644 index 0000000..d5417dc --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/SourceExtensions.cs @@ -0,0 +1,27 @@ +using System; +using System.Text.RegularExpressions; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.Helpers; + +internal static class SourceExtensions +{ + internal static string SelectSpan(this string source, string span) + { + MatchCollection matches = Regex.Matches(source, Regex.Escape(span)); + if (matches.Count != 1) + { + throw new InvalidOperationException( + $"Span '{span}' not found or found multiple times in the source code."); + } + + string replacement = $"[|{span}|]"; + return ReplaceMatch(source, matches[0], replacement); + + static string ReplaceMatch(string source, Match match, string replacement) + { + return source[..match.Index] + + replacement + + source[(match.Index + match.Length)..]; + } + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/UncommentedPropertyClass/Student.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/UncommentedPropertyClass/Student.cs new file mode 100644 index 0000000..ba96592 --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/UncommentedPropertyClass/Student.cs @@ -0,0 +1,14 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.UncommentedPropertyClass; + +[FluentApi] +public class Student +{ + [FluentMember(0)] + public string FirstName { get; set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/UncommentedPropertyClass/Student.fixed.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/UncommentedPropertyClass/Student.fixed.txt new file mode 100644 index 0000000..11277ac --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/UncommentedPropertyClass/Student.fixed.txt @@ -0,0 +1,18 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.UncommentedPropertyClass; + +[FluentApi] +public class Student +{ + /// + /// ... + /// + /// ... + [FluentMember(0)] + public string FirstName { get; set; } +} \ No newline at end of file From d7e7be8eacd0eafac40146111c7cb96b814ab344 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sat, 2 Aug 2025 11:45:52 +0200 Subject: [PATCH 24/61] fix: warning --- .../DocumentationComments/CommentedPropertiesClass/Student.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/Student.cs index 6f55bbc..1e88131 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/Student.cs @@ -1,6 +1,7 @@ // Non-nullable member is uninitialized #pragma warning disable CS8618 // ReSharper disable All +// ReSharper disable InvalidXmlDocComment using M31.FluentApi.Attributes; @@ -49,5 +50,5 @@ public class Student /// /// This parameter will not be taken into account. [FluentMember(4, "LivingIn")] - public string City { get; set; } + public string City { get; set; } } \ No newline at end of file From 3801ca4ec21340a054da246511ab11d057fab5e6 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sat, 2 Aug 2025 11:50:04 +0200 Subject: [PATCH 25/61] fix(CommentsGenerator): distinct single method names --- .../DocumentationGeneration/CommentsGenerator.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs index 980859e..e4e24e0 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs @@ -48,18 +48,18 @@ private void HandleMemberSymbolInfo(MemberSymbolInfo memberInfo, FluentApiInfo f private string? TryGetSingleMethodName(FluentApiInfo fluentApiInfo) { string[] fluentMethodNames = fluentApiInfo.AttributeInfo.FluentMethodNames - .Concat(fluentApiInfo.OrthogonalAttributeInfos.SelectMany(o => o.FluentMethodNames)).ToArray(); + .Concat(fluentApiInfo.OrthogonalAttributeInfos.SelectMany(o => o.FluentMethodNames)).Distinct().ToArray(); return fluentMethodNames.Length != 1 ? null : fluentMethodNames[0]; } private IGrouping[] GroupByMethodName(Comments transformedComments, string? fallbackMethodName) { - List<(string, Comments)> methodComments = new List<(string, Comments)>(); return transformedComments.List.GroupBy(GetMethodName).Where(g => g.Key != string.Empty).ToArray(); string GetMethodName(Comment comment) { - return comment.Attributes.FirstOrDefault(a => a.Key == "method")?.Value ?? fallbackMethodName ?? string.Empty; + return comment.Attributes.FirstOrDefault(a => a.Key == "method")?.Value ?? + fallbackMethodName ?? string.Empty; } } @@ -68,4 +68,4 @@ private void HandleMethodSymbolInfo(MethodSymbolInfo methodInfo, CodeBoard codeB Comments transformedComments = CommentsTransformer.TransformComments(methodInfo.Comments); codeBoard.TransformedComments.AssignMethodComments(methodInfo, transformedComments); } -} +} \ No newline at end of file From 87c2fc1f5e4d1cffd0a73064ead53bf94f8f9b2e Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sat, 2 Aug 2025 15:43:13 +0200 Subject: [PATCH 26/61] test: CommentedLambdaCollectionClass --- .../Commons/BuilderMethodFactory.cs | 23 +++- .../TransformedComments.cs | 21 ++- .../FluentMemberAttributeInfo.cs | 2 +- .../CommentedCompoundClass/Student.cs | 2 +- .../CreatePhone.g.cs | 59 ++++++++ .../CreateStudent.expected.txt | 127 ++++++++++++++++++ .../CreateStudent.g.cs | 127 ++++++++++++++++++ .../CommentedLambdaCollectionClass/Phone.cs | 18 +++ .../CommentedLambdaCollectionClass/Student.cs | 34 +++++ .../Student.cs | 5 +- .../CodeGeneration/TestDataProvider.cs | 1 + 11 files changed, 403 insertions(+), 16 deletions(-) create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreatePhone.g.cs create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreateStudent.expected.txt create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreateStudent.g.cs create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Phone.cs create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Student.cs diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs index 1ac08d6..4ab6a42 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs @@ -9,7 +9,8 @@ internal class BuilderMethodFactory private readonly InnerBodyCreationDelegates innerBodyCreationDelegates; private readonly TransformedComments transformedComments; - internal BuilderMethodFactory(InnerBodyCreationDelegates innerBodyCreationDelegates, TransformedComments transformedComments) + internal BuilderMethodFactory(InnerBodyCreationDelegates innerBodyCreationDelegates, + TransformedComments transformedComments) { this.innerBodyCreationDelegates = innerBodyCreationDelegates; this.transformedComments = transformedComments; @@ -18,8 +19,9 @@ internal BuilderMethodFactory(InnerBodyCreationDelegates innerBodyCreationDelega internal BuilderMethod CreateEmptyBuilderMethod(MemberSymbolInfo memberInfo, string methodName) { MemberCommentKey key = new MemberCommentKey(memberInfo.Name, methodName); - Comments comments = transformedComments.GetMemberComments(key); - return new BuilderMethod(methodName, null, new List(), null, (_, _, _) => new List(), comments); + Comments comments = transformedComments.GetMemberComments(key, Array.Empty()); + return new BuilderMethod(methodName, null, new List(), null, (_, _, _) => new List(), + comments); } internal BuilderMethod CreateBuilderMethod(string methodName, ComputeValueCode computeValue) @@ -41,7 +43,7 @@ List BuildBodyCode( } MemberCommentKey key = new MemberCommentKey(computeValue.TargetMember, methodName); - Comments comments = transformedComments.GetMemberComments(key); + Comments comments = transformedComments.GetMemberComments(key, parameters.Select(p => p.Name).ToArray()); return new BuilderMethod(methodName, null, parameters, null, BuildBodyCode, comments); } @@ -60,14 +62,21 @@ List BuildBodyCode( .ToList(); } - Comments comments = GetCompoundComments(methodName, computeValues.Select(v => v.TargetMember).ToArray()); + Comments comments = + GetCompoundComments(methodName, parameters, computeValues.Select(v => v.TargetMember).ToArray()); return new BuilderMethod(methodName, null, parameters, null, BuildBodyCode, comments); } - private Comments GetCompoundComments(string methodName, IReadOnlyCollection memberNames) + private Comments GetCompoundComments( + string methodName, + List parameters, + IReadOnlyCollection memberNames) { + string[] parameterNames = parameters.Select(p => p.Name).ToArray(); + return new Comments(memberNames - .SelectMany(n => transformedComments.GetMemberComments(new MemberCommentKey(n, methodName)).List) + .SelectMany(n => + transformedComments.GetMemberComments(new MemberCommentKey(n, methodName), parameterNames).List) .ToArray()); } diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs index ae30d14..eafc0e5 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs @@ -1,6 +1,4 @@ -using M31.FluentApi.Generator.Commons; - -namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; internal class TransformedComments { @@ -24,14 +22,25 @@ internal void AssignMemberComments(MemberCommentKey memberCommentKey, Comments c memberComments[memberCommentKey] = comments; } - internal Comments GetMemberComments(MemberCommentKey memberCommentKey) + internal Comments GetMemberComments(MemberCommentKey memberCommentKey, string[] parameterNames) { if (memberComments.TryGetValue(memberCommentKey, out Comments comments)) { - return comments; + return new Comments(comments.List.Where(IsRelevant).ToArray()); } return new Comments(Array.Empty()); + + bool IsRelevant(Comment comment) + { + if (comment.Tag != "param") + { + return true; + } + + string? parameterName = comment.Attributes.FirstOrDefault(a => a.Key == "name")?.Value; + return parameterName != null && parameterNames.Contains(parameterName, StringComparer.InvariantCulture); + } } internal void AssignMethodComments(MethodSymbolInfo methodSymbolInfo, Comments comments) @@ -49,4 +58,4 @@ internal Comments GetMethodComments(MethodSymbolInfo methodSymbolInfo) { return methodComments[methodSymbolInfo]; } -} +} \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs index 0b9fa8c..eafd3d8 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs @@ -20,7 +20,7 @@ private FluentMemberAttributeInfo( internal string Method { get; } internal int ParameterPosition { get; } internal override IReadOnlyList FluentMethodNames => new string[] { Method }; - internal LambdaBuilderInfo? LambdaBuilderInfo { get; } + internal LambdaBuilderInfo? LambdaBuilderInfo { get; } // Todo: add to fluent method names, also for the FluentCollection. internal static FluentMemberAttributeInfo Create( AttributeData attributeData, diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/Student.cs index 7bdf7c0..2b09665 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/Student.cs @@ -5,7 +5,7 @@ using M31.FluentApi.Attributes; namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedCompoundClass; - +// todo: rename folder to FluentApiComments, remove other occurrences of "DocumentationComments". Hm maybe not. [FluentApi] public class Student { diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreatePhone.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreatePhone.g.cs new file mode 100644 index 0000000..9ef520d --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreatePhone.g.cs @@ -0,0 +1,59 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass; + +public class CreatePhone : + CreatePhone.ICreatePhone, + CreatePhone.IWithNumber, + CreatePhone.IWithUsage +{ + private readonly Phone phone; + + private CreatePhone() + { + phone = new Phone(); + } + + public static ICreatePhone InitialStep() + { + return new CreatePhone(); + } + + public static IWithUsage WithNumber(string number) + { + CreatePhone createPhone = new CreatePhone(); + createPhone.phone.Number = number; + return createPhone; + } + + IWithUsage IWithNumber.WithNumber(string number) + { + phone.Number = number; + return this; + } + + Phone IWithUsage.WithUsage(string usage) + { + phone.Usage = usage; + return phone; + } + + public interface ICreatePhone : IWithNumber + { + } + + public interface IWithNumber + { + IWithUsage WithNumber(string number); + } + + public interface IWithUsage + { + Phone WithUsage(string usage); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreateStudent.expected.txt new file mode 100644 index 0000000..67acb0d --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreateStudent.expected.txt @@ -0,0 +1,127 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +using System.Collections.Generic; +using System.Reflection; +using System.Linq; +using System; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.IWithName, + CreateStudent.IWithPhoneNumbers +{ + private readonly Student student; + private static readonly PropertyInfo phoneNumbersPropertyInfo; + + static CreateStudent() + { + phoneNumbersPropertyInfo = typeof(Student).GetProperty("PhoneNumbers", BindingFlags.Instance | BindingFlags.Public)!; + } + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + public static IWithPhoneNumbers WithName(string name) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.Name = name; + return createStudent; + } + + IWithPhoneNumbers IWithName.WithName(string name) + { + student.Name = name; + return this; + } + + /// + Student IWithPhoneNumbers.WithPhoneNumbers(System.Collections.Generic.IReadOnlyCollection phoneNumbers) + { + CreateStudent.phoneNumbersPropertyInfo.SetValue(student, phoneNumbers); + return student; + } + + /// + Student IWithPhoneNumbers.WithPhoneNumbers(params M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[] phoneNumbers) + { + CreateStudent.phoneNumbersPropertyInfo.SetValue(student, phoneNumbers); + return student; + } + + /// + Student IWithPhoneNumbers.WithPhoneNumbers(params Func[] createPhoneNumbers) + { + CreateStudent.phoneNumbersPropertyInfo.SetValue(student, createPhoneNumbers.Select(createPhoneNumber => createPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.CreatePhone.InitialStep())).ToArray()); + return student; + } + + /// + Student IWithPhoneNumbers.WithPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone phoneNumber) + { + CreateStudent.phoneNumbersPropertyInfo.SetValue(student, new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[1]{ phoneNumber }); + return student; + } + + /// + Student IWithPhoneNumbers.WithPhoneNumber(Func createPhoneNumber) + { + CreateStudent.phoneNumbersPropertyInfo.SetValue(student, new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[1]{ createPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.CreatePhone.InitialStep()) }); + return student; + } + + /// + Student IWithPhoneNumbers.WithZeroPhoneNumbers() + { + CreateStudent.phoneNumbersPropertyInfo.SetValue(student, new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[0]); + return student; + } + + public interface ICreateStudent : IWithName + { + } + + public interface IWithName + { + IWithPhoneNumbers WithName(string name); + } + + public interface IWithPhoneNumbers + { + /// Sets the student's phone numbers. + /// The student's phone numbers. + Student WithPhoneNumbers(System.Collections.Generic.IReadOnlyCollection phoneNumbers); + + /// Sets the student's phone numbers. + /// The student's phone numbers. + Student WithPhoneNumbers(params M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[] phoneNumbers); + + /// Sets the student's phone numbers. + /// Functions for creating the student's phone numbers. + Student WithPhoneNumbers(params Func[] createPhoneNumbers); + + /// Sets the student's phone number. + /// The student's phone number. + Student WithPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone phoneNumber); + + /// Sets the student's phone number. + /// A function for creating the student's phone number. + Student WithPhoneNumber(Func createPhoneNumber); + + /// Specifies that the student has no phone numbers. + Student WithZeroPhoneNumbers(); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreateStudent.g.cs new file mode 100644 index 0000000..67acb0d --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreateStudent.g.cs @@ -0,0 +1,127 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +using System.Collections.Generic; +using System.Reflection; +using System.Linq; +using System; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.IWithName, + CreateStudent.IWithPhoneNumbers +{ + private readonly Student student; + private static readonly PropertyInfo phoneNumbersPropertyInfo; + + static CreateStudent() + { + phoneNumbersPropertyInfo = typeof(Student).GetProperty("PhoneNumbers", BindingFlags.Instance | BindingFlags.Public)!; + } + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + public static IWithPhoneNumbers WithName(string name) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.Name = name; + return createStudent; + } + + IWithPhoneNumbers IWithName.WithName(string name) + { + student.Name = name; + return this; + } + + /// + Student IWithPhoneNumbers.WithPhoneNumbers(System.Collections.Generic.IReadOnlyCollection phoneNumbers) + { + CreateStudent.phoneNumbersPropertyInfo.SetValue(student, phoneNumbers); + return student; + } + + /// + Student IWithPhoneNumbers.WithPhoneNumbers(params M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[] phoneNumbers) + { + CreateStudent.phoneNumbersPropertyInfo.SetValue(student, phoneNumbers); + return student; + } + + /// + Student IWithPhoneNumbers.WithPhoneNumbers(params Func[] createPhoneNumbers) + { + CreateStudent.phoneNumbersPropertyInfo.SetValue(student, createPhoneNumbers.Select(createPhoneNumber => createPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.CreatePhone.InitialStep())).ToArray()); + return student; + } + + /// + Student IWithPhoneNumbers.WithPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone phoneNumber) + { + CreateStudent.phoneNumbersPropertyInfo.SetValue(student, new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[1]{ phoneNumber }); + return student; + } + + /// + Student IWithPhoneNumbers.WithPhoneNumber(Func createPhoneNumber) + { + CreateStudent.phoneNumbersPropertyInfo.SetValue(student, new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[1]{ createPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.CreatePhone.InitialStep()) }); + return student; + } + + /// + Student IWithPhoneNumbers.WithZeroPhoneNumbers() + { + CreateStudent.phoneNumbersPropertyInfo.SetValue(student, new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[0]); + return student; + } + + public interface ICreateStudent : IWithName + { + } + + public interface IWithName + { + IWithPhoneNumbers WithName(string name); + } + + public interface IWithPhoneNumbers + { + /// Sets the student's phone numbers. + /// The student's phone numbers. + Student WithPhoneNumbers(System.Collections.Generic.IReadOnlyCollection phoneNumbers); + + /// Sets the student's phone numbers. + /// The student's phone numbers. + Student WithPhoneNumbers(params M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[] phoneNumbers); + + /// Sets the student's phone numbers. + /// Functions for creating the student's phone numbers. + Student WithPhoneNumbers(params Func[] createPhoneNumbers); + + /// Sets the student's phone number. + /// The student's phone number. + Student WithPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone phoneNumber); + + /// Sets the student's phone number. + /// A function for creating the student's phone number. + Student WithPhoneNumber(Func createPhoneNumber); + + /// Specifies that the student has no phone numbers. + Student WithZeroPhoneNumbers(); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Phone.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Phone.cs new file mode 100644 index 0000000..ad1f9dd --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Phone.cs @@ -0,0 +1,18 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable all + +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments. + CommentedLambdaCollectionClass; + +[FluentApi] +public class Phone +{ + [FluentMember(0)] + public string Number { get; set; } + + [FluentMember(1)] + public string Usage { get; set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Student.cs new file mode 100644 index 0000000..e99dea6 --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Student.cs @@ -0,0 +1,34 @@ +// Non-nullable member is uninitialized + +#pragma warning disable CS8618 +// ReSharper disable All + +using System.Collections.Generic; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments. + CommentedLambdaCollectionClass; + +[FluentApi] +public class Student +{ + [FluentMember(0)] public string Name { get; set; } + + /// + /// Sets the student's phone numbers. + /// + /// The student's phone numbers. + /// Functions for creating the student's phone numbers. + /// + /// + /// Sets the student's phone number. + /// + /// The student's phone number. + /// A function for creating the student's phone number. + /// + /// + /// Specifies that the student has no phone numbers. + /// + [FluentCollection(1, "PhoneNumber")] + public IReadOnlyCollection PhoneNumbers { get; private set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/Student.cs index 34855f2..7eee361 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/Student.cs @@ -4,7 +4,8 @@ using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedPropertiesClassAdvanced; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments. + CommentedPropertiesClassAdvanced; [FluentApi] public class Student @@ -23,9 +24,11 @@ public class Student /// Sets the student's city. /// /// The student's city. + /// /// /// Set's the student's city to Boston. /// + /// /// /// Set's the student's city to null. /// diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs index 954f69c..fc459c4 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs @@ -22,6 +22,7 @@ internal class TestDataProvider : IEnumerable new object[] { "Abstract", "CustomFluentMethodNameClass", "Student" }, new object[] { "Abstract", "DefaultFluentMethodNameClass", "Student" }, new object[] { "Abstract", "DocumentationComments", "CommentedCompoundClass", "Student" }, + new object[] { "Abstract", "DocumentationComments", "CommentedLambdaCollectionClass", "Student|Phone" }, new object[] { "Abstract", "DocumentationComments", "CommentedMethodsClass", "Student" }, new object[] { "Abstract", "DocumentationComments", "CommentedPropertiesClass", "Student" }, new object[] { "Abstract", "DocumentationComments", "CommentedPropertiesClassAdvanced", "Student" }, From f0cf4dd0b8f4b044f7e90953fbb62cd410d38e72 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sat, 2 Aug 2025 15:46:02 +0200 Subject: [PATCH 27/61] fix: CommentedCompoundClass test --- .../FluentApiCommentsProviderTests.cs | 2 +- .../CreateStudent.expected.txt | 56 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs index 226871d..6b69994 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs @@ -10,7 +10,7 @@ namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes; -public class MyRefactoringProviderTests +public class FluentApiCommentsProviderTests { [Theory] [InlineData("UncommentedPropertyClass", "FirstName")] diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.expected.txt index e69de29..5f340b9 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.expected.txt +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.expected.txt @@ -0,0 +1,56 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedCompoundClass; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.IWithName +{ + private readonly Student student; + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + /// Sets the student's name. + /// The student's first name. + /// The student's last name. + public static Student WithName(string firstName, string lastName) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.FirstName = firstName; + createStudent.student.LastName = lastName; + return createStudent.student; + } + + /// + Student IWithName.WithName(string firstName, string lastName) + { + student.FirstName = firstName; + student.LastName = lastName; + return student; + } + + public interface ICreateStudent : IWithName + { + } + + public interface IWithName + { + /// Sets the student's name. + /// The student's first name. + /// The student's last name. + Student WithName(string firstName, string lastName); + } +} \ No newline at end of file From 219882b5783181e11f76b7b1f53670983bbefc36 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sat, 2 Aug 2025 16:01:39 +0200 Subject: [PATCH 28/61] fix: remove todo --- .../SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs index eafd3d8..0b9fa8c 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentMemberAttributeInfo.cs @@ -20,7 +20,7 @@ private FluentMemberAttributeInfo( internal string Method { get; } internal int ParameterPosition { get; } internal override IReadOnlyList FluentMethodNames => new string[] { Method }; - internal LambdaBuilderInfo? LambdaBuilderInfo { get; } // Todo: add to fluent method names, also for the FluentCollection. + internal LambdaBuilderInfo? LambdaBuilderInfo { get; } internal static FluentMemberAttributeInfo Create( AttributeData attributeData, From 0d78818deaa1c0d090de7537f237ca19cc5a8518 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sun, 3 Aug 2025 11:20:36 +0200 Subject: [PATCH 29/61] feat: FluentApiCommentsProvider (wip) --- src/ExampleProject/MyPerson.cs | 2 +- .../FluentApiCommentsProvider.cs | 124 ++++++++++++------ .../SourceAnalyzers/FluentApiDiagnostics.cs | 4 +- .../AttributeDataExtended.cs | 2 +- src/M31.FluentApi.Storybook/01_Basics.cs | 6 +- .../Attributes/FluentLambdaAttribute.cs | 4 +- 6 files changed, 95 insertions(+), 47 deletions(-) diff --git a/src/ExampleProject/MyPerson.cs b/src/ExampleProject/MyPerson.cs index 17e085c..16da198 100644 --- a/src/ExampleProject/MyPerson.cs +++ b/src/ExampleProject/MyPerson.cs @@ -39,4 +39,4 @@ class MyPerson /// This parameter will not be taken into account. [FluentMember(4, "LivingIn")] public string City { get; set; } -} +} \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiCommentsProvider.cs b/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiCommentsProvider.cs index 98f1b7f..3b08968 100644 --- a/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiCommentsProvider.cs +++ b/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiCommentsProvider.cs @@ -1,11 +1,12 @@ using System.Composition; using M31.FluentApi.Generator.Commons; +using M31.FluentApi.Generator.SourceGenerators; +using M31.FluentApi.Generator.SourceGenerators.AttributeElements; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; namespace M31.FluentApi.Generator.SourceAnalyzers; @@ -23,15 +24,8 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; } - // SemanticModel? semanticModel = await document.GetSemanticModelAsync(context.CancellationToken); - // if (semanticModel == null) - // { - // return; - // } - SyntaxNode node = root.FindNode(context.Span); - // Check for property, field, or method declaration if (node is not MemberDeclarationSyntax memberSyntax) { return; @@ -42,6 +36,11 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; } + if (context.CancellationToken.IsCancellationRequested) + { + return; + } + if (!typeSyntax.AttributeLists .SelectMany(list => list.Attributes) .Any(attr => attr.IsFluentApiAttributeSyntax())) @@ -49,47 +48,96 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte return; } - // // Only offer refactoring if member has [FluentMember] or no comment yet - // var memberSymbol = semanticModel.GetDeclaredSymbol(memberSyntax, context.CancellationToken); - // if (memberSymbol == null) - // return; - // - // bool hasFluentMember = memberSymbol.GetAttributes().Any(attr => - // attr.AttributeClass?.Name == "FluentMemberAttribute" || - // attr.AttributeClass?.ToDisplayString() == "FluentMemberAttribute"); - // - // if (!hasFluentMember) - // return; - - CodeAction action = CodeAction.Create("Add doc comment b", - c => AddFluentSummaryAsync(context.Document, null!, c), + SemanticModel? semanticModel = await document.GetSemanticModelAsync(context.CancellationToken); + if (semanticModel == null) + { + return; + } + + ISymbol? memberSymbol = semanticModel.GetDeclaredSymbol(memberSyntax, context.CancellationToken); + if (memberSymbol == null) + { + return; + } + + if (context.CancellationToken.IsCancellationRequested) + { + return; + } + + AttributeDataExtended[] attributeDataExtended = + memberSymbol.GetAttributes() + .Select(AttributeDataExtended.Create) + .OfType() + .ToArray(); + + if (!attributeDataExtended.Any(a => Attributes.IsMainAttribute(a.FullName))) + { + return; + } + + CodeAction action = CodeAction.Create("Create fluent API documentation comments", + c => AddFluentSummaryAsync( + memberSyntax, memberSymbol, context.Document, root, semanticModel, typeSyntax, c), nameof(FluentApiCommentsProvider)); context.RegisterRefactoring(action); } - private async Task AddFluentSummaryAsync(Document document, MethodDeclarationSyntax methodDecl, + private async Task AddFluentSummaryAsync( + MemberDeclarationSyntax memberSyntax, + ISymbol memberSymbol, + Document document, + SyntaxNode root, + SemanticModel semanticModel, + TypeDeclarationSyntax typeSyntax, CancellationToken cancellationToken) { - return document; - var leadingTrivia = methodDecl.GetLeadingTrivia(); - - var xmlComment = SyntaxFactory.TriviaList( - SyntaxFactory.Comment("/// "), - SyntaxFactory.CarriageReturnLineFeed, - SyntaxFactory.Comment("/// ..."), - SyntaxFactory.CarriageReturnLineFeed, - SyntaxFactory.Comment("/// "), - SyntaxFactory.CarriageReturnLineFeed - ); + ClassInfoResult classInfoResult = + ClassInfoFactory.CreateFluentApiClassInfo( + semanticModel, + typeSyntax, + SourceGenerator.GeneratorConfig, + cancellationToken); + + if (classInfoResult.ClassInfo == null) + { + return document; + } - var newMethod = methodDecl.WithLeadingTrivia(xmlComment.AddRange(leadingTrivia)); + // todo: handle groups. + FluentApiInfo? info = + classInfoResult.ClassInfo.FluentApiInfos.FirstOrDefault(i => i.SymbolInfo.Name == memberSymbol.Name); - var root = await document.GetSyntaxRootAsync(cancellationToken); - if (root == null) + if (info == null) + { return document; + } - var newRoot = root.ReplaceNode(methodDecl, newMethod); + SyntaxTriviaList leadingTrivia = memberSyntax.GetLeadingTrivia(); + string padding = GetPadding(leadingTrivia); + string nl = classInfoResult.ClassInfo.NewLineString; + + var xmlComment = SyntaxFactory.TriviaList( + SyntaxFactory.Comment($"/// {nl}"), + SyntaxFactory.Comment($"{padding}/// ...{nl}"), + SyntaxFactory.Comment($"{padding}/// {nl}{padding}")); + + MemberDeclarationSyntax newMemberSyntax = memberSyntax.WithLeadingTrivia(leadingTrivia.AddRange(xmlComment)); + + SyntaxNode newRoot = root.ReplaceNode(memberSyntax, newMemberSyntax); return document.WithSyntaxRoot(newRoot); } + + private static string GetPadding(SyntaxTriviaList leadingTrivia) + { + const string fallback = " "; + SyntaxTrivia? first = leadingTrivia.FirstOrDefault(); + if (first == null || first.Value.Kind() != SyntaxKind.WhitespaceTrivia) + { + return fallback; + } + + return first.Value.ToString(); + } } \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiDiagnostics.cs b/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiDiagnostics.cs index 4572acf..9d0b5c0 100644 --- a/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiDiagnostics.cs +++ b/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiDiagnostics.cs @@ -360,9 +360,9 @@ internal static class FluentLambdaMemberWithoutFluentApi { internal static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor( id: "M31FA022", - title: "Fluent lambda member without Fluent API", + title: "Fluent lambda member without fluent API", messageFormat: "FluentLambda can not be used on a member of type '{0}'. " + - "Type '{0}' does not have a Fluent API.", + "Type '{0}' does not have a fluent API.", category: "M31.Usage", defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeElements/AttributeDataExtended.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeElements/AttributeDataExtended.cs index bc6b31c..9d9c1f2 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeElements/AttributeDataExtended.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeElements/AttributeDataExtended.cs @@ -4,7 +4,7 @@ namespace M31.FluentApi.Generator.SourceGenerators.AttributeElements; internal class AttributeDataExtended { - internal AttributeDataExtended(AttributeData attributeData, string fullName, string shortName) + private AttributeDataExtended(AttributeData attributeData, string fullName, string shortName) { AttributeData = attributeData; FullName = fullName; diff --git a/src/M31.FluentApi.Storybook/01_Basics.cs b/src/M31.FluentApi.Storybook/01_Basics.cs index e5ad1f2..43ef494 100644 --- a/src/M31.FluentApi.Storybook/01_Basics.cs +++ b/src/M31.FluentApi.Storybook/01_Basics.cs @@ -311,12 +311,12 @@ public static void UseTheGeneratedFluentApi() namespace NestedFluentApis { /* Lastly, I would like to demonstrate the effect of applying the FluentMember attribute to a member whose type has - its own Fluent API. In this scenario, an additional builder method that accepts a lambda expression is generated. - In the example below, the Student class has an Address property, and the Address class has its own Fluent API. + its own fluent API. In this scenario, an additional builder method that accepts a lambda expression is generated. + In the example below, the Student class has an Address property, and the Address class has its own fluent API. The advantage of the lambda method is that the user does not have to figure out the builder name of the Address class when creating a student. Similarly, additional lambda methods are generated if the FluentCollection attribute is applied to a collection - whose element type has its own Fluent API. The employee class below is modeled with several addresses that can be + whose element type has its own fluent API. The employee class below is modeled with several addresses that can be conveniently set using lambda methods. In the next chapter, you will learn how to create non-linear paths with control attributes. diff --git a/src/M31.FluentApi/Attributes/FluentLambdaAttribute.cs b/src/M31.FluentApi/Attributes/FluentLambdaAttribute.cs index 6961f32..1f99c06 100644 --- a/src/M31.FluentApi/Attributes/FluentLambdaAttribute.cs +++ b/src/M31.FluentApi/Attributes/FluentLambdaAttribute.cs @@ -4,10 +4,10 @@ namespace M31.FluentApi.Attributes; /// /// Generates a builder method that accepts a lambda expression for creating the target field or property with its -/// Fluent API. +/// fluent API. /// [Obsolete( - "Use FluentMember instead. Lambda methods are generated by default if the decorated member has a Fluent API.", + "Use FluentMember instead. Lambda methods are generated by default if the decorated member has a fluent API.", false)] [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public class FluentLambdaAttribute : Attribute From 29e9475697a485d35fc1df0373768f8b3475c836 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Sun, 3 Aug 2025 11:36:33 +0200 Subject: [PATCH 30/61] test: improve CommentedCompoundClass test --- .../CreateStudent.expected.txt | 27 +++++++++++++++---- .../CommentedCompoundClass/CreateStudent.g.cs | 27 +++++++++++++++---- .../CommentedCompoundClass/Student.cs | 11 ++++++++ 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.expected.txt index 5f340b9..9efe234 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.expected.txt +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.expected.txt @@ -9,7 +9,8 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationC public class CreateStudent : CreateStudent.ICreateStudent, - CreateStudent.IWithName + CreateStudent.IWithName, + CreateStudent.IStudies { private readonly Student student; @@ -26,19 +27,27 @@ public class CreateStudent : /// Sets the student's name. /// The student's first name. /// The student's last name. - public static Student WithName(string firstName, string lastName) + public static IStudies WithName(string firstName, string lastName) { CreateStudent createStudent = new CreateStudent(); createStudent.student.FirstName = firstName; createStudent.student.LastName = lastName; - return createStudent.student; + return createStudent; } /// - Student IWithName.WithName(string firstName, string lastName) + IStudies IWithName.WithName(string firstName, string lastName) { student.FirstName = firstName; student.LastName = lastName; + return this; + } + + /// + Student IStudies.Studies(string courseOfStudy, int semester) + { + student.CourseOfStudy = courseOfStudy; + student.Semester = semester; return student; } @@ -51,6 +60,14 @@ public class CreateStudent : /// Sets the student's name. /// The student's first name. /// The student's last name. - Student WithName(string firstName, string lastName); + IStudies WithName(string firstName, string lastName); + } + + public interface IStudies + { + /// Sets the student's course of study and current semester. + /// The student's course of study. + /// The student's current semester. + Student Studies(string courseOfStudy, int semester); } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.g.cs index 5f340b9..9efe234 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.g.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.g.cs @@ -9,7 +9,8 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationC public class CreateStudent : CreateStudent.ICreateStudent, - CreateStudent.IWithName + CreateStudent.IWithName, + CreateStudent.IStudies { private readonly Student student; @@ -26,19 +27,27 @@ public static ICreateStudent InitialStep() /// Sets the student's name. /// The student's first name. /// The student's last name. - public static Student WithName(string firstName, string lastName) + public static IStudies WithName(string firstName, string lastName) { CreateStudent createStudent = new CreateStudent(); createStudent.student.FirstName = firstName; createStudent.student.LastName = lastName; - return createStudent.student; + return createStudent; } /// - Student IWithName.WithName(string firstName, string lastName) + IStudies IWithName.WithName(string firstName, string lastName) { student.FirstName = firstName; student.LastName = lastName; + return this; + } + + /// + Student IStudies.Studies(string courseOfStudy, int semester) + { + student.CourseOfStudy = courseOfStudy; + student.Semester = semester; return student; } @@ -51,6 +60,14 @@ public interface IWithName /// Sets the student's name. /// The student's first name. /// The student's last name. - Student WithName(string firstName, string lastName); + IStudies WithName(string firstName, string lastName); + } + + public interface IStudies + { + /// Sets the student's course of study and current semester. + /// The student's course of study. + /// The student's current semester. + Student Studies(string courseOfStudy, int semester); } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/Student.cs index 2b09665..b06512a 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/Student.cs @@ -19,4 +19,15 @@ public class Student /// The student's last name. [FluentMember(0, "WithName")] public string LastName { get; set; } + + /// + /// Sets the student's course of study and current semester. + /// + /// The student's course of study. + [FluentMember(1, "Studies")] + public string CourseOfStudy { get; set; } + + /// The student's current semester. + [FluentMember(1, "Studies")] + public int Semester { get; set; } } \ No newline at end of file From 3810a6d44dab4e2f35ebabf6ac4403de4ac8e30e Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Mon, 4 Aug 2025 09:23:51 +0200 Subject: [PATCH 31/61] feat: MethodsToCommentsTemplate --- .../MethodsToCommentsTemplate.cs | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/MethodsToCommentsTemplate.cs diff --git a/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/MethodsToCommentsTemplate.cs b/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/MethodsToCommentsTemplate.cs new file mode 100644 index 0000000..b7d6831 --- /dev/null +++ b/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/MethodsToCommentsTemplate.cs @@ -0,0 +1,71 @@ +using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.Commons; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.MethodCreation; + +namespace M31.FluentApi.Generator.SourceAnalyzers.DocumentationComments; + +internal class MethodsToCommentsTemplate +{ + private readonly List comments; + + private MethodsToCommentsTemplate() + { + comments = new List(); + } + + internal static IReadOnlyCollection CreateCommentsTemplate(BuilderMethods builderMethods) + { + if (builderMethods.Methods.Count == 0) + { + return Array.Empty(); + } + + MethodsToCommentsTemplate instance = new MethodsToCommentsTemplate(); + + IGrouping[] groups = builderMethods.Methods.GroupBy(m => m.MethodName).ToArray(); + + if (groups.Length == 1) + { + instance.CreateCommentsTemplateWithoutMethodNames(groups[0].ToArray()); + } + else + { + foreach (IGrouping group in groups) + { + instance.CreateCommentsTemplateWithMethodNames(group.ToArray()); + } + } + + return instance.comments; + } + + private void CreateCommentsTemplateWithoutMethodNames(BuilderMethod[] sameNameBuilderMethods) + { + comments.Add("/// "); + comments.Add("/// ..."); + comments.Add("/// "); + + foreach (string parameterName in GetDistinctParameterNames(sameNameBuilderMethods)) + { + comments.Add($"/// ..."); + } + } + + private void CreateCommentsTemplateWithMethodNames(BuilderMethod[] sameNameBuilderMethods) + { + string method = sameNameBuilderMethods[0].MethodName; + + comments.Add($"/// "); + comments.Add("/// ..."); + comments.Add("/// "); + + foreach (string parameterName in GetDistinctParameterNames(sameNameBuilderMethods)) + { + comments.Add($"/// ..."); + } + } + + private static IEnumerable GetDistinctParameterNames(BuilderMethod[] builderMethods) + { + return builderMethods.SelectMany(m => m.Parameters).Select(p => p.Name).Distinct(); + } +} \ No newline at end of file From fdd161dd1cbd0947089552661a15b973924a634d Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Mon, 4 Aug 2025 09:25:06 +0200 Subject: [PATCH 32/61] chore: minor improvements --- .../DocumentationGeneration/CommentsGenerator.cs | 2 +- .../SourceGenerators/FluentApiInfoGroup.cs | 4 ++-- .../SourceGenerators/FluentApiInfoGroupCreator.cs | 3 ++- .../AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs index e4e24e0..76b36af 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs @@ -45,7 +45,7 @@ private void HandleMemberSymbolInfo(MemberSymbolInfo memberInfo, FluentApiInfo f } } - private string? TryGetSingleMethodName(FluentApiInfo fluentApiInfo) + private static string? TryGetSingleMethodName(FluentApiInfo fluentApiInfo) { string[] fluentMethodNames = fluentApiInfo.AttributeInfo.FluentMethodNames .Concat(fluentApiInfo.OrthogonalAttributeInfos.SelectMany(o => o.FluentMethodNames)).Distinct().ToArray(); diff --git a/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoGroup.cs b/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoGroup.cs index b73b858..e508e47 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoGroup.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoGroup.cs @@ -8,7 +8,7 @@ internal FluentApiInfoGroup( bool isSkippable, string fluentMethodName, Type attributeInfoType, - IReadOnlyCollection fluentApiInfos) + IReadOnlyList fluentApiInfos) { BuilderStep = builderStep; NextBuilderStep = nextBuilderStep; @@ -23,6 +23,6 @@ internal FluentApiInfoGroup( internal bool IsSkippable { get; } internal string FluentMethodName { get; } internal Type AttributeInfoType { get; } - internal IReadOnlyCollection FluentApiInfos { get; } + internal IReadOnlyList FluentApiInfos { get; } internal bool IsCompoundGroup => FluentApiInfos.Count > 1; } \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoGroupCreator.cs b/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoGroupCreator.cs index 479786b..7987e13 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoGroupCreator.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoGroupCreator.cs @@ -66,7 +66,8 @@ private IReadOnlyCollection CreateGroups(IReadOnlyCollection nextBuilderStep, groupIsSkippable, group.fluentMethodName, - group.type, group.infoArray)); + group.type, + group.infoArray)); } return infoGroups; diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs index 6b69994..b32ef67 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs @@ -1,7 +1,7 @@ using System.IO; using System.Threading.Tasks; using M31.FluentApi.Attributes; -using M31.FluentApi.Generator.SourceAnalyzers; +using M31.FluentApi.Generator.SourceAnalyzers.DocumentationComments; using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.Testing.Verifiers; using Xunit; From 90fe87539e2552a44c65463e4fbb8822e4cf1915 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Tue, 5 Aug 2025 10:46:23 +0200 Subject: [PATCH 33/61] feat: FluentApiCommentsProvider (wip) --- src/ExampleProject/MyPerson.cs | 16 ++--- src/ExampleProject/Program.cs | 4 +- .../MethodCreation/BuilderMethodCreator.cs | 7 ++ .../MethodCreation/BuilderMethods.cs | 2 +- .../CodeBoardElements/CodeBoard.cs | 5 +- .../TransformedComments.cs | 7 +- .../CodeGeneration/CodeGenerator.cs | 65 +++++++++++++++++-- .../FluentApiCommentsProvider.cs | 44 +++++++++---- .../MethodsToCommentsTemplate.cs | 14 +++- 9 files changed, 125 insertions(+), 39 deletions(-) rename src/M31.FluentApi.Generator/SourceAnalyzers/{ => DocumentationComments}/FluentApiCommentsProvider.cs (71%) diff --git a/src/ExampleProject/MyPerson.cs b/src/ExampleProject/MyPerson.cs index 16da198..1ea299c 100644 --- a/src/ExampleProject/MyPerson.cs +++ b/src/ExampleProject/MyPerson.cs @@ -5,38 +5,30 @@ namespace ExampleProject; [FluentApi] class MyPerson { - /// - /// Sets the student's first name. - /// - /// The student's first name. [FluentMember(0)] public string FirstName { get; set; } - /// - /// Sets the student's last name. - /// - /// The student's last name. - [FluentMember(1)] + [FluentMember(0)] public string LastName { get; set; } /// /// Sets the student's age. /// /// The student's age. - [FluentMember(2, "OfAge")] + [FluentMember(1, "OfAge")] public int Age { get; set; } /// /// Sets the student's semester. /// /// The student's semester. - [FluentMember(3, "InSemester")] + [FluentMember(2, "InSemester")] public int Semester { get; set; } /// /// This summary will not be taken into account. /// /// This parameter will not be taken into account. - [FluentMember(4, "LivingIn")] + [FluentMember(3, "LivingIn")] public string City { get; set; } } \ No newline at end of file diff --git a/src/ExampleProject/Program.cs b/src/ExampleProject/Program.cs index ba3b1ec..aaf1009 100644 --- a/src/ExampleProject/Program.cs +++ b/src/ExampleProject/Program.cs @@ -6,8 +6,8 @@ // MyPerson // -MyPerson mp = CreateMyPerson.WithFirstName("Kevin").WithLastName("Schaal") - .OfAge(22).InSemester(4).LivingIn("Tübingen"); +// MyPerson mp = CreateMyPerson.WithFirstName("Kevin").WithLastName("Schaal") +// .OfAge(22).InSemester(4).LivingIn("Tübingen"); // Student // diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethodCreator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethodCreator.cs index 2bdd68e..f10d7bf 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethodCreator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethodCreator.cs @@ -22,6 +22,13 @@ internal BuilderMethodCreator(FluentApiInfoGroup group, CodeBoard codeBoard) internal IReadOnlyCollection FluentApiInfos => group.FluentApiInfos; public BuilderMethods CreateBuilderMethods(MethodCreator methodCreator) + { + BuilderMethods builderMethods = CreateBuilderMethodsInternal(methodCreator); + codeBoard.GroupsToMethods[group] = builderMethods; + return builderMethods; + } + + private BuilderMethods CreateBuilderMethodsInternal(MethodCreator methodCreator) { if (FluentApiInfos.Count == 0) { diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethods.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethods.cs index 5f72b3d..e725aa6 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethods.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethods.cs @@ -2,7 +2,7 @@ namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.MethodCreation; -internal class BuilderMethods +internal class BuilderMethods // todo: rename this or the other class with the same name. { private readonly List methods; private readonly HashSet requiredUsings; diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/CodeBoard.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/CodeBoard.cs index 1b1127a..15967aa 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/CodeBoard.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/CodeBoard.cs @@ -1,5 +1,6 @@ using M31.FluentApi.Generator.CodeBuilding; using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.Commons; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.MethodCreation; using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.MethodCreation.Forks; using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; using M31.FluentApi.Generator.SourceAnalyzers; @@ -32,8 +33,9 @@ private CodeBoard( Constructor = null; StaticConstructor = null; InnerBodyCreationDelegates = new InnerBodyCreationDelegates(); - BuilderMethodToAttributeData = new Dictionary(); TransformedComments = new TransformedComments(); + GroupsToMethods = new Dictionary(); + BuilderMethodToAttributeData = new Dictionary(); Forks = new List(); ReservedVariableNames = new ReservedVariableNames(); diagnostics = new List(); @@ -50,6 +52,7 @@ private CodeBoard( internal Method? StaticConstructor { get; set; } internal InnerBodyCreationDelegates InnerBodyCreationDelegates { get; } internal TransformedComments TransformedComments { get; } + internal Dictionary GroupsToMethods { get; } internal Dictionary BuilderMethodToAttributeData { get; } internal IReadOnlyList Forks { get; set; } internal ReservedVariableNames ReservedVariableNames { get; } diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs index eafc0e5..5420de0 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs @@ -56,6 +56,11 @@ internal void AssignMethodComments(MethodSymbolInfo methodSymbolInfo, Comments c internal Comments GetMethodComments(MethodSymbolInfo methodSymbolInfo) { - return methodComments[methodSymbolInfo]; + if (methodComments.TryGetValue(methodSymbolInfo, out Comments comments)) + { + return comments; + } + + return new Comments(Array.Empty()); } } \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeGenerator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeGenerator.cs index 4ee0370..4e5778c 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeGenerator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeGenerator.cs @@ -6,12 +6,53 @@ using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.MethodCreation.Forks; using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements; using M31.FluentApi.Generator.SourceGenerators; +using BuilderMethods = M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.MethodCreation.BuilderMethods; namespace M31.FluentApi.Generator.CodeGeneration; internal static class CodeGenerator { internal static CodeGeneratorResult GenerateCode(FluentApiClassInfo classInfo, CancellationToken cancellationToken) + { + BuilderAndTargetInfo builderAndTargetInfo = CreateBuilderAndTargetInfo(classInfo); + CodeBoard codeBoard = CreateCodeBoard(classInfo, cancellationToken, builderAndTargetInfo); + + List actors = new List() + { + new CommentsGenerator(), + new EntityFieldGenerator(), + new ConstructorGenerator(), + new InnerBodyCreator(), + new ForkCreator(), + new DuplicateMethodsChecker(), + new InitialStepMethodGenerator(), + new BuilderGenerator(), + }; + + foreach (ICodeBoardActor actor in actors) + { + if (cancellationToken.IsCancellationRequested || codeBoard.HasErrors) + { + break; + } + + actor.Modify(codeBoard); + } + + if (cancellationToken.IsCancellationRequested) + { + return CodeGeneratorResult.Cancelled(); + } + + if (codeBoard.HasErrors) + { + return CodeGeneratorResult.WithErrors(codeBoard.Diagnostics); + } + + return new CodeGeneratorResult(codeBoard.CodeFile.ToString(), codeBoard.Diagnostics); + } + + private static BuilderAndTargetInfo CreateBuilderAndTargetInfo(FluentApiClassInfo classInfo) { BuilderAndTargetInfo builderAndTargetInfo = new BuilderAndTargetInfo( @@ -22,7 +63,12 @@ internal static CodeGeneratorResult GenerateCode(FluentApiClassInfo classInfo, C classInfo.IsInternal, classInfo.ConstructorInfo, classInfo.BuilderClassName); + return builderAndTargetInfo; + } + private static CodeBoard CreateCodeBoard(FluentApiClassInfo classInfo, CancellationToken cancellationToken, + BuilderAndTargetInfo builderAndTargetInfo) + { CodeBoard codeBoard = CodeBoard.Create( builderAndTargetInfo, classInfo.FluentApiInfos, @@ -30,17 +76,22 @@ internal static CodeGeneratorResult GenerateCode(FluentApiClassInfo classInfo, C classInfo.UsingStatements, classInfo.NewLineString, cancellationToken); + return codeBoard; + } + + internal static Dictionary GenerateBuilderMethods( + FluentApiClassInfo classInfo, + CancellationToken cancellationToken) + { + BuilderAndTargetInfo builderAndTargetInfo = CreateBuilderAndTargetInfo(classInfo); + CodeBoard codeBoard = CreateCodeBoard(classInfo, cancellationToken, builderAndTargetInfo); List actors = new List() { - new CommentsGenerator(), new EntityFieldGenerator(), new ConstructorGenerator(), new InnerBodyCreator(), new ForkCreator(), - new DuplicateMethodsChecker(), - new InitialStepMethodGenerator(), - new BuilderGenerator(), }; foreach (ICodeBoardActor actor in actors) @@ -55,14 +106,14 @@ internal static CodeGeneratorResult GenerateCode(FluentApiClassInfo classInfo, C if (cancellationToken.IsCancellationRequested) { - return CodeGeneratorResult.Cancelled(); + return new Dictionary(); } if (codeBoard.HasErrors) { - return CodeGeneratorResult.WithErrors(codeBoard.Diagnostics); + return new Dictionary(); } - return new CodeGeneratorResult(codeBoard.CodeFile.ToString(), codeBoard.Diagnostics); + return codeBoard.GroupsToMethods; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiCommentsProvider.cs b/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/FluentApiCommentsProvider.cs similarity index 71% rename from src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiCommentsProvider.cs rename to src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/FluentApiCommentsProvider.cs index 3b08968..b0f6164 100644 --- a/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiCommentsProvider.cs +++ b/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/FluentApiCommentsProvider.cs @@ -1,4 +1,6 @@ using System.Composition; +using M31.FluentApi.Generator.CodeGeneration; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.MethodCreation; using M31.FluentApi.Generator.Commons; using M31.FluentApi.Generator.SourceGenerators; using M31.FluentApi.Generator.SourceGenerators.AttributeElements; @@ -8,7 +10,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace M31.FluentApi.Generator.SourceAnalyzers; +namespace M31.FluentApi.Generator.SourceAnalyzers.DocumentationComments; [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(FluentApiCommentsProvider))] [Shared] @@ -84,7 +86,7 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte context.RegisterRefactoring(action); } - private async Task AddFluentSummaryAsync( + private Task AddFluentSummaryAsync( MemberDeclarationSyntax memberSyntax, ISymbol memberSymbol, Document document, @@ -102,31 +104,47 @@ private async Task AddFluentSummaryAsync( if (classInfoResult.ClassInfo == null) { - return document; + return Task.FromResult(document); } - // todo: handle groups. - FluentApiInfo? info = - classInfoResult.ClassInfo.FluentApiInfos.FirstOrDefault(i => i.SymbolInfo.Name == memberSymbol.Name); + FluentApiInfoGroup? group = classInfoResult.ClassInfo.AdditionalInfo.FluentApiInfoGroups.FirstOrDefault( + g => g.FluentApiInfos.Any(i => i.SymbolInfo.Name == memberSymbol.Name)); - if (info == null) + if (group == null) { - return document; + return Task.FromResult(document); + } + + Dictionary groupToMethods = + CodeGenerator.GenerateBuilderMethods(classInfoResult.ClassInfo, cancellationToken); + BuilderMethods builderMethods = groupToMethods[group]; + List commentsTemplate = MethodsToCommentsTemplate.CreateCommentsTemplate(builderMethods); + + if (commentsTemplate.Count <= 2) + { + return Task.FromResult(document); } SyntaxTriviaList leadingTrivia = memberSyntax.GetLeadingTrivia(); string padding = GetPadding(leadingTrivia); string nl = classInfoResult.ClassInfo.NewLineString; - var xmlComment = SyntaxFactory.TriviaList( - SyntaxFactory.Comment($"/// {nl}"), - SyntaxFactory.Comment($"{padding}/// ...{nl}"), - SyntaxFactory.Comment($"{padding}/// {nl}{padding}")); + commentsTemplate[0] = $"{commentsTemplate[0]}{nl}"; + for (int i = 1; i < commentsTemplate.Count - 1; i++) + { + commentsTemplate[i] = $"{padding}{commentsTemplate[i]}{nl}"; + } + + commentsTemplate[commentsTemplate.Count - 1] = + $"{padding}{commentsTemplate[commentsTemplate.Count - 1]}{nl}{padding}"; + + SyntaxTriviaList xmlComment = + SyntaxFactory.TriviaList(commentsTemplate.Select(SyntaxFactory.Comment).ToArray()); MemberDeclarationSyntax newMemberSyntax = memberSyntax.WithLeadingTrivia(leadingTrivia.AddRange(xmlComment)); SyntaxNode newRoot = root.ReplaceNode(memberSyntax, newMemberSyntax); - return document.WithSyntaxRoot(newRoot); + return Task.FromResult(document.WithSyntaxRoot(newRoot)); } private static string GetPadding(SyntaxTriviaList leadingTrivia) diff --git a/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/MethodsToCommentsTemplate.cs b/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/MethodsToCommentsTemplate.cs index b7d6831..15dc29e 100644 --- a/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/MethodsToCommentsTemplate.cs +++ b/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/MethodsToCommentsTemplate.cs @@ -12,11 +12,11 @@ private MethodsToCommentsTemplate() comments = new List(); } - internal static IReadOnlyCollection CreateCommentsTemplate(BuilderMethods builderMethods) + internal static List CreateCommentsTemplate(BuilderMethods builderMethods) { if (builderMethods.Methods.Count == 0) { - return Array.Empty(); + return new List(); } MethodsToCommentsTemplate instance = new MethodsToCommentsTemplate(); @@ -40,6 +40,11 @@ internal static IReadOnlyCollection CreateCommentsTemplate(BuilderMethod private void CreateCommentsTemplateWithoutMethodNames(BuilderMethod[] sameNameBuilderMethods) { + if (comments.Count != 0) + { + comments.Add("///"); + } + comments.Add("/// "); comments.Add("/// ..."); comments.Add("/// "); @@ -54,6 +59,11 @@ private void CreateCommentsTemplateWithMethodNames(BuilderMethod[] sameNameBuild { string method = sameNameBuilderMethods[0].MethodName; + if (comments.Count != 0) + { + comments.Add("///"); + } + comments.Add($"/// "); comments.Add("/// ..."); comments.Add("/// "); From 2d2ae79049648de722b81817ae7abfb8a62c25b6 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Tue, 5 Aug 2025 11:48:36 +0200 Subject: [PATCH 34/61] test: FluentApiCommentsProviderTests --- src/ExampleProject/MyPerson.cs | 15 ++++- .../FluentApiCommentsProviderTests.cs | 29 +++++++-- .../Helpers/AnalyzerAndCodeFixVerifier.cs | 4 +- .../Helpers/TestSourceCodeReader.cs | 5 +- .../FluentApiComments/Student.Age.txt | 51 ++++++++++++++++ .../FluentApiComments/Student.BornOn.txt | 51 ++++++++++++++++ .../FluentApiComments/Student.City.txt | 59 ++++++++++++++++++ .../FluentApiComments/Student.FirstName.txt | 52 ++++++++++++++++ .../FluentApiComments/Student.Friends.txt | 60 +++++++++++++++++++ .../FluentApiComments/Student.IsHappy.txt | 59 ++++++++++++++++++ .../FluentApiComments/Student.LastName.txt | 52 ++++++++++++++++ .../FluentApiComments/Student.Semester.txt | 55 +++++++++++++++++ .../TestClasses/FluentApiComments/Student.cs | 47 +++++++++++++++ .../UncommentedPropertyClass/Student.cs | 14 ----- .../Student.fixed.txt | 18 ------ 15 files changed, 527 insertions(+), 44 deletions(-) create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Age.txt create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.BornOn.txt create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.City.txt create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.FirstName.txt create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Friends.txt create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.IsHappy.txt create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.LastName.txt create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Semester.txt create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.cs delete mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/UncommentedPropertyClass/Student.cs delete mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/UncommentedPropertyClass/Student.fixed.txt diff --git a/src/ExampleProject/MyPerson.cs b/src/ExampleProject/MyPerson.cs index 1ea299c..462c8e7 100644 --- a/src/ExampleProject/MyPerson.cs +++ b/src/ExampleProject/MyPerson.cs @@ -5,10 +5,21 @@ namespace ExampleProject; [FluentApi] class MyPerson { - [FluentMember(0)] + /// + /// ... + /// + /// ... + /// ... + [FluentMember(0, "Name")] public string FirstName { get; set; } - [FluentMember(0)] + // todo: check generated code for this duplicate. Also test lambda! + /// + /// ... + /// + /// ... + /// ... + [FluentMember(0, "Name")] public string LastName { get; set; } /// diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs index b32ef67..77ca763 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.Testing.Verifiers; using Xunit; using M31.FluentApi.Tests.AnalyzerAndCodeFixes.Helpers; +using Microsoft.CodeAnalysis.Testing; using static M31.FluentApi.Tests.AnalyzerAndCodeFixes.Helpers.TestSourceCodeReader; namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes; @@ -13,18 +14,34 @@ namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes; public class FluentApiCommentsProviderTests { [Theory] - [InlineData("UncommentedPropertyClass", "FirstName")] - public async Task CanProvideFluentApiComments(string commentTestClass, string selectedSpan) + [InlineData("FluentApiComments", "FirstName", "FirstName")] + [InlineData("FluentApiComments", "LastNa", "LastName")] + [InlineData("FluentApiComments", "int Age ", "Age")] + [InlineData("FluentApiComments", " BornOn", "BornOn")] + [InlineData("FluentApiComments", "int Sem", "Semester")] + [InlineData("FluentApiComments", " City ", "City")] + [InlineData("FluentApiComments", "IsHappy ", "IsHappy")] + [InlineData("FluentApiComments", " Friends", "Friends")] + public async Task CanProvideFluentApiComments(string commentTestClass, string selectedSpan, string member) { - SourceWithFix source = ReadSource(Path.Join("FluentApiComments", commentTestClass), "Student"); - await new CSharpCodeRefactoringTest + SourceWithFix source = ReadSource(commentTestClass, "Student", $"Student.{member}.txt"); + var test = new CSharpCodeRefactoringTest { TestCode = source.Source.SelectSpan(selectedSpan), FixedCode = source.FixedSource!, +#if NET6_0 + ReferenceAssemblies = new ReferenceAssemblies( + "net6.0", + new PackageIdentity("Microsoft.NETCore.App.Ref", "6.0.0"), + Path.Combine("ref", "net6.0")), +#else + throw new NotImplementedException(); +#endif TestState = { - AdditionalReferences = { typeof(FluentApiAttribute).Assembly } + AdditionalReferences = { typeof(FluentApiAttribute).Assembly } } - }.RunAsync(); + }; + await test.RunAsync(); } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/AnalyzerAndCodeFixVerifier.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/AnalyzerAndCodeFixVerifier.cs index 296830a..14c3b6f 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/AnalyzerAndCodeFixVerifier.cs +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/AnalyzerAndCodeFixVerifier.cs @@ -62,8 +62,8 @@ internal CodeFixTest( new PackageIdentity("Microsoft.NETCore.App.Ref", "6.0.0"), Path.Combine("ref", "net6.0")); #else - ReferenceAssemblies = ReferenceAssemblies.Net.Net50; -#endif // todo: remove + throw new NotImplementedException(); +#endif TestState.AdditionalReferences.Add(typeof(FluentApiAttribute).Assembly); } diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/TestSourceCodeReader.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/TestSourceCodeReader.cs index f0703e0..59cb0d5 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/TestSourceCodeReader.cs +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/TestSourceCodeReader.cs @@ -4,10 +4,11 @@ namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.Helpers; internal static class TestSourceCodeReader { - internal static SourceWithFix ReadSource(string testClassFolder, string @class) + internal static SourceWithFix ReadSource(string testClassFolder, string @class, string? @fixed = null) { + @fixed ??= $"{@class}.fixed.txt"; string source = ReadTestClassCode(testClassFolder, $"{@class}.cs"); - string fixedSource = TryReadTestClassCode(testClassFolder, $"{@class}.fixed.txt") ?? source; + string fixedSource = TryReadTestClassCode(testClassFolder, @fixed) ?? source; return new SourceWithFix(source, fixedSource); } diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Age.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Age.txt new file mode 100644 index 0000000..fc221c5 --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Age.txt @@ -0,0 +1,51 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using System; +using System.Collections.Generic; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; + +[FluentApi] +public class Student +{ + [FluentMember(0, "Named", 0)] + public string FirstName { get; private set; } + + [FluentMember(0, "Named", 1)] + public string LastName { get; private set; } + + /// + /// ... + /// + /// ... + [FluentMember(1, "OfAge")] + public int Age { get; private set; } + + [FluentMethod(1)] + private void BornOn(DateOnly dateOfBirth) + { + DateOnly today = DateOnly.FromDateTime(DateTime.Today); + int age = today.Year - dateOfBirth.Year; + if (dateOfBirth > today.AddYears(-age)) age--; + Age = age; + } + + [FluentMember(2, "InSemester")] + [FluentDefault("WhoStartsUniversity")] + public int Semester { get; private set; } = 0; + + [FluentMember(3, "LivingIn")] + [FluentDefault("LivingInBoston")] + [FluentNullable("InUnknownCity")] + public string? City { get; private set; } = "Boston"; + + [FluentPredicate(4, "WhoIsHappy", "WhoIsSad")] + [FluentNullable("WithUnknownMood")] + public bool? IsHappy { get; private set; } + + [FluentCollection(5, "Friend", "WhoseFriendsAre", "WhoseFriendIs", "WhoHasNoFriends")] + public IReadOnlyCollection Friends { get; private set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.BornOn.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.BornOn.txt new file mode 100644 index 0000000..7d021d5 --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.BornOn.txt @@ -0,0 +1,51 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using System; +using System.Collections.Generic; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; + +[FluentApi] +public class Student +{ + [FluentMember(0, "Named", 0)] + public string FirstName { get; private set; } + + [FluentMember(0, "Named", 1)] + public string LastName { get; private set; } + + [FluentMember(1, "OfAge")] + public int Age { get; private set; } + + /// + /// ... + /// + /// ... + [FluentMethod(1)] + private void BornOn(DateOnly dateOfBirth) + { + DateOnly today = DateOnly.FromDateTime(DateTime.Today); + int age = today.Year - dateOfBirth.Year; + if (dateOfBirth > today.AddYears(-age)) age--; + Age = age; + } + + [FluentMember(2, "InSemester")] + [FluentDefault("WhoStartsUniversity")] + public int Semester { get; private set; } = 0; + + [FluentMember(3, "LivingIn")] + [FluentDefault("LivingInBoston")] + [FluentNullable("InUnknownCity")] + public string? City { get; private set; } = "Boston"; + + [FluentPredicate(4, "WhoIsHappy", "WhoIsSad")] + [FluentNullable("WithUnknownMood")] + public bool? IsHappy { get; private set; } + + [FluentCollection(5, "Friend", "WhoseFriendsAre", "WhoseFriendIs", "WhoHasNoFriends")] + public IReadOnlyCollection Friends { get; private set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.City.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.City.txt new file mode 100644 index 0000000..16d9cf4 --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.City.txt @@ -0,0 +1,59 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using System; +using System.Collections.Generic; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; + +[FluentApi] +public class Student +{ + [FluentMember(0, "Named", 0)] + public string FirstName { get; private set; } + + [FluentMember(0, "Named", 1)] + public string LastName { get; private set; } + + [FluentMember(1, "OfAge")] + public int Age { get; private set; } + + [FluentMethod(1)] + private void BornOn(DateOnly dateOfBirth) + { + DateOnly today = DateOnly.FromDateTime(DateTime.Today); + int age = today.Year - dateOfBirth.Year; + if (dateOfBirth > today.AddYears(-age)) age--; + Age = age; + } + + [FluentMember(2, "InSemester")] + [FluentDefault("WhoStartsUniversity")] + public int Semester { get; private set; } = 0; + + /// + /// ... + /// + /// ... + /// + /// + /// ... + /// + /// + /// + /// ... + /// + [FluentMember(3, "LivingIn")] + [FluentDefault("LivingInBoston")] + [FluentNullable("InUnknownCity")] + public string? City { get; private set; } = "Boston"; + + [FluentPredicate(4, "WhoIsHappy", "WhoIsSad")] + [FluentNullable("WithUnknownMood")] + public bool? IsHappy { get; private set; } + + [FluentCollection(5, "Friend", "WhoseFriendsAre", "WhoseFriendIs", "WhoHasNoFriends")] + public IReadOnlyCollection Friends { get; private set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.FirstName.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.FirstName.txt new file mode 100644 index 0000000..a4a9e24 --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.FirstName.txt @@ -0,0 +1,52 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using System; +using System.Collections.Generic; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; + +[FluentApi] +public class Student +{ + /// + /// ... + /// + /// ... + /// ... + [FluentMember(0, "Named", 0)] + public string FirstName { get; private set; } + + [FluentMember(0, "Named", 1)] + public string LastName { get; private set; } + + [FluentMember(1, "OfAge")] + public int Age { get; private set; } + + [FluentMethod(1)] + private void BornOn(DateOnly dateOfBirth) + { + DateOnly today = DateOnly.FromDateTime(DateTime.Today); + int age = today.Year - dateOfBirth.Year; + if (dateOfBirth > today.AddYears(-age)) age--; + Age = age; + } + + [FluentMember(2, "InSemester")] + [FluentDefault("WhoStartsUniversity")] + public int Semester { get; private set; } = 0; + + [FluentMember(3, "LivingIn")] + [FluentDefault("LivingInBoston")] + [FluentNullable("InUnknownCity")] + public string? City { get; private set; } = "Boston"; + + [FluentPredicate(4, "WhoIsHappy", "WhoIsSad")] + [FluentNullable("WithUnknownMood")] + public bool? IsHappy { get; private set; } + + [FluentCollection(5, "Friend", "WhoseFriendsAre", "WhoseFriendIs", "WhoHasNoFriends")] + public IReadOnlyCollection Friends { get; private set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Friends.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Friends.txt new file mode 100644 index 0000000..40b6637 --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Friends.txt @@ -0,0 +1,60 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using System; +using System.Collections.Generic; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; + +[FluentApi] +public class Student +{ + [FluentMember(0, "Named", 0)] + public string FirstName { get; private set; } + + [FluentMember(0, "Named", 1)] + public string LastName { get; private set; } + + [FluentMember(1, "OfAge")] + public int Age { get; private set; } + + [FluentMethod(1)] + private void BornOn(DateOnly dateOfBirth) + { + DateOnly today = DateOnly.FromDateTime(DateTime.Today); + int age = today.Year - dateOfBirth.Year; + if (dateOfBirth > today.AddYears(-age)) age--; + Age = age; + } + + [FluentMember(2, "InSemester")] + [FluentDefault("WhoStartsUniversity")] + public int Semester { get; private set; } = 0; + + [FluentMember(3, "LivingIn")] + [FluentDefault("LivingInBoston")] + [FluentNullable("InUnknownCity")] + public string? City { get; private set; } = "Boston"; + + [FluentPredicate(4, "WhoIsHappy", "WhoIsSad")] + [FluentNullable("WithUnknownMood")] + public bool? IsHappy { get; private set; } + + /// + /// ... + /// + /// ... + /// + /// + /// ... + /// + /// ... + /// + /// + /// ... + /// + [FluentCollection(5, "Friend", "WhoseFriendsAre", "WhoseFriendIs", "WhoHasNoFriends")] + public IReadOnlyCollection Friends { get; private set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.IsHappy.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.IsHappy.txt new file mode 100644 index 0000000..4ba93cb --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.IsHappy.txt @@ -0,0 +1,59 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using System; +using System.Collections.Generic; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; + +[FluentApi] +public class Student +{ + [FluentMember(0, "Named", 0)] + public string FirstName { get; private set; } + + [FluentMember(0, "Named", 1)] + public string LastName { get; private set; } + + [FluentMember(1, "OfAge")] + public int Age { get; private set; } + + [FluentMethod(1)] + private void BornOn(DateOnly dateOfBirth) + { + DateOnly today = DateOnly.FromDateTime(DateTime.Today); + int age = today.Year - dateOfBirth.Year; + if (dateOfBirth > today.AddYears(-age)) age--; + Age = age; + } + + [FluentMember(2, "InSemester")] + [FluentDefault("WhoStartsUniversity")] + public int Semester { get; private set; } = 0; + + [FluentMember(3, "LivingIn")] + [FluentDefault("LivingInBoston")] + [FluentNullable("InUnknownCity")] + public string? City { get; private set; } = "Boston"; + + /// + /// ... + /// + /// ... + /// + /// + /// ... + /// + /// + /// + /// ... + /// + [FluentPredicate(4, "WhoIsHappy", "WhoIsSad")] + [FluentNullable("WithUnknownMood")] + public bool? IsHappy { get; private set; } + + [FluentCollection(5, "Friend", "WhoseFriendsAre", "WhoseFriendIs", "WhoHasNoFriends")] + public IReadOnlyCollection Friends { get; private set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.LastName.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.LastName.txt new file mode 100644 index 0000000..d9dacdf --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.LastName.txt @@ -0,0 +1,52 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using System; +using System.Collections.Generic; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; + +[FluentApi] +public class Student +{ + [FluentMember(0, "Named", 0)] + public string FirstName { get; private set; } + + /// + /// ... + /// + /// ... + /// ... + [FluentMember(0, "Named", 1)] + public string LastName { get; private set; } + + [FluentMember(1, "OfAge")] + public int Age { get; private set; } + + [FluentMethod(1)] + private void BornOn(DateOnly dateOfBirth) + { + DateOnly today = DateOnly.FromDateTime(DateTime.Today); + int age = today.Year - dateOfBirth.Year; + if (dateOfBirth > today.AddYears(-age)) age--; + Age = age; + } + + [FluentMember(2, "InSemester")] + [FluentDefault("WhoStartsUniversity")] + public int Semester { get; private set; } = 0; + + [FluentMember(3, "LivingIn")] + [FluentDefault("LivingInBoston")] + [FluentNullable("InUnknownCity")] + public string? City { get; private set; } = "Boston"; + + [FluentPredicate(4, "WhoIsHappy", "WhoIsSad")] + [FluentNullable("WithUnknownMood")] + public bool? IsHappy { get; private set; } + + [FluentCollection(5, "Friend", "WhoseFriendsAre", "WhoseFriendIs", "WhoHasNoFriends")] + public IReadOnlyCollection Friends { get; private set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Semester.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Semester.txt new file mode 100644 index 0000000..7c0b9fb --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Semester.txt @@ -0,0 +1,55 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using System; +using System.Collections.Generic; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; + +[FluentApi] +public class Student +{ + [FluentMember(0, "Named", 0)] + public string FirstName { get; private set; } + + [FluentMember(0, "Named", 1)] + public string LastName { get; private set; } + + [FluentMember(1, "OfAge")] + public int Age { get; private set; } + + [FluentMethod(1)] + private void BornOn(DateOnly dateOfBirth) + { + DateOnly today = DateOnly.FromDateTime(DateTime.Today); + int age = today.Year - dateOfBirth.Year; + if (dateOfBirth > today.AddYears(-age)) age--; + Age = age; + } + + /// + /// ... + /// + /// ... + /// + /// + /// ... + /// + [FluentMember(2, "InSemester")] + [FluentDefault("WhoStartsUniversity")] + public int Semester { get; private set; } = 0; + + [FluentMember(3, "LivingIn")] + [FluentDefault("LivingInBoston")] + [FluentNullable("InUnknownCity")] + public string? City { get; private set; } = "Boston"; + + [FluentPredicate(4, "WhoIsHappy", "WhoIsSad")] + [FluentNullable("WithUnknownMood")] + public bool? IsHappy { get; private set; } + + [FluentCollection(5, "Friend", "WhoseFriendsAre", "WhoseFriendIs", "WhoHasNoFriends")] + public IReadOnlyCollection Friends { get; private set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.cs new file mode 100644 index 0000000..3ceab24 --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.cs @@ -0,0 +1,47 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using System; +using System.Collections.Generic; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; + +[FluentApi] +public class Student +{ + [FluentMember(0, "Named", 0)] + public string FirstName { get; private set; } + + [FluentMember(0, "Named", 1)] + public string LastName { get; private set; } + + [FluentMember(1, "OfAge")] + public int Age { get; private set; } + + [FluentMethod(1)] + private void BornOn(DateOnly dateOfBirth) + { + DateOnly today = DateOnly.FromDateTime(DateTime.Today); + int age = today.Year - dateOfBirth.Year; + if (dateOfBirth > today.AddYears(-age)) age--; + Age = age; + } + + [FluentMember(2, "InSemester")] + [FluentDefault("WhoStartsUniversity")] + public int Semester { get; private set; } = 0; + + [FluentMember(3, "LivingIn")] + [FluentDefault("LivingInBoston")] + [FluentNullable("InUnknownCity")] + public string? City { get; private set; } = "Boston"; + + [FluentPredicate(4, "WhoIsHappy", "WhoIsSad")] + [FluentNullable("WithUnknownMood")] + public bool? IsHappy { get; private set; } + + [FluentCollection(5, "Friend", "WhoseFriendsAre", "WhoseFriendIs", "WhoHasNoFriends")] + public IReadOnlyCollection Friends { get; private set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/UncommentedPropertyClass/Student.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/UncommentedPropertyClass/Student.cs deleted file mode 100644 index ba96592..0000000 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/UncommentedPropertyClass/Student.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Non-nullable member is uninitialized -#pragma warning disable CS8618 -// ReSharper disable All - -using M31.FluentApi.Attributes; - -namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.UncommentedPropertyClass; - -[FluentApi] -public class Student -{ - [FluentMember(0)] - public string FirstName { get; set; } -} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/UncommentedPropertyClass/Student.fixed.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/UncommentedPropertyClass/Student.fixed.txt deleted file mode 100644 index 11277ac..0000000 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/UncommentedPropertyClass/Student.fixed.txt +++ /dev/null @@ -1,18 +0,0 @@ -// Non-nullable member is uninitialized -#pragma warning disable CS8618 -// ReSharper disable All - -using M31.FluentApi.Attributes; - -namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.UncommentedPropertyClass; - -[FluentApi] -public class Student -{ - /// - /// ... - /// - /// ... - [FluentMember(0)] - public string FirstName { get; set; } -} \ No newline at end of file From 5916e98f9417454c15a65b9271044eb1e85cad91 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Tue, 5 Aug 2025 14:46:31 +0200 Subject: [PATCH 35/61] test: FluentApiComments LambdaCollectionClass --- .../FluentApiCommentsProviderTests.cs | 23 +++++----- .../{ => CommentedClass}/Student.Age.txt | 2 +- .../{ => CommentedClass}/Student.BornOn.txt | 2 +- .../{ => CommentedClass}/Student.City.txt | 2 +- .../Student.FirstName.txt | 2 +- .../{ => CommentedClass}/Student.Friends.txt | 2 +- .../{ => CommentedClass}/Student.IsHappy.txt | 2 +- .../{ => CommentedClass}/Student.LastName.txt | 2 +- .../{ => CommentedClass}/Student.Semester.txt | 2 +- .../{ => CommentedClass}/Student.cs | 2 +- .../Student.PhoneNumbers.txt | 42 +++++++++++++++++++ .../LambdaCollectionClass/Student.cs | 27 ++++++++++++ .../CommentedLambdaCollectionClass/Student.cs | 6 +-- 13 files changed, 94 insertions(+), 22 deletions(-) rename src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/{ => CommentedClass}/Student.Age.txt (97%) rename src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/{ => CommentedClass}/Student.BornOn.txt (97%) rename src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/{ => CommentedClass}/Student.City.txt (98%) rename src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/{ => CommentedClass}/Student.FirstName.txt (97%) rename src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/{ => CommentedClass}/Student.Friends.txt (98%) rename src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/{ => CommentedClass}/Student.IsHappy.txt (98%) rename src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/{ => CommentedClass}/Student.LastName.txt (97%) rename src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/{ => CommentedClass}/Student.Semester.txt (98%) rename src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/{ => CommentedClass}/Student.cs (97%) create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/LambdaCollectionClass/Student.PhoneNumbers.txt create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/LambdaCollectionClass/Student.cs diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs index 77ca763..553938e 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs @@ -14,17 +14,20 @@ namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes; public class FluentApiCommentsProviderTests { [Theory] - [InlineData("FluentApiComments", "FirstName", "FirstName")] - [InlineData("FluentApiComments", "LastNa", "LastName")] - [InlineData("FluentApiComments", "int Age ", "Age")] - [InlineData("FluentApiComments", " BornOn", "BornOn")] - [InlineData("FluentApiComments", "int Sem", "Semester")] - [InlineData("FluentApiComments", " City ", "City")] - [InlineData("FluentApiComments", "IsHappy ", "IsHappy")] - [InlineData("FluentApiComments", " Friends", "Friends")] - public async Task CanProvideFluentApiComments(string commentTestClass, string selectedSpan, string member) + [InlineData("CommentedClass", "Student", "FirstName", "FirstName")] + [InlineData("CommentedClass", "Student", "LastNa", "LastName")] + [InlineData("CommentedClass", "Student", "int Age ", "Age")] + [InlineData("CommentedClass", "Student", " BornOn", "BornOn")] + [InlineData("CommentedClass", "Student", "int Sem", "Semester")] + [InlineData("CommentedClass", "Student", " City ", "City")] + [InlineData("CommentedClass", "Student", "IsHappy ", "IsHappy")] + [InlineData("CommentedClass", "Student", " Friends", "Friends")] + [InlineData("LambdaCollectionClass", "Student", "PhoneNumbers", "PhoneNumbers")] + public async Task CanProvideFluentApiComments( + string commentTestClass, string @class, string selectedSpan, string member) { - SourceWithFix source = ReadSource(commentTestClass, "Student", $"Student.{member}.txt"); + SourceWithFix source = ReadSource(Path.Combine("FluentApiComments", commentTestClass), @class, + $"Student.{member}.txt"); var test = new CSharpCodeRefactoringTest { TestCode = source.Source.SelectSpan(selectedSpan), diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Age.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Age.txt similarity index 97% rename from src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Age.txt rename to src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Age.txt index fc221c5..7e8c7ef 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Age.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Age.txt @@ -6,7 +6,7 @@ using System; using System.Collections.Generic; using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.CommentedClass; [FluentApi] public class Student diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.BornOn.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.BornOn.txt similarity index 97% rename from src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.BornOn.txt rename to src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.BornOn.txt index 7d021d5..69cd27b 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.BornOn.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.BornOn.txt @@ -6,7 +6,7 @@ using System; using System.Collections.Generic; using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.CommentedClass; [FluentApi] public class Student diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.City.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.City.txt similarity index 98% rename from src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.City.txt rename to src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.City.txt index 16d9cf4..9541784 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.City.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.City.txt @@ -6,7 +6,7 @@ using System; using System.Collections.Generic; using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.CommentedClass; [FluentApi] public class Student diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.FirstName.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.FirstName.txt similarity index 97% rename from src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.FirstName.txt rename to src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.FirstName.txt index a4a9e24..32629c8 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.FirstName.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.FirstName.txt @@ -6,7 +6,7 @@ using System; using System.Collections.Generic; using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.CommentedClass; [FluentApi] public class Student diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Friends.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Friends.txt similarity index 98% rename from src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Friends.txt rename to src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Friends.txt index 40b6637..a24e57d 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Friends.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Friends.txt @@ -6,7 +6,7 @@ using System; using System.Collections.Generic; using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.CommentedClass; [FluentApi] public class Student diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.IsHappy.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.IsHappy.txt similarity index 98% rename from src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.IsHappy.txt rename to src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.IsHappy.txt index 4ba93cb..fd52c6f 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.IsHappy.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.IsHappy.txt @@ -6,7 +6,7 @@ using System; using System.Collections.Generic; using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.CommentedClass; [FluentApi] public class Student diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.LastName.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.LastName.txt similarity index 97% rename from src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.LastName.txt rename to src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.LastName.txt index d9dacdf..ff622b3 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.LastName.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.LastName.txt @@ -6,7 +6,7 @@ using System; using System.Collections.Generic; using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.CommentedClass; [FluentApi] public class Student diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Semester.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Semester.txt similarity index 98% rename from src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Semester.txt rename to src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Semester.txt index 7c0b9fb..5e36c69 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.Semester.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Semester.txt @@ -6,7 +6,7 @@ using System; using System.Collections.Generic; using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.CommentedClass; [FluentApi] public class Student diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.cs similarity index 97% rename from src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.cs rename to src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.cs index 3ceab24..a84a7ee 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/Student.cs +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments; +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.CommentedClass; [FluentApi] public class Student diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/LambdaCollectionClass/Student.PhoneNumbers.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/LambdaCollectionClass/Student.PhoneNumbers.txt new file mode 100644 index 0000000..371dafc --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/LambdaCollectionClass/Student.PhoneNumbers.txt @@ -0,0 +1,42 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using System.Collections.Generic; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.LambdaCollectionClass; + +[FluentApi] +public class Student +{ + [FluentMember(0)] public string Name { get; set; } + + /// + /// ... + /// + /// ... + /// ... + /// + /// + /// ... + /// + /// ... + /// ... + /// + /// + /// ... + /// + [FluentCollection(1, "PhoneNumber")] + public IReadOnlyCollection PhoneNumbers { get; set; } +} + +[FluentApi] +public class Phone +{ + [FluentMember(0)] + public string Number { get; set; } + + [FluentMember(1)] + public string Usage { get; set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/LambdaCollectionClass/Student.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/LambdaCollectionClass/Student.cs new file mode 100644 index 0000000..064b3e9 --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/LambdaCollectionClass/Student.cs @@ -0,0 +1,27 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using System.Collections.Generic; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.LambdaCollectionClass; + +[FluentApi] +public class Student +{ + [FluentMember(0)] public string Name { get; set; } + + [FluentCollection(1, "PhoneNumber")] + public IReadOnlyCollection PhoneNumbers { get; set; } +} + +[FluentApi] +public class Phone +{ + [FluentMember(0)] + public string Number { get; set; } + + [FluentMember(1)] + public string Usage { get; set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Student.cs index e99dea6..fbf5f94 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Student.cs @@ -1,5 +1,4 @@ // Non-nullable member is uninitialized - #pragma warning disable CS8618 // ReSharper disable All @@ -12,7 +11,8 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationC [FluentApi] public class Student { - [FluentMember(0)] public string Name { get; set; } + [FluentMember(0)] + public string Name { get; set; } /// /// Sets the student's phone numbers. @@ -30,5 +30,5 @@ public class Student /// Specifies that the student has no phone numbers. /// [FluentCollection(1, "PhoneNumber")] - public IReadOnlyCollection PhoneNumbers { get; private set; } + public IReadOnlyCollection PhoneNumbers { get; set; } } \ No newline at end of file From 2436fe371f5fcee1a4c556ab70056da0d61fee23 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Tue, 5 Aug 2025 16:05:47 +0200 Subject: [PATCH 36/61] fix: CommentsProvider for compounds --- .../FluentApiCommentsProvider.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/FluentApiCommentsProvider.cs b/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/FluentApiCommentsProvider.cs index b0f6164..2e27a5c 100644 --- a/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/FluentApiCommentsProvider.cs +++ b/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/FluentApiCommentsProvider.cs @@ -115,6 +115,19 @@ private Task AddFluentSummaryAsync( return Task.FromResult(document); } + if (group.IsCompoundGroup && group.FluentApiInfos.First().SymbolInfo.Name != memberSymbol.Name) + { + memberSymbol = group.FluentApiInfos.First().AdditionalInfo.Symbol; + MemberDeclarationSyntax? firstMemberSyntax = + memberSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as MemberDeclarationSyntax; + if (firstMemberSyntax == null) + { + return Task.FromResult(document); + } + + memberSyntax = firstMemberSyntax; + } + Dictionary groupToMethods = CodeGenerator.GenerateBuilderMethods(classInfoResult.ClassInfo, cancellationToken); BuilderMethods builderMethods = groupToMethods[group]; From 16858962daf84e538a3230b6f291a3b55b3b8c16 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Tue, 5 Aug 2025 16:05:56 +0200 Subject: [PATCH 37/61] test: RedundantCommentCompoundClass --- .../CreateStudent.expected.txt | 62 +++++++++++++++++++ .../CreateStudent.g.cs | 62 +++++++++++++++++++ .../RedundantCommentCompoundClass/Student.cs | 27 ++++++++ .../CodeGeneration/TestDataProvider.cs | 1 + 4 files changed, 152 insertions(+) create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/CreateStudent.expected.txt create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/CreateStudent.g.cs create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/Student.cs diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/CreateStudent.expected.txt new file mode 100644 index 0000000..9b187e0 --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/CreateStudent.expected.txt @@ -0,0 +1,62 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.RedundantCommentCompoundClass; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.IWithName +{ + private readonly Student student; + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + /// Sets the student's name. + /// The student's first name. + /// The student's last name. + /// Sets the student's name. + /// The student's first name. + /// The student's last name. + public static Student WithName(string firstName, string lastName) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.FirstName = firstName; + createStudent.student.LastName = lastName; + return createStudent.student; + } + + /// + Student IWithName.WithName(string firstName, string lastName) + { + student.FirstName = firstName; + student.LastName = lastName; + return student; + } + + public interface ICreateStudent : IWithName + { + } + + public interface IWithName + { + /// Sets the student's name. + /// The student's first name. + /// The student's last name. + /// Sets the student's name. + /// The student's first name. + /// The student's last name. + Student WithName(string firstName, string lastName); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/CreateStudent.g.cs new file mode 100644 index 0000000..9b187e0 --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/CreateStudent.g.cs @@ -0,0 +1,62 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.RedundantCommentCompoundClass; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.IWithName +{ + private readonly Student student; + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + /// Sets the student's name. + /// The student's first name. + /// The student's last name. + /// Sets the student's name. + /// The student's first name. + /// The student's last name. + public static Student WithName(string firstName, string lastName) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.FirstName = firstName; + createStudent.student.LastName = lastName; + return createStudent.student; + } + + /// + Student IWithName.WithName(string firstName, string lastName) + { + student.FirstName = firstName; + student.LastName = lastName; + return student; + } + + public interface ICreateStudent : IWithName + { + } + + public interface IWithName + { + /// Sets the student's name. + /// The student's first name. + /// The student's last name. + /// Sets the student's name. + /// The student's first name. + /// The student's last name. + Student WithName(string firstName, string lastName); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/Student.cs new file mode 100644 index 0000000..8e4ea6b --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/Student.cs @@ -0,0 +1,27 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.RedundantCommentCompoundClass; + +[FluentApi] +public class Student +{ + /// + /// Sets the student's name. + /// + /// The student's first name. + /// The student's last name. + [FluentMember(0, "WithName")] + public string FirstName { get; set; } + + /// + /// Sets the student's name. + /// + /// The student's first name. + /// The student's last name. + [FluentMember(0, "WithName")] + public string LastName { get; set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs index fc459c4..cc8e3ee 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs @@ -26,6 +26,7 @@ internal class TestDataProvider : IEnumerable new object[] { "Abstract", "DocumentationComments", "CommentedMethodsClass", "Student" }, new object[] { "Abstract", "DocumentationComments", "CommentedPropertiesClass", "Student" }, new object[] { "Abstract", "DocumentationComments", "CommentedPropertiesClassAdvanced", "Student" }, + new object[] { "Abstract", "DocumentationComments", "RedundantCommentCompoundClass", "Student" }, new object[] { "Abstract", "EmptyClass", "Student" }, new object[] { "Abstract", "FluentDefaultMemberClass", "Student" }, new object[] { "Abstract", "FluentLambdaClass", "Student|Address" }, From 6ab4b8a93d5e4f66c50f28ec178c85c7767dd99a Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Wed, 6 Aug 2025 11:15:25 +0200 Subject: [PATCH 38/61] refactor: renaming --- .../BuilderGenerator.cs | 16 ++++++++-------- ...BuilderMethods.cs => BuilderStepMethods.cs} | 4 ++-- ...Creator.cs => BuilderStepMethodsCreator.cs} | 18 +++++++++--------- .../MethodCreation/BuilderMethods.cs | 2 +- ...thodCreator.cs => BuilderMethodsCreator.cs} | 4 ++-- .../MethodCreation/Forks/ForkCreator.cs | 4 ++-- 6 files changed, 24 insertions(+), 24 deletions(-) rename src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/{BuilderMethods.cs => BuilderStepMethods.cs} (96%) rename src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/{BuilderMethodsCreator.cs => BuilderStepMethodsCreator.cs} (89%) rename src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/{BuilderMethodCreator.cs => BuilderMethodsCreator.cs} (98%) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderGenerator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderGenerator.cs index 1509b18..5417d3b 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderGenerator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderGenerator.cs @@ -7,10 +7,10 @@ internal class BuilderGenerator : ICodeBoardActor { public void Modify(CodeBoard codeBoard) { - BuilderMethods builderMethods = - BuilderMethodsCreator.CreateBuilderMethods(codeBoard.Forks, codeBoard.CancellationToken); + BuilderStepMethods builderStepMethods = + BuilderStepMethodsCreator.CreateBuilderMethods(codeBoard.Forks, codeBoard.CancellationToken); - foreach (BuilderStepMethod staticMethod in builderMethods.StaticMethods) + foreach (BuilderStepMethod staticMethod in builderStepMethods.StaticMethods) { if (codeBoard.CancellationToken.IsCancellationRequested) { @@ -21,10 +21,10 @@ public void Modify(CodeBoard codeBoard) codeBoard.BuilderClass.AddMethod(method); } - List interfaces = new List(builderMethods.Interfaces.Count); - interfaces.Add(CreateInitialStepInterface(builderMethods, codeBoard)); + List interfaces = new List(builderStepMethods.Interfaces.Count); + interfaces.Add(CreateInitialStepInterface(builderStepMethods, codeBoard)); - foreach (BuilderInterface builderInterface in builderMethods.Interfaces) + foreach (BuilderInterface builderInterface in builderStepMethods.Interfaces) { if (codeBoard.CancellationToken.IsCancellationRequested) { @@ -75,9 +75,9 @@ private Method CreateMethod(BuilderStepMethod builderStepMethod, CodeBoard codeB return method; } - private Interface CreateInitialStepInterface(BuilderMethods builderMethods, CodeBoard codeBoard) + private Interface CreateInitialStepInterface(BuilderStepMethods builderStepMethods, CodeBoard codeBoard) { - string? firstInterfaceName = builderMethods.Interfaces.FirstOrDefault()?.InterfaceName; + string? firstInterfaceName = builderStepMethods.Interfaces.FirstOrDefault()?.InterfaceName; Interface initialStepInterface = new Interface(codeBoard.Info.DefaultAccessModifier, codeBoard.Info.InitialStepInterfaceName); diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderMethods.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethods.cs similarity index 96% rename from src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderMethods.cs rename to src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethods.cs index 402b0c5..6878168 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderMethods.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethods.cs @@ -2,9 +2,9 @@ namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.BuilderMethodsGeneration; -internal class BuilderMethods +internal class BuilderStepMethods { - public BuilderMethods( + public BuilderStepMethods( IReadOnlyCollection staticMethods, IReadOnlyCollection interfaces) { diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderMethodsCreator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethodsCreator.cs similarity index 89% rename from src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderMethodsCreator.cs rename to src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethodsCreator.cs index ddfee87..e174b5f 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderMethodsCreator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethodsCreator.cs @@ -4,12 +4,12 @@ namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.BuilderMethodsGeneration; -internal class BuilderMethodsCreator +internal class BuilderStepMethodsCreator { - internal static BuilderMethods CreateBuilderMethods(IReadOnlyList forks, CancellationToken cancellationToken) + internal static BuilderStepMethods CreateBuilderMethods(IReadOnlyList forks, CancellationToken cancellationToken) { return forks.Count == 0 ? EmptyBuilderMethods() - : new BuilderMethodsCreator(forks, cancellationToken).Create(); + : new BuilderStepMethodsCreator(forks, cancellationToken).Create(); } private readonly IReadOnlyList forks; @@ -17,7 +17,7 @@ internal static BuilderMethods CreateBuilderMethods(IReadOnlyList forks, C private readonly ListDictionary interfaceNameToBuilderMethods; private readonly Dictionary builderStepToFork; - private BuilderMethodsCreator(IReadOnlyList forks, CancellationToken cancellationToken) + private BuilderStepMethodsCreator(IReadOnlyList forks, CancellationToken cancellationToken) { this.forks = forks; this.cancellationToken = cancellationToken; @@ -25,12 +25,12 @@ private BuilderMethodsCreator(IReadOnlyList forks, CancellationToken cance builderStepToFork = forks.ToDictionary(f => f.BuilderStep); } - private static BuilderMethods EmptyBuilderMethods() + private static BuilderStepMethods EmptyBuilderMethods() { - return new BuilderMethods(Array.Empty(), Array.Empty()); + return new BuilderStepMethods(Array.Empty(), Array.Empty()); } - private BuilderMethods Create() + private BuilderStepMethods Create() { if (cancellationToken.IsCancellationRequested) { @@ -45,7 +45,7 @@ private BuilderMethods Create() } IReadOnlyCollection interfaces = - BuilderMethods.CreateInterfaces(interfaceMethods, cancellationToken); + BuilderStepMethods.CreateInterfaces(interfaceMethods, cancellationToken); if (cancellationToken.IsCancellationRequested) { @@ -66,7 +66,7 @@ private BuilderMethods Create() return EmptyBuilderMethods(); } - return new BuilderMethods(staticMethods, interfaces); + return new BuilderStepMethods(staticMethods, interfaces); } private IEnumerable CreateInterfaceBuilderMethods(Fork fork) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethods.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethods.cs index e725aa6..5f72b3d 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethods.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethods.cs @@ -2,7 +2,7 @@ namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.MethodCreation; -internal class BuilderMethods // todo: rename this or the other class with the same name. +internal class BuilderMethods { private readonly List methods; private readonly HashSet requiredUsings; diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethodCreator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethodsCreator.cs similarity index 98% rename from src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethodCreator.cs rename to src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethodsCreator.cs index f10d7bf..21d73a2 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethodCreator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/BuilderMethodsCreator.cs @@ -7,12 +7,12 @@ namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.MethodCreation; -internal class BuilderMethodCreator : IBuilderMethodCreator +internal class BuilderMethodsCreator : IBuilderMethodCreator { private readonly FluentApiInfoGroup group; private readonly CodeBoard codeBoard; - internal BuilderMethodCreator(FluentApiInfoGroup group, CodeBoard codeBoard) + internal BuilderMethodsCreator(FluentApiInfoGroup group, CodeBoard codeBoard) { this.group = group; this.codeBoard = codeBoard; diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Forks/ForkCreator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Forks/ForkCreator.cs index cfb7c1d..67e2c49 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Forks/ForkCreator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Forks/ForkCreator.cs @@ -32,8 +32,8 @@ private void CreateForks( return; } - BuilderMethodCreator builderMethodCreator = new BuilderMethodCreator(group, codeBoard); - BuilderMethods builderMethods = builderMethodCreator.CreateBuilderMethods(methodCreator); + BuilderMethodsCreator builderMethodsCreator = new BuilderMethodsCreator(group, codeBoard); + BuilderMethods builderMethods = builderMethodsCreator.CreateBuilderMethods(methodCreator); foreach (string @using in builderMethods.RequiredUsings) { From 65f0dc94c985c9b2c5b0e7c203dff8f9b24d15a9 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Wed, 6 Aug 2025 11:21:09 +0200 Subject: [PATCH 39/61] chore: use cancellation token --- .../FluentCommentsParser.cs | 13 ++++++++---- .../SourceGenerators/ClassInfoFactory.cs | 9 ++++++--- .../SourceGenerators/FluentApiInfoCreator.cs | 20 +++++++++++++++++-- .../SourceGenerators/SymbolInfoCreator.cs | 1 - 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentCommentsParser.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentCommentsParser.cs index b78843e..ccadf90 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentCommentsParser.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentCommentsParser.cs @@ -4,8 +4,12 @@ namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.Documentation internal static class FluentCommentsParser { - private static readonly Regex commentRegex = new Regex(@"<(?fluent\w+)(\s+(?[^>]+))?>\s*(?.*?)\s*>", RegexOptions.Compiled | RegexOptions.Singleline); - private static readonly Regex attributeRegex = new Regex(@"(?\w+)\s*=\s*""(?[^""]*)""", RegexOptions.Compiled | RegexOptions.Singleline); + private static readonly Regex commentRegex = + new Regex(@"<(?fluent\w+)(\s+(?[^>]+))?>\s*(?.*?)\s*>", + RegexOptions.Compiled | RegexOptions.Singleline); + + private static readonly Regex attributeRegex = new Regex(@"(?\w+)\s*=\s*""(?[^""]*)""", + RegexOptions.Compiled | RegexOptions.Singleline); internal static Comments Parse(string? commentXml) { @@ -32,6 +36,7 @@ internal static Comments Parse(string? commentXml) private static IReadOnlyList ParseCommentAttributes(string commentAttributes) { MatchCollection matches = attributeRegex.Matches(commentAttributes); - return matches.Cast().Select(m => new CommentAttribute(m.Groups["key"].Value, m.Groups["value"].Value)).ToArray(); + return matches.Cast().Select(m => new CommentAttribute(m.Groups["key"].Value, m.Groups["value"].Value)) + .ToArray(); } -} +} \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/SourceGenerators/ClassInfoFactory.cs b/src/M31.FluentApi.Generator/SourceGenerators/ClassInfoFactory.cs index 829d953..7137f75 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/ClassInfoFactory.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/ClassInfoFactory.cs @@ -108,7 +108,8 @@ private ClassInfoResult CreateFluentApiClassInfoInternal( return null; } - FluentApiInfo? fluentApiInfo = TryCreateFluentApiInfo(member, declaringClassNameWithGenericParameters); + FluentApiInfo? fluentApiInfo = TryCreateFluentApiInfo( + member, declaringClassNameWithGenericParameters, cancellationToken); if (fluentApiInfo != null) { @@ -201,7 +202,8 @@ with the fewest parameters that is explicitly declared. */ constructors[0].DeclaredAccessibility != Accessibility.Public); } - private FluentApiInfo? TryCreateFluentApiInfo(ISymbol symbol, string declaringClassNameWithTypeParameters) + private FluentApiInfo? TryCreateFluentApiInfo( + ISymbol symbol, string declaringClassNameWithTypeParameters, CancellationToken cancellationToken) { AttributeDataExtractor extractor = new AttributeDataExtractor(report); FluentApiAttributeData? attributeData = extractor.GetAttributeData(symbol); @@ -218,7 +220,8 @@ with the fewest parameters that is explicitly declared. */ } FluentApiInfoCreator fluentApiInfoCreator = new FluentApiInfoCreator(report); - return fluentApiInfoCreator.Create(symbol, attributeData, declaringClassNameWithTypeParameters); + return fluentApiInfoCreator.Create( + symbol, attributeData, declaringClassNameWithTypeParameters, cancellationToken); } public static string AugmentTypeNameWithGenericParameters(string typeName, GenericInfo? genericInfo) diff --git a/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoCreator.cs b/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoCreator.cs index 9cc3d4b..272cf3f 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoCreator.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoCreator.cs @@ -20,12 +20,18 @@ internal FluentApiInfoCreator(ClassInfoReport classInfoReport) internal FluentApiInfo? Create( ISymbol symbol, FluentApiAttributeData attributeData, - string declaringClassNameWithTypeParameters) + string declaringClassNameWithTypeParameters, + CancellationToken cancellationToken) { + if (cancellationToken.IsCancellationRequested) + { + return null; + } + FluentApiSymbolInfo symbolInfo = SymbolInfoCreator.Create(symbol, declaringClassNameWithTypeParameters); AttributeInfoBase? attributeInfo = CreateAttributeInfo(attributeData.MainAttributeData, symbol, symbolInfo); - if (attributeInfo == null) + if (attributeInfo == null || cancellationToken.IsCancellationRequested) { return null; } @@ -35,11 +41,21 @@ internal FluentApiInfoCreator(ClassInfoReport classInfoReport) .Select(data => (data, CreateOrthogonalAttributeInfo(data, symbol.Name))) .ToArray(); + if (cancellationToken.IsCancellationRequested) + { + return null; + } + (AttributeDataExtended data, ControlAttributeInfoBase info)[] controlDataAndInfos = attributeData.ControlAttributeData .Select(data => (data, CreateControlAttributeInfo(data))) .ToArray(); + if (cancellationToken.IsCancellationRequested) + { + return null; + } + FluentReturnAttributeInfo? fluentReturnAttributeInfo = controlDataAndInfos.Select(d => d.info) .OfType().FirstOrDefault(); diff --git a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs index dfd825f..4ac86f1 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs @@ -200,7 +200,6 @@ private static ParameterKinds GetParameterKinds(IParameterSymbol parameterSymbol private static Comments GetFluentSymbolComments(ISymbol symbol) { - // todo: pass cancellation token. string? commentXml = symbol.GetDocumentationCommentXml(); return FluentCommentsParser.Parse(commentXml); } From 2846dcebb4c28225b04104d7ad283d05a668dc00 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Wed, 6 Aug 2025 11:22:23 +0200 Subject: [PATCH 40/61] fix: use NotSupportedException --- .../AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs | 5 ++++- .../Helpers/AnalyzerAndCodeFixVerifier.cs | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs index 553938e..6534ff0 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs @@ -1,3 +1,4 @@ +using System; using System.IO; using System.Threading.Tasks; using M31.FluentApi.Attributes; @@ -38,13 +39,15 @@ public async Task CanProvideFluentApiComments( new PackageIdentity("Microsoft.NETCore.App.Ref", "6.0.0"), Path.Combine("ref", "net6.0")), #else - throw new NotImplementedException(); + throw new NotSupportedException(); #endif + TestState = { AdditionalReferences = { typeof(FluentApiAttribute).Assembly } } }; + await test.RunAsync(); } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/AnalyzerAndCodeFixVerifier.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/AnalyzerAndCodeFixVerifier.cs index 14c3b6f..2c9db80 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/AnalyzerAndCodeFixVerifier.cs +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/Helpers/AnalyzerAndCodeFixVerifier.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Threading; @@ -62,7 +63,7 @@ internal CodeFixTest( new PackageIdentity("Microsoft.NETCore.App.Ref", "6.0.0"), Path.Combine("ref", "net6.0")); #else - throw new NotImplementedException(); + throw new NotSupportedException(); #endif TestState.AdditionalReferences.Add(typeof(FluentApiAttribute).Assembly); From ed893726d441f8c1ad6e6a8280a7aec3584944ce Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Wed, 6 Aug 2025 11:22:41 +0200 Subject: [PATCH 41/61] feat(Example): MyPerson (wip) --- src/ExampleProject/MyPerson.cs | 11 ----------- src/ExampleProject/Program.cs | 4 ++-- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/ExampleProject/MyPerson.cs b/src/ExampleProject/MyPerson.cs index 462c8e7..24e2f3b 100644 --- a/src/ExampleProject/MyPerson.cs +++ b/src/ExampleProject/MyPerson.cs @@ -5,20 +5,9 @@ namespace ExampleProject; [FluentApi] class MyPerson { - /// - /// ... - /// - /// ... - /// ... [FluentMember(0, "Name")] public string FirstName { get; set; } - // todo: check generated code for this duplicate. Also test lambda! - /// - /// ... - /// - /// ... - /// ... [FluentMember(0, "Name")] public string LastName { get; set; } diff --git a/src/ExampleProject/Program.cs b/src/ExampleProject/Program.cs index aaf1009..f283279 100644 --- a/src/ExampleProject/Program.cs +++ b/src/ExampleProject/Program.cs @@ -6,8 +6,8 @@ // MyPerson // -// MyPerson mp = CreateMyPerson.WithFirstName("Kevin").WithLastName("Schaal") -// .OfAge(22).InSemester(4).LivingIn("Tübingen"); +MyPerson mp = CreateMyPerson.Name("Kevin", "Schaal") + .OfAge(22).InSemester(4).LivingIn("Tübingen"); // Student // From 4a4c08c558f9927e48788e5f4ea25255d7ca8d04 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Wed, 6 Aug 2025 11:38:29 +0200 Subject: [PATCH 42/61] refactor: namespaces --- .../CodeBoardActors/Commons/BuilderMethod.cs | 2 +- .../Commons/BuilderMethodFactory.cs | 2 +- .../CommentsGenerator.cs | 4 +- .../CommentsTransformer.cs | 6 +-- .../CodeBoardElements/CodeBoard.cs | 2 +- .../Comment.cs | 9 ++-- .../CommentAttribute.cs | 7 ++-- .../Comments.cs | 4 +- .../FluentCommentsParser.cs | 2 +- .../MemberCommentKey.cs | 4 +- .../TransformedComments.cs | 2 +- .../CodeBoardElements/FluentApiSymbolInfo.cs | 2 +- .../CodeBoardElements/MemberSymbolInfo.cs | 2 +- .../CodeBoardElements/MethodSymbolInfo.cs | 2 +- .../CodeGeneration/CodeGenerator.cs | 2 +- .../FluentApiCommentsProvider.cs | 2 +- .../MethodsToCommentsTemplate.cs | 2 +- .../SourceGenerators/SymbolInfoCreator.cs | 2 +- .../FluentApiCommentsProviderTests.cs | 2 +- .../CommentedClass/Student.LastName.txt | 6 +-- .../CreateStudent.expected.txt | 2 +- .../CommentedCompoundClass/CreateStudent.g.cs | 2 +- .../CommentedCompoundClass/Student.cs | 4 +- .../CreatePhone.g.cs | 2 +- .../CreateStudent.expected.txt | 2 +- .../CreateStudent.g.cs | 41 ++++++++----------- .../CommentedLambdaCollectionClass/Phone.cs | 2 +- .../CommentedLambdaCollectionClass/Student.cs | 2 +- .../CreateStudent.expected.txt | 2 +- .../CommentedMethodsClass/CreateStudent.g.cs | 2 +- .../CommentedMethodsClass/Student.cs | 2 +- .../CreateStudent.expected.txt | 2 +- .../CreateStudent.g.cs | 2 +- .../CommentedPropertiesClass/Student.cs | 2 +- .../CreateStudent.expected.txt | 2 +- .../CreateStudent.g.cs | 2 +- .../Student.cs | 2 +- .../CreateStudent.expected.txt | 2 +- .../CreateStudent.g.cs | 2 +- .../RedundantCommentCompoundClass/Student.cs | 2 +- .../CodeGeneration/TestDataProvider.cs | 12 +++--- .../CommentGetLineTests.cs | 6 +-- .../CommentsTransformerTests.cs | 8 ++-- .../FluentCommentsParserTests.cs | 6 +-- 44 files changed, 87 insertions(+), 92 deletions(-) rename src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/{DocumentationGeneration => FluentApiComments}/CommentsGenerator.cs (96%) rename src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/{DocumentationGeneration => FluentApiComments}/CommentsTransformer.cs (96%) rename src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/{DocumentationComments => FluentApiComments}/Comment.cs (89%) rename src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/{DocumentationComments => FluentApiComments}/CommentAttribute.cs (93%) rename src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/{DocumentationComments => FluentApiComments}/Comments.cs (96%) rename src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/{DocumentationComments => FluentApiComments}/FluentCommentsParser.cs (98%) rename src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/{DocumentationComments => FluentApiComments}/MemberCommentKey.cs (92%) rename src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/{DocumentationComments => FluentApiComments}/TransformedComments.cs (98%) rename src/M31.FluentApi.Generator/SourceAnalyzers/{DocumentationComments => FluentApiComments}/FluentApiCommentsProvider.cs (98%) rename src/M31.FluentApi.Generator/SourceAnalyzers/{DocumentationComments => FluentApiComments}/MethodsToCommentsTemplate.cs (97%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedCompoundClass/CreateStudent.expected.txt (97%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedCompoundClass/CreateStudent.g.cs (97%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedCompoundClass/Student.cs (87%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedLambdaCollectionClass/CreatePhone.g.cs (95%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedLambdaCollectionClass/CreateStudent.expected.txt (99%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedLambdaCollectionClass/CreateStudent.g.cs (51%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedLambdaCollectionClass/Phone.cs (93%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedLambdaCollectionClass/Student.cs (98%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedMethodsClass/CreateStudent.expected.txt (98%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedMethodsClass/CreateStudent.g.cs (98%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedMethodsClass/Student.cs (97%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedPropertiesClass/CreateStudent.expected.txt (98%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedPropertiesClass/CreateStudent.g.cs (98%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedPropertiesClass/Student.cs (97%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedPropertiesClassAdvanced/CreateStudent.expected.txt (98%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedPropertiesClassAdvanced/CreateStudent.g.cs (98%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/CommentedPropertiesClassAdvanced/Student.cs (98%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/RedundantCommentCompoundClass/CreateStudent.expected.txt (97%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/RedundantCommentCompoundClass/CreateStudent.g.cs (97%) rename src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/{DocumentationComments => FluentApiComments}/RedundantCommentCompoundClass/Student.cs (93%) rename src/M31.FluentApi.Tests/Components/{DocumentationComments => FluentApiComments}/CommentGetLineTests.cs (83%) rename src/M31.FluentApi.Tests/Components/{DocumentationComments => FluentApiComments}/CommentsTransformerTests.cs (91%) rename src/M31.FluentApi.Tests/Components/{DocumentationComments => FluentApiComments}/FluentCommentsParserTests.cs (96%) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethod.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethod.cs index f360993..6ec41ec 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethod.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethod.cs @@ -1,5 +1,5 @@ using M31.FluentApi.Generator.CodeBuilding; -using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; using M31.FluentApi.Generator.SourceGenerators.Generics; namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.Commons; diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs index 4ab6a42..0f76129 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/Commons/BuilderMethodFactory.cs @@ -1,6 +1,6 @@ using M31.FluentApi.Generator.CodeBuilding; using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements; -using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.Commons; diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/FluentApiComments/CommentsGenerator.cs similarity index 96% rename from src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs rename to src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/FluentApiComments/CommentsGenerator.cs index 76b36af..bc249e6 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsGenerator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/FluentApiComments/CommentsGenerator.cs @@ -1,8 +1,8 @@ using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements; -using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; using M31.FluentApi.Generator.SourceGenerators; -namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.DocumentationGeneration; +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.FluentApiComments; internal class CommentsGenerator : ICodeBoardActor { diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsTransformer.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/FluentApiComments/CommentsTransformer.cs similarity index 96% rename from src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsTransformer.cs rename to src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/FluentApiComments/CommentsTransformer.cs index 2e68c3f..4adc898 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/DocumentationGeneration/CommentsTransformer.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/FluentApiComments/CommentsTransformer.cs @@ -1,7 +1,7 @@ -using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; using M31.FluentApi.Generator.Commons; -namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.DocumentationGeneration; +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.FluentApiComments; internal static class CommentsTransformer { @@ -37,4 +37,4 @@ private static IReadOnlyList TransformAttributes(IReadOnlyList CommentAttribute? firstMethodAttribute = attributes.FirstOrDefault(a => a.Key == "method"); return firstMethodAttribute == null ? attributes : attributes.Where(a => a != firstMethodAttribute).ToArray(); } -} +} \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/CodeBoard.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/CodeBoard.cs index 15967aa..2710b08 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/CodeBoard.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/CodeBoard.cs @@ -2,7 +2,7 @@ using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.Commons; using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.MethodCreation; using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.MethodCreation.Forks; -using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; using M31.FluentApi.Generator.SourceAnalyzers; using M31.FluentApi.Generator.SourceGenerators; using M31.FluentApi.Generator.SourceGenerators.AttributeElements; diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comment.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/Comment.cs similarity index 89% rename from src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comment.cs rename to src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/Comment.cs index 136f7d3..ace85c7 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comment.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/Comment.cs @@ -1,6 +1,7 @@ using M31.FluentApi.Generator.Commons; -namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; + internal class Comment { internal string Tag { get; } @@ -22,8 +23,8 @@ internal string GetLine() protected bool Equals(Comment other) { return Tag == other.Tag && - Attributes.SequenceEqual(other.Attributes) && - Content == other.Content; + Attributes.SequenceEqual(other.Attributes) && + Content == other.Content; } public override bool Equals(object? obj) @@ -41,4 +42,4 @@ public override int GetHashCode() .AddSequence(Attributes) .Add(Content); } -} +} \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/CommentAttribute.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/CommentAttribute.cs similarity index 93% rename from src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/CommentAttribute.cs rename to src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/CommentAttribute.cs index 10c73ca..d1dcfbd 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/CommentAttribute.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/CommentAttribute.cs @@ -1,6 +1,7 @@ using M31.FluentApi.Generator.Commons; -namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; + internal class CommentAttribute { internal CommentAttribute(string key, string value) @@ -15,7 +16,7 @@ internal CommentAttribute(string key, string value) protected bool Equals(CommentAttribute other) { return Key == other.Key && - Value == other.Value; + Value == other.Value; } public override bool Equals(object? obj) @@ -35,4 +36,4 @@ public override string ToString() { return @$"{Key}=""{Value}"""; } -} +} \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/Comments.cs similarity index 96% rename from src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs rename to src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/Comments.cs index 53e741c..44dabcd 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/Comments.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/Comments.cs @@ -1,6 +1,6 @@ using M31.FluentApi.Generator.Commons; -namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; internal class Comments { @@ -34,4 +34,4 @@ public override int GetHashCode() { return new HashCode().AddSequence(List); } -} +} \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentCommentsParser.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/FluentCommentsParser.cs similarity index 98% rename from src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentCommentsParser.cs rename to src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/FluentCommentsParser.cs index ccadf90..a7e7489 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/FluentCommentsParser.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/FluentCommentsParser.cs @@ -1,6 +1,6 @@ using System.Text.RegularExpressions; -namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; internal static class FluentCommentsParser { diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/MemberCommentKey.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/MemberCommentKey.cs similarity index 92% rename from src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/MemberCommentKey.cs rename to src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/MemberCommentKey.cs index d184441..ed0a0f5 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/MemberCommentKey.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/MemberCommentKey.cs @@ -1,4 +1,4 @@ -namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; internal record MemberCommentKey { @@ -15,4 +15,4 @@ public override string ToString() { return $"{MemberName}-{Method}"; } -} +} \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/TransformedComments.cs similarity index 98% rename from src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs rename to src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/TransformedComments.cs index 5420de0..504135c 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/DocumentationComments/TransformedComments.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiComments/TransformedComments.cs @@ -1,4 +1,4 @@ -namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; internal class TransformedComments { diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiSymbolInfo.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiSymbolInfo.cs index e3ec41e..1ef3654 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiSymbolInfo.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/FluentApiSymbolInfo.cs @@ -1,4 +1,4 @@ -using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; using M31.FluentApi.Generator.Commons; using Microsoft.CodeAnalysis; diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MemberSymbolInfo.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MemberSymbolInfo.cs index db4ec9d..cd19d0e 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MemberSymbolInfo.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MemberSymbolInfo.cs @@ -1,4 +1,4 @@ -using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; using M31.FluentApi.Generator.Commons; using M31.FluentApi.Generator.SourceGenerators.Collections; using Microsoft.CodeAnalysis; diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MethodSymbolInfo.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MethodSymbolInfo.cs index ae5fd20..fd10609 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MethodSymbolInfo.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardElements/MethodSymbolInfo.cs @@ -1,4 +1,4 @@ -using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; using M31.FluentApi.Generator.Commons; using M31.FluentApi.Generator.SourceGenerators.Generics; using Microsoft.CodeAnalysis; diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeGenerator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeGenerator.cs index 4e5778c..62b917a 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeGenerator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeGenerator.cs @@ -1,7 +1,7 @@ using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors; using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.BuilderMethodsGeneration; -using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.DocumentationGeneration; using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.DuplicateMethodsChecking; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.FluentApiComments; using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.InnerBodyGeneration; using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.MethodCreation.Forks; using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements; diff --git a/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/FluentApiCommentsProvider.cs b/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiComments/FluentApiCommentsProvider.cs similarity index 98% rename from src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/FluentApiCommentsProvider.cs rename to src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiComments/FluentApiCommentsProvider.cs index 2e27a5c..45167f6 100644 --- a/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/FluentApiCommentsProvider.cs +++ b/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiComments/FluentApiCommentsProvider.cs @@ -10,7 +10,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace M31.FluentApi.Generator.SourceAnalyzers.DocumentationComments; +namespace M31.FluentApi.Generator.SourceAnalyzers.FluentApiComments; [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(FluentApiCommentsProvider))] [Shared] diff --git a/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/MethodsToCommentsTemplate.cs b/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiComments/MethodsToCommentsTemplate.cs similarity index 97% rename from src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/MethodsToCommentsTemplate.cs rename to src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiComments/MethodsToCommentsTemplate.cs index 15dc29e..e186382 100644 --- a/src/M31.FluentApi.Generator/SourceAnalyzers/DocumentationComments/MethodsToCommentsTemplate.cs +++ b/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiComments/MethodsToCommentsTemplate.cs @@ -1,7 +1,7 @@ using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.Commons; using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.MethodCreation; -namespace M31.FluentApi.Generator.SourceAnalyzers.DocumentationComments; +namespace M31.FluentApi.Generator.SourceAnalyzers.FluentApiComments; internal class MethodsToCommentsTemplate { diff --git a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs index 4ac86f1..b71b739 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs @@ -1,6 +1,6 @@ using M31.FluentApi.Generator.CodeBuilding; using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements; -using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; using M31.FluentApi.Generator.Commons; using M31.FluentApi.Generator.SourceGenerators.Collections; using M31.FluentApi.Generator.SourceGenerators.Generics; diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs index 6534ff0..781c3bf 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs @@ -2,7 +2,7 @@ using System.IO; using System.Threading.Tasks; using M31.FluentApi.Attributes; -using M31.FluentApi.Generator.SourceAnalyzers.DocumentationComments; +using M31.FluentApi.Generator.SourceAnalyzers.FluentApiComments; using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.Testing.Verifiers; using Xunit; diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.LastName.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.LastName.txt index ff622b3..32629c8 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.LastName.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.LastName.txt @@ -11,14 +11,14 @@ namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments [FluentApi] public class Student { - [FluentMember(0, "Named", 0)] - public string FirstName { get; private set; } - /// /// ... /// /// ... /// ... + [FluentMember(0, "Named", 0)] + public string FirstName { get; private set; } + [FluentMember(0, "Named", 1)] public string LastName { get; private set; } diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedCompoundClass/CreateStudent.expected.txt similarity index 97% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.expected.txt rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedCompoundClass/CreateStudent.expected.txt index 9efe234..a52bae8 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.expected.txt +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedCompoundClass/CreateStudent.expected.txt @@ -5,7 +5,7 @@ #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member #nullable enable -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedCompoundClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedCompoundClass; public class CreateStudent : CreateStudent.ICreateStudent, diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedCompoundClass/CreateStudent.g.cs similarity index 97% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.g.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedCompoundClass/CreateStudent.g.cs index 9efe234..a52bae8 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/CreateStudent.g.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedCompoundClass/CreateStudent.g.cs @@ -5,7 +5,7 @@ #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member #nullable enable -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedCompoundClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedCompoundClass; public class CreateStudent : CreateStudent.ICreateStudent, diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedCompoundClass/Student.cs similarity index 87% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/Student.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedCompoundClass/Student.cs index b06512a..c436870 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedCompoundClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedCompoundClass/Student.cs @@ -4,8 +4,8 @@ using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedCompoundClass; -// todo: rename folder to FluentApiComments, remove other occurrences of "DocumentationComments". Hm maybe not. +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedCompoundClass; + [FluentApi] public class Student { diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreatePhone.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/CreatePhone.g.cs similarity index 95% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreatePhone.g.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/CreatePhone.g.cs index 9ef520d..49e1704 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreatePhone.g.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/CreatePhone.g.cs @@ -5,7 +5,7 @@ #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member #nullable enable -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass; public class CreatePhone : CreatePhone.ICreatePhone, diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/CreateStudent.expected.txt similarity index 99% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreateStudent.expected.txt rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/CreateStudent.expected.txt index 67acb0d..fe9013f 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreateStudent.expected.txt +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/CreateStudent.expected.txt @@ -10,7 +10,7 @@ using System.Reflection; using System.Linq; using System; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass; public class CreateStudent : CreateStudent.ICreateStudent, diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/CreateStudent.g.cs similarity index 51% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreateStudent.g.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/CreateStudent.g.cs index 67acb0d..0215330 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/CreateStudent.g.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/CreateStudent.g.cs @@ -6,11 +6,10 @@ #nullable enable using System.Collections.Generic; -using System.Reflection; using System.Linq; using System; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass; public class CreateStudent : CreateStudent.ICreateStudent, @@ -18,12 +17,6 @@ public class CreateStudent : CreateStudent.IWithPhoneNumbers { private readonly Student student; - private static readonly PropertyInfo phoneNumbersPropertyInfo; - - static CreateStudent() - { - phoneNumbersPropertyInfo = typeof(Student).GetProperty("PhoneNumbers", BindingFlags.Instance | BindingFlags.Public)!; - } private CreateStudent() { @@ -49,44 +42,44 @@ IWithPhoneNumbers IWithName.WithName(string name) } /// - Student IWithPhoneNumbers.WithPhoneNumbers(System.Collections.Generic.IReadOnlyCollection phoneNumbers) + Student IWithPhoneNumbers.WithPhoneNumbers(System.Collections.Generic.IReadOnlyCollection phoneNumbers) { - CreateStudent.phoneNumbersPropertyInfo.SetValue(student, phoneNumbers); + student.PhoneNumbers = phoneNumbers; return student; } /// - Student IWithPhoneNumbers.WithPhoneNumbers(params M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[] phoneNumbers) + Student IWithPhoneNumbers.WithPhoneNumbers(params M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.Phone[] phoneNumbers) { - CreateStudent.phoneNumbersPropertyInfo.SetValue(student, phoneNumbers); + student.PhoneNumbers = phoneNumbers; return student; } /// - Student IWithPhoneNumbers.WithPhoneNumbers(params Func[] createPhoneNumbers) + Student IWithPhoneNumbers.WithPhoneNumbers(params Func[] createPhoneNumbers) { - CreateStudent.phoneNumbersPropertyInfo.SetValue(student, createPhoneNumbers.Select(createPhoneNumber => createPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.CreatePhone.InitialStep())).ToArray()); + student.PhoneNumbers = createPhoneNumbers.Select(createPhoneNumber => createPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.CreatePhone.InitialStep())).ToArray(); return student; } /// - Student IWithPhoneNumbers.WithPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone phoneNumber) + Student IWithPhoneNumbers.WithPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.Phone phoneNumber) { - CreateStudent.phoneNumbersPropertyInfo.SetValue(student, new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[1]{ phoneNumber }); + student.PhoneNumbers = new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.Phone[1]{ phoneNumber }; return student; } /// - Student IWithPhoneNumbers.WithPhoneNumber(Func createPhoneNumber) + Student IWithPhoneNumbers.WithPhoneNumber(Func createPhoneNumber) { - CreateStudent.phoneNumbersPropertyInfo.SetValue(student, new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[1]{ createPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.CreatePhone.InitialStep()) }); + student.PhoneNumbers = new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.Phone[1]{ createPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.CreatePhone.InitialStep()) }; return student; } /// Student IWithPhoneNumbers.WithZeroPhoneNumbers() { - CreateStudent.phoneNumbersPropertyInfo.SetValue(student, new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[0]); + student.PhoneNumbers = new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.Phone[0]; return student; } @@ -103,23 +96,23 @@ public interface IWithPhoneNumbers { /// Sets the student's phone numbers. /// The student's phone numbers. - Student WithPhoneNumbers(System.Collections.Generic.IReadOnlyCollection phoneNumbers); + Student WithPhoneNumbers(System.Collections.Generic.IReadOnlyCollection phoneNumbers); /// Sets the student's phone numbers. /// The student's phone numbers. - Student WithPhoneNumbers(params M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[] phoneNumbers); + Student WithPhoneNumbers(params M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.Phone[] phoneNumbers); /// Sets the student's phone numbers. /// Functions for creating the student's phone numbers. - Student WithPhoneNumbers(params Func[] createPhoneNumbers); + Student WithPhoneNumbers(params Func[] createPhoneNumbers); /// Sets the student's phone number. /// The student's phone number. - Student WithPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone phoneNumber); + Student WithPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.Phone phoneNumber); /// Sets the student's phone number. /// A function for creating the student's phone number. - Student WithPhoneNumber(Func createPhoneNumber); + Student WithPhoneNumber(Func createPhoneNumber); /// Specifies that the student has no phone numbers. Student WithZeroPhoneNumbers(); diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Phone.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/Phone.cs similarity index 93% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Phone.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/Phone.cs index ad1f9dd..f7752a5 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Phone.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/Phone.cs @@ -4,7 +4,7 @@ using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments. +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments. CommentedLambdaCollectionClass; [FluentApi] diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/Student.cs similarity index 98% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Student.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/Student.cs index fbf5f94..bdd468a 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedLambdaCollectionClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/Student.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments. +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments. CommentedLambdaCollectionClass; [FluentApi] diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedMethodsClass/CreateStudent.expected.txt similarity index 98% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/CreateStudent.expected.txt rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedMethodsClass/CreateStudent.expected.txt index c624978..2525ae9 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/CreateStudent.expected.txt +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedMethodsClass/CreateStudent.expected.txt @@ -8,7 +8,7 @@ using System; using System.Reflection; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedMethodsClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedMethodsClass; public class CreateStudent : CreateStudent.ICreateStudent, diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedMethodsClass/CreateStudent.g.cs similarity index 98% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/CreateStudent.g.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedMethodsClass/CreateStudent.g.cs index c624978..2525ae9 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/CreateStudent.g.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedMethodsClass/CreateStudent.g.cs @@ -8,7 +8,7 @@ using System; using System.Reflection; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedMethodsClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedMethodsClass; public class CreateStudent : CreateStudent.ICreateStudent, diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedMethodsClass/Student.cs similarity index 97% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/Student.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedMethodsClass/Student.cs index 458e4ec..3fee24e 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedMethodsClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedMethodsClass/Student.cs @@ -5,7 +5,7 @@ using System; using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedMethodsClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedMethodsClass; [FluentApi] public class Student diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/CreateStudent.expected.txt similarity index 98% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/CreateStudent.expected.txt rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/CreateStudent.expected.txt index c64238d..40f765d 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/CreateStudent.expected.txt +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/CreateStudent.expected.txt @@ -5,7 +5,7 @@ #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member #nullable enable -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedPropertiesClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedPropertiesClass; public class CreateStudent : CreateStudent.ICreateStudent, diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/CreateStudent.g.cs similarity index 98% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/CreateStudent.g.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/CreateStudent.g.cs index c64238d..40f765d 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/CreateStudent.g.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/CreateStudent.g.cs @@ -5,7 +5,7 @@ #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member #nullable enable -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedPropertiesClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedPropertiesClass; public class CreateStudent : CreateStudent.ICreateStudent, diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/Student.cs similarity index 97% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/Student.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/Student.cs index 1e88131..0e668ce 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/Student.cs @@ -5,7 +5,7 @@ using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedPropertiesClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedPropertiesClass; [FluentApi] public class Student diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClassAdvanced/CreateStudent.expected.txt similarity index 98% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.expected.txt rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClassAdvanced/CreateStudent.expected.txt index f60870f..43cc102 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.expected.txt +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClassAdvanced/CreateStudent.expected.txt @@ -7,7 +7,7 @@ using System.Reflection; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedPropertiesClassAdvanced; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedPropertiesClassAdvanced; public class CreateStudent : CreateStudent.ICreateStudent, diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClassAdvanced/CreateStudent.g.cs similarity index 98% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.g.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClassAdvanced/CreateStudent.g.cs index f60870f..43cc102 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/CreateStudent.g.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClassAdvanced/CreateStudent.g.cs @@ -7,7 +7,7 @@ using System.Reflection; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedPropertiesClassAdvanced; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedPropertiesClassAdvanced; public class CreateStudent : CreateStudent.ICreateStudent, diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClassAdvanced/Student.cs similarity index 98% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/Student.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClassAdvanced/Student.cs index 7eee361..e75ff82 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/CommentedPropertiesClassAdvanced/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClassAdvanced/Student.cs @@ -4,7 +4,7 @@ using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments. +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments. CommentedPropertiesClassAdvanced; [FluentApi] diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/RedundantCommentCompoundClass/CreateStudent.expected.txt similarity index 97% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/CreateStudent.expected.txt rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/RedundantCommentCompoundClass/CreateStudent.expected.txt index 9b187e0..a609f51 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/CreateStudent.expected.txt +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/RedundantCommentCompoundClass/CreateStudent.expected.txt @@ -5,7 +5,7 @@ #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member #nullable enable -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.RedundantCommentCompoundClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.RedundantCommentCompoundClass; public class CreateStudent : CreateStudent.ICreateStudent, diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/RedundantCommentCompoundClass/CreateStudent.g.cs similarity index 97% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/CreateStudent.g.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/RedundantCommentCompoundClass/CreateStudent.g.cs index 9b187e0..a609f51 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/CreateStudent.g.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/RedundantCommentCompoundClass/CreateStudent.g.cs @@ -5,7 +5,7 @@ #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member #nullable enable -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.RedundantCommentCompoundClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.RedundantCommentCompoundClass; public class CreateStudent : CreateStudent.ICreateStudent, diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/RedundantCommentCompoundClass/Student.cs similarity index 93% rename from src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/Student.cs rename to src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/RedundantCommentCompoundClass/Student.cs index 8e4ea6b..2b113ba 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/DocumentationComments/RedundantCommentCompoundClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/RedundantCommentCompoundClass/Student.cs @@ -4,7 +4,7 @@ using M31.FluentApi.Attributes; -namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.RedundantCommentCompoundClass; +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.RedundantCommentCompoundClass; [FluentApi] public class Student diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs index cc8e3ee..0025fde 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs @@ -21,12 +21,12 @@ internal class TestDataProvider : IEnumerable new object[] { "Abstract", "ContinueWithSelfClass", "Student" }, new object[] { "Abstract", "CustomFluentMethodNameClass", "Student" }, new object[] { "Abstract", "DefaultFluentMethodNameClass", "Student" }, - new object[] { "Abstract", "DocumentationComments", "CommentedCompoundClass", "Student" }, - new object[] { "Abstract", "DocumentationComments", "CommentedLambdaCollectionClass", "Student|Phone" }, - new object[] { "Abstract", "DocumentationComments", "CommentedMethodsClass", "Student" }, - new object[] { "Abstract", "DocumentationComments", "CommentedPropertiesClass", "Student" }, - new object[] { "Abstract", "DocumentationComments", "CommentedPropertiesClassAdvanced", "Student" }, - new object[] { "Abstract", "DocumentationComments", "RedundantCommentCompoundClass", "Student" }, + new object[] { "Abstract", "FluentApiComments", "CommentedCompoundClass", "Student" }, + new object[] { "Abstract", "FluentApiComments", "CommentedLambdaCollectionClass", "Student|Phone" }, + new object[] { "Abstract", "FluentApiComments", "CommentedMethodsClass", "Student" }, + new object[] { "Abstract", "FluentApiComments", "CommentedPropertiesClass", "Student" }, + new object[] { "Abstract", "FluentApiComments", "CommentedPropertiesClassAdvanced", "Student" }, + new object[] { "Abstract", "FluentApiComments", "RedundantCommentCompoundClass", "Student" }, new object[] { "Abstract", "EmptyClass", "Student" }, new object[] { "Abstract", "FluentDefaultMemberClass", "Student" }, new object[] { "Abstract", "FluentLambdaClass", "Student|Address" }, diff --git a/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentGetLineTests.cs b/src/M31.FluentApi.Tests/Components/FluentApiComments/CommentGetLineTests.cs similarity index 83% rename from src/M31.FluentApi.Tests/Components/DocumentationComments/CommentGetLineTests.cs rename to src/M31.FluentApi.Tests/Components/FluentApiComments/CommentGetLineTests.cs index 6dccbcd..690019f 100644 --- a/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentGetLineTests.cs +++ b/src/M31.FluentApi.Tests/Components/FluentApiComments/CommentGetLineTests.cs @@ -1,8 +1,8 @@ -using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; -using System; +using System; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; using Xunit; -namespace M31.FluentApi.Tests.Components.DocumentationComments; +namespace M31.FluentApi.Tests.Components.FluentApiComments; public class CommentGetLineTests { diff --git a/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsTransformerTests.cs b/src/M31.FluentApi.Tests/Components/FluentApiComments/CommentsTransformerTests.cs similarity index 91% rename from src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsTransformerTests.cs rename to src/M31.FluentApi.Tests/Components/FluentApiComments/CommentsTransformerTests.cs index 93056a9..9dfc3d9 100644 --- a/src/M31.FluentApi.Tests/Components/DocumentationComments/CommentsTransformerTests.cs +++ b/src/M31.FluentApi.Tests/Components/FluentApiComments/CommentsTransformerTests.cs @@ -1,9 +1,9 @@ -using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.DocumentationGeneration; -using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; -using System; +using System; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.FluentApiComments; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; using Xunit; -namespace M31.FluentApi.Tests.Components.DocumentationComments; +namespace M31.FluentApi.Tests.Components.FluentApiComments; public class CommentsTransformerTests { diff --git a/src/M31.FluentApi.Tests/Components/DocumentationComments/FluentCommentsParserTests.cs b/src/M31.FluentApi.Tests/Components/FluentApiComments/FluentCommentsParserTests.cs similarity index 96% rename from src/M31.FluentApi.Tests/Components/DocumentationComments/FluentCommentsParserTests.cs rename to src/M31.FluentApi.Tests/Components/FluentApiComments/FluentCommentsParserTests.cs index 59b2157..a11a6b5 100644 --- a/src/M31.FluentApi.Tests/Components/DocumentationComments/FluentCommentsParserTests.cs +++ b/src/M31.FluentApi.Tests/Components/FluentApiComments/FluentCommentsParserTests.cs @@ -1,11 +1,11 @@ -using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.DocumentationComments; +using System.Linq; +using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Linq; using Xunit; -namespace M31.FluentApi.Tests.Components.DocumentationComments; +namespace M31.FluentApi.Tests.Components.FluentApiComments; public class FluentCommentsParserTests { From 2739d98adfb725929c2f0164b05977e72f81b794 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Wed, 6 Aug 2025 11:40:12 +0200 Subject: [PATCH 43/61] fix: unit test --- .../CodeGeneration/CodeGenerationTests.cs | 2 +- .../CreateStudent.expected.txt | 39 ++++++++----------- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/M31.FluentApi.Tests/CodeGeneration/CodeGenerationTests.cs b/src/M31.FluentApi.Tests/CodeGeneration/CodeGenerationTests.cs index 59d8c26..b7b05bd 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/CodeGenerationTests.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/CodeGenerationTests.cs @@ -16,7 +16,7 @@ public void CanGenerateBuilderForAbstractTestClasses(params string[] testClassPa TestClassCodeGenerator testClassCodeGenerator = TestClassCodeGenerator.Create(testClassPathAndName); GeneratorOutputs generatorOutputs = testClassCodeGenerator.RunGenerators(); Assert.NotNull(generatorOutputs.MainOutput); - + foreach (GeneratorOutput generatorOutput in generatorOutputs.Outputs) { testClassCodeGenerator.WriteGeneratedCodeIfChanged(generatorOutput); diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/CreateStudent.expected.txt index fe9013f..0215330 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/CreateStudent.expected.txt +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/CreateStudent.expected.txt @@ -6,7 +6,6 @@ #nullable enable using System.Collections.Generic; -using System.Reflection; using System.Linq; using System; @@ -18,12 +17,6 @@ public class CreateStudent : CreateStudent.IWithPhoneNumbers { private readonly Student student; - private static readonly PropertyInfo phoneNumbersPropertyInfo; - - static CreateStudent() - { - phoneNumbersPropertyInfo = typeof(Student).GetProperty("PhoneNumbers", BindingFlags.Instance | BindingFlags.Public)!; - } private CreateStudent() { @@ -49,44 +42,44 @@ public class CreateStudent : } /// - Student IWithPhoneNumbers.WithPhoneNumbers(System.Collections.Generic.IReadOnlyCollection phoneNumbers) + Student IWithPhoneNumbers.WithPhoneNumbers(System.Collections.Generic.IReadOnlyCollection phoneNumbers) { - CreateStudent.phoneNumbersPropertyInfo.SetValue(student, phoneNumbers); + student.PhoneNumbers = phoneNumbers; return student; } /// - Student IWithPhoneNumbers.WithPhoneNumbers(params M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[] phoneNumbers) + Student IWithPhoneNumbers.WithPhoneNumbers(params M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.Phone[] phoneNumbers) { - CreateStudent.phoneNumbersPropertyInfo.SetValue(student, phoneNumbers); + student.PhoneNumbers = phoneNumbers; return student; } /// - Student IWithPhoneNumbers.WithPhoneNumbers(params Func[] createPhoneNumbers) + Student IWithPhoneNumbers.WithPhoneNumbers(params Func[] createPhoneNumbers) { - CreateStudent.phoneNumbersPropertyInfo.SetValue(student, createPhoneNumbers.Select(createPhoneNumber => createPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.CreatePhone.InitialStep())).ToArray()); + student.PhoneNumbers = createPhoneNumbers.Select(createPhoneNumber => createPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.CreatePhone.InitialStep())).ToArray(); return student; } /// - Student IWithPhoneNumbers.WithPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone phoneNumber) + Student IWithPhoneNumbers.WithPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.Phone phoneNumber) { - CreateStudent.phoneNumbersPropertyInfo.SetValue(student, new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[1]{ phoneNumber }); + student.PhoneNumbers = new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.Phone[1]{ phoneNumber }; return student; } /// - Student IWithPhoneNumbers.WithPhoneNumber(Func createPhoneNumber) + Student IWithPhoneNumbers.WithPhoneNumber(Func createPhoneNumber) { - CreateStudent.phoneNumbersPropertyInfo.SetValue(student, new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[1]{ createPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.CreatePhone.InitialStep()) }); + student.PhoneNumbers = new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.Phone[1]{ createPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.CreatePhone.InitialStep()) }; return student; } /// Student IWithPhoneNumbers.WithZeroPhoneNumbers() { - CreateStudent.phoneNumbersPropertyInfo.SetValue(student, new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[0]); + student.PhoneNumbers = new M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.Phone[0]; return student; } @@ -103,23 +96,23 @@ public class CreateStudent : { /// Sets the student's phone numbers. /// The student's phone numbers. - Student WithPhoneNumbers(System.Collections.Generic.IReadOnlyCollection phoneNumbers); + Student WithPhoneNumbers(System.Collections.Generic.IReadOnlyCollection phoneNumbers); /// Sets the student's phone numbers. /// The student's phone numbers. - Student WithPhoneNumbers(params M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone[] phoneNumbers); + Student WithPhoneNumbers(params M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.Phone[] phoneNumbers); /// Sets the student's phone numbers. /// Functions for creating the student's phone numbers. - Student WithPhoneNumbers(params Func[] createPhoneNumbers); + Student WithPhoneNumbers(params Func[] createPhoneNumbers); /// Sets the student's phone number. /// The student's phone number. - Student WithPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.DocumentationComments.CommentedLambdaCollectionClass.Phone phoneNumber); + Student WithPhoneNumber(M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.CommentedLambdaCollectionClass.Phone phoneNumber); /// Sets the student's phone number. /// A function for creating the student's phone number. - Student WithPhoneNumber(Func createPhoneNumber); + Student WithPhoneNumber(Func createPhoneNumber); /// Specifies that the student has no phone numbers. Student WithZeroPhoneNumbers(); From e128af2ec1b3c12e1414f364bc102b142fb57383 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Wed, 6 Aug 2025 15:35:00 +0200 Subject: [PATCH 44/61] chore: format code --- .github/workflows/ci.yml | 28 +++++----- src/ExampleProject/ExampleProject.csproj | 20 +++---- .../CodeBuilding/CodeBuilder.cs | 3 +- .../CodeBuilding/MethodComments.cs | 2 +- .../BuilderGenerator.cs | 8 +-- .../BuilderStepMethodsCreator.cs | 6 ++- .../Collections/CollectionMethodCreator.cs | 5 +- .../MethodCreation/Forks/ForkCreator.cs | 3 +- .../MethodCreation/MethodCreator.cs | 6 ++- .../CodeGeneration/CodeGenerator.cs | 2 +- .../Commons/HashCode.cs | 1 - .../Commons/StringBuilderExtensions.cs | 16 +++--- .../FluentCollectionAttributeInfo.cs | 1 + .../FluentPredicateAttributeInfo.cs | 3 +- .../FluentApiInfoGroupCreator.cs | 3 +- .../SourceGenerators/Generics/GenericInfo.cs | 1 + .../M31.FluentApi.Storybook.csproj | 28 +++++----- .../FluentApiComments/CommentGetLineTests.cs | 4 +- .../CommentsTransformerTests.cs | 3 +- .../FluentCommentsParserTests.cs | 2 +- .../Helpers/SyntaxExtensions.cs | 3 +- .../M31.FluentApi.Tests.csproj | 54 +++++++++---------- .../Attributes/FluentApiAttribute.cs | 1 - .../Attributes/FluentBreakAttribute.cs | 1 - .../Attributes/FluentContinueWithAttribute.cs | 1 - .../Attributes/FluentDefaultAttribute.cs | 1 - .../Attributes/FluentMethodAttribute.cs | 1 - .../Attributes/FluentNullableAttribute.cs | 1 - .../Attributes/FluentReturnAttribute.cs | 1 - .../Attributes/FluentSkippableAttribute.cs | 1 - 30 files changed, 106 insertions(+), 104 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88fa693..b25f3c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,17 +13,17 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Setup .NET - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 6.0.x - - name: Restore dependencies - run: dotnet restore - working-directory: src - - name: Build - run: dotnet build --no-restore --maxcpucount:1 - working-directory: src - - name: Test - run: dotnet test --no-build --verbosity normal - working-directory: src + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + - name: Restore dependencies + run: dotnet restore + working-directory: src + - name: Build + run: dotnet build --no-restore --maxcpucount:1 + working-directory: src + - name: Test + run: dotnet test --no-build --verbosity normal + working-directory: src \ No newline at end of file diff --git a/src/ExampleProject/ExampleProject.csproj b/src/ExampleProject/ExampleProject.csproj index e73b4a0..ef23671 100644 --- a/src/ExampleProject/ExampleProject.csproj +++ b/src/ExampleProject/ExampleProject.csproj @@ -1,16 +1,16 @@ - - Exe - net6.0 - enable - enable - + + Exe + net6.0 + enable + enable + - - - - + + + + $(BaseIntermediateOutputPath)Generated diff --git a/src/M31.FluentApi.Generator/CodeBuilding/CodeBuilder.cs b/src/M31.FluentApi.Generator/CodeBuilding/CodeBuilder.cs index a3beaef..4d51b29 100644 --- a/src/M31.FluentApi.Generator/CodeBuilding/CodeBuilder.cs +++ b/src/M31.FluentApi.Generator/CodeBuilding/CodeBuilder.cs @@ -191,8 +191,7 @@ private CodeBuilder AppendSeparated(IEnumerable code, Action separationAct { separationAction(); append(en.Current); - } - while (en.MoveNext()); + } while (en.MoveNext()); return this; } diff --git a/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs b/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs index 4bb1f8c..09c4829 100644 --- a/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs +++ b/src/M31.FluentApi.Generator/CodeBuilding/MethodComments.cs @@ -27,4 +27,4 @@ public CodeBuilder AppendCode(CodeBuilder codeBuilder) return codeBuilder .AppendLines(comments); } -} +} \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderGenerator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderGenerator.cs index 5417d3b..0ef6b32 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderGenerator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderGenerator.cs @@ -58,9 +58,11 @@ public void Modify(CodeBoard codeBoard) private Method CreateMethodWithInheritedDocs(Method method) { return new Method( - new MethodComments(method.MethodComments.Any ? new List() { "/// " } : new List()), - method.MethodSignature, - method.MethodBody); + new MethodComments(method.MethodComments.Any + ? new List() { "/// " } + : new List()), + method.MethodSignature, + method.MethodBody); } private Method CreateMethod(BuilderStepMethod builderStepMethod, CodeBoard codeBoard) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethodsCreator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethodsCreator.cs index e174b5f..46be7dc 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethodsCreator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethodsCreator.cs @@ -6,9 +6,11 @@ namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.BuilderMethodsG internal class BuilderStepMethodsCreator { - internal static BuilderStepMethods CreateBuilderMethods(IReadOnlyList forks, CancellationToken cancellationToken) + internal static BuilderStepMethods CreateBuilderMethods(IReadOnlyList forks, + CancellationToken cancellationToken) { - return forks.Count == 0 ? EmptyBuilderMethods() + return forks.Count == 0 + ? EmptyBuilderMethods() : new BuilderStepMethodsCreator(forks, cancellationToken).Create(); } diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Collections/CollectionMethodCreator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Collections/CollectionMethodCreator.cs index b317922..ab6a8ec 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Collections/CollectionMethodCreator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Collections/CollectionMethodCreator.cs @@ -25,8 +25,9 @@ internal CollectionMethodCreator( internal BuilderMethod? CreateWithItemsMethod(MethodCreator methodCreator) { - return !ShouldCreateWithItemsMethod() ? null : - methodCreator.CreateMethod(symbolInfo, collectionAttributeInfo.WithItems); + return !ShouldCreateWithItemsMethod() + ? null + : methodCreator.CreateMethod(symbolInfo, collectionAttributeInfo.WithItems); } private bool ShouldCreateWithItemsMethod() diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Forks/ForkCreator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Forks/ForkCreator.cs index 67e2c49..d3ec021 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Forks/ForkCreator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/Forks/ForkCreator.cs @@ -15,7 +15,8 @@ internal ForkCreator() public void Modify(CodeBoard codeBoard) { - BuilderMethodFactory builderMethodFactory = new BuilderMethodFactory(codeBoard.InnerBodyCreationDelegates, codeBoard.TransformedComments); + BuilderMethodFactory builderMethodFactory = + new BuilderMethodFactory(codeBoard.InnerBodyCreationDelegates, codeBoard.TransformedComments); MethodCreator methodCreator = new MethodCreator(builderMethodFactory); CreateForks(methodCreator, codeBoard); codeBoard.Forks = GetForks(); diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/MethodCreator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/MethodCreator.cs index d6b1406..9424891 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/MethodCreator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/MethodCreation/MethodCreator.cs @@ -16,7 +16,8 @@ internal MethodCreator(BuilderMethodFactory builderMethodFactory) internal BuilderMethod CreateMethod(MemberSymbolInfo symbolInfo, string methodName) { return BuilderMethodFactory - .CreateBuilderMethod(methodName, ComputeValueCode.Create(symbolInfo.Name, GetStandardParameter(symbolInfo))); + .CreateBuilderMethod(methodName, + ComputeValueCode.Create(symbolInfo.Name, GetStandardParameter(symbolInfo))); } internal BuilderMethod CreateMethodWithDefaultValue( @@ -42,7 +43,8 @@ internal BuilderMethod CreateMethodWithComputedValue( Func buildCodeWithParameter) { return BuilderMethodFactory - .CreateBuilderMethod(methodName, ComputeValueCode.Create(symbolInfo.Name, parameter, buildCodeWithParameter)); + .CreateBuilderMethod(methodName, + ComputeValueCode.Create(symbolInfo.Name, parameter, buildCodeWithParameter)); } internal BuilderMethod CreateMethodThatDoesNothing(MemberSymbolInfo memberSymbolInfo, string methodName) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeGenerator.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeGenerator.cs index 62b917a..3a3544d 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeGenerator.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeGenerator.cs @@ -46,7 +46,7 @@ internal static CodeGeneratorResult GenerateCode(FluentApiClassInfo classInfo, C if (codeBoard.HasErrors) { - return CodeGeneratorResult.WithErrors(codeBoard.Diagnostics); + return CodeGeneratorResult.WithErrors(codeBoard.Diagnostics); } return new CodeGeneratorResult(codeBoard.CodeFile.ToString(), codeBoard.Diagnostics); diff --git a/src/M31.FluentApi.Generator/Commons/HashCode.cs b/src/M31.FluentApi.Generator/Commons/HashCode.cs index 55559dd..883b64f 100644 --- a/src/M31.FluentApi.Generator/Commons/HashCode.cs +++ b/src/M31.FluentApi.Generator/Commons/HashCode.cs @@ -77,7 +77,6 @@ internal HashCode AddSequence(IEnumerable items) { hash = hash * 23 + item?.GetHashCode() ?? 0; } - } return this; diff --git a/src/M31.FluentApi.Generator/Commons/StringBuilderExtensions.cs b/src/M31.FluentApi.Generator/Commons/StringBuilderExtensions.cs index ed2ea5e..8c49458 100644 --- a/src/M31.FluentApi.Generator/Commons/StringBuilderExtensions.cs +++ b/src/M31.FluentApi.Generator/Commons/StringBuilderExtensions.cs @@ -4,13 +4,13 @@ namespace M31.FluentApi.Generator.Commons; internal static class StringBuilderExtensions { - internal static StringBuilder Append(this StringBuilder stringBuilder, string? value, bool condition) - { - if (condition) - { - stringBuilder.Append(value); - } + internal static StringBuilder Append(this StringBuilder stringBuilder, string? value, bool condition) + { + if (condition) + { + stringBuilder.Append(value); + } - return stringBuilder; - } + return stringBuilder; + } } \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentCollectionAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentCollectionAttributeInfo.cs index 24935e0..b0ef0d5 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentCollectionAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentCollectionAttributeInfo.cs @@ -29,6 +29,7 @@ private FluentCollectionAttributeInfo( internal string? WithItem { get; } internal string? WithZeroItems { get; } internal LambdaBuilderInfo? LambdaBuilderInfo { get; } + internal override IReadOnlyList FluentMethodNames => new string?[] { WithItems, WithItem, WithZeroItems }.OfType().ToArray(); diff --git a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentPredicateAttributeInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentPredicateAttributeInfo.cs index 44ee898..1d89ab3 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentPredicateAttributeInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/AttributeInfo/FluentPredicateAttributeInfo.cs @@ -18,7 +18,8 @@ private FluentPredicateAttributeInfo(int builderStep, string method, string nega internal static FluentPredicateAttributeInfo Create(AttributeData attributeData, string memberName) { - (int builderStep, string method, string negatedMethod) = attributeData.GetConstructorArguments(); + (int builderStep, string method, string negatedMethod) = + attributeData.GetConstructorArguments(); method = NameCreator.CreateName(method, memberName); negatedMethod = NameCreator.CreateName(negatedMethod, memberName); return new FluentPredicateAttributeInfo(builderStep, method, negatedMethod); diff --git a/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoGroupCreator.cs b/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoGroupCreator.cs index 7987e13..99810be 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoGroupCreator.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/FluentApiInfoGroupCreator.cs @@ -30,7 +30,8 @@ private IReadOnlyCollection CreateGroups(IReadOnlyCollection // Group FluentApiInfos that have the same builder step, the same fluent method name, and represent // FluentMembers (compounds). (int builderStep, string fluentMethodName, Type type, FluentApiInfo[] infoArray)[] grouping = infos - .GroupBy(i => (i.AttributeInfo.BuilderStep, i.AttributeInfo.FluentMethodNames[0], i.AttributeInfo.GetType())) + .GroupBy( + i => (i.AttributeInfo.BuilderStep, i.AttributeInfo.FluentMethodNames[0], i.AttributeInfo.GetType())) .SelectMany(g => g.First().AttributeInfo.GetType() == typeof(FluentMemberAttributeInfo) ? new[] { (g.Key.BuilderStep, g.Key.Item2, g.Key.Item3, g.ToArray()) } diff --git a/src/M31.FluentApi.Generator/SourceGenerators/Generics/GenericInfo.cs b/src/M31.FluentApi.Generator/SourceGenerators/Generics/GenericInfo.cs index 6c97a46..ed26fa6 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/Generics/GenericInfo.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/Generics/GenericInfo.cs @@ -25,6 +25,7 @@ internal static GenericInfo Create(IEnumerable typeParamet internal IReadOnlyCollection Parameters { get; } internal int ParameterCount => Parameters.Count; internal IEnumerable ParameterStrings => Parameters.Select(p => p.ParameterName); + internal string ParameterListInAngleBrackets => Parameters.Count == 0 ? string.Empty : $"<{string.Join(", ", ParameterStrings)}>"; diff --git a/src/M31.FluentApi.Storybook/M31.FluentApi.Storybook.csproj b/src/M31.FluentApi.Storybook/M31.FluentApi.Storybook.csproj index c1b1f28..33b1729 100644 --- a/src/M31.FluentApi.Storybook/M31.FluentApi.Storybook.csproj +++ b/src/M31.FluentApi.Storybook/M31.FluentApi.Storybook.csproj @@ -1,17 +1,17 @@ - - Exe - net6.0 - enable - enable - false - $(BaseIntermediateOutputPath)Generated - true - + + Exe + net6.0 + enable + enable + false + $(BaseIntermediateOutputPath)Generated + true + - - - - - + + + + + \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/Components/FluentApiComments/CommentGetLineTests.cs b/src/M31.FluentApi.Tests/Components/FluentApiComments/CommentGetLineTests.cs index 690019f..636fc2f 100644 --- a/src/M31.FluentApi.Tests/Components/FluentApiComments/CommentGetLineTests.cs +++ b/src/M31.FluentApi.Tests/Components/FluentApiComments/CommentGetLineTests.cs @@ -26,6 +26,4 @@ public void CanGetLineOfCommentWithAttributes() string line = comment.GetLine(); Assert.Equal(@"/// The parameter1.", line); } - - -} +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/Components/FluentApiComments/CommentsTransformerTests.cs b/src/M31.FluentApi.Tests/Components/FluentApiComments/CommentsTransformerTests.cs index 9dfc3d9..1612a99 100644 --- a/src/M31.FluentApi.Tests/Components/FluentApiComments/CommentsTransformerTests.cs +++ b/src/M31.FluentApi.Tests/Components/FluentApiComments/CommentsTransformerTests.cs @@ -63,6 +63,7 @@ public void FurtherMethodAttributesAreRetained() Comment comment = new Comment("fluentParam", attributes, "The new value of Property1."); Comment? transformed = CommentsTransformer.TransformComment(comment); Assert.NotNull(transformed); - Assert.Equal(@"/// The new value of Property1.", transformed!.GetLine()); + Assert.Equal(@"/// The new value of Property1.", + transformed!.GetLine()); } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/Components/FluentApiComments/FluentCommentsParserTests.cs b/src/M31.FluentApi.Tests/Components/FluentApiComments/FluentCommentsParserTests.cs index a11a6b5..0302d10 100644 --- a/src/M31.FluentApi.Tests/Components/FluentApiComments/FluentCommentsParserTests.cs +++ b/src/M31.FluentApi.Tests/Components/FluentApiComments/FluentCommentsParserTests.cs @@ -12,7 +12,7 @@ public class FluentCommentsParserTests [Fact] public void CanParseSingleTag() { - string sourceCode = @" + string sourceCode = @" namespace M31.FluentApi.Tests.Components { public class DocumentationCommentsTests diff --git a/src/M31.FluentApi.Tests/Helpers/SyntaxExtensions.cs b/src/M31.FluentApi.Tests/Helpers/SyntaxExtensions.cs index ecbfbb6..ea98028 100644 --- a/src/M31.FluentApi.Tests/Helpers/SyntaxExtensions.cs +++ b/src/M31.FluentApi.Tests/Helpers/SyntaxExtensions.cs @@ -10,7 +10,8 @@ internal static class SyntaxExtensions internal static TypeDeclarationSyntax? GetFluentApiTypeDeclaration(this SyntaxTree syntaxTree) { SyntaxNode root = syntaxTree.GetRoot(); - TypeDeclarationSyntax? typeDeclaration = (TypeDeclarationSyntax?)root.Find(n => n.IsClassStructOrRecordSyntax()); + TypeDeclarationSyntax? typeDeclaration = + (TypeDeclarationSyntax?)root.Find(n => n.IsClassStructOrRecordSyntax()); return typeDeclaration; } diff --git a/src/M31.FluentApi.Tests/M31.FluentApi.Tests.csproj b/src/M31.FluentApi.Tests/M31.FluentApi.Tests.csproj index 32f36a6..c8f01bc 100644 --- a/src/M31.FluentApi.Tests/M31.FluentApi.Tests.csproj +++ b/src/M31.FluentApi.Tests/M31.FluentApi.Tests.csproj @@ -1,33 +1,33 @@ - - net6.0 - enable - false - M31.FluentApi.Tests - + + net6.0 + enable + false + M31.FluentApi.Tests + - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + - - - - - + + + + + diff --git a/src/M31.FluentApi/Attributes/FluentApiAttribute.cs b/src/M31.FluentApi/Attributes/FluentApiAttribute.cs index a3301f6..df7cf49 100644 --- a/src/M31.FluentApi/Attributes/FluentApiAttribute.cs +++ b/src/M31.FluentApi/Attributes/FluentApiAttribute.cs @@ -14,6 +14,5 @@ public class FluentApiAttribute : Attribute /// The name of the generated builder class. public FluentApiAttribute(string builderClassName = "Create{Name}") { - } } \ No newline at end of file diff --git a/src/M31.FluentApi/Attributes/FluentBreakAttribute.cs b/src/M31.FluentApi/Attributes/FluentBreakAttribute.cs index b86a166..7c42e67 100644 --- a/src/M31.FluentApi/Attributes/FluentBreakAttribute.cs +++ b/src/M31.FluentApi/Attributes/FluentBreakAttribute.cs @@ -11,6 +11,5 @@ public class FluentBreakAttribute : Attribute /// public FluentBreakAttribute() { - } } \ No newline at end of file diff --git a/src/M31.FluentApi/Attributes/FluentContinueWithAttribute.cs b/src/M31.FluentApi/Attributes/FluentContinueWithAttribute.cs index 34fd1ed..188dfa3 100644 --- a/src/M31.FluentApi/Attributes/FluentContinueWithAttribute.cs +++ b/src/M31.FluentApi/Attributes/FluentContinueWithAttribute.cs @@ -14,6 +14,5 @@ public class FluentContinueWithAttribute : Attribute /// The builder step to continue with. public FluentContinueWithAttribute(int builderStep) { - } } \ No newline at end of file diff --git a/src/M31.FluentApi/Attributes/FluentDefaultAttribute.cs b/src/M31.FluentApi/Attributes/FluentDefaultAttribute.cs index 2bf29d1..c8bb50a 100644 --- a/src/M31.FluentApi/Attributes/FluentDefaultAttribute.cs +++ b/src/M31.FluentApi/Attributes/FluentDefaultAttribute.cs @@ -16,6 +16,5 @@ public class FluentDefaultAttribute : Attribute /// public FluentDefaultAttribute(string method = "WithDefault{Name}") { - } } \ No newline at end of file diff --git a/src/M31.FluentApi/Attributes/FluentMethodAttribute.cs b/src/M31.FluentApi/Attributes/FluentMethodAttribute.cs index e1a53af..d280cfd 100644 --- a/src/M31.FluentApi/Attributes/FluentMethodAttribute.cs +++ b/src/M31.FluentApi/Attributes/FluentMethodAttribute.cs @@ -16,6 +16,5 @@ public class FluentMethodAttribute : Attribute /// public FluentMethodAttribute(int builderStep, string method = "{Name}") { - } } \ No newline at end of file diff --git a/src/M31.FluentApi/Attributes/FluentNullableAttribute.cs b/src/M31.FluentApi/Attributes/FluentNullableAttribute.cs index e44bd3d..ea9b6a2 100644 --- a/src/M31.FluentApi/Attributes/FluentNullableAttribute.cs +++ b/src/M31.FluentApi/Attributes/FluentNullableAttribute.cs @@ -15,6 +15,5 @@ public class FluentNullableAttribute : Attribute /// 'null'. public FluentNullableAttribute(string method = "Without{Name}") { - } } \ No newline at end of file diff --git a/src/M31.FluentApi/Attributes/FluentReturnAttribute.cs b/src/M31.FluentApi/Attributes/FluentReturnAttribute.cs index 0b63a20..abdf3ce 100644 --- a/src/M31.FluentApi/Attributes/FluentReturnAttribute.cs +++ b/src/M31.FluentApi/Attributes/FluentReturnAttribute.cs @@ -11,6 +11,5 @@ public class FluentReturnAttribute : Attribute /// public FluentReturnAttribute() { - } } \ No newline at end of file diff --git a/src/M31.FluentApi/Attributes/FluentSkippableAttribute.cs b/src/M31.FluentApi/Attributes/FluentSkippableAttribute.cs index 51bef63..259ce77 100644 --- a/src/M31.FluentApi/Attributes/FluentSkippableAttribute.cs +++ b/src/M31.FluentApi/Attributes/FluentSkippableAttribute.cs @@ -11,6 +11,5 @@ public class FluentSkippableAttribute : Attribute /// public FluentSkippableAttribute() { - } } \ No newline at end of file From cc58597b0876ac526cb6709392e727e81a88977e Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Wed, 6 Aug 2025 17:35:31 +0200 Subject: [PATCH 45/61] refactor: format code --- src/ExampleProject/HashCode.cs | 1 - .../Student.cs | 1 - .../ReservedMethodClass1/Student.cs | 2 - .../Abstract/FluentMethodClass/Student.cs | 2 +- .../FluentMethodDefaultValuesClass/Student.cs | 2 +- .../Abstract/FluentNullableClass/Student.cs | 2 +- .../FullyQualifiedTypeClass/Student.cs | 2 +- .../Student.cs | 1 - .../GetPrivateInitPropertyClass/Student.cs | 1 + .../GetPrivateSetPropertyClass/Student.cs | 1 + .../Abstract/InheritedClass/Person.cs | 2 +- .../InheritedClassProtectedMembers/Person.cs | 2 +- .../InheritedClassProtectedSetters/Person.cs | 2 +- .../Abstract/InternalClass/Student.cs | 2 +- .../PrivateConstructorClass/Student.cs | 1 - .../Abstract/ThreeMemberClass/Student.cs | 2 +- .../Abstract/ThreeMemberRecord/Student.cs | 2 +- .../ThreeMemberRecordStruct/Student.cs | 2 +- .../Abstract/ThreeMemberStruct/Student.cs | 2 +- .../ThreePrivateMembersClass/Student.cs | 2 +- .../TryBreakFluentApiClass1/Student.cs | 1 - .../Abstract/TwoMemberClass/Student.cs | 2 +- .../TestClasses/PersonClass/Person.cs | 46 +++++++++---------- 23 files changed, 39 insertions(+), 44 deletions(-) diff --git a/src/ExampleProject/HashCode.cs b/src/ExampleProject/HashCode.cs index 501ad92..5d58dd4 100644 --- a/src/ExampleProject/HashCode.cs +++ b/src/ExampleProject/HashCode.cs @@ -82,7 +82,6 @@ public void AddSequence(IEnumerable items) { hash = hash * 23 + item?.GetHashCode() ?? 0; } - } } diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/ConflictingControlAttributesClass3/Student.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/ConflictingControlAttributesClass3/Student.cs index 7e9adc9..4009507 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/ConflictingControlAttributesClass3/Student.cs +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/ConflictingControlAttributesClass3/Student.cs @@ -17,6 +17,5 @@ public class Student [FluentReturn] public void Method1() { - } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/ReservedMethodClass1/Student.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/ReservedMethodClass1/Student.cs index df7318e..95d8d6b 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/ReservedMethodClass1/Student.cs +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/ReservedMethodClass1/Student.cs @@ -12,12 +12,10 @@ public class Student [FluentMethod(0)] public void InitialStep() { - } [FluentMethod(1, "InitialStep")] public void Method1() { - } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentMethodClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentMethodClass/Student.cs index c43b41b..e6b65a4 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentMethodClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentMethodClass/Student.cs @@ -11,7 +11,7 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentMethodCl public class Student { public string Name { get; set; } - public DateOnly DateOfBirth{ get; set; } + public DateOnly DateOfBirth { get; set; } public int Semester { get; set; } [FluentMethod(0)] diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentMethodDefaultValuesClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentMethodDefaultValuesClass/Student.cs index f725b43..59158c8 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentMethodDefaultValuesClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentMethodDefaultValuesClass/Student.cs @@ -12,7 +12,7 @@ public class Student { public string FirstName { get; set; } public string? LastName { get; set; } - public DateOnly DateOfBirth{ get; set; } + public DateOnly DateOfBirth { get; set; } public DateOnly EnrollmentDate { get; set; } public int Semester { get; set; } public int? NumberOfPassedExams { get; set; } diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentNullableClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentNullableClass/Student.cs index 5fe4ce3..41b7a53 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentNullableClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentNullableClass/Student.cs @@ -14,5 +14,5 @@ public class Student [FluentMember(1, "BornOn")] [FluentNullable()] - public DateOnly? DateOfBirth{ get; set; } + public DateOnly? DateOfBirth { get; set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FullyQualifiedTypeClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FullyQualifiedTypeClass/Student.cs index 63a1865..3d59998 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FullyQualifiedTypeClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FullyQualifiedTypeClass/Student.cs @@ -10,7 +10,7 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FullyQualified public class Student { [FluentMember(0, "BornOn")] - public System.DateOnly DateOfBirth{ get; set; } + public System.DateOnly DateOfBirth { get; set; } [FluentCollection(0, "Friend")] public System.Collections.Generic.List Friends { get; set; } diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/GenericClassPrivateDefaultConstructor/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/GenericClassPrivateDefaultConstructor/Student.cs index 210ff24..2de2079 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/GenericClassPrivateDefaultConstructor/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/GenericClassPrivateDefaultConstructor/Student.cs @@ -11,7 +11,6 @@ public class Student { private Student() { - } [FluentMember(0)] diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/GetPrivateInitPropertyClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/GetPrivateInitPropertyClass/Student.cs index f8ba4d3..21a2e61 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/GetPrivateInitPropertyClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/GetPrivateInitPropertyClass/Student.cs @@ -1,4 +1,5 @@ using M31.FluentApi.Attributes; + // ReSharper disable All namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.GetPrivateInitPropertyClass; diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/GetPrivateSetPropertyClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/GetPrivateSetPropertyClass/Student.cs index f4f1cb1..46e12c3 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/GetPrivateSetPropertyClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/GetPrivateSetPropertyClass/Student.cs @@ -1,4 +1,5 @@ using M31.FluentApi.Attributes; + // ReSharper disable All namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.GetPrivateSetPropertyClass; diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/InheritedClass/Person.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/InheritedClass/Person.cs index 9138a12..1da3b09 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/InheritedClass/Person.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/InheritedClass/Person.cs @@ -14,5 +14,5 @@ public class Person public string Name { get; set; } [FluentMember(1, "BornOn")] - public DateOnly DateOfBirth{ get; set; } + public DateOnly DateOfBirth { get; set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/InheritedClassProtectedMembers/Person.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/InheritedClassProtectedMembers/Person.cs index 8fb6b94..ac03eca 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/InheritedClassProtectedMembers/Person.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/InheritedClassProtectedMembers/Person.cs @@ -14,5 +14,5 @@ public class Person protected string Name { get; set; } [FluentMember(1, "BornOn")] - protected DateOnly DateOfBirth{ get; set; } + protected DateOnly DateOfBirth { get; set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/InheritedClassProtectedSetters/Person.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/InheritedClassProtectedSetters/Person.cs index e5206b2..5214e85 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/InheritedClassProtectedSetters/Person.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/InheritedClassProtectedSetters/Person.cs @@ -14,5 +14,5 @@ public class Person public string Name { get; protected set; } [FluentMember(1, "BornOn")] - public DateOnly DateOfBirth{ get; protected set; } + public DateOnly DateOfBirth { get; protected set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/InternalClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/InternalClass/Student.cs index 799f25e..4442adc 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/InternalClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/InternalClass/Student.cs @@ -14,7 +14,7 @@ internal class Student public string Name { get; set; } [FluentMember(1, "BornOn")] - public DateOnly DateOfBirth{ get; set; } + public DateOnly DateOfBirth { get; set; } [FluentMember(2, "InSemester")] public int Semester { get; set; } diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/PrivateConstructorClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/PrivateConstructorClass/Student.cs index c488029..532a5d1 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/PrivateConstructorClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/PrivateConstructorClass/Student.cs @@ -9,7 +9,6 @@ public class Student { private Student() { - } [FluentMember(0, "InSemester")] diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreeMemberClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreeMemberClass/Student.cs index 2504510..5133152 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreeMemberClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreeMemberClass/Student.cs @@ -14,7 +14,7 @@ public class Student public string Name { get; set; } [FluentMember(1, "BornOn")] - public DateOnly DateOfBirth{ get; set; } + public DateOnly DateOfBirth { get; set; } [FluentMember(2, "InSemester")] public int Semester { get; set; } diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreeMemberRecord/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreeMemberRecord/Student.cs index fa00dc1..a50bcae 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreeMemberRecord/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreeMemberRecord/Student.cs @@ -14,7 +14,7 @@ public record Student public string Name { get; set; } [FluentMember(1, "BornOn")] - public DateOnly DateOfBirth{ get; set; } + public DateOnly DateOfBirth { get; set; } [FluentMember(2, "InSemester")] public int Semester { get; set; } diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreeMemberRecordStruct/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreeMemberRecordStruct/Student.cs index eee6a54..1051c4e 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreeMemberRecordStruct/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreeMemberRecordStruct/Student.cs @@ -14,7 +14,7 @@ public record struct Student public string Name { get; set; } [FluentMember(1, "BornOn")] - public DateOnly DateOfBirth{ get; set; } + public DateOnly DateOfBirth { get; set; } [FluentMember(2, "InSemester")] public int Semester { get; set; } diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreeMemberStruct/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreeMemberStruct/Student.cs index b3d6e0c..e83488f 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreeMemberStruct/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreeMemberStruct/Student.cs @@ -14,7 +14,7 @@ public struct Student public string Name { get; set; } [FluentMember(1, "BornOn")] - public DateOnly DateOfBirth{ get; set; } + public DateOnly DateOfBirth { get; set; } [FluentMember(2, "InSemester")] public int Semester { get; set; } diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreePrivateMembersClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreePrivateMembersClass/Student.cs index d5d0ddc..34f298c 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreePrivateMembersClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/ThreePrivateMembersClass/Student.cs @@ -14,7 +14,7 @@ public class Student public string Name { get; private set; } [FluentMember(1, "BornOn")] - public DateOnly DateOfBirth{ get; private set; } + public DateOnly DateOfBirth { get; private set; } [FluentMember(2, "InSemester")] public int Semester { get; private set; } diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/TryBreakFluentApiClass1/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/TryBreakFluentApiClass1/Student.cs index 1d87f73..dd94bfd 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/TryBreakFluentApiClass1/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/TryBreakFluentApiClass1/Student.cs @@ -12,6 +12,5 @@ public class Student [FluentMethod(0)] private void SomeMethod(string someMethodMethodInfo) { - } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/TwoMemberClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/TwoMemberClass/Student.cs index 165d426..0f8b07c 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/TwoMemberClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/TwoMemberClass/Student.cs @@ -14,5 +14,5 @@ public class Student public string Name { get; set; } [FluentMember(1, "BornOn")] - public DateOnly DateOfBirth{ get; set; } + public DateOnly DateOfBirth { get; set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/PersonClass/Person.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/PersonClass/Person.cs index 21713e3..2f39d3a 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/PersonClass/Person.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/PersonClass/Person.cs @@ -38,24 +38,24 @@ private void WhoLivesAtAddress() { } - [FluentMethod(4)] - private void WithHouseNumber(string houseNumber) - { - HouseNumber = houseNumber; - } - - [FluentMethod(5)] - private void WithStreet(string street) - { - Street = street; - } - - [FluentMethod(6)] - [FluentBreak] - private void InCity(string city) - { - City = city; - } + [FluentMethod(4)] + private void WithHouseNumber(string houseNumber) + { + HouseNumber = houseNumber; + } + + [FluentMethod(5)] + private void WithStreet(string street) + { + Street = street; + } + + [FluentMethod(6)] + [FluentBreak] + private void InCity(string city) + { + City = city; + } [FluentMethod(3)] [FluentContinueWith(7)] @@ -64,9 +64,9 @@ private void WhoIsADigitalNomad() IsDigitalNomad = true; } - [FluentMethod(7)] - private void LivingInCity(string city) - { - City = city; - } + [FluentMethod(7)] + private void LivingInCity(string city) + { + City = city; + } } \ No newline at end of file From 1c775ad85acd548cca87784910ceac3b3750ca0b Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Wed, 6 Aug 2025 17:38:09 +0200 Subject: [PATCH 46/61] fix: unit test --- .../AnalyzerAndCodeFixes/DiagnosticsDuringGenerationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/DiagnosticsDuringGenerationTests.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/DiagnosticsDuringGenerationTests.cs index 5aad575..4a29203 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/DiagnosticsDuringGenerationTests.cs +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/DiagnosticsDuringGenerationTests.cs @@ -86,7 +86,7 @@ public void CanDetectReservedMethod1() ExpectedDiagnostic expectedDiagnostic2 = new ExpectedDiagnostic( ReservedMethodName.Descriptor, "InitialStep", - (18, 6)); + (17, 6)); RunGeneratorAndCheckDiagnostics("ReservedMethodClass1", "Student", expectedDiagnostic1, expectedDiagnostic2); } From 55a30c9f63587259e8c770610ce3e8660aa53c48 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Wed, 6 Aug 2025 19:53:25 +0200 Subject: [PATCH 47/61] test: VoidMethodClass --- .../CodeBuilding/CodeBuilder.cs | 3 +- .../MethodsToCommentsTemplate.cs | 10 ++++++ .../FluentApiCommentsProviderTests.cs | 2 ++ .../CommentedClass/Student.Age.txt | 1 + .../CommentedClass/Student.BornOn.txt | 1 + .../CommentedClass/Student.City.txt | 3 ++ .../CommentedClass/Student.FirstName.txt | 1 + .../CommentedClass/Student.Friends.txt | 3 ++ .../CommentedClass/Student.IsHappy.txt | 3 ++ .../CommentedClass/Student.LastName.txt | 1 + .../CommentedClass/Student.Semester.txt | 2 ++ .../Student.PhoneNumbers.txt | 3 ++ .../VoidMethodClass/Student.Sleep.txt | 30 ++++++++++++++++++ .../VoidMethodClass/Student.Study.txt | 31 +++++++++++++++++++ .../VoidMethodClass/Student.cs | 27 ++++++++++++++++ 15 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.Sleep.txt create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.Study.txt create mode 100644 src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.cs diff --git a/src/M31.FluentApi.Generator/CodeBuilding/CodeBuilder.cs b/src/M31.FluentApi.Generator/CodeBuilding/CodeBuilder.cs index 4d51b29..a3beaef 100644 --- a/src/M31.FluentApi.Generator/CodeBuilding/CodeBuilder.cs +++ b/src/M31.FluentApi.Generator/CodeBuilding/CodeBuilder.cs @@ -191,7 +191,8 @@ private CodeBuilder AppendSeparated(IEnumerable code, Action separationAct { separationAction(); append(en.Current); - } while (en.MoveNext()); + } + while (en.MoveNext()); return this; } diff --git a/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiComments/MethodsToCommentsTemplate.cs b/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiComments/MethodsToCommentsTemplate.cs index e186382..b4d7f04 100644 --- a/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiComments/MethodsToCommentsTemplate.cs +++ b/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiComments/MethodsToCommentsTemplate.cs @@ -53,6 +53,11 @@ private void CreateCommentsTemplateWithoutMethodNames(BuilderMethod[] sameNameBu { comments.Add($"/// ..."); } + + if (sameNameBuilderMethods.Any(b => b.ReturnTypeToRespect != "void")) + { + comments.Add("/// ..."); + } } private void CreateCommentsTemplateWithMethodNames(BuilderMethod[] sameNameBuilderMethods) @@ -72,6 +77,11 @@ private void CreateCommentsTemplateWithMethodNames(BuilderMethod[] sameNameBuild { comments.Add($"/// ..."); } + + if (sameNameBuilderMethods.Any(b => b.ReturnTypeToRespect != "void")) + { + comments.Add($"/// ..."); + } } private static IEnumerable GetDistinctParameterNames(BuilderMethod[] builderMethods) diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs index 781c3bf..af0c6ad 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/FluentApiCommentsProviderTests.cs @@ -24,6 +24,8 @@ public class FluentApiCommentsProviderTests [InlineData("CommentedClass", "Student", "IsHappy ", "IsHappy")] [InlineData("CommentedClass", "Student", " Friends", "Friends")] [InlineData("LambdaCollectionClass", "Student", "PhoneNumbers", "PhoneNumbers")] + [InlineData("VoidMethodClass", "Student", "Study", "Study")] + [InlineData("VoidMethodClass", "Student", "Sleep", "Sleep")] public async Task CanProvideFluentApiComments( string commentTestClass, string @class, string selectedSpan, string member) { diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Age.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Age.txt index 7e8c7ef..d226635 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Age.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Age.txt @@ -21,6 +21,7 @@ public class Student /// ... /// /// ... + /// ... [FluentMember(1, "OfAge")] public int Age { get; private set; } diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.BornOn.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.BornOn.txt index 69cd27b..412e078 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.BornOn.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.BornOn.txt @@ -24,6 +24,7 @@ public class Student /// ... /// /// ... + /// ... [FluentMethod(1)] private void BornOn(DateOnly dateOfBirth) { diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.City.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.City.txt index 9541784..fb10efe 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.City.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.City.txt @@ -37,14 +37,17 @@ public class Student /// ... /// /// ... + /// ... /// /// /// ... /// + /// ... /// /// /// ... /// + /// ... [FluentMember(3, "LivingIn")] [FluentDefault("LivingInBoston")] [FluentNullable("InUnknownCity")] diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.FirstName.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.FirstName.txt index 32629c8..0e092e9 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.FirstName.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.FirstName.txt @@ -16,6 +16,7 @@ public class Student /// /// ... /// ... + /// ... [FluentMember(0, "Named", 0)] public string FirstName { get; private set; } diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Friends.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Friends.txt index a24e57d..b905000 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Friends.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Friends.txt @@ -46,15 +46,18 @@ public class Student /// ... /// /// ... + /// ... /// /// /// ... /// /// ... + /// ... /// /// /// ... /// + /// ... [FluentCollection(5, "Friend", "WhoseFriendsAre", "WhoseFriendIs", "WhoHasNoFriends")] public IReadOnlyCollection Friends { get; private set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.IsHappy.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.IsHappy.txt index fd52c6f..7127eca 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.IsHappy.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.IsHappy.txt @@ -42,14 +42,17 @@ public class Student /// ... /// /// ... + /// ... /// /// /// ... /// + /// ... /// /// /// ... /// + /// ... [FluentPredicate(4, "WhoIsHappy", "WhoIsSad")] [FluentNullable("WithUnknownMood")] public bool? IsHappy { get; private set; } diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.LastName.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.LastName.txt index 32629c8..0e092e9 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.LastName.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.LastName.txt @@ -16,6 +16,7 @@ public class Student /// /// ... /// ... + /// ... [FluentMember(0, "Named", 0)] public string FirstName { get; private set; } diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Semester.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Semester.txt index 5e36c69..fdefb8c 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Semester.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Semester.txt @@ -33,10 +33,12 @@ public class Student /// ... /// /// ... + /// ... /// /// /// ... /// + /// ... [FluentMember(2, "InSemester")] [FluentDefault("WhoStartsUniversity")] public int Semester { get; private set; } = 0; diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/LambdaCollectionClass/Student.PhoneNumbers.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/LambdaCollectionClass/Student.PhoneNumbers.txt index 371dafc..7f4f8da 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/LambdaCollectionClass/Student.PhoneNumbers.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/LambdaCollectionClass/Student.PhoneNumbers.txt @@ -17,16 +17,19 @@ public class Student /// /// ... /// ... + /// ... /// /// /// ... /// /// ... /// ... + /// ... /// /// /// ... /// + /// ... [FluentCollection(1, "PhoneNumber")] public IReadOnlyCollection PhoneNumbers { get; set; } } diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.Sleep.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.Sleep.txt new file mode 100644 index 0000000..f486a34 --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.Sleep.txt @@ -0,0 +1,30 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using System; +using System.Collections.Generic; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.VoidMethodClass; + +[FluentApi] +public class Student +{ + [FluentMember(0)] + public string Name { get; private set; } + + [FluentMethod(1)] + public void Study() + { + } + + /// + /// ... + /// + [FluentMethod(2)] + [FluentReturn] + public void Sleep() + { + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.Study.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.Study.txt new file mode 100644 index 0000000..49bf519 --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.Study.txt @@ -0,0 +1,31 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using System; +using System.Collections.Generic; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.VoidMethodClass; + +[FluentApi] +public class Student +{ + [FluentMember(0)] + public string Name { get; private set; } + + /// + /// ... + /// + /// ... + [FluentMethod(1)] + public void Study() + { + } + + [FluentMethod(2)] + [FluentReturn] + public void Sleep() + { + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.cs b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.cs new file mode 100644 index 0000000..5e590e2 --- /dev/null +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.cs @@ -0,0 +1,27 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using System; +using System.Collections.Generic; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments.VoidMethodClass; + +[FluentApi] +public class Student +{ + [FluentMember(0)] + public string Name { get; private set; } + + [FluentMethod(1)] + public void Study() + { + } + + [FluentMethod(2)] + [FluentReturn] + public void Sleep() + { + } +} \ No newline at end of file From 2d2e43604c5ac2362bb65f58800d13182577c992 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Wed, 6 Aug 2025 21:08:22 +0200 Subject: [PATCH 48/61] fix: remove MyPerson test class --- src/ExampleProject/MyPerson.cs | 34 ---------------------------------- src/ExampleProject/Program.cs | 6 ------ 2 files changed, 40 deletions(-) delete mode 100644 src/ExampleProject/MyPerson.cs diff --git a/src/ExampleProject/MyPerson.cs b/src/ExampleProject/MyPerson.cs deleted file mode 100644 index 24e2f3b..0000000 --- a/src/ExampleProject/MyPerson.cs +++ /dev/null @@ -1,34 +0,0 @@ -using M31.FluentApi.Attributes; - -namespace ExampleProject; - -[FluentApi] -class MyPerson -{ - [FluentMember(0, "Name")] - public string FirstName { get; set; } - - [FluentMember(0, "Name")] - public string LastName { get; set; } - - /// - /// Sets the student's age. - /// - /// The student's age. - [FluentMember(1, "OfAge")] - public int Age { get; set; } - - /// - /// Sets the student's semester. - /// - /// The student's semester. - [FluentMember(2, "InSemester")] - public int Semester { get; set; } - - /// - /// This summary will not be taken into account. - /// - /// This parameter will not be taken into account. - [FluentMember(3, "LivingIn")] - public string City { get; set; } -} \ No newline at end of file diff --git a/src/ExampleProject/Program.cs b/src/ExampleProject/Program.cs index f283279..50ddc6e 100644 --- a/src/ExampleProject/Program.cs +++ b/src/ExampleProject/Program.cs @@ -3,12 +3,6 @@ #pragma warning disable CS8321 // Local function is declared but never used -// MyPerson -// - -MyPerson mp = CreateMyPerson.Name("Kevin", "Schaal") - .OfAge(22).InSemester(4).LivingIn("Tübingen"); - // Student // From 18d6101dda6223afda69b2fcccb69942393f60bf Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Wed, 6 Aug 2025 21:13:57 +0200 Subject: [PATCH 49/61] test: IncompletelyCommentedPropertyClass --- .../CreateStudent.expected.txt | 75 +++++++++++++++++++ .../CreateStudent.g.cs | 75 +++++++++++++++++++ .../Student.cs | 29 +++++++ .../CodeGeneration/TestDataProvider.cs | 1 + 4 files changed, 180 insertions(+) create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/IncompletelyCommentedPropertyClass/CreateStudent.expected.txt create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/IncompletelyCommentedPropertyClass/CreateStudent.g.cs create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/IncompletelyCommentedPropertyClass/Student.cs diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/IncompletelyCommentedPropertyClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/IncompletelyCommentedPropertyClass/CreateStudent.expected.txt new file mode 100644 index 0000000..1c8abe3 --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/IncompletelyCommentedPropertyClass/CreateStudent.expected.txt @@ -0,0 +1,75 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.IncompletelyCommentedPropertyClass; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.ILivingIn +{ + private readonly Student student; + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + public static Student LivingIn(string? city) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.City = city; + return createStudent.student; + } + + public static Student LivingInBoston() + { + CreateStudent createStudent = new CreateStudent(); + return createStudent.student; + } + + public static Student InUnknownCity() + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.City = null; + return createStudent.student; + } + + Student ILivingIn.LivingIn(string? city) + { + student.City = city; + return student; + } + + Student ILivingIn.LivingInBoston() + { + return student; + } + + Student ILivingIn.InUnknownCity() + { + student.City = null; + return student; + } + + public interface ICreateStudent : ILivingIn + { + } + + public interface ILivingIn + { + Student LivingIn(string? city); + + Student LivingInBoston(); + + Student InUnknownCity(); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/IncompletelyCommentedPropertyClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/IncompletelyCommentedPropertyClass/CreateStudent.g.cs new file mode 100644 index 0000000..1c8abe3 --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/IncompletelyCommentedPropertyClass/CreateStudent.g.cs @@ -0,0 +1,75 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.IncompletelyCommentedPropertyClass; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.ILivingIn +{ + private readonly Student student; + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + public static Student LivingIn(string? city) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.City = city; + return createStudent.student; + } + + public static Student LivingInBoston() + { + CreateStudent createStudent = new CreateStudent(); + return createStudent.student; + } + + public static Student InUnknownCity() + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.City = null; + return createStudent.student; + } + + Student ILivingIn.LivingIn(string? city) + { + student.City = city; + return student; + } + + Student ILivingIn.LivingInBoston() + { + return student; + } + + Student ILivingIn.InUnknownCity() + { + student.City = null; + return student; + } + + public interface ICreateStudent : ILivingIn + { + } + + public interface ILivingIn + { + Student LivingIn(string? city); + + Student LivingInBoston(); + + Student InUnknownCity(); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/IncompletelyCommentedPropertyClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/IncompletelyCommentedPropertyClass/Student.cs new file mode 100644 index 0000000..cae60d9 --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/IncompletelyCommentedPropertyClass/Student.cs @@ -0,0 +1,29 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments. + IncompletelyCommentedPropertyClass; + +[FluentApi] +public class Student +{ + /// + /// Sets the student's city. + /// + /// The student's city. + /// + /// + /// Set's the student's city to Boston. + /// + /// + /// + /// Set's the student's city to null. + /// + [FluentMember(0, "LivingIn")] + [FluentDefault("LivingInBoston")] + [FluentNullable("InUnknownCity")] + public string? City { get; set; } = "Boston"; +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs index 0025fde..a7ca13e 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs @@ -27,6 +27,7 @@ internal class TestDataProvider : IEnumerable new object[] { "Abstract", "FluentApiComments", "CommentedPropertiesClass", "Student" }, new object[] { "Abstract", "FluentApiComments", "CommentedPropertiesClassAdvanced", "Student" }, new object[] { "Abstract", "FluentApiComments", "RedundantCommentCompoundClass", "Student" }, + new object[] { "Abstract", "FluentApiComments", "IncompletelyCommentedPropertyClass", "Student"}, new object[] { "Abstract", "EmptyClass", "Student" }, new object[] { "Abstract", "FluentDefaultMemberClass", "Student" }, new object[] { "Abstract", "FluentLambdaClass", "Student|Address" }, From 1e8d13581df3b350b32d928f6ba936e786fa629b Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Thu, 7 Aug 2025 10:20:47 +0200 Subject: [PATCH 50/61] feat(ExampleProject): DocumentedStudent --- src/ExampleProject/DocumentedStudent.cs | 117 ++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/ExampleProject/DocumentedStudent.cs diff --git a/src/ExampleProject/DocumentedStudent.cs b/src/ExampleProject/DocumentedStudent.cs new file mode 100644 index 0000000..b2b13d1 --- /dev/null +++ b/src/ExampleProject/DocumentedStudent.cs @@ -0,0 +1,117 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using M31.FluentApi.Attributes; + +namespace ExampleProject; + +[FluentApi] +public class DocumentedStudent +{ + /// + /// Sets the student's name. + /// + /// The student's first name. + /// The student's last name. + /// A builder for setting the student's age. + [FluentMember(0, "Named", 0)] + public string FirstName { get; private set; } + + [FluentMember(0, "Named", 1)] + public string LastName { get; private set; } + + /// + /// Sets the student's age. + /// + /// The student's age. + /// A builder for setting the student's semester. + [FluentMember(1, "OfAge")] + public int Age { get; private set; } + + /// + /// Sets the student's age based on their date of birth. + /// + /// The student's date of birth. + /// A builder for setting the student's semester. + [FluentMethod(1)] + private void BornOn(DateOnly dateOfBirth) + { + DateOnly today = DateOnly.FromDateTime(DateTime.Today); + int age = today.Year - dateOfBirth.Year; + if (dateOfBirth > today.AddYears(-age)) age--; + Age = age; + } + + /// + /// Sets the student's current semester. + /// + /// The student's current semester. + /// A builder for setting the student's city. + /// + /// + /// Sets the student's semester to 0. + /// + /// A builder for setting the student's city. + [FluentMember(2, "InSemester")] + [FluentDefault("WhoStartsUniversity")] + public int Semester { get; private set; } = 0; + + /// + /// Sets the student's city. + /// + /// The student's city. + /// A builder for setting whether the student is happy. + /// + /// + /// Sets the student's city to Boston. + /// + /// A builder for setting whether the student is happy. + /// + /// + /// Sets the student's city to null. + /// + /// A builder for setting whether the student is happy. + [FluentMember(3, "LivingIn")] + [FluentDefault("LivingInBoston")] + [FluentNullable("InUnknownCity")] + public string? City { get; private set; } = "Boston"; + + /// + /// Sets the property. + /// + /// Indicates whether the student is happy. + /// A builder for setting the student's friends. + /// + /// + /// Sets the property to false. + /// + /// A builder for setting the student's friends. + /// + /// + /// Sets the property to null. + /// + /// A builder for setting the student's friends. + [FluentPredicate(4, "WhoIsHappy", "WhoIsSad")] + [FluentNullable("WithUnknownMood")] + public bool? IsHappy { get; private set; } + + /// + /// Sets the student's friends. + /// + /// The student's friends. + /// The . + /// + /// + /// Sets a single friend. + /// + /// The student's friend. + /// The . + /// + /// + /// Sets the student's friends to an empty collection. + /// + /// The . + [FluentCollection(5, "Friend", "WhoseFriendsAre", "WhoseFriendIs", "WhoHasNoFriends")] + public IReadOnlyCollection Friends { get; private set; } +} \ No newline at end of file From 5fbfdf060f820e2d78e19d56f9c14a6759e572fe Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Thu, 7 Aug 2025 13:35:42 +0200 Subject: [PATCH 51/61] docs: Documentation comments readme section (wip) --- README.md | 55 +++++++++++++++++++++++++++ media/create-doc-comments-action.png | Bin 0 -> 67112 bytes 2 files changed, 55 insertions(+) create mode 100644 media/create-doc-comments-action.png diff --git a/README.md b/README.md index f663127..70f217b 100644 --- a/README.md +++ b/README.md @@ -458,6 +458,61 @@ university.AddStudent(s => s.Named("Alice", "King").OfAge(22)...); Note that if you want to set a member of a Fluent API class, you can simply use `FluentMember` or `FluentCollection` instead of the pattern above. +### Documentation comments + +Documentation comments can be added to the members of the Fluent API class by using the desired XML tags prefixed with `fluent`, e.g. + +```cs +/// +/// Sets the student's name. +/// +/// The student's name. +/// A builder for setting the student's age. +[FluentMember(0)] +public string Name { get; private set; } +``` + +All XML tags with the prefix `fluent` will be copied to the generated builder method with the prefix removed and the first letter lowercased (e.g. `fluentSummary` becomes `summary`). + +For a compound, add the documentation comments to the first member, in order to avoid duplication: + +```cs +/// +/// Sets the student's name. +/// +/// The student's first name. +/// The student's last name. +/// A builder for setting the student's age. +[FluentMember(0, "Named", 0)] +public string FirstName { get; private set; } + +[FluentMember(0, "Named", 1)] +public string LastName { get; private set; } +``` + +If more than one method is generated for a member, the target method of the documentation comment can be specified by using the `method` XML attribute: + +```cs +/// +/// Sets the student's current semester. +/// +/// The student's current semester. +/// A builder for setting the student's city. +/// +/// +/// Sets the student's semester to 0. +/// +/// A builder for setting the student's city. +[FluentMember(2, "InSemester")] +[FluentDefault("WhoStartsUniversity")] +public int Semester { get; private set; } = 0; +``` + +For your convenience, there is also a code action available to create the boilerplate of the documentation comments for the selected member: + +![doc-comments-action](https://raw.githubusercontent.com/m31coding/M31.FluentAPI/main/media/create-doc-comments-action.png) + +TODO: CommentedStudent class. Link class and generated code here. ## Problems with the IDE diff --git a/media/create-doc-comments-action.png b/media/create-doc-comments-action.png new file mode 100644 index 0000000000000000000000000000000000000000..4170cbd2ac6ef569ca9d09daab7017b06f5d59d3 GIT binary patch literal 67112 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 literal 0 HcmV?d00001 From 2ce74b72c8bc689a95fc592037465e9e70725e14 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Thu, 7 Aug 2025 15:11:50 +0200 Subject: [PATCH 52/61] fix: four slashes instead of three --- README.md | 47 +++--- src/ExampleProject/DocumentedStudent.cs | 144 +++++++++--------- .../MethodsToCommentsTemplate.cs | 24 +-- .../SourceGenerators/SymbolInfoCreator.cs | 20 ++- .../CommentedClass/Student.Age.txt | 10 +- .../CommentedClass/Student.BornOn.txt | 10 +- .../CommentedClass/Student.City.txt | 30 ++-- .../CommentedClass/Student.FirstName.txt | 12 +- .../CommentedClass/Student.Friends.txt | 32 ++-- .../CommentedClass/Student.IsHappy.txt | 30 ++-- .../CommentedClass/Student.LastName.txt | 12 +- .../CommentedClass/Student.Semester.txt | 20 +-- .../Student.PhoneNumbers.txt | 36 ++--- .../VoidMethodClass/Student.Sleep.txt | 6 +- .../VoidMethodClass/Student.Study.txt | 8 +- .../CommentedCompoundClass/Student.cs | 20 +-- .../CommentedLambdaCollectionClass/Student.cs | 30 ++-- .../CommentedMethodsClass/Student.cs | 28 ++-- .../CommentedPropertiesClass/Student.cs | 48 +++--- .../Student.cs | 32 ++-- .../Student.cs | 24 +-- .../RedundantCommentCompoundClass/Student.cs | 20 +-- 22 files changed, 330 insertions(+), 313 deletions(-) diff --git a/README.md b/README.md index 70f217b..908c7f2 100644 --- a/README.md +++ b/README.md @@ -460,29 +460,30 @@ Note that if you want to set a member of a Fluent API class, you can simply use ### Documentation comments -Documentation comments can be added to the members of the Fluent API class by using the desired XML tags prefixed with `fluent`, e.g. +Documentation comments can be added to the members of the Fluent API class by using four slashes and the desired XML tags prefixed with `fluent`, e.g. ```cs -/// -/// Sets the student's name. -/// -/// The student's name. -/// A builder for setting the student's age. +//// +//// Sets the student's name. +//// +//// The student's name. +//// A builder for setting the student's age. [FluentMember(0)] public string Name { get; private set; } ``` -All XML tags with the prefix `fluent` will be copied to the generated builder method with the prefix removed and the first letter lowercased (e.g. `fluentSummary` becomes `summary`). +Four slashes instead of three are necessary to prevent the IDE from interpreting the comments as XML documentation comments for the member itself. +All XML tags with the prefix `fluent` will be copied to the generated builder method and transformed, e.g. `//// ` becomes `/// `. For a compound, add the documentation comments to the first member, in order to avoid duplication: ```cs -/// -/// Sets the student's name. -/// -/// The student's first name. -/// The student's last name. -/// A builder for setting the student's age. +//// +//// Sets the student's name. +//// +//// The student's first name. +//// The student's last name. +//// A builder for setting the student's age. [FluentMember(0, "Named", 0)] public string FirstName { get; private set; } @@ -493,16 +494,16 @@ public string LastName { get; private set; } If more than one method is generated for a member, the target method of the documentation comment can be specified by using the `method` XML attribute: ```cs -/// -/// Sets the student's current semester. -/// -/// The student's current semester. -/// A builder for setting the student's city. -/// -/// -/// Sets the student's semester to 0. -/// -/// A builder for setting the student's city. +//// +//// Sets the student's current semester. +//// +//// The student's current semester. +//// A builder for setting the student's city. +//// +//// +//// Sets the student's semester to 0. +//// +//// A builder for setting the student's city. [FluentMember(2, "InSemester")] [FluentDefault("WhoStartsUniversity")] public int Semester { get; private set; } = 0; diff --git a/src/ExampleProject/DocumentedStudent.cs b/src/ExampleProject/DocumentedStudent.cs index b2b13d1..709a169 100644 --- a/src/ExampleProject/DocumentedStudent.cs +++ b/src/ExampleProject/DocumentedStudent.cs @@ -9,31 +9,31 @@ namespace ExampleProject; [FluentApi] public class DocumentedStudent { - /// - /// Sets the student's name. - /// - /// The student's first name. - /// The student's last name. - /// A builder for setting the student's age. + //// + //// Sets the student's name. + //// + //// The student's first name. + //// The student's last name. + //// A builder for setting the student's age. [FluentMember(0, "Named", 0)] public string FirstName { get; private set; } [FluentMember(0, "Named", 1)] public string LastName { get; private set; } - /// - /// Sets the student's age. - /// - /// The student's age. - /// A builder for setting the student's semester. + //// + //// Sets the student's age. + //// + //// The student's age. + //// A builder for setting the student's semester. [FluentMember(1, "OfAge")] public int Age { get; private set; } - /// - /// Sets the student's age based on their date of birth. - /// - /// The student's date of birth. - /// A builder for setting the student's semester. + //// + //// Sets the student's age based on their date of birth. + //// + //// The student's date of birth. + //// A builder for setting the student's semester. [FluentMethod(1)] private void BornOn(DateOnly dateOfBirth) { @@ -43,75 +43,75 @@ private void BornOn(DateOnly dateOfBirth) Age = age; } - /// - /// Sets the student's current semester. - /// - /// The student's current semester. - /// A builder for setting the student's city. - /// - /// - /// Sets the student's semester to 0. - /// - /// A builder for setting the student's city. + //// + //// Sets the student's current semester. + //// + //// The student's current semester. + //// A builder for setting the student's city. + //// + //// + //// Sets the student's semester to 0. + //// + //// A builder for setting the student's city. [FluentMember(2, "InSemester")] [FluentDefault("WhoStartsUniversity")] public int Semester { get; private set; } = 0; - /// - /// Sets the student's city. - /// - /// The student's city. - /// A builder for setting whether the student is happy. - /// - /// - /// Sets the student's city to Boston. - /// - /// A builder for setting whether the student is happy. - /// - /// - /// Sets the student's city to null. - /// - /// A builder for setting whether the student is happy. + //// + //// Sets the student's city. + //// + //// The student's city. + //// A builder for setting whether the student is happy. + //// + //// + //// Sets the student's city to Boston. + //// + //// A builder for setting whether the student is happy. + //// + //// + //// Sets the student's city to null. + //// + //// A builder for setting whether the student is happy. [FluentMember(3, "LivingIn")] [FluentDefault("LivingInBoston")] [FluentNullable("InUnknownCity")] public string? City { get; private set; } = "Boston"; - /// - /// Sets the property. - /// - /// Indicates whether the student is happy. - /// A builder for setting the student's friends. - /// - /// - /// Sets the property to false. - /// - /// A builder for setting the student's friends. - /// - /// - /// Sets the property to null. - /// - /// A builder for setting the student's friends. + //// + //// Sets the property. + //// + //// Indicates whether the student is happy. + //// A builder for setting the student's friends. + //// + //// + //// Sets the property to false. + //// + //// A builder for setting the student's friends. + //// + //// + //// Sets the property to null. + //// + //// A builder for setting the student's friends. [FluentPredicate(4, "WhoIsHappy", "WhoIsSad")] [FluentNullable("WithUnknownMood")] public bool? IsHappy { get; private set; } - /// - /// Sets the student's friends. - /// - /// The student's friends. - /// The . - /// - /// - /// Sets a single friend. - /// - /// The student's friend. - /// The . - /// - /// - /// Sets the student's friends to an empty collection. - /// - /// The . + //// + //// Sets the student's friends. + //// + //// The student's friends. + //// The . + //// + //// + //// Sets a single friend. + //// + //// The student's friend. + //// The . + //// + //// + //// Sets the student's friends to an empty collection. + //// + //// The . [FluentCollection(5, "Friend", "WhoseFriendsAre", "WhoseFriendIs", "WhoHasNoFriends")] public IReadOnlyCollection Friends { get; private set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiComments/MethodsToCommentsTemplate.cs b/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiComments/MethodsToCommentsTemplate.cs index b4d7f04..d5c8680 100644 --- a/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiComments/MethodsToCommentsTemplate.cs +++ b/src/M31.FluentApi.Generator/SourceAnalyzers/FluentApiComments/MethodsToCommentsTemplate.cs @@ -42,21 +42,21 @@ private void CreateCommentsTemplateWithoutMethodNames(BuilderMethod[] sameNameBu { if (comments.Count != 0) { - comments.Add("///"); + comments.Add("////"); } - comments.Add("/// "); - comments.Add("/// ..."); - comments.Add("/// "); + comments.Add("//// "); + comments.Add("//// ..."); + comments.Add("//// "); foreach (string parameterName in GetDistinctParameterNames(sameNameBuilderMethods)) { - comments.Add($"/// ..."); + comments.Add($"//// ..."); } if (sameNameBuilderMethods.Any(b => b.ReturnTypeToRespect != "void")) { - comments.Add("/// ..."); + comments.Add("//// ..."); } } @@ -66,21 +66,21 @@ private void CreateCommentsTemplateWithMethodNames(BuilderMethod[] sameNameBuild if (comments.Count != 0) { - comments.Add("///"); + comments.Add("////"); } - comments.Add($"/// "); - comments.Add("/// ..."); - comments.Add("/// "); + comments.Add($"//// "); + comments.Add("//// ..."); + comments.Add("//// "); foreach (string parameterName in GetDistinctParameterNames(sameNameBuilderMethods)) { - comments.Add($"/// ..."); + comments.Add($"//// ..."); } if (sameNameBuilderMethods.Any(b => b.ReturnTypeToRespect != "void")) { - comments.Add($"/// ..."); + comments.Add($"//// ..."); } } diff --git a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs index b71b739..359bc53 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs @@ -5,6 +5,7 @@ using M31.FluentApi.Generator.SourceGenerators.Collections; using M31.FluentApi.Generator.SourceGenerators.Generics; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; namespace M31.FluentApi.Generator.SourceGenerators; @@ -200,7 +201,22 @@ private static ParameterKinds GetParameterKinds(IParameterSymbol parameterSymbol private static Comments GetFluentSymbolComments(ISymbol symbol) { - string? commentXml = symbol.GetDocumentationCommentXml(); - return FluentCommentsParser.Parse(commentXml); + SyntaxReference? syntaxRef = symbol.DeclaringSyntaxReferences.FirstOrDefault(); + if (syntaxRef == null) + { + return new Comments(Array.Empty()); + } + + SyntaxNode syntaxNode = syntaxRef.GetSyntax(); + SyntaxTriviaList leadingTrivia = syntaxNode.GetLeadingTrivia(); + + string[] commentLines = leadingTrivia + .Where(t => t.IsKind(SyntaxKind.SingleLineCommentTrivia) || + t.IsKind(SyntaxKind.MultiLineCommentTrivia)) + .Select(t => t.ToString().TrimStart('/', ' ')) + .ToArray(); + + string comments = string.Join(Environment.NewLine, commentLines); + return FluentCommentsParser.Parse(comments); } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Age.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Age.txt index d226635..55b69b3 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Age.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Age.txt @@ -17,11 +17,11 @@ public class Student [FluentMember(0, "Named", 1)] public string LastName { get; private set; } - /// - /// ... - /// - /// ... - /// ... + //// + //// ... + //// + //// ... + //// ... [FluentMember(1, "OfAge")] public int Age { get; private set; } diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.BornOn.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.BornOn.txt index 412e078..1a8ab98 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.BornOn.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.BornOn.txt @@ -20,11 +20,11 @@ public class Student [FluentMember(1, "OfAge")] public int Age { get; private set; } - /// - /// ... - /// - /// ... - /// ... + //// + //// ... + //// + //// ... + //// ... [FluentMethod(1)] private void BornOn(DateOnly dateOfBirth) { diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.City.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.City.txt index fb10efe..b33dcb6 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.City.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.City.txt @@ -33,21 +33,21 @@ public class Student [FluentDefault("WhoStartsUniversity")] public int Semester { get; private set; } = 0; - /// - /// ... - /// - /// ... - /// ... - /// - /// - /// ... - /// - /// ... - /// - /// - /// ... - /// - /// ... + //// + //// ... + //// + //// ... + //// ... + //// + //// + //// ... + //// + //// ... + //// + //// + //// ... + //// + //// ... [FluentMember(3, "LivingIn")] [FluentDefault("LivingInBoston")] [FluentNullable("InUnknownCity")] diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.FirstName.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.FirstName.txt index 0e092e9..3d7ff18 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.FirstName.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.FirstName.txt @@ -11,12 +11,12 @@ namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments [FluentApi] public class Student { - /// - /// ... - /// - /// ... - /// ... - /// ... + //// + //// ... + //// + //// ... + //// ... + //// ... [FluentMember(0, "Named", 0)] public string FirstName { get; private set; } diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Friends.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Friends.txt index b905000..6ae0dcc 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Friends.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Friends.txt @@ -42,22 +42,22 @@ public class Student [FluentNullable("WithUnknownMood")] public bool? IsHappy { get; private set; } - /// - /// ... - /// - /// ... - /// ... - /// - /// - /// ... - /// - /// ... - /// ... - /// - /// - /// ... - /// - /// ... + //// + //// ... + //// + //// ... + //// ... + //// + //// + //// ... + //// + //// ... + //// ... + //// + //// + //// ... + //// + //// ... [FluentCollection(5, "Friend", "WhoseFriendsAre", "WhoseFriendIs", "WhoHasNoFriends")] public IReadOnlyCollection Friends { get; private set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.IsHappy.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.IsHappy.txt index 7127eca..7a753fe 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.IsHappy.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.IsHappy.txt @@ -38,21 +38,21 @@ public class Student [FluentNullable("InUnknownCity")] public string? City { get; private set; } = "Boston"; - /// - /// ... - /// - /// ... - /// ... - /// - /// - /// ... - /// - /// ... - /// - /// - /// ... - /// - /// ... + //// + //// ... + //// + //// ... + //// ... + //// + //// + //// ... + //// + //// ... + //// + //// + //// ... + //// + //// ... [FluentPredicate(4, "WhoIsHappy", "WhoIsSad")] [FluentNullable("WithUnknownMood")] public bool? IsHappy { get; private set; } diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.LastName.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.LastName.txt index 0e092e9..3d7ff18 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.LastName.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.LastName.txt @@ -11,12 +11,12 @@ namespace M31.FluentApi.Tests.AnalyzerAndCodeFixes.TestClasses.FluentApiComments [FluentApi] public class Student { - /// - /// ... - /// - /// ... - /// ... - /// ... + //// + //// ... + //// + //// ... + //// ... + //// ... [FluentMember(0, "Named", 0)] public string FirstName { get; private set; } diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Semester.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Semester.txt index fdefb8c..957f36c 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Semester.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/CommentedClass/Student.Semester.txt @@ -29,16 +29,16 @@ public class Student Age = age; } - /// - /// ... - /// - /// ... - /// ... - /// - /// - /// ... - /// - /// ... + //// + //// ... + //// + //// ... + //// ... + //// + //// + //// ... + //// + //// ... [FluentMember(2, "InSemester")] [FluentDefault("WhoStartsUniversity")] public int Semester { get; private set; } = 0; diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/LambdaCollectionClass/Student.PhoneNumbers.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/LambdaCollectionClass/Student.PhoneNumbers.txt index 7f4f8da..9081ebb 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/LambdaCollectionClass/Student.PhoneNumbers.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/LambdaCollectionClass/Student.PhoneNumbers.txt @@ -12,24 +12,24 @@ public class Student { [FluentMember(0)] public string Name { get; set; } - /// - /// ... - /// - /// ... - /// ... - /// ... - /// - /// - /// ... - /// - /// ... - /// ... - /// ... - /// - /// - /// ... - /// - /// ... + //// + //// ... + //// + //// ... + //// ... + //// ... + //// + //// + //// ... + //// + //// ... + //// ... + //// ... + //// + //// + //// ... + //// + //// ... [FluentCollection(1, "PhoneNumber")] public IReadOnlyCollection PhoneNumbers { get; set; } } diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.Sleep.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.Sleep.txt index f486a34..fdcac4e 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.Sleep.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.Sleep.txt @@ -19,9 +19,9 @@ public class Student { } - /// - /// ... - /// + //// + //// ... + //// [FluentMethod(2)] [FluentReturn] public void Sleep() diff --git a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.Study.txt b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.Study.txt index 49bf519..8dd6a97 100644 --- a/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.Study.txt +++ b/src/M31.FluentApi.Tests/AnalyzerAndCodeFixes/TestClasses/FluentApiComments/VoidMethodClass/Student.Study.txt @@ -14,10 +14,10 @@ public class Student [FluentMember(0)] public string Name { get; private set; } - /// - /// ... - /// - /// ... + //// + //// ... + //// + //// ... [FluentMethod(1)] public void Study() { diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedCompoundClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedCompoundClass/Student.cs index c436870..c91bcca 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedCompoundClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedCompoundClass/Student.cs @@ -9,25 +9,25 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComme [FluentApi] public class Student { - /// - /// Sets the student's name. - /// - /// The student's first name. + //// + //// Sets the student's name. + //// + //// The student's first name. [FluentMember(0, "WithName")] public string FirstName { get; set; } - /// The student's last name. + //// The student's last name. [FluentMember(0, "WithName")] public string LastName { get; set; } - /// - /// Sets the student's course of study and current semester. - /// - /// The student's course of study. + //// + //// Sets the student's course of study and current semester. + //// + //// The student's course of study. [FluentMember(1, "Studies")] public string CourseOfStudy { get; set; } - /// The student's current semester. + //// The student's current semester. [FluentMember(1, "Studies")] public int Semester { get; set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/Student.cs index bdd468a..bb01569 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedLambdaCollectionClass/Student.cs @@ -14,21 +14,21 @@ public class Student [FluentMember(0)] public string Name { get; set; } - /// - /// Sets the student's phone numbers. - /// - /// The student's phone numbers. - /// Functions for creating the student's phone numbers. - /// - /// - /// Sets the student's phone number. - /// - /// The student's phone number. - /// A function for creating the student's phone number. - /// - /// - /// Specifies that the student has no phone numbers. - /// + //// + //// Sets the student's phone numbers. + //// + //// The student's phone numbers. + //// Functions for creating the student's phone numbers. + //// + //// + //// Sets the student's phone number. + //// + //// The student's phone number. + //// A function for creating the student's phone number. + //// + //// + //// Specifies that the student has no phone numbers. + //// [FluentCollection(1, "PhoneNumber")] public IReadOnlyCollection PhoneNumbers { get; set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedMethodsClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedMethodsClass/Student.cs index 3fee24e..2f64848 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedMethodsClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedMethodsClass/Student.cs @@ -13,16 +13,16 @@ public class Student public string FirstName { get; private set; } public string LastName { get; private set; } - /// - /// This summary will not be taken into account. - /// - /// This parameter documentation will not be taken into account. - /// This parameter documentation will not be taken into account. - /// - /// Sets the student's first and last name. - /// - /// The student's first name. - /// The student's last name. + //// + //// This summary will not be taken into account. + //// + //// This parameter documentation will not be taken into account. + //// This parameter documentation will not be taken into account. + //// + //// Sets the student's first and last name. + //// + //// The student's first name. + //// The student's last name. [FluentMethod(0)] private void WithName(string firstName, string lastName) { @@ -33,10 +33,10 @@ private void WithName(string firstName, string lastName) [FluentMember(1, "OfAge")] public int Age { get; private set; } - /// - /// Calculates and sets the student's age based on the provided date of birth. - /// - /// The student's date of birth. + //// + //// Calculates and sets the student's age based on the provided date of birth. + //// + //// The student's date of birth. [FluentMethod(1)] private void BornOn(DateOnly dateOfBirth) { diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/Student.cs index 0e668ce..b224525 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/Student.cs @@ -10,45 +10,45 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComme [FluentApi] public class Student { - /// - /// This summary will not be taken into account. - /// - /// This parameter will not be taken into account. + //// + //// This summary will not be taken into account. + //// + //// This parameter will not be taken into account. [FluentMember(0)] public string GivenName { get; set; } - /// - /// Sets the student's first name. - /// - /// The student's first name. + //// + //// Sets the student's first name. + //// + //// The student's first name. [FluentMember(0)] public string FirstName { get; set; } - /// - /// Sets the student's last name. - /// - /// The student's last name. + // + // Sets the student's last name. + // + // The student's last name. [FluentMember(1)] public string LastName { get; set; } - /// - /// Sets the student's age. - /// - /// The student's age. + //// + //// Sets the student's age. + //// + //// The student's age. [FluentMember(2, "OfAge")] public int Age { get; set; } - /// - /// Sets the student's semester. - /// - /// The student's semester. + //// + //// Sets the student's semester. + //// + //// The student's semester. [FluentMember(3, "InSemester")] public int Semester { get; set; } - /// - /// This summary will not be taken into account. - /// - /// This parameter will not be taken into account. + //// + //// This summary will not be taken into account. + //// + //// This parameter will not be taken into account. [FluentMember(4, "LivingIn")] public string City { get; set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClassAdvanced/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClassAdvanced/Student.cs index e75ff82..e3f8143 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClassAdvanced/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClassAdvanced/Student.cs @@ -13,25 +13,25 @@ public class Student [FluentMember(0)] public string Name { get; set; } - /// - /// Sets the students's age. - /// - /// The student's age. + //// + //// Sets the students's age. + //// + //// The student's age. [FluentMember(1, "OfAge")] public int Age { get; set; } - /// - /// Sets the student's city. - /// - /// The student's city. - /// - /// - /// Set's the student's city to Boston. - /// - /// - /// - /// Set's the student's city to null. - /// + //// + //// Sets the student's city. + //// + //// The student's city. + //// + //// + //// Set's the student's city to Boston. + //// + //// + //// + //// Set's the student's city to null. + //// [FluentMember(2, "LivingIn")] [FluentDefault("LivingInBoston")] [FluentNullable("InUnknownCity")] diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/IncompletelyCommentedPropertyClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/IncompletelyCommentedPropertyClass/Student.cs index cae60d9..c657c1e 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/IncompletelyCommentedPropertyClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/IncompletelyCommentedPropertyClass/Student.cs @@ -10,18 +10,18 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComme [FluentApi] public class Student { - /// - /// Sets the student's city. - /// - /// The student's city. - /// - /// - /// Set's the student's city to Boston. - /// - /// - /// - /// Set's the student's city to null. - /// + //// + //// Sets the student's city. + //// + //// The student's city. + //// + //// + //// Set's the student's city to Boston. + //// + //// + //// + //// Set's the student's city to null. + //// [FluentMember(0, "LivingIn")] [FluentDefault("LivingInBoston")] [FluentNullable("InUnknownCity")] diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/RedundantCommentCompoundClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/RedundantCommentCompoundClass/Student.cs index 2b113ba..41ea6f3 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/RedundantCommentCompoundClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/RedundantCommentCompoundClass/Student.cs @@ -9,19 +9,19 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComme [FluentApi] public class Student { - /// - /// Sets the student's name. - /// - /// The student's first name. - /// The student's last name. + //// + //// Sets the student's name. + //// + //// The student's first name. + //// The student's last name. [FluentMember(0, "WithName")] public string FirstName { get; set; } - /// - /// Sets the student's name. - /// - /// The student's first name. - /// The student's last name. + //// + //// Sets the student's name. + //// + //// The student's first name. + //// The student's last name. [FluentMember(0, "WithName")] public string LastName { get; set; } } \ No newline at end of file From 0c479cea5253ac864c3069e5417a76c939e08ee7 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Thu, 7 Aug 2025 15:16:20 +0200 Subject: [PATCH 53/61] test: DocumentedStudentClass --- .../CreateDocumentedStudent.expected.txt | 268 ++++++++++++++++++ .../CreateDocumentedStudent.g.cs | 268 ++++++++++++++++++ .../DocumentedStudent.cs | 119 ++++++++ .../CodeGeneration/TestDataProvider.cs | 1 + 4 files changed, 656 insertions(+) create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/CreateDocumentedStudent.expected.txt create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/CreateDocumentedStudent.g.cs create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/DocumentedStudent.cs diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/CreateDocumentedStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/CreateDocumentedStudent.expected.txt new file mode 100644 index 0000000..9753a24 --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/CreateDocumentedStudent.expected.txt @@ -0,0 +1,268 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.DocumentedStudentClass; + +public class CreateDocumentedStudent : + CreateDocumentedStudent.ICreateDocumentedStudent, + CreateDocumentedStudent.INamed, + CreateDocumentedStudent.IOfAgeBornOn, + CreateDocumentedStudent.IInSemester, + CreateDocumentedStudent.ILivingIn, + CreateDocumentedStudent.IWhoIsHappy, + CreateDocumentedStudent.IWhoseFriendsAre +{ + private readonly DocumentedStudent documentedStudent; + private static readonly PropertyInfo firstNamePropertyInfo; + private static readonly PropertyInfo lastNamePropertyInfo; + private static readonly PropertyInfo agePropertyInfo; + private static readonly MethodInfo bornOnMethodInfo; + private static readonly PropertyInfo semesterPropertyInfo; + private static readonly PropertyInfo cityPropertyInfo; + private static readonly PropertyInfo isHappyPropertyInfo; + private static readonly PropertyInfo friendsPropertyInfo; + + static CreateDocumentedStudent() + { + firstNamePropertyInfo = typeof(DocumentedStudent).GetProperty("FirstName", BindingFlags.Instance | BindingFlags.Public)!; + lastNamePropertyInfo = typeof(DocumentedStudent).GetProperty("LastName", BindingFlags.Instance | BindingFlags.Public)!; + agePropertyInfo = typeof(DocumentedStudent).GetProperty("Age", BindingFlags.Instance | BindingFlags.Public)!; + bornOnMethodInfo = typeof(DocumentedStudent).GetMethod( + "BornOn", + 0, + BindingFlags.Instance | BindingFlags.NonPublic, + null, + new Type[] { typeof(System.DateOnly) }, + null)!; + semesterPropertyInfo = typeof(DocumentedStudent).GetProperty("Semester", BindingFlags.Instance | BindingFlags.Public)!; + cityPropertyInfo = typeof(DocumentedStudent).GetProperty("City", BindingFlags.Instance | BindingFlags.Public)!; + isHappyPropertyInfo = typeof(DocumentedStudent).GetProperty("IsHappy", BindingFlags.Instance | BindingFlags.Public)!; + friendsPropertyInfo = typeof(DocumentedStudent).GetProperty("Friends", BindingFlags.Instance | BindingFlags.Public)!; + } + + private CreateDocumentedStudent() + { + documentedStudent = new DocumentedStudent(); + } + + public static ICreateDocumentedStudent InitialStep() + { + return new CreateDocumentedStudent(); + } + + /// Sets the student's name. + /// The student's first name. + /// The student's last name. + /// A builder for setting the student's age. + public static IOfAgeBornOn Named(string firstName, string lastName) + { + CreateDocumentedStudent createDocumentedStudent = new CreateDocumentedStudent(); + CreateDocumentedStudent.firstNamePropertyInfo.SetValue(createDocumentedStudent.documentedStudent, firstName); + CreateDocumentedStudent.lastNamePropertyInfo.SetValue(createDocumentedStudent.documentedStudent, lastName); + return createDocumentedStudent; + } + + /// + IOfAgeBornOn INamed.Named(string firstName, string lastName) + { + CreateDocumentedStudent.firstNamePropertyInfo.SetValue(documentedStudent, firstName); + CreateDocumentedStudent.lastNamePropertyInfo.SetValue(documentedStudent, lastName); + return this; + } + + /// + IInSemester IOfAgeBornOn.OfAge(int age) + { + CreateDocumentedStudent.agePropertyInfo.SetValue(documentedStudent, age); + return this; + } + + /// + IInSemester IOfAgeBornOn.BornOn(System.DateOnly dateOfBirth) + { + CreateDocumentedStudent.bornOnMethodInfo.Invoke(documentedStudent, new object?[] { dateOfBirth }); + return this; + } + + /// + ILivingIn IInSemester.InSemester(int semester) + { + CreateDocumentedStudent.semesterPropertyInfo.SetValue(documentedStudent, semester); + return this; + } + + /// + ILivingIn IInSemester.WhoStartsUniversity() + { + return this; + } + + /// + IWhoIsHappy ILivingIn.LivingIn(string? city) + { + CreateDocumentedStudent.cityPropertyInfo.SetValue(documentedStudent, city); + return this; + } + + /// + IWhoIsHappy ILivingIn.LivingInBoston() + { + return this; + } + + /// + IWhoIsHappy ILivingIn.InUnknownCity() + { + CreateDocumentedStudent.cityPropertyInfo.SetValue(documentedStudent, null); + return this; + } + + /// + IWhoseFriendsAre IWhoIsHappy.WhoIsHappy(bool? isHappy) + { + CreateDocumentedStudent.isHappyPropertyInfo.SetValue(documentedStudent, isHappy); + return this; + } + + /// + IWhoseFriendsAre IWhoIsHappy.WhoIsSad() + { + CreateDocumentedStudent.isHappyPropertyInfo.SetValue(documentedStudent, false); + return this; + } + + /// + IWhoseFriendsAre IWhoIsHappy.WithUnknownMood() + { + CreateDocumentedStudent.isHappyPropertyInfo.SetValue(documentedStudent, null); + return this; + } + + /// + DocumentedStudent IWhoseFriendsAre.WhoseFriendsAre(System.Collections.Generic.IReadOnlyCollection friends) + { + CreateDocumentedStudent.friendsPropertyInfo.SetValue(documentedStudent, friends); + return documentedStudent; + } + + /// + DocumentedStudent IWhoseFriendsAre.WhoseFriendsAre(params string[] friends) + { + CreateDocumentedStudent.friendsPropertyInfo.SetValue(documentedStudent, friends); + return documentedStudent; + } + + /// + DocumentedStudent IWhoseFriendsAre.WhoseFriendIs(string friend) + { + CreateDocumentedStudent.friendsPropertyInfo.SetValue(documentedStudent, new string[1]{ friend }); + return documentedStudent; + } + + /// + DocumentedStudent IWhoseFriendsAre.WhoHasNoFriends() + { + CreateDocumentedStudent.friendsPropertyInfo.SetValue(documentedStudent, new string[0]); + return documentedStudent; + } + + public interface ICreateDocumentedStudent : INamed + { + } + + public interface INamed + { + /// Sets the student's name. + /// The student's first name. + /// The student's last name. + /// A builder for setting the student's age. + IOfAgeBornOn Named(string firstName, string lastName); + } + + public interface IOfAgeBornOn + { + /// Sets the student's age. + /// The student's age. + /// A builder for setting the student's semester. + IInSemester OfAge(int age); + + /// Sets the student's age based on their date of birth. + /// The student's date of birth. + /// A builder for setting the student's semester. + IInSemester BornOn(System.DateOnly dateOfBirth); + } + + public interface IInSemester + { + /// Sets the student's current semester. + /// The student's current semester. + /// A builder for setting the student's city. + ILivingIn InSemester(int semester); + + /// Sets the student's semester to 0. + /// A builder for setting the student's city. + ILivingIn WhoStartsUniversity(); + } + + public interface ILivingIn + { + /// Sets the student's city. + /// The student's city. + /// A builder for setting whether the student is happy. + IWhoIsHappy LivingIn(string? city); + + /// Sets the student's city to Boston. + /// A builder for setting whether the student is happy. + IWhoIsHappy LivingInBoston(); + + /// Sets the student's city to null. + /// A builder for setting whether the student is happy. + IWhoIsHappy InUnknownCity(); + } + + public interface IWhoIsHappy + { + /// Sets the property. + /// Indicates whether the student is happy. + /// A builder for setting the student's friends. + IWhoseFriendsAre WhoIsHappy(bool? isHappy = true); + + /// Sets the property to false. + /// A builder for setting the student's friends. + IWhoseFriendsAre WhoIsSad(); + + /// Sets the property to null. + /// A builder for setting the student's friends. + IWhoseFriendsAre WithUnknownMood(); + } + + public interface IWhoseFriendsAre + { + /// Sets the student's friends. + /// The student's friends. + /// The . + DocumentedStudent WhoseFriendsAre(System.Collections.Generic.IReadOnlyCollection friends); + + /// Sets the student's friends. + /// The student's friends. + /// The . + DocumentedStudent WhoseFriendsAre(params string[] friends); + + /// Sets a single friend. + /// The student's friend. + /// The . + DocumentedStudent WhoseFriendIs(string friend); + + /// Sets the student's friends to an empty collection. + /// The . + DocumentedStudent WhoHasNoFriends(); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/CreateDocumentedStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/CreateDocumentedStudent.g.cs new file mode 100644 index 0000000..9753a24 --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/CreateDocumentedStudent.g.cs @@ -0,0 +1,268 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.DocumentedStudentClass; + +public class CreateDocumentedStudent : + CreateDocumentedStudent.ICreateDocumentedStudent, + CreateDocumentedStudent.INamed, + CreateDocumentedStudent.IOfAgeBornOn, + CreateDocumentedStudent.IInSemester, + CreateDocumentedStudent.ILivingIn, + CreateDocumentedStudent.IWhoIsHappy, + CreateDocumentedStudent.IWhoseFriendsAre +{ + private readonly DocumentedStudent documentedStudent; + private static readonly PropertyInfo firstNamePropertyInfo; + private static readonly PropertyInfo lastNamePropertyInfo; + private static readonly PropertyInfo agePropertyInfo; + private static readonly MethodInfo bornOnMethodInfo; + private static readonly PropertyInfo semesterPropertyInfo; + private static readonly PropertyInfo cityPropertyInfo; + private static readonly PropertyInfo isHappyPropertyInfo; + private static readonly PropertyInfo friendsPropertyInfo; + + static CreateDocumentedStudent() + { + firstNamePropertyInfo = typeof(DocumentedStudent).GetProperty("FirstName", BindingFlags.Instance | BindingFlags.Public)!; + lastNamePropertyInfo = typeof(DocumentedStudent).GetProperty("LastName", BindingFlags.Instance | BindingFlags.Public)!; + agePropertyInfo = typeof(DocumentedStudent).GetProperty("Age", BindingFlags.Instance | BindingFlags.Public)!; + bornOnMethodInfo = typeof(DocumentedStudent).GetMethod( + "BornOn", + 0, + BindingFlags.Instance | BindingFlags.NonPublic, + null, + new Type[] { typeof(System.DateOnly) }, + null)!; + semesterPropertyInfo = typeof(DocumentedStudent).GetProperty("Semester", BindingFlags.Instance | BindingFlags.Public)!; + cityPropertyInfo = typeof(DocumentedStudent).GetProperty("City", BindingFlags.Instance | BindingFlags.Public)!; + isHappyPropertyInfo = typeof(DocumentedStudent).GetProperty("IsHappy", BindingFlags.Instance | BindingFlags.Public)!; + friendsPropertyInfo = typeof(DocumentedStudent).GetProperty("Friends", BindingFlags.Instance | BindingFlags.Public)!; + } + + private CreateDocumentedStudent() + { + documentedStudent = new DocumentedStudent(); + } + + public static ICreateDocumentedStudent InitialStep() + { + return new CreateDocumentedStudent(); + } + + /// Sets the student's name. + /// The student's first name. + /// The student's last name. + /// A builder for setting the student's age. + public static IOfAgeBornOn Named(string firstName, string lastName) + { + CreateDocumentedStudent createDocumentedStudent = new CreateDocumentedStudent(); + CreateDocumentedStudent.firstNamePropertyInfo.SetValue(createDocumentedStudent.documentedStudent, firstName); + CreateDocumentedStudent.lastNamePropertyInfo.SetValue(createDocumentedStudent.documentedStudent, lastName); + return createDocumentedStudent; + } + + /// + IOfAgeBornOn INamed.Named(string firstName, string lastName) + { + CreateDocumentedStudent.firstNamePropertyInfo.SetValue(documentedStudent, firstName); + CreateDocumentedStudent.lastNamePropertyInfo.SetValue(documentedStudent, lastName); + return this; + } + + /// + IInSemester IOfAgeBornOn.OfAge(int age) + { + CreateDocumentedStudent.agePropertyInfo.SetValue(documentedStudent, age); + return this; + } + + /// + IInSemester IOfAgeBornOn.BornOn(System.DateOnly dateOfBirth) + { + CreateDocumentedStudent.bornOnMethodInfo.Invoke(documentedStudent, new object?[] { dateOfBirth }); + return this; + } + + /// + ILivingIn IInSemester.InSemester(int semester) + { + CreateDocumentedStudent.semesterPropertyInfo.SetValue(documentedStudent, semester); + return this; + } + + /// + ILivingIn IInSemester.WhoStartsUniversity() + { + return this; + } + + /// + IWhoIsHappy ILivingIn.LivingIn(string? city) + { + CreateDocumentedStudent.cityPropertyInfo.SetValue(documentedStudent, city); + return this; + } + + /// + IWhoIsHappy ILivingIn.LivingInBoston() + { + return this; + } + + /// + IWhoIsHappy ILivingIn.InUnknownCity() + { + CreateDocumentedStudent.cityPropertyInfo.SetValue(documentedStudent, null); + return this; + } + + /// + IWhoseFriendsAre IWhoIsHappy.WhoIsHappy(bool? isHappy) + { + CreateDocumentedStudent.isHappyPropertyInfo.SetValue(documentedStudent, isHappy); + return this; + } + + /// + IWhoseFriendsAre IWhoIsHappy.WhoIsSad() + { + CreateDocumentedStudent.isHappyPropertyInfo.SetValue(documentedStudent, false); + return this; + } + + /// + IWhoseFriendsAre IWhoIsHappy.WithUnknownMood() + { + CreateDocumentedStudent.isHappyPropertyInfo.SetValue(documentedStudent, null); + return this; + } + + /// + DocumentedStudent IWhoseFriendsAre.WhoseFriendsAre(System.Collections.Generic.IReadOnlyCollection friends) + { + CreateDocumentedStudent.friendsPropertyInfo.SetValue(documentedStudent, friends); + return documentedStudent; + } + + /// + DocumentedStudent IWhoseFriendsAre.WhoseFriendsAre(params string[] friends) + { + CreateDocumentedStudent.friendsPropertyInfo.SetValue(documentedStudent, friends); + return documentedStudent; + } + + /// + DocumentedStudent IWhoseFriendsAre.WhoseFriendIs(string friend) + { + CreateDocumentedStudent.friendsPropertyInfo.SetValue(documentedStudent, new string[1]{ friend }); + return documentedStudent; + } + + /// + DocumentedStudent IWhoseFriendsAre.WhoHasNoFriends() + { + CreateDocumentedStudent.friendsPropertyInfo.SetValue(documentedStudent, new string[0]); + return documentedStudent; + } + + public interface ICreateDocumentedStudent : INamed + { + } + + public interface INamed + { + /// Sets the student's name. + /// The student's first name. + /// The student's last name. + /// A builder for setting the student's age. + IOfAgeBornOn Named(string firstName, string lastName); + } + + public interface IOfAgeBornOn + { + /// Sets the student's age. + /// The student's age. + /// A builder for setting the student's semester. + IInSemester OfAge(int age); + + /// Sets the student's age based on their date of birth. + /// The student's date of birth. + /// A builder for setting the student's semester. + IInSemester BornOn(System.DateOnly dateOfBirth); + } + + public interface IInSemester + { + /// Sets the student's current semester. + /// The student's current semester. + /// A builder for setting the student's city. + ILivingIn InSemester(int semester); + + /// Sets the student's semester to 0. + /// A builder for setting the student's city. + ILivingIn WhoStartsUniversity(); + } + + public interface ILivingIn + { + /// Sets the student's city. + /// The student's city. + /// A builder for setting whether the student is happy. + IWhoIsHappy LivingIn(string? city); + + /// Sets the student's city to Boston. + /// A builder for setting whether the student is happy. + IWhoIsHappy LivingInBoston(); + + /// Sets the student's city to null. + /// A builder for setting whether the student is happy. + IWhoIsHappy InUnknownCity(); + } + + public interface IWhoIsHappy + { + /// Sets the property. + /// Indicates whether the student is happy. + /// A builder for setting the student's friends. + IWhoseFriendsAre WhoIsHappy(bool? isHappy = true); + + /// Sets the property to false. + /// A builder for setting the student's friends. + IWhoseFriendsAre WhoIsSad(); + + /// Sets the property to null. + /// A builder for setting the student's friends. + IWhoseFriendsAre WithUnknownMood(); + } + + public interface IWhoseFriendsAre + { + /// Sets the student's friends. + /// The student's friends. + /// The . + DocumentedStudent WhoseFriendsAre(System.Collections.Generic.IReadOnlyCollection friends); + + /// Sets the student's friends. + /// The student's friends. + /// The . + DocumentedStudent WhoseFriendsAre(params string[] friends); + + /// Sets a single friend. + /// The student's friend. + /// The . + DocumentedStudent WhoseFriendIs(string friend); + + /// Sets the student's friends to an empty collection. + /// The . + DocumentedStudent WhoHasNoFriends(); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/DocumentedStudent.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/DocumentedStudent.cs new file mode 100644 index 0000000..113bde8 --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/DocumentedStudent.cs @@ -0,0 +1,119 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using System; +using System.Collections.Generic; +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.DocumentedStudentClass; + +[FluentApi] +public class DocumentedStudent +{ + //// + //// Sets the student's name. + //// + //// The student's first name. + //// The student's last name. + //// A builder for setting the student's age. + [FluentMember(0, "Named", 0)] + public string FirstName { get; private set; } + + [FluentMember(0, "Named", 1)] + public string LastName { get; private set; } + + //// + //// Sets the student's age. + //// + //// The student's age. + //// A builder for setting the student's semester. + [FluentMember(1, "OfAge")] + public int Age { get; private set; } + + //// + //// Sets the student's age based on their date of birth. + //// + //// The student's date of birth. + //// A builder for setting the student's semester. + [FluentMethod(1)] + private void BornOn(DateOnly dateOfBirth) + { + DateOnly today = DateOnly.FromDateTime(DateTime.Today); + int age = today.Year - dateOfBirth.Year; + if (dateOfBirth > today.AddYears(-age)) age--; + Age = age; + } + + //// + //// Sets the student's current semester. + //// + //// The student's current semester. + //// A builder for setting the student's city. + //// + //// + //// Sets the student's semester to 0. + //// + //// A builder for setting the student's city. + [FluentMember(2, "InSemester")] + [FluentDefault("WhoStartsUniversity")] + public int Semester { get; private set; } = 0; + + //// + //// Sets the student's city. + //// + //// The student's city. + //// A builder for setting whether the student is happy. + //// + //// + //// Sets the student's city to Boston. + //// + //// A builder for setting whether the student is happy. + //// + //// + //// Sets the student's city to null. + //// + //// A builder for setting whether the student is happy. + [FluentMember(3, "LivingIn")] + [FluentDefault("LivingInBoston")] + [FluentNullable("InUnknownCity")] + public string? City { get; private set; } = "Boston"; + + //// + //// Sets the property. + //// + //// Indicates whether the student is happy. + //// A builder for setting the student's friends. + //// + //// + //// Sets the property to false. + //// + //// A builder for setting the student's friends. + //// + //// + //// Sets the property to null. + //// + //// A builder for setting the student's friends. + [FluentPredicate(4, "WhoIsHappy", "WhoIsSad")] + [FluentNullable("WithUnknownMood")] + public bool? IsHappy { get; private set; } + + //// + //// Sets the student's friends. + //// + //// The student's friends. + //// The . + //// + //// + //// Sets a single friend. + //// + //// The student's friend. + //// The . + //// + //// + //// Sets the student's friends to an empty collection. + //// + //// The . + [FluentCollection(5, "Friend", "WhoseFriendsAre", "WhoseFriendIs", "WhoHasNoFriends")] + public IReadOnlyCollection Friends { get; private set; } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs index a7ca13e..9555b97 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs @@ -107,6 +107,7 @@ internal class TestDataProvider : IEnumerable new object[] { "Abstract", "TwoMemberClass", "Student" }, new object[] { "Abstract", "TwoParameterCompoundClass", "Student" }, new object[] { "Abstract", "TwoParameterCompoundClassReversedParameters", "Student" }, + new object[] { "DocumentedStudentClass", "DocumentedStudent" }, new object[] { "PersonClass", "Person" }, new object[] { "StudentClass", "Student" } }).Select(l => new string[] { "..", "..", "..", "CodeGeneration", "TestClasses" } From b9fcdb25944716d5610db83c72a7bba36278c86a Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Thu, 7 Aug 2025 15:36:32 +0200 Subject: [PATCH 54/61] docs: readme --- README.md | 53 ++++++++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 908c7f2..36946a6 100644 --- a/README.md +++ b/README.md @@ -440,27 +440,9 @@ private void BornOn(DateOnly dateOfBirth) ``` -### Lambda pattern - -Instances of Fluent API classes can be created and passed into methods of other classes using the lambda pattern. For example, given a `University` class that needs to be augmented with an `AddStudent` method, the following code demonstrates the lambda pattern: - -```cs -public void AddStudent(Func createStudent) -{ - Student student = createStudent(CreateStudent.InitialStep()); - students.Add(student); -} -``` - -```cs -university.AddStudent(s => s.Named("Alice", "King").OfAge(22)...); -``` - -Note that if you want to set a member of a Fluent API class, you can simply use `FluentMember` or `FluentCollection` instead of the pattern above. - ### Documentation comments -Documentation comments can be added to the members of the Fluent API class by using four slashes and the desired XML tags prefixed with `fluent`, e.g. +Documentation comments for fluent API members can be added using four slashes (////) followed by XML tags prefixed with `fluent`, e.g.: ```cs //// @@ -472,10 +454,11 @@ Documentation comments can be added to the members of the Fluent API class by us public string Name { get; private set; } ``` -Four slashes instead of three are necessary to prevent the IDE from interpreting the comments as XML documentation comments for the member itself. -All XML tags with the prefix `fluent` will be copied to the generated builder method and transformed, e.g. `//// ` becomes `/// `. +Using four slashes instead of three prevents the IDE from interpreting these comments as standard XML documentation for the member. -For a compound, add the documentation comments to the first member, in order to avoid duplication: +All `fluent`-prefixed tags are copied to the generated builder method and automatically transformed, e.g., `//// ` becomes `/// ` in the generated code. + +For compounds, add the documentation comments to the first member of the compound only to avoid duplication: ```cs //// @@ -491,7 +474,7 @@ public string FirstName { get; private set; } public string LastName { get; private set; } ``` -If more than one method is generated for a member, the target method of the documentation comment can be specified by using the `method` XML attribute: +If multiple methods are generated for a member, you can target a specific method by adding the `method` XML attribute to the documentation tags: ```cs //// @@ -509,11 +492,31 @@ If more than one method is generated for a member, the target method of the docu public int Semester { get; private set; } = 0; ``` -For your convenience, there is also a code action available to create the boilerplate of the documentation comments for the selected member: +To simplify adding documentation comments, a code action is available to generate the boilerplate for the selected member: ![doc-comments-action](https://raw.githubusercontent.com/m31coding/M31.FluentAPI/main/media/create-doc-comments-action.png) -TODO: CommentedStudent class. Link class and generated code here. +The documented version of the Student class can be found in [DocumentedStudent.cs](src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/DocumentedStudent.cs). The corresponding generated code is located in [DocumentedStudent.g.cs](src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/CreateDocumentedStudent.g.cs) + + +### Lambda pattern + +Instances of Fluent API classes can be created and passed into methods of other classes using the lambda pattern. For example, given a `University` class that needs to be augmented with an `AddStudent` method, the following code demonstrates the lambda pattern: + +```cs +public void AddStudent(Func createStudent) +{ + Student student = createStudent(CreateStudent.InitialStep()); + students.Add(student); +} +``` + +```cs +university.AddStudent(s => s.Named("Alice", "King").OfAge(22)...); +``` + +Note that if you want to set a member of a Fluent API class, you can simply use `FluentMember` or `FluentCollection` instead of the pattern above. + ## Problems with the IDE From 471d98c4c1e9ee33619d1bc58a9f4d6e74f28b99 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Thu, 7 Aug 2025 18:07:09 +0200 Subject: [PATCH 55/61] test: WronglyCommentCompoundClass (wip) --- .../SourceGenerators/SymbolInfoCreator.cs | 22 ++++++++++---- .../CommentedPropertiesClass/Student.cs | 8 ++--- .../CreateStudent.expected.txt | 0 .../WronglyCommentedClass/CreateStudent.g.cs | 0 .../WronglyCommentedClass/Student.cs | 29 +++++++++++++++++++ .../CreateStudent.expected.txt | 0 .../WronglyCommentedClass2/CreateStudent.g.cs | 0 .../WronglyCommentedClass2/Student.cs | 29 +++++++++++++++++++ .../CodeGeneration/TestDataProvider.cs | 4 ++- 9 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/CreateStudent.expected.txt create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/CreateStudent.g.cs create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/Student.cs create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/CreateStudent.expected.txt create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/CreateStudent.g.cs create mode 100644 src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/Student.cs diff --git a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs index 359bc53..4589cd6 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs @@ -210,11 +210,23 @@ private static Comments GetFluentSymbolComments(ISymbol symbol) SyntaxNode syntaxNode = syntaxRef.GetSyntax(); SyntaxTriviaList leadingTrivia = syntaxNode.GetLeadingTrivia(); - string[] commentLines = leadingTrivia - .Where(t => t.IsKind(SyntaxKind.SingleLineCommentTrivia) || - t.IsKind(SyntaxKind.MultiLineCommentTrivia)) - .Select(t => t.ToString().TrimStart('/', ' ')) - .ToArray(); + List commentLines = new List(); + + foreach (SyntaxTrivia syntaxTrivia in leadingTrivia) + { + if (!syntaxTrivia.IsKind(SyntaxKind.SingleLineCommentTrivia)) + { + continue; + } + + string str = syntaxTrivia.ToString(); + if (!str.Trim().StartsWith("////")) // todo: regex + { + continue; + } + + commentLines.Add(str.TrimStart('/', ' ')); + } string comments = string.Join(Environment.NewLine, commentLines); return FluentCommentsParser.Parse(comments); diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/Student.cs index b224525..043831c 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/CommentedPropertiesClass/Student.cs @@ -24,10 +24,10 @@ public class Student [FluentMember(0)] public string FirstName { get; set; } - // - // Sets the student's last name. - // - // The student's last name. + //// + //// Sets the student's last name. + //// + //// The student's last name. [FluentMember(1)] public string LastName { get; set; } diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/CreateStudent.expected.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/CreateStudent.g.cs new file mode 100644 index 0000000..e69de29 diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/Student.cs new file mode 100644 index 0000000..29fb1e8 --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/Student.cs @@ -0,0 +1,29 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments. + WronglyCommentedClass; + +[FluentApi] +public class Student +{ + // + // Sets the student's city. + // + // The student's city. + // + // + // Set's the student's city to Boston. + // + // + // + // Set's the student's city to null. + // + [FluentMember(0, "LivingIn")] + [FluentDefault("LivingInBoston")] + [FluentNullable("InUnknownCity")] + public string? City { get; set; } = "Boston"; +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/CreateStudent.expected.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/CreateStudent.g.cs new file mode 100644 index 0000000..e69de29 diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/Student.cs new file mode 100644 index 0000000..dd476cb --- /dev/null +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/Student.cs @@ -0,0 +1,29 @@ +// Non-nullable member is uninitialized +#pragma warning disable CS8618 +// ReSharper disable All + +using M31.FluentApi.Attributes; + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments. + WronglyCommentedClass2; + +[FluentApi] +public class Student +{ + ///// + ///// Sets the student's city. + ///// + ///// The student's city. + ///// + ///// + ///// Set's the student's city to Boston. + ///// + ///// + ///// + ///// Set's the student's city to null. + ///// + [FluentMember(0, "LivingIn")] + [FluentDefault("LivingInBoston")] + [FluentNullable("InUnknownCity")] + public string? City { get; set; } = "Boston"; +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs index 9555b97..78fa227 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs @@ -26,8 +26,10 @@ internal class TestDataProvider : IEnumerable new object[] { "Abstract", "FluentApiComments", "CommentedMethodsClass", "Student" }, new object[] { "Abstract", "FluentApiComments", "CommentedPropertiesClass", "Student" }, new object[] { "Abstract", "FluentApiComments", "CommentedPropertiesClassAdvanced", "Student" }, - new object[] { "Abstract", "FluentApiComments", "RedundantCommentCompoundClass", "Student" }, new object[] { "Abstract", "FluentApiComments", "IncompletelyCommentedPropertyClass", "Student"}, + new object[] { "Abstract", "FluentApiComments", "RedundantCommentCompoundClass", "Student" }, + new object[] { "Abstract", "FluentApiComments", "WronglyCommentCompoundClass", "Student" }, + new object[] { "Abstract", "FluentApiComments", "WronglyCommentCompoundClass2", "Student" }, new object[] { "Abstract", "EmptyClass", "Student" }, new object[] { "Abstract", "FluentDefaultMemberClass", "Student" }, new object[] { "Abstract", "FluentLambdaClass", "Student|Address" }, From 1966721936af3ad737cc59e84a351b3e36b0fcef Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Fri, 8 Aug 2025 16:03:19 +0200 Subject: [PATCH 56/61] fix: WronglyCommentedClass and WronglyCommentedClass2 tests --- .../CreateStudent.expected.txt | 47 +++++++++++++++++++ .../WronglyCommentedClass/Student.cs | 18 ++----- .../CreateStudent.expected.txt | 47 +++++++++++++++++++ .../WronglyCommentedClass2/Student.cs | 18 ++----- .../CodeGeneration/TestDataProvider.cs | 4 +- 5 files changed, 104 insertions(+), 30 deletions(-) diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/CreateStudent.expected.txt index e69de29..fe90a50 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/CreateStudent.expected.txt +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/CreateStudent.expected.txt @@ -0,0 +1,47 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.WronglyCommentedClass; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.IWithName +{ + private readonly Student student; + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + public static Student WithName(string name) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.Name = name; + return createStudent.student; + } + + Student IWithName.WithName(string name) + { + student.Name = name; + return student; + } + + public interface ICreateStudent : IWithName + { + } + + public interface IWithName + { + Student WithName(string name); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/Student.cs index 29fb1e8..be17027 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/Student.cs @@ -11,19 +11,9 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComme public class Student { // - // Sets the student's city. + // Sets the student's name. // - // The student's city. - // - // - // Set's the student's city to Boston. - // - // - // - // Set's the student's city to null. - // - [FluentMember(0, "LivingIn")] - [FluentDefault("LivingInBoston")] - [FluentNullable("InUnknownCity")] - public string? City { get; set; } = "Boston"; + // The student's name. + [FluentMember(0)] + public string Name { get; set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/CreateStudent.expected.txt b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/CreateStudent.expected.txt index e69de29..d7e758b 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/CreateStudent.expected.txt +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/CreateStudent.expected.txt @@ -0,0 +1,47 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.WronglyCommentedClass2; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.IWithName +{ + private readonly Student student; + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + public static Student WithName(string name) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.Name = name; + return createStudent.student; + } + + Student IWithName.WithName(string name) + { + student.Name = name; + return student; + } + + public interface ICreateStudent : IWithName + { + } + + public interface IWithName + { + Student WithName(string name); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/Student.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/Student.cs index dd476cb..d4235ee 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/Student.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/Student.cs @@ -11,19 +11,9 @@ namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComme public class Student { ///// - ///// Sets the student's city. + ///// Sets the student's name. ///// - ///// The student's city. - ///// - ///// - ///// Set's the student's city to Boston. - ///// - ///// - ///// - ///// Set's the student's city to null. - ///// - [FluentMember(0, "LivingIn")] - [FluentDefault("LivingInBoston")] - [FluentNullable("InUnknownCity")] - public string? City { get; set; } = "Boston"; + ///// The student's name. + [FluentMember(0)] + public string Name { get; set; } } \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs index 78fa227..00a65b8 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestDataProvider.cs @@ -28,8 +28,8 @@ internal class TestDataProvider : IEnumerable new object[] { "Abstract", "FluentApiComments", "CommentedPropertiesClassAdvanced", "Student" }, new object[] { "Abstract", "FluentApiComments", "IncompletelyCommentedPropertyClass", "Student"}, new object[] { "Abstract", "FluentApiComments", "RedundantCommentCompoundClass", "Student" }, - new object[] { "Abstract", "FluentApiComments", "WronglyCommentCompoundClass", "Student" }, - new object[] { "Abstract", "FluentApiComments", "WronglyCommentCompoundClass2", "Student" }, + new object[] { "Abstract", "FluentApiComments", "WronglyCommentedClass", "Student" }, + new object[] { "Abstract", "FluentApiComments", "WronglyCommentedClass2", "Student" }, new object[] { "Abstract", "EmptyClass", "Student" }, new object[] { "Abstract", "FluentDefaultMemberClass", "Student" }, new object[] { "Abstract", "FluentLambdaClass", "Student|Address" }, From 3441c443cf453d59d18a002a3f4a00ebafb0bf7d Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Fri, 8 Aug 2025 16:13:42 +0200 Subject: [PATCH 57/61] fix(SymbolInfoCreator): use regex --- .../SourceGenerators/SymbolInfoCreator.cs | 8 ++-- .../WronglyCommentedClass/CreateStudent.g.cs | 47 +++++++++++++++++++ .../WronglyCommentedClass2/CreateStudent.g.cs | 47 +++++++++++++++++++ 3 files changed, 98 insertions(+), 4 deletions(-) diff --git a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs index 4589cd6..0e41c91 100644 --- a/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs +++ b/src/M31.FluentApi.Generator/SourceGenerators/SymbolInfoCreator.cs @@ -1,3 +1,4 @@ +using System.Text.RegularExpressions; using M31.FluentApi.Generator.CodeBuilding; using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements; using M31.FluentApi.Generator.CodeGeneration.CodeBoardElements.FluentApiComments; @@ -199,6 +200,7 @@ private static ParameterKinds GetParameterKinds(IParameterSymbol parameterSymbol return parameterKinds; } + private static readonly Regex fluentApiCommentStart = new Regex(@"^\s*////(?!/)", RegexOptions.Compiled); private static Comments GetFluentSymbolComments(ISymbol symbol) { SyntaxReference? syntaxRef = symbol.DeclaringSyntaxReferences.FirstOrDefault(); @@ -220,12 +222,10 @@ private static Comments GetFluentSymbolComments(ISymbol symbol) } string str = syntaxTrivia.ToString(); - if (!str.Trim().StartsWith("////")) // todo: regex + if (fluentApiCommentStart.IsMatch(str)) { - continue; + commentLines.Add(str.TrimStart('/', ' ')); } - - commentLines.Add(str.TrimStart('/', ' ')); } string comments = string.Join(Environment.NewLine, commentLines); diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/CreateStudent.g.cs index e69de29..fe90a50 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/CreateStudent.g.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass/CreateStudent.g.cs @@ -0,0 +1,47 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.WronglyCommentedClass; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.IWithName +{ + private readonly Student student; + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + public static Student WithName(string name) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.Name = name; + return createStudent.student; + } + + Student IWithName.WithName(string name) + { + student.Name = name; + return student; + } + + public interface ICreateStudent : IWithName + { + } + + public interface IWithName + { + Student WithName(string name); + } +} \ No newline at end of file diff --git a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/CreateStudent.g.cs b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/CreateStudent.g.cs index e69de29..d7e758b 100644 --- a/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/CreateStudent.g.cs +++ b/src/M31.FluentApi.Tests/CodeGeneration/TestClasses/Abstract/FluentApiComments/WronglyCommentedClass2/CreateStudent.g.cs @@ -0,0 +1,47 @@ +// +// This code was generated by the library M31.FluentAPI. +// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated. + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable enable + +namespace M31.FluentApi.Tests.CodeGeneration.TestClasses.Abstract.FluentApiComments.WronglyCommentedClass2; + +public class CreateStudent : + CreateStudent.ICreateStudent, + CreateStudent.IWithName +{ + private readonly Student student; + + private CreateStudent() + { + student = new Student(); + } + + public static ICreateStudent InitialStep() + { + return new CreateStudent(); + } + + public static Student WithName(string name) + { + CreateStudent createStudent = new CreateStudent(); + createStudent.student.Name = name; + return createStudent.student; + } + + Student IWithName.WithName(string name) + { + student.Name = name; + return student; + } + + public interface ICreateStudent : IWithName + { + } + + public interface IWithName + { + Student WithName(string name); + } +} \ No newline at end of file From d2367c17f4b09902a369de7eaeb4b00de4324689 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Fri, 8 Aug 2025 16:18:22 +0200 Subject: [PATCH 58/61] fix: readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 36946a6..ad490a1 100644 --- a/README.md +++ b/README.md @@ -480,8 +480,8 @@ If multiple methods are generated for a member, you can target a specific method //// //// Sets the student's current semester. //// -//// The student's current semester. -//// A builder for setting the student's city. +//// The student's current semester. +//// A builder for setting the student's city. //// //// //// Sets the student's semester to 0. @@ -496,7 +496,7 @@ To simplify adding documentation comments, a code action is available to generat ![doc-comments-action](https://raw.githubusercontent.com/m31coding/M31.FluentAPI/main/media/create-doc-comments-action.png) -The documented version of the Student class can be found in [DocumentedStudent.cs](src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/DocumentedStudent.cs). The corresponding generated code is located in [DocumentedStudent.g.cs](src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/CreateDocumentedStudent.g.cs) +For reference, you can view the documented version of the `Student` class in [DocumentedStudent.cs](src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/DocumentedStudent.cs). The corresponding generated code is located in [DocumentedStudent.g.cs](src/M31.FluentApi.Tests/CodeGeneration/TestClasses/DocumentedStudentClass/CreateDocumentedStudent.g.cs) ### Lambda pattern From 23ec5a4759f6ed9931e78aa1356ce5fad8f76c61 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Fri, 8 Aug 2025 16:20:30 +0200 Subject: [PATCH 59/61] chore: bump package version --- README.md | 2 +- src/M31.FluentApi.Generator/M31.FluentApi.Generator.csproj | 2 +- src/M31.FluentApi/M31.FluentApi.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ad490a1..9ba9f60 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ PM> Install-Package M31.FluentApi A package reference will be added to your `csproj` file. Moreover, since this library provides code via source code generation, consumers of your project don't need the reference to `M31.FluentApi`. Therefore, it is recommended to use the `PrivateAssets` metadata tag: ```xml - + ``` If you would like to examine the generated code, you may emit it by adding the following lines to your `csproj` file: diff --git a/src/M31.FluentApi.Generator/M31.FluentApi.Generator.csproj b/src/M31.FluentApi.Generator/M31.FluentApi.Generator.csproj index 228681a..e8c74d4 100644 --- a/src/M31.FluentApi.Generator/M31.FluentApi.Generator.csproj +++ b/src/M31.FluentApi.Generator/M31.FluentApi.Generator.csproj @@ -11,7 +11,7 @@ true true true - 1.10.0 + 1.11.0 Kevin Schaal The generator package for M31.FluentAPI. Don't install this package explicitly, install M31.FluentAPI instead. fluentapi fluentbuilder fluentinterface fluentdesign fluent codegeneration diff --git a/src/M31.FluentApi/M31.FluentApi.csproj b/src/M31.FluentApi/M31.FluentApi.csproj index e522469..95052a9 100644 --- a/src/M31.FluentApi/M31.FluentApi.csproj +++ b/src/M31.FluentApi/M31.FluentApi.csproj @@ -7,7 +7,7 @@ enable true true - 1.10.0 + 1.11.0 Kevin Schaal Generate fluent builders in C#. fluentapi fluentbuilder fluentinterface fluentdesign fluent codegeneration From 5fea6312924911902b435d66608258e99de0d4b0 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Fri, 8 Aug 2025 16:24:02 +0200 Subject: [PATCH 60/61] feat(ExampleProject): DocumentedStudent --- src/ExampleProject/Program.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ExampleProject/Program.cs b/src/ExampleProject/Program.cs index 50ddc6e..1b08632 100644 --- a/src/ExampleProject/Program.cs +++ b/src/ExampleProject/Program.cs @@ -14,6 +14,17 @@ Console.WriteLine(JsonSerializer.Serialize(student1)); Console.WriteLine(JsonSerializer.Serialize(student2)); +// DocumentedStudent (generated code includes `summary`, `param` and `returns` XML comments) +// + +DocumentedStudent documentedStudent1 = CreateDocumentedStudent.Named("Alice", "King").OfAge(22).WhoStartsUniversity() + .LivingIn("New York").WhoIsHappy().WhoseFriendsAre("Bob", "Carol", "Eve"); +DocumentedStudent documentedStudent2 = CreateDocumentedStudent.Named("Bob", "Bishop").BornOn(new DateOnly(2002, 8, 3)).InSemester(2) + .LivingInBoston().WithUnknownMood().WhoseFriendIs("Alice"); + +Console.WriteLine(JsonSerializer.Serialize(documentedStudent1)); +Console.WriteLine(JsonSerializer.Serialize(documentedStudent2)); + // ExchangeStudent (inherited from Student) // From baddba9b92c1a9e5b4306dd5d73abca741e09348 Mon Sep 17 00:00:00 2001 From: Kevin Schaal Date: Mon, 18 Aug 2025 11:23:51 +0200 Subject: [PATCH 61/61] fix(BuilderStepMethods): make constructor internal --- .../BuilderMethodsGeneration/BuilderStepMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethods.cs b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethods.cs index 6878168..478541b 100644 --- a/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethods.cs +++ b/src/M31.FluentApi.Generator/CodeGeneration/CodeBoardActors/BuilderMethodsGeneration/BuilderStepMethods.cs @@ -4,7 +4,7 @@ namespace M31.FluentApi.Generator.CodeGeneration.CodeBoardActors.BuilderMethodsG internal class BuilderStepMethods { - public BuilderStepMethods( + internal BuilderStepMethods( IReadOnlyCollection staticMethods, IReadOnlyCollection interfaces) {