diff --git a/dotnet/.editorconfig b/dotnet/.editorconfig index 2ead8ad1b..b013b5202 100644 --- a/dotnet/.editorconfig +++ b/dotnet/.editorconfig @@ -1,5 +1,4 @@ -<<<<<<< HEAD -# EditorConfig is awesome:http://EditorConfig.org +# EditorConfig is awesome:http://EditorConfig.org # top-most EditorConfig file root = true @@ -93,7 +92,6 @@ csharp_style_inlined_variable_declaration = true:error csharp_style_throw_expression = true:suggestion csharp_style_conditional_delegate_call = true:suggestion csharp_style_deconstructed_variable_declaration = true:suggestion -======= ; EditorConfig to support per-solution formatting. ; Use the EditorConfig VS add-in to make this work. ; http://editorconfig.org/ @@ -137,7 +135,6 @@ csharp_style_var_elsewhere = true:suggestion # Disallow throw expressions. csharp_style_throw_expression = false:suggestion ->>>>>>> agnext_main # Newline settings csharp_new_line_before_open_brace = all @@ -146,7 +143,6 @@ csharp_new_line_before_catch = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true -<<<<<<< HEAD csharp_new_line_between_query_expression_clauses = true # Identation options @@ -236,7 +232,6 @@ dotnet_diagnostic.CA2016.severity = suggestion # disable check for generated code [*.generated.cs] generated_code = true -======= # Namespace settings csharp_style_namespace_declarations = file_scoped:silent @@ -708,4 +703,3 @@ generated_code = true # IDE1591 Missing XML comment for publicly visible type or member dotnet_diagnostic.CS1591.severity = none ->>>>>>> agnext_main diff --git a/dotnet/.gitignore b/dotnet/.gitignore index 962ccf134..25f613c79 100644 --- a/dotnet/.gitignore +++ b/dotnet/.gitignore @@ -1,6 +1,4 @@ -<<<<<<< HEAD # gitignore file for C#/VS -======= ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## @@ -20,7 +18,6 @@ mono_crash.* output ->>>>>>> agnext_main # Build results [Dd]ebug/ @@ -29,7 +26,6 @@ output [Rr]eleases/ x64/ x86/ -<<<<<<< HEAD build/ bld/ [Bb]in/ @@ -51,7 +47,6 @@ output/ # JetBrains Rider .idea/ -======= [Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ @@ -525,4 +520,3 @@ sk-azfunc-server/local.settings.json temp .mono/** **/values.xml ->>>>>>> agnext_main diff --git a/dotnet/src/AutoGen.Mistral/DTOs/Error.cs b/dotnet/src/AutoGen.Mistral/DTOs/Error.cs index 8bddcfc77..79a2c2e2f 100644 --- a/dotnet/src/AutoGen.Mistral/DTOs/Error.cs +++ b/dotnet/src/AutoGen.Mistral/DTOs/Error.cs @@ -3,37 +3,36 @@ using System.Text.Json.Serialization; -namespace AutoGen.Mistral +namespace AutoGen.Mistral; + +public class Error { - public class Error + public Error(string type, string message, string? param = default(string), string? code = default(string)) { - public Error(string type, string message, string? param = default(string), string? code = default(string)) - { - Type = type; - Message = message; - Param = param; - Code = code; - } - - [JsonPropertyName("type")] - public string Type { get; set; } - - /// - /// Gets or Sets Message - /// - [JsonPropertyName("message")] - public string Message { get; set; } - - /// - /// Gets or Sets Param - /// - [JsonPropertyName("param")] - public string? Param { get; set; } - - /// - /// Gets or Sets Code - /// - [JsonPropertyName("code")] - public string? Code { get; set; } + Type = type; + Message = message; + Param = param; + Code = code; } + + [JsonPropertyName("type")] + public string Type { get; set; } + + /// + /// Gets or Sets Message + /// + [JsonPropertyName("message")] + public string Message { get; set; } + + /// + /// Gets or Sets Param + /// + [JsonPropertyName("param")] + public string? Param { get; set; } + + /// + /// Gets or Sets Code + /// + [JsonPropertyName("code")] + public string? Code { get; set; } } diff --git a/dotnet/src/AutoGen.SourceGenerator/DocumentCommentExtension.cs b/dotnet/src/AutoGen.SourceGenerator/DocumentCommentExtension.cs index 0892d597a..14db58a4b 100644 --- a/dotnet/src/AutoGen.SourceGenerator/DocumentCommentExtension.cs +++ b/dotnet/src/AutoGen.SourceGenerator/DocumentCommentExtension.cs @@ -10,286 +10,285 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; // copyright: https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/StyleCop.Analyzers/StyleCop.Analyzers/Helpers/DocumentationCommentExtensions.cs#L17 -namespace AutoGen.SourceGenerator +namespace AutoGen.SourceGenerator; + +internal static class DocumentCommentExtension { - internal static class DocumentCommentExtension + public static bool IsMissingOrDefault(this SyntaxToken token) { - public static bool IsMissingOrDefault(this SyntaxToken token) + return token.IsKind(SyntaxKind.None) + || token.IsMissing; + } + + public static string? GetParameterDescriptionFromDocumentationCommentTriviaSyntax(this DocumentationCommentTriviaSyntax documentationCommentTrivia, string parameterName) + { + var parameterElements = documentationCommentTrivia.Content.GetXmlElements("param"); + + var parameter = parameterElements.FirstOrDefault(element => { - return token.IsKind(SyntaxKind.None) - || token.IsMissing; + var xml = XElement.Parse(element.ToString()); + var nameAttribute = xml.Attribute("name"); + return nameAttribute != null && nameAttribute.Value == parameterName; + }); + + if (parameter is not null) + { + var xml = XElement.Parse(parameter.ToString()); + + return xml.Nodes().OfType().FirstOrDefault()?.Value; } - public static string? GetParameterDescriptionFromDocumentationCommentTriviaSyntax(this DocumentationCommentTriviaSyntax documentationCommentTrivia, string parameterName) + return null; + } + + public static string? GetNamespaceNameFromClassDeclarationSyntax(this ClassDeclarationSyntax classDeclaration) + { + return classDeclaration.Parent is NamespaceDeclarationSyntax namespaceDeclarationSyntax ? namespaceDeclarationSyntax.Name.ToString() + : classDeclaration.Parent is FileScopedNamespaceDeclarationSyntax fileScopedNamespaceDeclarationSyntax ? fileScopedNamespaceDeclarationSyntax.Name.ToString() + : null; + } + + public static DocumentationCommentTriviaSyntax? GetDocumentationCommentTriviaSyntax(this SyntaxNode node) + { + if (node == null) { - var parameterElements = documentationCommentTrivia.Content.GetXmlElements("param"); - - var parameter = parameterElements.FirstOrDefault(element => - { - var xml = XElement.Parse(element.ToString()); - var nameAttribute = xml.Attribute("name"); - return nameAttribute != null && nameAttribute.Value == parameterName; - }); - - if (parameter is not null) - { - var xml = XElement.Parse(parameter.ToString()); - - return xml.Nodes().OfType().FirstOrDefault()?.Value; - } - return null; } - public static string? GetNamespaceNameFromClassDeclarationSyntax(this ClassDeclarationSyntax classDeclaration) + foreach (var leadingTrivia in node.GetLeadingTrivia()) { - return classDeclaration.Parent is NamespaceDeclarationSyntax namespaceDeclarationSyntax ? namespaceDeclarationSyntax.Name.ToString() - : classDeclaration.Parent is FileScopedNamespaceDeclarationSyntax fileScopedNamespaceDeclarationSyntax ? fileScopedNamespaceDeclarationSyntax.Name.ToString() - : null; - } - - public static DocumentationCommentTriviaSyntax? GetDocumentationCommentTriviaSyntax(this SyntaxNode node) - { - if (node == null) + if (leadingTrivia.GetStructure() is DocumentationCommentTriviaSyntax structure) { - return null; - } - - foreach (var leadingTrivia in node.GetLeadingTrivia()) - { - if (leadingTrivia.GetStructure() is DocumentationCommentTriviaSyntax structure) - { - return structure; - } - } - - return null; - } - - public static XmlNodeSyntax GetFirstXmlElement(this SyntaxList content, string elementName) - { - return content.GetXmlElements(elementName).FirstOrDefault(); - } - - public static IEnumerable GetXmlElements(this SyntaxList content, string elementName) - { - foreach (XmlNodeSyntax syntax in content) - { - if (syntax is XmlEmptyElementSyntax emptyElement) - { - if (string.Equals(elementName, emptyElement.Name.ToString(), StringComparison.Ordinal)) - { - yield return emptyElement; - } - - continue; - } - - if (syntax is XmlElementSyntax elementSyntax) - { - if (string.Equals(elementName, elementSyntax.StartTag?.Name?.ToString(), StringComparison.Ordinal)) - { - yield return elementSyntax; - } - - continue; - } + return structure; } } - public static T ReplaceExteriorTrivia(this T node, SyntaxTrivia trivia) - where T : XmlNodeSyntax - { - // Make sure to include a space after the '///' characters. - SyntaxTrivia triviaWithSpace = SyntaxFactory.DocumentationCommentExterior(trivia.ToString() + " "); + return null; + } - return node.ReplaceTrivia( - node.DescendantTrivia(descendIntoTrivia: true).Where(i => i.IsKind(SyntaxKind.DocumentationCommentExteriorTrivia)), - (originalTrivia, rewrittenTrivia) => SelectExteriorTrivia(rewrittenTrivia, trivia, triviaWithSpace)); + public static XmlNodeSyntax GetFirstXmlElement(this SyntaxList content, string elementName) + { + return content.GetXmlElements(elementName).FirstOrDefault(); + } + + public static IEnumerable GetXmlElements(this SyntaxList content, string elementName) + { + foreach (XmlNodeSyntax syntax in content) + { + if (syntax is XmlEmptyElementSyntax emptyElement) + { + if (string.Equals(elementName, emptyElement.Name.ToString(), StringComparison.Ordinal)) + { + yield return emptyElement; + } + + continue; + } + + if (syntax is XmlElementSyntax elementSyntax) + { + if (string.Equals(elementName, elementSyntax.StartTag?.Name?.ToString(), StringComparison.Ordinal)) + { + yield return elementSyntax; + } + + continue; + } } + } - public static SyntaxList WithoutFirstAndLastNewlines(this SyntaxList summaryContent) + public static T ReplaceExteriorTrivia(this T node, SyntaxTrivia trivia) + where T : XmlNodeSyntax + { + // Make sure to include a space after the '///' characters. + SyntaxTrivia triviaWithSpace = SyntaxFactory.DocumentationCommentExterior(trivia.ToString() + " "); + + return node.ReplaceTrivia( + node.DescendantTrivia(descendIntoTrivia: true).Where(i => i.IsKind(SyntaxKind.DocumentationCommentExteriorTrivia)), + (originalTrivia, rewrittenTrivia) => SelectExteriorTrivia(rewrittenTrivia, trivia, triviaWithSpace)); + } + + public static SyntaxList WithoutFirstAndLastNewlines(this SyntaxList summaryContent) + { + if (summaryContent.Count == 0) { - if (summaryContent.Count == 0) - { - return summaryContent; - } - - if (!(summaryContent[0] is XmlTextSyntax firstSyntax)) - { - return summaryContent; - } - - if (!(summaryContent[summaryContent.Count - 1] is XmlTextSyntax lastSyntax)) - { - return summaryContent; - } - - SyntaxTokenList firstSyntaxTokens = firstSyntax.TextTokens; - - int removeFromStart; - if (IsXmlNewLine(firstSyntaxTokens[0])) - { - removeFromStart = 1; - } - else - { - if (!IsXmlWhitespace(firstSyntaxTokens[0])) - { - return summaryContent; - } - - if (!IsXmlNewLine(firstSyntaxTokens[1])) - { - return summaryContent; - } - - removeFromStart = 2; - } - - SyntaxTokenList lastSyntaxTokens = lastSyntax.TextTokens; - - int removeFromEnd; - if (IsXmlNewLine(lastSyntaxTokens[lastSyntaxTokens.Count - 1])) - { - removeFromEnd = 1; - } - else - { - if (!IsXmlWhitespace(lastSyntaxTokens[lastSyntaxTokens.Count - 1])) - { - return summaryContent; - } - - if (!IsXmlNewLine(lastSyntaxTokens[lastSyntaxTokens.Count - 2])) - { - return summaryContent; - } - - removeFromEnd = 2; - } - - for (int i = 0; i < removeFromStart; i++) - { - firstSyntaxTokens = firstSyntaxTokens.RemoveAt(0); - } - - if (firstSyntax == lastSyntax) - { - lastSyntaxTokens = firstSyntaxTokens; - } - - for (int i = 0; i < removeFromEnd; i++) - { - if (!lastSyntaxTokens.Any()) - { - break; - } - - lastSyntaxTokens = lastSyntaxTokens.RemoveAt(lastSyntaxTokens.Count - 1); - } - - summaryContent = summaryContent.RemoveAt(summaryContent.Count - 1); - if (lastSyntaxTokens.Count != 0) - { - summaryContent = summaryContent.Add(lastSyntax.WithTextTokens(lastSyntaxTokens)); - } - - if (firstSyntax != lastSyntax) - { - summaryContent = summaryContent.RemoveAt(0); - if (firstSyntaxTokens.Count != 0) - { - summaryContent = summaryContent.Insert(0, firstSyntax.WithTextTokens(firstSyntaxTokens)); - } - } - - if (summaryContent.Count > 0) - { - // Make sure to remove the leading trivia - summaryContent = summaryContent.Replace(summaryContent[0], summaryContent[0].WithLeadingTrivia()); - - // Remove leading spaces (between the start tag and the start of the paragraph content) - if (summaryContent[0] is XmlTextSyntax firstTextSyntax && firstTextSyntax.TextTokens.Count > 0) - { - SyntaxToken firstTextToken = firstTextSyntax.TextTokens[0]; - string firstTokenText = firstTextToken.Text; - string trimmed = firstTokenText.TrimStart(); - if (trimmed != firstTokenText) - { - SyntaxToken newFirstToken = SyntaxFactory.Token( - firstTextToken.LeadingTrivia, - firstTextToken.Kind(), - trimmed, - firstTextToken.ValueText.TrimStart(), - firstTextToken.TrailingTrivia); - - summaryContent = summaryContent.Replace(firstTextSyntax, firstTextSyntax.ReplaceToken(firstTextToken, newFirstToken)); - } - } - } - return summaryContent; } - public static bool IsXmlNewLine(this SyntaxToken node) + if (!(summaryContent[0] is XmlTextSyntax firstSyntax)) { - return node.IsKind(SyntaxKind.XmlTextLiteralNewLineToken); + return summaryContent; } - public static bool IsXmlWhitespace(this SyntaxToken node) + if (!(summaryContent[summaryContent.Count - 1] is XmlTextSyntax lastSyntax)) { - return node.IsKind(SyntaxKind.XmlTextLiteralToken) - && string.IsNullOrWhiteSpace(node.Text); + return summaryContent; } - /// - /// Adjust the leading and trailing trivia associated with - /// tokens to ensure the formatter properly indents the exterior trivia. - /// - /// The type of syntax node. - /// The syntax node to adjust tokens. - /// A equivalent to the input , adjusted by moving any - /// trailing trivia from tokens to be leading trivia of the - /// following token. - public static T AdjustDocumentationCommentNewLineTrivia(this T node) - where T : SyntaxNode - { - var tokensForAdjustment = - from token in node.DescendantTokens() - where token.IsKind(SyntaxKind.XmlTextLiteralNewLineToken) - where token.HasTrailingTrivia - let next = token.GetNextToken(includeZeroWidth: true, includeSkipped: true, includeDirectives: true, includeDocumentationComments: true) - where !next.IsMissingOrDefault() - select new KeyValuePair(token, next); + SyntaxTokenList firstSyntaxTokens = firstSyntax.TextTokens; - Dictionary replacements = new Dictionary(); - foreach (var pair in tokensForAdjustment) + int removeFromStart; + if (IsXmlNewLine(firstSyntaxTokens[0])) + { + removeFromStart = 1; + } + else + { + if (!IsXmlWhitespace(firstSyntaxTokens[0])) { - replacements[pair.Key] = pair.Key.WithTrailingTrivia(); - replacements[pair.Value] = pair.Value.WithLeadingTrivia(pair.Value.LeadingTrivia.InsertRange(0, pair.Key.TrailingTrivia)); + return summaryContent; } - return node.ReplaceTokens(replacements.Keys, (originalToken, rewrittenToken) => replacements[originalToken]); - } - - public static XmlNameSyntax? GetName(this XmlNodeSyntax element) - { - return (element as XmlElementSyntax)?.StartTag?.Name - ?? (element as XmlEmptyElementSyntax)?.Name; - } - - private static SyntaxTrivia SelectExteriorTrivia(SyntaxTrivia rewrittenTrivia, SyntaxTrivia trivia, SyntaxTrivia triviaWithSpace) - { - // if the trivia had a trailing space, make sure to preserve it - if (rewrittenTrivia.ToString().EndsWith(" ")) + if (!IsXmlNewLine(firstSyntaxTokens[1])) { - return triviaWithSpace; + return summaryContent; } - // otherwise the space is part of the leading trivia of the following token, so don't add an extra one to - // the exterior trivia - return trivia; + removeFromStart = 2; } + + SyntaxTokenList lastSyntaxTokens = lastSyntax.TextTokens; + + int removeFromEnd; + if (IsXmlNewLine(lastSyntaxTokens[lastSyntaxTokens.Count - 1])) + { + removeFromEnd = 1; + } + else + { + if (!IsXmlWhitespace(lastSyntaxTokens[lastSyntaxTokens.Count - 1])) + { + return summaryContent; + } + + if (!IsXmlNewLine(lastSyntaxTokens[lastSyntaxTokens.Count - 2])) + { + return summaryContent; + } + + removeFromEnd = 2; + } + + for (int i = 0; i < removeFromStart; i++) + { + firstSyntaxTokens = firstSyntaxTokens.RemoveAt(0); + } + + if (firstSyntax == lastSyntax) + { + lastSyntaxTokens = firstSyntaxTokens; + } + + for (int i = 0; i < removeFromEnd; i++) + { + if (!lastSyntaxTokens.Any()) + { + break; + } + + lastSyntaxTokens = lastSyntaxTokens.RemoveAt(lastSyntaxTokens.Count - 1); + } + + summaryContent = summaryContent.RemoveAt(summaryContent.Count - 1); + if (lastSyntaxTokens.Count != 0) + { + summaryContent = summaryContent.Add(lastSyntax.WithTextTokens(lastSyntaxTokens)); + } + + if (firstSyntax != lastSyntax) + { + summaryContent = summaryContent.RemoveAt(0); + if (firstSyntaxTokens.Count != 0) + { + summaryContent = summaryContent.Insert(0, firstSyntax.WithTextTokens(firstSyntaxTokens)); + } + } + + if (summaryContent.Count > 0) + { + // Make sure to remove the leading trivia + summaryContent = summaryContent.Replace(summaryContent[0], summaryContent[0].WithLeadingTrivia()); + + // Remove leading spaces (between the start tag and the start of the paragraph content) + if (summaryContent[0] is XmlTextSyntax firstTextSyntax && firstTextSyntax.TextTokens.Count > 0) + { + SyntaxToken firstTextToken = firstTextSyntax.TextTokens[0]; + string firstTokenText = firstTextToken.Text; + string trimmed = firstTokenText.TrimStart(); + if (trimmed != firstTokenText) + { + SyntaxToken newFirstToken = SyntaxFactory.Token( + firstTextToken.LeadingTrivia, + firstTextToken.Kind(), + trimmed, + firstTextToken.ValueText.TrimStart(), + firstTextToken.TrailingTrivia); + + summaryContent = summaryContent.Replace(firstTextSyntax, firstTextSyntax.ReplaceToken(firstTextToken, newFirstToken)); + } + } + } + + return summaryContent; + } + + public static bool IsXmlNewLine(this SyntaxToken node) + { + return node.IsKind(SyntaxKind.XmlTextLiteralNewLineToken); + } + + public static bool IsXmlWhitespace(this SyntaxToken node) + { + return node.IsKind(SyntaxKind.XmlTextLiteralToken) + && string.IsNullOrWhiteSpace(node.Text); + } + + /// + /// Adjust the leading and trailing trivia associated with + /// tokens to ensure the formatter properly indents the exterior trivia. + /// + /// The type of syntax node. + /// The syntax node to adjust tokens. + /// A equivalent to the input , adjusted by moving any + /// trailing trivia from tokens to be leading trivia of the + /// following token. + public static T AdjustDocumentationCommentNewLineTrivia(this T node) + where T : SyntaxNode + { + var tokensForAdjustment = + from token in node.DescendantTokens() + where token.IsKind(SyntaxKind.XmlTextLiteralNewLineToken) + where token.HasTrailingTrivia + let next = token.GetNextToken(includeZeroWidth: true, includeSkipped: true, includeDirectives: true, includeDocumentationComments: true) + where !next.IsMissingOrDefault() + select new KeyValuePair(token, next); + + Dictionary replacements = new Dictionary(); + foreach (var pair in tokensForAdjustment) + { + replacements[pair.Key] = pair.Key.WithTrailingTrivia(); + replacements[pair.Value] = pair.Value.WithLeadingTrivia(pair.Value.LeadingTrivia.InsertRange(0, pair.Key.TrailingTrivia)); + } + + return node.ReplaceTokens(replacements.Keys, (originalToken, rewrittenToken) => replacements[originalToken]); + } + + public static XmlNameSyntax? GetName(this XmlNodeSyntax element) + { + return (element as XmlElementSyntax)?.StartTag?.Name + ?? (element as XmlEmptyElementSyntax)?.Name; + } + + private static SyntaxTrivia SelectExteriorTrivia(SyntaxTrivia rewrittenTrivia, SyntaxTrivia trivia, SyntaxTrivia triviaWithSpace) + { + // if the trivia had a trailing space, make sure to preserve it + if (rewrittenTrivia.ToString().EndsWith(" ")) + { + return triviaWithSpace; + } + + // otherwise the space is part of the leading trivia of the following token, so don't add an extra one to + // the exterior trivia + return trivia; } } diff --git a/dotnet/src/AutoGen.SourceGenerator/FunctionCallGenerator.cs b/dotnet/src/AutoGen.SourceGenerator/FunctionCallGenerator.cs index 4fa6169f7..d58136820 100644 --- a/dotnet/src/AutoGen.SourceGenerator/FunctionCallGenerator.cs +++ b/dotnet/src/AutoGen.SourceGenerator/FunctionCallGenerator.cs @@ -12,236 +12,235 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using Newtonsoft.Json; -namespace AutoGen.SourceGenerator +namespace AutoGen.SourceGenerator; + +[Generator] +public partial class FunctionCallGenerator : IIncrementalGenerator { - [Generator] - public partial class FunctionCallGenerator : IIncrementalGenerator + private const string FUNCTION_CALL_ATTRIBUTION = "AutoGen.Core.FunctionAttribute"; + + public void Initialize(IncrementalGeneratorInitializationContext context) { - private const string FUNCTION_CALL_ATTRIBUTION = "AutoGen.Core.FunctionAttribute"; - - public void Initialize(IncrementalGeneratorInitializationContext context) - { #if LAUNCH_DEBUGGER - if (!System.Diagnostics.Debugger.IsAttached) - { - System.Diagnostics.Debugger.Launch(); - } + if (!System.Diagnostics.Debugger.IsAttached) + { + System.Diagnostics.Debugger.Launch(); + } #endif - var optionProvider = context.AnalyzerConfigOptionsProvider.Select((provider, ct) => + var optionProvider = context.AnalyzerConfigOptionsProvider.Select((provider, ct) => + { + var generateFunctionDefinitionContract = provider.GlobalOptions.TryGetValue("build_property.EnableContract", out var value) && value?.ToLowerInvariant() == "true"; + + return generateFunctionDefinitionContract; + }); + // step 1 + // filter syntax tree and search syntax node that satisfied the following conditions + // - is partial class + var partialClassSyntaxProvider = context.SyntaxProvider.CreateSyntaxProvider( + (node, ct) => { - var generateFunctionDefinitionContract = provider.GlobalOptions.TryGetValue("build_property.EnableContract", out var value) && value?.ToLowerInvariant() == "true"; + return node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.Modifiers.Any(SyntaxKind.PartialKeyword); + }, + (ctx, ct) => + { + // first check if any method of the class has FunctionAttribution attribute + // if not, then return null + var filePath = ctx.Node.SyntaxTree.FilePath; + var fileName = Path.GetFileNameWithoutExtension(filePath); - return generateFunctionDefinitionContract; - }); - // step 1 - // filter syntax tree and search syntax node that satisfied the following conditions - // - is partial class - var partialClassSyntaxProvider = context.SyntaxProvider.CreateSyntaxProvider( - (node, ct) => + var classDeclarationSyntax = ctx.Node as ClassDeclarationSyntax; + var nameSpace = classDeclarationSyntax?.Parent as NamespaceDeclarationSyntax; + var fullClassName = $"{nameSpace?.Name}.{classDeclarationSyntax!.Identifier}"; + if (classDeclarationSyntax == null) { - return node is ClassDeclarationSyntax classDeclarationSyntax && classDeclarationSyntax.Modifiers.Any(SyntaxKind.PartialKeyword); - }, - (ctx, ct) => + return null; + } + + if (!classDeclarationSyntax.Members.Any(member => member.AttributeLists.Any(attributeList => attributeList.Attributes.Any(attribute => { - // first check if any method of the class has FunctionAttribution attribute - // if not, then return null - var filePath = ctx.Node.SyntaxTree.FilePath; - var fileName = Path.GetFileNameWithoutExtension(filePath); - - var classDeclarationSyntax = ctx.Node as ClassDeclarationSyntax; - var nameSpace = classDeclarationSyntax?.Parent as NamespaceDeclarationSyntax; - var fullClassName = $"{nameSpace?.Name}.{classDeclarationSyntax!.Identifier}"; - if (classDeclarationSyntax == null) - { - return null; - } - - if (!classDeclarationSyntax.Members.Any(member => member.AttributeLists.Any(attributeList => attributeList.Attributes.Any(attribute => - { - return ctx.SemanticModel.GetSymbolInfo(attribute).Symbol is IMethodSymbol methodSymbol && methodSymbol.ContainingType.ToDisplayString() == FUNCTION_CALL_ATTRIBUTION; - })))) - { - return null; - } - - // collect methods that has FunctionAttribution attribute - var methodDeclarationSyntaxes = classDeclarationSyntax.Members.Where(member => member.AttributeLists.Any(attributeList => attributeList.Attributes.Any(attribute => - { - return ctx.SemanticModel.GetSymbolInfo(attribute).Symbol is IMethodSymbol methodSymbol && methodSymbol.ContainingType.ToDisplayString() == FUNCTION_CALL_ATTRIBUTION; - }))) - .Select(member => member as MethodDeclarationSyntax) - .Where(method => method != null); - - var className = classDeclarationSyntax.Identifier.ToString(); - var namespaceName = classDeclarationSyntax.GetNamespaceNameFromClassDeclarationSyntax(); - var functionContracts = methodDeclarationSyntaxes.Select(method => CreateFunctionContract(method!, className, namespaceName)); - - return new PartialClassOutput(fullClassName, classDeclarationSyntax, functionContracts); - }) - .Where(node => node != null) - .Collect(); - - var aggregateProvider = optionProvider.Combine(partialClassSyntaxProvider); - // step 2 - context.RegisterSourceOutput(aggregateProvider, - (ctx, source) => + return ctx.SemanticModel.GetSymbolInfo(attribute).Symbol is IMethodSymbol methodSymbol && methodSymbol.ContainingType.ToDisplayString() == FUNCTION_CALL_ATTRIBUTION; + })))) { - var groups = source.Right.GroupBy(item => item!.FullClassName); - foreach (var group in groups) + return null; + } + + // collect methods that has FunctionAttribution attribute + var methodDeclarationSyntaxes = classDeclarationSyntax.Members.Where(member => member.AttributeLists.Any(attributeList => attributeList.Attributes.Any(attribute => + { + return ctx.SemanticModel.GetSymbolInfo(attribute).Symbol is IMethodSymbol methodSymbol && methodSymbol.ContainingType.ToDisplayString() == FUNCTION_CALL_ATTRIBUTION; + }))) + .Select(member => member as MethodDeclarationSyntax) + .Where(method => method != null); + + var className = classDeclarationSyntax.Identifier.ToString(); + var namespaceName = classDeclarationSyntax.GetNamespaceNameFromClassDeclarationSyntax(); + var functionContracts = methodDeclarationSyntaxes.Select(method => CreateFunctionContract(method!, className, namespaceName)); + + return new PartialClassOutput(fullClassName, classDeclarationSyntax, functionContracts); + }) + .Where(node => node != null) + .Collect(); + + var aggregateProvider = optionProvider.Combine(partialClassSyntaxProvider); + // step 2 + context.RegisterSourceOutput(aggregateProvider, + (ctx, source) => + { + var groups = source.Right.GroupBy(item => item!.FullClassName); + foreach (var group in groups) + { + var functionContracts = group.SelectMany(item => item!.FunctionContracts).ToArray(); + var className = group.First()!.ClassDeclarationSyntax.Identifier.ToString(); + var namespaceName = group.First()!.ClassDeclarationSyntax.GetNamespaceNameFromClassDeclarationSyntax() ?? string.Empty; + var functionTT = new FunctionCallTemplate { - var functionContracts = group.SelectMany(item => item!.FunctionContracts).ToArray(); - var className = group.First()!.ClassDeclarationSyntax.Identifier.ToString(); - var namespaceName = group.First()!.ClassDeclarationSyntax.GetNamespaceNameFromClassDeclarationSyntax() ?? string.Empty; - var functionTT = new FunctionCallTemplate + NameSpace = namespaceName, + ClassName = className, + FunctionContracts = functionContracts.ToArray(), + }; + + var functionSource = functionTT.TransformText(); + var fileName = $"{className}.generated.cs"; + + ctx.AddSource(fileName, SourceText.From(functionSource, System.Text.Encoding.UTF8)); + File.WriteAllText(Path.Combine(Path.GetTempPath(), fileName), functionSource); + } + + if (source.Left) + { + var overallFunctionDefinition = source.Right.SelectMany(x => x!.FunctionContracts.Select(y => new { fullClassName = x.FullClassName, y = y })); + var overallFunctionDefinitionObject = overallFunctionDefinition.Select( + x => new { - NameSpace = namespaceName, - ClassName = className, - FunctionContracts = functionContracts.ToArray(), - }; - - var functionSource = functionTT.TransformText(); - var fileName = $"{className}.generated.cs"; - - ctx.AddSource(fileName, SourceText.From(functionSource, System.Text.Encoding.UTF8)); - File.WriteAllText(Path.Combine(Path.GetTempPath(), fileName), functionSource); - } - - if (source.Left) - { - var overallFunctionDefinition = source.Right.SelectMany(x => x!.FunctionContracts.Select(y => new { fullClassName = x.FullClassName, y = y })); - var overallFunctionDefinitionObject = overallFunctionDefinition.Select( - x => new + fullClassName = x.fullClassName, + functionDefinition = new { - fullClassName = x.fullClassName, - functionDefinition = new + x.y.Name, + x.y.Description, + x.y.ReturnType, + Parameters = x.y.Parameters.Select(y => new { - x.y.Name, - x.y.Description, - x.y.ReturnType, - Parameters = x.y.Parameters.Select(y => new - { - y.Name, - y.Description, - y.JsonType, - y.JsonItemType, - y.Type, - y.IsOptional, - y.DefaultValue, - }), - }, - }); + y.Name, + y.Description, + y.JsonType, + y.JsonItemType, + y.Type, + y.IsOptional, + y.DefaultValue, + }), + }, + }); - var json = JsonConvert.SerializeObject(overallFunctionDefinitionObject, formatting: Formatting.Indented); - // wrap json inside csharp block, as SG doesn't support generating non-source file - json = $@"/* wrap json inside csharp block, as SG doesn't support generating non-source file + var json = JsonConvert.SerializeObject(overallFunctionDefinitionObject, formatting: Formatting.Indented); + // wrap json inside csharp block, as SG doesn't support generating non-source file + json = $@"/* wrap json inside csharp block, as SG doesn't support generating non-source file {json} */"; - ctx.AddSource("FunctionDefinition.json", SourceText.From(json, System.Text.Encoding.UTF8)); - } - }); - } - - private class PartialClassOutput - { - public PartialClassOutput(string fullClassName, ClassDeclarationSyntax classDeclarationSyntax, IEnumerable functionContracts) - { - FullClassName = fullClassName; - ClassDeclarationSyntax = classDeclarationSyntax; - FunctionContracts = functionContracts; - } - - public string FullClassName { get; } - - public ClassDeclarationSyntax ClassDeclarationSyntax { get; } - - public IEnumerable FunctionContracts { get; } - } - - private SourceGeneratorFunctionContract CreateFunctionContract(MethodDeclarationSyntax method, string? className, string? namespaceName) - { - // get function_call attribute - var functionCallAttribute = method.AttributeLists.SelectMany(attributeList => attributeList.Attributes) - .FirstOrDefault(attribute => attribute.Name.ToString() == FUNCTION_CALL_ATTRIBUTION); - // get document string if exist - var documentationCommentTrivia = method.GetDocumentationCommentTriviaSyntax(); - - var functionName = method.Identifier.ToString(); - var functionDescription = functionCallAttribute?.ArgumentList?.Arguments.FirstOrDefault(argument => argument.NameEquals?.Name.ToString() == "Description")?.Expression.ToString() ?? string.Empty; - - if (string.IsNullOrEmpty(functionDescription)) - { - // if functionDescription is empty, then try to get it from documentationCommentTrivia - // firstly, try getting from tag - var summary = documentationCommentTrivia?.Content.GetFirstXmlElement("summary"); - if (summary is not null && XElement.Parse(summary.ToString()) is XElement element) - { - functionDescription = element.Nodes().OfType().FirstOrDefault()?.Value; - - // remove [space...][//|///][space...] from functionDescription - // replace [^\S\r\n]+[\/]+\s* with empty string - functionDescription = System.Text.RegularExpressions.Regex.Replace(functionDescription, @"[^\S\r\n]+\/[\/]+\s*", string.Empty); + ctx.AddSource("FunctionDefinition.json", SourceText.From(json, System.Text.Encoding.UTF8)); } - else - { - // if tag is not exist, then simply use the entire leading trivia as functionDescription - functionDescription = method.GetLeadingTrivia().ToString(); - - // remove [space...][//|///][space...] from functionDescription - // replace [^\S\r\n]+[\/]+\s* with empty string - functionDescription = System.Text.RegularExpressions.Regex.Replace(functionDescription, @"[^\S\r\n]+\/[\/]+\s*", string.Empty); - } - } - - // get parameters - var parameters = method.ParameterList.Parameters.Select(parameter => - { - var description = $"{parameter.Identifier}. type is {parameter.Type}"; - - // try to get parameter description from documentationCommentTrivia - var parameterDocumentationComment = documentationCommentTrivia?.GetParameterDescriptionFromDocumentationCommentTriviaSyntax(parameter.Identifier.ToString()); - if (parameterDocumentationComment is not null) - { - description = parameterDocumentationComment.ToString(); - // remove [space...][//|///][space...] from functionDescription - // replace [^\S\r\n]+[\/]+\s* with empty string - description = System.Text.RegularExpressions.Regex.Replace(description, @"[^\S\r\n]+\/[\/]+\s*", string.Empty); - } - var jsonItemType = parameter.Type!.ToString().EndsWith("[]") ? parameter.Type!.ToString().Substring(0, parameter.Type!.ToString().Length - 2) : null; - return new SourceGeneratorParameterContract - { - Name = parameter.Identifier.ToString(), - JsonType = parameter.Type!.ToString() switch - { - "string" => "string", - "string[]" => "array", - "System.Int32" or "int" => "integer", - "System.Int64" or "long" => "integer", - "System.Single" or "float" => "number", - "System.Double" or "double" => "number", - "System.Boolean" or "bool" => "boolean", - "System.DateTime" => "string", - "System.Guid" => "string", - "System.Object" => "object", - _ => "object", - }, - JsonItemType = jsonItemType, - Type = parameter.Type!.ToString(), - Description = description, - IsOptional = parameter.Default != null, - // if Default is null or "null", then DefaultValue is null - DefaultValue = parameter.Default?.ToString() == "null" ? null : parameter.Default?.Value.ToString(), - }; }); + } - return new SourceGeneratorFunctionContract - { - ClassName = className, - Namespace = namespaceName, - Name = functionName, - Description = functionDescription?.Trim() ?? functionName, - Parameters = parameters.ToArray(), - ReturnType = method.ReturnType.ToString(), - }; + private class PartialClassOutput + { + public PartialClassOutput(string fullClassName, ClassDeclarationSyntax classDeclarationSyntax, IEnumerable functionContracts) + { + FullClassName = fullClassName; + ClassDeclarationSyntax = classDeclarationSyntax; + FunctionContracts = functionContracts; } + + public string FullClassName { get; } + + public ClassDeclarationSyntax ClassDeclarationSyntax { get; } + + public IEnumerable FunctionContracts { get; } + } + + private SourceGeneratorFunctionContract CreateFunctionContract(MethodDeclarationSyntax method, string? className, string? namespaceName) + { + // get function_call attribute + var functionCallAttribute = method.AttributeLists.SelectMany(attributeList => attributeList.Attributes) + .FirstOrDefault(attribute => attribute.Name.ToString() == FUNCTION_CALL_ATTRIBUTION); + // get document string if exist + var documentationCommentTrivia = method.GetDocumentationCommentTriviaSyntax(); + + var functionName = method.Identifier.ToString(); + var functionDescription = functionCallAttribute?.ArgumentList?.Arguments.FirstOrDefault(argument => argument.NameEquals?.Name.ToString() == "Description")?.Expression.ToString() ?? string.Empty; + + if (string.IsNullOrEmpty(functionDescription)) + { + // if functionDescription is empty, then try to get it from documentationCommentTrivia + // firstly, try getting from tag + var summary = documentationCommentTrivia?.Content.GetFirstXmlElement("summary"); + if (summary is not null && XElement.Parse(summary.ToString()) is XElement element) + { + functionDescription = element.Nodes().OfType().FirstOrDefault()?.Value; + + // remove [space...][//|///][space...] from functionDescription + // replace [^\S\r\n]+[\/]+\s* with empty string + functionDescription = System.Text.RegularExpressions.Regex.Replace(functionDescription, @"[^\S\r\n]+\/[\/]+\s*", string.Empty); + } + else + { + // if tag is not exist, then simply use the entire leading trivia as functionDescription + functionDescription = method.GetLeadingTrivia().ToString(); + + // remove [space...][//|///][space...] from functionDescription + // replace [^\S\r\n]+[\/]+\s* with empty string + functionDescription = System.Text.RegularExpressions.Regex.Replace(functionDescription, @"[^\S\r\n]+\/[\/]+\s*", string.Empty); + } + } + + // get parameters + var parameters = method.ParameterList.Parameters.Select(parameter => + { + var description = $"{parameter.Identifier}. type is {parameter.Type}"; + + // try to get parameter description from documentationCommentTrivia + var parameterDocumentationComment = documentationCommentTrivia?.GetParameterDescriptionFromDocumentationCommentTriviaSyntax(parameter.Identifier.ToString()); + if (parameterDocumentationComment is not null) + { + description = parameterDocumentationComment.ToString(); + // remove [space...][//|///][space...] from functionDescription + // replace [^\S\r\n]+[\/]+\s* with empty string + description = System.Text.RegularExpressions.Regex.Replace(description, @"[^\S\r\n]+\/[\/]+\s*", string.Empty); + } + var jsonItemType = parameter.Type!.ToString().EndsWith("[]") ? parameter.Type!.ToString().Substring(0, parameter.Type!.ToString().Length - 2) : null; + return new SourceGeneratorParameterContract + { + Name = parameter.Identifier.ToString(), + JsonType = parameter.Type!.ToString() switch + { + "string" => "string", + "string[]" => "array", + "System.Int32" or "int" => "integer", + "System.Int64" or "long" => "integer", + "System.Single" or "float" => "number", + "System.Double" or "double" => "number", + "System.Boolean" or "bool" => "boolean", + "System.DateTime" => "string", + "System.Guid" => "string", + "System.Object" => "object", + _ => "object", + }, + JsonItemType = jsonItemType, + Type = parameter.Type!.ToString(), + Description = description, + IsOptional = parameter.Default != null, + // if Default is null or "null", then DefaultValue is null + DefaultValue = parameter.Default?.ToString() == "null" ? null : parameter.Default?.Value.ToString(), + }; + }); + + return new SourceGeneratorFunctionContract + { + ClassName = className, + Namespace = namespaceName, + Name = functionName, + Description = functionDescription?.Trim() ?? functionName, + Parameters = parameters.ToArray(), + ReturnType = method.ReturnType.ToString(), + }; } } diff --git a/dotnet/src/AutoGen.SourceGenerator/SourceGeneratorFunctionContract.cs b/dotnet/src/AutoGen.SourceGenerator/SourceGeneratorFunctionContract.cs index 45650fb01..df954da45 100644 --- a/dotnet/src/AutoGen.SourceGenerator/SourceGeneratorFunctionContract.cs +++ b/dotnet/src/AutoGen.SourceGenerator/SourceGeneratorFunctionContract.cs @@ -1,40 +1,39 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SourceGeneratorFunctionContract.cs -namespace AutoGen.SourceGenerator +namespace AutoGen.SourceGenerator; + +internal class SourceGeneratorFunctionContract { - internal class SourceGeneratorFunctionContract - { - public string? Namespace { get; set; } + public string? Namespace { get; set; } - public string? ClassName { get; set; } + public string? ClassName { get; set; } - public string? Name { get; set; } + public string? Name { get; set; } - public string? Description { get; set; } + public string? Description { get; set; } - public string? ReturnDescription { get; set; } + public string? ReturnDescription { get; set; } - public SourceGeneratorParameterContract[]? Parameters { get; set; } + public SourceGeneratorParameterContract[]? Parameters { get; set; } + + public string? ReturnType { get; set; } +} + +internal class SourceGeneratorParameterContract +{ + public string? Name { get; set; } + + public string? Description { get; set; } + + public string? JsonType { get; set; } + + public string? JsonItemType { get; set; } + + public string? Type { get; set; } + + public bool IsOptional { get; set; } + + public string? DefaultValue { get; set; } - public string? ReturnType { get; set; } - } - - internal class SourceGeneratorParameterContract - { - public string? Name { get; set; } - - public string? Description { get; set; } - - public string? JsonType { get; set; } - - public string? JsonItemType { get; set; } - - public string? Type { get; set; } - - public bool IsOptional { get; set; } - - public string? DefaultValue { get; set; } - - } }