










- Roslyn支持从ISymbol获取XML注释文档
- Roslyn支持从SyntaxTree节点获取XML注释文档
- Roslyn支持对SyntaxTree节点添加XML注释文档
- XML注释文档是源代码很重要的一部分
- 可用于代码提示
- 用于提高代码可读性
- 用于生成API文档
- 如果源生成器不支持XML注释文档,大多只能停留在demo层面用于装十三
SyntaxTrivia comment = SyntaxFactory.Comment("// singleLine Comment");
Assert.True(comment.IsKind(SyntaxKind.SingleLineCommentTrivia));
SyntaxTrivia comment = SyntaxFactory.Comment("/* MultiLine\r\n Comment */");
Assert.True(comment.IsKind(SyntaxKind.MultiLineCommentTrivia));
- XmlElementSyntax是构成 XML文档注释的核心部分之一
- 由开始标签(StartTag)、子元素(Content)和结束标签(EndTag)组成
- 是个标准的XML结构
class XmlElementSyntax : XmlNodeSyntax
{
XmlElementStartTagSyntax StartTag { get; }
SyntaxList<XmlNodeSyntax> Content { get; }
XmlElementEndTagSyntax EndTag { get; }
}
- 通过 SyntaxFactory.XmlElement方法构造 XmlElementSyntax
SyntaxList<XmlNodeSyntax> content = [SyntaxFactory.XmlText("用户名")];
XmlElementSyntax summary = SyntaxFactory.XmlElement("summary", content);
Assert.Equal("<summary>", summary.StartTag.ToFullString());
Assert.Equal("</summary>", summary.EndTag.ToFullString());
Assert.Equal("<summary>用户名</summary>", summary.ToFullString());
- XmlNameAttributeSyntax是表示 XML文档中的属性主要的类之一
- XmlAttributeSyntax是属性的抽象基类
- 由属性名(Name)、等号(EqualsToken)、引号(StartQuoteToken和EndQuoteToken)和属性值(Identifier)组成
class XmlNameAttributeSyntax : XmlAttributeSyntax
{
XmlNameSyntax Name { get; }
SyntaxToken EqualsToken { get; }
SyntaxToken StartQuoteToken { get; }
IdentifierNameSyntax Identifier { get; }
SyntaxToken EndQuoteToken { get; }
}
- 使用SyntaxFactory.XmlNameAttribute方法构造XmlNameAttributeSyntax
XmlNameAttributeSyntax attribute = SyntaxFactory.XmlNameAttribute("id");
Assert.Equal(" name=\"id\"", attribute.ToFullString());
- XmlNameAttributeSyntax一般都是结合XmlElementSyntax来使用的
- XmlElementSyntax通过StartTag的AddAttributes方法添加属性
SyntaxList<XmlNodeSyntax> content = [SyntaxFactory.XmlText("用户")];
XmlElementSyntax element = SyntaxFactory.XmlElement("param", content);
XmlNameAttributeSyntax attribute = SyntaxFactory.XmlNameAttribute("userId");
element = element.WithStartTag(element.StartTag.AddAttributes(attribute));
Assert.Equal("<param name=\"userId\">", element.StartTag.ToFullString());
Assert.Equal("</param>", element.EndTag.ToFullString());
Assert.Equal("<param name=\"userId\">用户</param>", element.ToFullString());
- XML注释文档是单行注释
- DocumentationCommentTriviaSyntax是用于表示XML注释文档的类
- DocumentationCommentTriviaSyntax主要是包含子元素(Content)
class DocumentationCommentTriviaSyntax : StructuredTriviaSyntax
{
SyntaxList<XmlNodeSyntax> Content { get; }
}
- 调用SyntaxFactory.XmlSummaryElement方法生成XmlElementSyntax
- SyntaxFactory.XmlSummaryElement方法是对上面SyntaxFactory.XmlElement方法的封装
- 调用SyntaxGenerator.CreateDocumentation生成
SyntaxList<XmlNodeSyntax> context = [SyntaxFactory.XmlText("用户名")];
XmlElementSyntax summary = SyntaxFactory.XmlSummaryElement(context);
Assert.Equal("<summary>", summary.StartTag.ToFullString());
Assert.Equal("</summary>", summary.EndTag.ToFullString());
Assert.Equal("<summary>用户名</summary>", summary.ToFullString());
DocumentationCommentTriviaSyntax documentation = SyntaxGenerator.CreateDocumentation(summary);
Assert.True(documentation.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia));
Assert.Equal("/// <summary>用户名</summary>\r\n", documentation.ToFullString());
- 换行XML注释文档如下
- 这样的XML注释文档也是可以实现的
/// <summary>
/// 用户名
/// </summary>
- context对比4.2前后多了XmlNewLine(true)
SyntaxList<XmlNodeSyntax> context = [XmlNewLine(true), SyntaxFactory.XmlText("用户名"), XmlNewLine(true)];
XmlElementSyntax summary = SyntaxFactory.XmlSummaryElement(context);
Assert.Equal("<summary>", summary.StartTag.ToFullString());
Assert.Equal("</summary>", summary.EndTag.ToFullString());
Assert.Equal("<summary>\r\n/// 用户名\r\n/// </summary>", summary.ToFullString());
DocumentationCommentTriviaSyntax documentation = SyntaxGenerator.CreateDocumentation(summary);
Assert.True(documentation.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia));
Assert.Equal("/// <summary>\r\n/// 用户名\r\n/// </summary>\r\n", documentation.ToFullString());
static XmlTextSyntax XmlNewLine(bool continueComment)
=> SyntaxFactory.XmlText(SyntaxFactory.XmlTextNewLine("\r\n", continueComment));
- EntityKey类的XML注释文档含summary、typeparam和param
- 其中typeparam和param还都含name属性
/// <summary>
/// 实体主键
/// </summary>
/// <typeparam name="TKey">主键类型</typeparam>
/// <param name="key">主键</param>
public class EntityKey<TKey>(TKey key)
{
/// <summary>
/// 主键
/// </summary>
public TKey Key { get; } = key;
}
- 构造summary、typeparam和paramd等3个XmlElementSyntax
- 使用换行符line分割3个XmlElementSyntax及XmlNewLine(false)作为documentation的子元素
- 调用SyntaxFactory.DocumentationComment构造XML注释文档
var line = XmlNewLine(true);
XmlElementSyntax summary = XmlSummary("实体主键", separator);
XmlElementSyntax typeparam = NamedXmlElement("typeparam", "TKey", "主键类型");
XmlElementSyntax param = NamedXmlElement("param", "key", "主键");
XmlNodeSyntax[] content = [summary, separator, typeparam, separator, param, XmlNewLine(false)];
DocumentationCommentTriviaSyntax documentation = SyntaxFactory.DocumentationComment(content);
var code = documentation.ToFullString();
Assert.Contains("<summary>", code);
static XmlElementSyntax NamedXmlElement(string elementName, string name, string text)
{
SyntaxList<XmlNodeSyntax> content = [SyntaxFactory.XmlText(text)];
XmlElementSyntax element = SyntaxFactory.XmlElement(elementName, content);
XmlNameAttributeSyntax attribute = SyntaxFactory.XmlNameAttribute(name);
return element.WithStartTag(element.StartTag.AddAttributes(attribute));
}
static XmlElementSyntax XmlSummary(string summary, XmlTextSyntax line)
{
SyntaxList<XmlNodeSyntax> summaryContext = [line, SyntaxFactory.XmlText(summary), line];
return SyntaxFactory.XmlSummaryElement(summaryContext);
}
static XmlTextSyntax XmlNewLine(bool continueComment)
=> SyntaxFactory.XmlText(SyntaxFactory.XmlTextNewLine("\r\n", continueComment));
- 生成的XML注释文档与源代码中的还原度为100%
/// <summary>
/// 实体主键
/// </summary>
/// <typeparam name="TKey">主键类型</typeparam>
/// <param name="key">主键</param>
- 先构造Comment对象
- Comment支持Summary、多个TypeParams、多个Params及Returns
- 调用SyntaxGenerator.CreateDocumentation生成XML注释文档
- 该示例生成结果与5.2完全一致
var comment = new Comment()
{
Summary = "实体主键",
TypeParams = { { "TKey", "主键类型" } },
Params = { { "key", "主键" } }
};
DocumentationCommentTriviaSyntax? documentation = SyntaxGenerator.CreateDocumentation(comment);
Assert.NotNull(documentation);
var code = documentation.ToFullString();
Assert.Contains("<summary>", code);
- 单行注释生成XML注释文档是可行的
- 但是不推荐
- SyntaxFactory.Comment生成单行注释
- 这种方式生成复杂文档大概率、需要大量字符串拼接
SyntaxTrivia comment = SyntaxFactory.Comment("/// <summary>This is comment</summary>");
var method = SyntaxGenerator.VoidType.Method("Test")
.WithBody(SyntaxFactory.Block())
.WithLeadingTrivia(comment);
var code = method.ToFullString();
Assert.Contains("<summary>", code);
/// <summary>This is comment</summary>
void Test()
{
}
- 通过方法GetDocumentationCommentXml读取XML注释文档
var sourceCode = @"
/// <summary>
/// C
/// </summary>
/// <param name=""Id"">Id</param>
/// <param name=""Name"">Name</param>
record C(int Id, string Name);";
var compilation = SyntaxTreeDriver.CreateDefaultDriver()
.Compile(sourceCode);
INamedTypeSymbol? classSymbol = compilation.GetTypeByMetadataName("C");
Assert.NotNull(classSymbol);
string? xml = classSymbol.GetDocumentationCommentXml();
Assert.NotNull(xml);
- 结果是一个完整的XML节点member
<member name="T:C">
<summary>
C
</summary>
<param name="Id">Id</param>
<param name="Name">Name</param>
</member>
- CommentParser.GetSummary解析Summary
- CommentParser.Comment解析Summary及参数
var sourceCode = @"
/// <summary>
/// C
/// </summary>
/// <param name=""Id"">Id</param>
/// <param name=""Name"">Name</param>
record C(int Id, string Name);";
var compilation = SyntaxTreeDriver.CreateDefaultDriver()
.Compile(sourceCode);
INamedTypeSymbol? classSymbol = compilation.GetTypeByMetadataName("C");
Assert.NotNull(classSymbol);
string? xml = classSymbol.GetDocumentationCommentXml();
Assert.NotNull(xml);
string summary = CommentParser.GetSummary(xml);
Assert.Equal("C", summary);
Comment comment = CommentParser.Instance.Get(xml);
Assert.Equal("C", comment.Summary.Trim());
Assert.Equal(2, comment.Params.Count);
Comment comment2 = CommentParser.Comment(classSymbol);
Assert.Equal("C", comment2.Summary.Trim());
Assert.Equal(2, comment2.Params.Count);
- 使用 SyntaxGenerator.Clone复制类型信息
- 使用 SymbolReflection.GetPublicPropertiesWithBase遍历属性
- CommentParser.GetSummary解析XML注释文档的Summary
- WithSummary扩展方法添加XML注释文档的Summary
var sourceCode = @"
namespace ExampleNamespace;
/// <summary>
/// 用户
/// </summary>
/// <param name=""UserName"">用户名</param>
public record User(string UserName);
public partial class UserDTO;
";
var compilation = SyntaxTreeDriver.DefaultDriver.Compile(sourceCode);
var syntaxTree = compilation.SyntaxTrees.FirstOrDefault();
Assert.NotNull(syntaxTree);
var classDeclaration = syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().LastOrDefault();
Assert.NotNull(classDeclaration);
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var symbol = semanticModel.GetDeclaredSymbol(classDeclaration);
Assert.NotNull(symbol);
var sourceSymbol = compilation.GetTypeByMetadataName("ExampleNamespace.User");
Assert.NotNull(sourceSymbol);
var generator = SyntaxGenerator.Clone(classDeclaration);
foreach (var propertySymbol in SymbolReflection.GetPublicPropertiesWithBase(sourceSymbol))
{
string propertySummary = CommentParser.GetSummary(propertySymbol);
var propertyType = propertySymbol.Type.ToSyntax();
var property = propertyType.GetSetProperty(propertySymbol.Name)
.Public()
.WithSummary(propertySummary);
generator.AddProperty(property);
}
string sourceSummary = CommentParser.GetSummary(sourceSymbol);
generator.Apply(type => type.WithSummary(sourceSummary));
var code = generator.Build().ToFullString();
Assert.Contains("<summary>", code);
namespace ExampleNamespace;
///<summary>
///用户
///</summary>
partial class UserDTO
{
///<summary>
///用户名
///</summary>
public string UserName { get; set; }
}
- 以上代码执行依赖开源项目EasySyntax、Hand.GenerateCore和Hand.Generators.SyntaxScripting,nuget包如下
- Hand.Generators.EasySyntax --version 0.2.1.5
- Hand.GenerateCore --version 0.2.1.7
- Hand.Generators.SyntaxScripting --version 0.2.1.5-alpha
- 源码托管地址: https://github.com/donetsoftwork/Hand.Generators ,欢迎大家直接查看源码。
- gitee同步更新:https://gitee.com/donetsoftwork/hand.-generators
如果大家喜欢请动动您发财的小手手帮忙点一下Star,谢谢!!!
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。