[.Net] Remove merging primitive from .editorconfig and gitignore (#3676)

* remove merging primitive from .editorconfig and gitignore

* use file scope namespace

* remove file-scope name

* fix format
This commit is contained in:
Xiaoyun Zhang 2024-10-07 10:20:33 -07:00 committed by GitHub
parent 54eaa2bb4e
commit e2c2f98eff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 513 additions and 529 deletions

View File

@ -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

6
dotnet/.gitignore vendored
View File

@ -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

View File

@ -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; }
/// <summary>
/// Gets or Sets Message
/// </summary>
[JsonPropertyName("message")]
public string Message { get; set; }
/// <summary>
/// Gets or Sets Param
/// </summary>
[JsonPropertyName("param")]
public string? Param { get; set; }
/// <summary>
/// Gets or Sets Code
/// </summary>
[JsonPropertyName("code")]
public string? Code { get; set; }
Type = type;
Message = message;
Param = param;
Code = code;
}
[JsonPropertyName("type")]
public string Type { get; set; }
/// <summary>
/// Gets or Sets Message
/// </summary>
[JsonPropertyName("message")]
public string Message { get; set; }
/// <summary>
/// Gets or Sets Param
/// </summary>
[JsonPropertyName("param")]
public string? Param { get; set; }
/// <summary>
/// Gets or Sets Code
/// </summary>
[JsonPropertyName("code")]
public string? Code { get; set; }
}

View File

@ -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<XText>().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<XText>().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<XmlNodeSyntax> content, string elementName)
{
return content.GetXmlElements(elementName).FirstOrDefault();
}
public static IEnumerable<XmlNodeSyntax> GetXmlElements(this SyntaxList<XmlNodeSyntax> 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<T>(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<XmlNodeSyntax> content, string elementName)
{
return content.GetXmlElements(elementName).FirstOrDefault();
}
public static IEnumerable<XmlNodeSyntax> GetXmlElements(this SyntaxList<XmlNodeSyntax> 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<XmlNodeSyntax> WithoutFirstAndLastNewlines(this SyntaxList<XmlNodeSyntax> summaryContent)
public static T ReplaceExteriorTrivia<T>(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<XmlNodeSyntax> WithoutFirstAndLastNewlines(this SyntaxList<XmlNodeSyntax> 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 <para> 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;
}
/// <summary>
/// Adjust the leading and trailing trivia associated with <see cref="SyntaxKind.XmlTextLiteralNewLineToken"/>
/// tokens to ensure the formatter properly indents the exterior trivia.
/// </summary>
/// <typeparam name="T">The type of syntax node.</typeparam>
/// <param name="node">The syntax node to adjust tokens.</param>
/// <returns>A <see cref="SyntaxNode"/> equivalent to the input <paramref name="node"/>, adjusted by moving any
/// trailing trivia from <see cref="SyntaxKind.XmlTextLiteralNewLineToken"/> tokens to be leading trivia of the
/// following token.</returns>
public static T AdjustDocumentationCommentNewLineTrivia<T>(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<SyntaxToken, SyntaxToken>(token, next);
SyntaxTokenList firstSyntaxTokens = firstSyntax.TextTokens;
Dictionary<SyntaxToken, SyntaxToken> replacements = new Dictionary<SyntaxToken, SyntaxToken>();
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 <para> 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);
}
/// <summary>
/// Adjust the leading and trailing trivia associated with <see cref="SyntaxKind.XmlTextLiteralNewLineToken"/>
/// tokens to ensure the formatter properly indents the exterior trivia.
/// </summary>
/// <typeparam name="T">The type of syntax node.</typeparam>
/// <param name="node">The syntax node to adjust tokens.</param>
/// <returns>A <see cref="SyntaxNode"/> equivalent to the input <paramref name="node"/>, adjusted by moving any
/// trailing trivia from <see cref="SyntaxKind.XmlTextLiteralNewLineToken"/> tokens to be leading trivia of the
/// following token.</returns>
public static T AdjustDocumentationCommentNewLineTrivia<T>(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<SyntaxToken, SyntaxToken>(token, next);
Dictionary<SyntaxToken, SyntaxToken> replacements = new Dictionary<SyntaxToken, SyntaxToken>();
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;
}
}

View File

@ -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<PartialClassOutput?>(
(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<PartialClassOutput?>(
(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 = $@"/* <auto-generated> 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 = $@"/* <auto-generated> wrap json inside csharp block, as SG doesn't support generating non-source file
{json}
</auto-generated>*/";
ctx.AddSource("FunctionDefinition.json", SourceText.From(json, System.Text.Encoding.UTF8));
}
});
}
private class PartialClassOutput
{
public PartialClassOutput(string fullClassName, ClassDeclarationSyntax classDeclarationSyntax, IEnumerable<SourceGeneratorFunctionContract> functionContracts)
{
FullClassName = fullClassName;
ClassDeclarationSyntax = classDeclarationSyntax;
FunctionContracts = functionContracts;
}
public string FullClassName { get; }
public ClassDeclarationSyntax ClassDeclarationSyntax { get; }
public IEnumerable<SourceGeneratorFunctionContract> 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 <summary> tag
var summary = documentationCommentTrivia?.Content.GetFirstXmlElement("summary");
if (summary is not null && XElement.Parse(summary.ToString()) is XElement element)
{
functionDescription = element.Nodes().OfType<XText>().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 <summary> 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<SourceGeneratorFunctionContract> functionContracts)
{
FullClassName = fullClassName;
ClassDeclarationSyntax = classDeclarationSyntax;
FunctionContracts = functionContracts;
}
public string FullClassName { get; }
public ClassDeclarationSyntax ClassDeclarationSyntax { get; }
public IEnumerable<SourceGeneratorFunctionContract> 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 <summary> tag
var summary = documentationCommentTrivia?.Content.GetFirstXmlElement("summary");
if (summary is not null && XElement.Parse(summary.ToString()) is XElement element)
{
functionDescription = element.Nodes().OfType<XText>().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 <summary> 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(),
};
}
}

View File

@ -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; }
}
}