Skip to content

Commit

Permalink
提供代码修补建议(CodeFixProvider) #1
Browse files Browse the repository at this point in the history
  • Loading branch information
vipwan committed Nov 3, 2023
1 parent 5260614 commit 79fcc3e
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 5 deletions.
12 changes: 8 additions & 4 deletions Biwen.AutoClassGen.Gen/SourceGenAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@ public class SourceGenAnalyzer : DiagnosticAnalyzer

const string helplink = "https://github.com/vipwan/Biwen.AutoClassGen#gen-error-code";

public const string GEN001 = "GEN001";
public const string GEN011 = "GEN011";
public const string GEN021 = "GEN021";


/// <summary>
/// 无法生成类的错误
/// </summary>
#pragma warning disable RS2008 // 启用分析器发布跟踪
private static readonly DiagnosticDescriptor InvalidDeclareError = new(id: "GEN001",
private static readonly DiagnosticDescriptor InvalidDeclareError = new(id: GEN001,
#pragma warning restore RS2008 // 启用分析器发布跟踪
title: "标注接口没有继承基础接口因此不能生成类",
#pragma warning disable RS1032 // 正确定义诊断消息
Expand All @@ -36,7 +41,7 @@ public class SourceGenAnalyzer : DiagnosticAnalyzer
/// 重名错误
/// </summary>
#pragma warning disable RS2008 // 启用分析器发布跟踪
private static readonly DiagnosticDescriptor InvalidDeclareNameError = new(id: "GEN011",
private static readonly DiagnosticDescriptor InvalidDeclareNameError = new(id: GEN011,
#pragma warning restore RS2008 // 启用分析器发布跟踪
title: "生成类的类名称不可和接口名重名",
#pragma warning disable RS1032 // 正确定义诊断消息
Expand All @@ -47,12 +52,11 @@ public class SourceGenAnalyzer : DiagnosticAnalyzer
helpLinkUri: helplink,
isEnabledByDefault: true);


/// <summary>
/// 命名空间规范警告
/// </summary>
#pragma warning disable RS2008 // 启用分析器发布跟踪
private static readonly DiagnosticDescriptor SuggestDeclareNameWarning = new(id: "GEN021",
private static readonly DiagnosticDescriptor SuggestDeclareNameWarning = new(id: GEN021,
#pragma warning restore RS2008 // 启用分析器发布跟踪
title: "推荐使用相同的命名空间",
#pragma warning disable RS1032 // 正确定义诊断消息
Expand Down
117 changes: 117 additions & 0 deletions Biwen.AutoClassGen.Gen/SourceGenCodeFixProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Biwen.AutoClassGen
{

/// <summary>
/// 代码修补提供者
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SourceGenCodeFixProvider)), Shared]
public sealed class SourceGenCodeFixProvider : CodeFixProvider
{
public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
SourceGenAnalyzer.GEN001,
SourceGenAnalyzer.GEN011,
SourceGenAnalyzer.GEN021
);

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
if (root == null) return;

foreach (var diagnostic in context.Diagnostics)
{
// Only allow code fixes for diagnostics with the matching id.
if (diagnostic.Id == SourceGenAnalyzer.GEN021)
{
var diagnosticSpan = context.Span;

// getInnerModeNodeForTie = true so we are replacing the string literal node and not the whole argument node
var nodeToReplace = root.FindNode(diagnosticSpan, getInnermostNodeForTie: true);
if (nodeToReplace == null)
{
return;
}

//AncestorsAndSelf 祖先和自己
//var @namespace = root.Parent?.AncestorsAndSelf().OfType<NamespaceDeclarationSyntax>().First().Name.ToString();

var rootCompUnit = (CompilationUnitSyntax)root;
var @namespace = ((rootCompUnit.Members.Where(m => m.IsKind(SyntaxKind.NamespaceDeclaration)).Single()) as NamespaceDeclarationSyntax)?.Name.ToString();

// Register a code action that will invoke the fix.
CodeAction action = CodeAction.Create(
"GEN:使用推荐的命名空间",
c => ReplaceWithNameOfAsync(context.Document, nodeToReplace, @namespace!, c),
equivalenceKey: nameof(SourceGenCodeFixProvider));
context.RegisterCodeFix(action, diagnostic);
}
else if (diagnostic.Id == SourceGenAnalyzer.GEN011)
{
var diagnosticSpan = context.Span;
// getInnerModeNodeForTie = true so we are replacing the string literal node and not the whole argument node
var nodeToReplace = root.FindNode(diagnosticSpan, getInnermostNodeForTie: true);
if (nodeToReplace == null)
{
return;
}
//移除第一个字母I eg."IRequest" -> "Request"
var raw = nodeToReplace.GetText().ToString().Substring(2, nodeToReplace.GetText().ToString().Length - 3);
CodeAction action = CodeAction.Create(
"GEN:使用推荐的类名称",
c => ReplaceWithNameOfAsync(context.Document, nodeToReplace, raw, c),
equivalenceKey: nameof(SourceGenCodeFixProvider));
context.RegisterCodeFix(action, diagnostic);
}
else if (diagnostic.Id == SourceGenAnalyzer.GEN001)
{
CodeAction action = CodeAction.Create(
"GEN:删除无意义的特性[AutoGen]", async c =>
{
var root = await context.Document.GetSyntaxRootAsync(c).ConfigureAwait(false);
var nowRoot = root?.RemoveNode(root?.FindNode(context.Span)!, SyntaxRemoveOptions.KeepExteriorTrivia);
if (nowRoot == null) return context.Document.WithSyntaxRoot(root!);
return context.Document.WithSyntaxRoot(nowRoot);
},
equivalenceKey: nameof(SourceGenCodeFixProvider));
context.RegisterCodeFix(action, diagnostic);
}
}
}
private static async Task<Document> ReplaceWithNameOfAsync(Document document, SyntaxNode nodeToReplace,
string stringText, CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
var generator = editor.Generator;

var trailingTrivia = nodeToReplace.GetTrailingTrivia();
var leadingTrivia = nodeToReplace.GetLeadingTrivia();

var textExpression = generator.LiteralExpression(stringText)
.WithTrailingTrivia(trailingTrivia)
.WithLeadingTrivia(leadingTrivia);

//var nameOfExpression = generator.NameOfExpression(generator.IdentifierName(stringText))
// .WithTrailingTrivia(trailingTrivia)
// .WithLeadingTrivia(leadingTrivia);

var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var newRoot = root?.ReplaceNode(nodeToReplace, textExpression);

return document.WithSyntaxRoot(newRoot!);
}
}
}
2 changes: 1 addition & 1 deletion Biwen.AutoClassGen.TestConsole/Classes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public interface IQueryRequest : IPager, IQuery
/// <summary>
/// 多租户请求
/// </summary>
[AutoGen("TenantRealRequest", "Biwen.AutoClassGen.Models1")]
[AutoGen("TenantRealRequest", "Biwen.AutoClassGen.Models")]
public interface ITenantRealRequest : ITenantRequest
{

Expand Down

0 comments on commit 79fcc3e

Please sign in to comment.