From c0972b23b79dafe3d635af1058da76a9f045f147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Sat, 24 Feb 2024 18:39:32 -0500 Subject: [PATCH] Add code fixer for MA0154 --- .../Rules/UseLangwordInXmlCommentFixer.cs | 54 +++++++++++++++++++ .../Rules/UseLangwordInXmlCommentAnalyzer.cs | 3 +- .../UseLangwordInXmlCommentAnalyzerTests.cs | 24 +++++++-- 3 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 src/Meziantou.Analyzer.CodeFixers/Rules/UseLangwordInXmlCommentFixer.cs diff --git a/src/Meziantou.Analyzer.CodeFixers/Rules/UseLangwordInXmlCommentFixer.cs b/src/Meziantou.Analyzer.CodeFixers/Rules/UseLangwordInXmlCommentFixer.cs new file mode 100644 index 000000000..496506a19 --- /dev/null +++ b/src/Meziantou.Analyzer.CodeFixers/Rules/UseLangwordInXmlCommentFixer.cs @@ -0,0 +1,54 @@ +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Editing; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace Meziantou.Analyzer.Rules; + +[ExportCodeFixProvider(LanguageNames.CSharp), Shared] +public sealed class UseLangwordInXmlCommentFixer : CodeFixProvider +{ + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(RuleIdentifiers.UseLangwordInXmlComment); + + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var nodeToFix = root?.FindNode(context.Span, getInnermostNodeForTie: true, findInsideTrivia: true); + if (nodeToFix is null) + return; + + if (!context.Diagnostics[0].Properties.TryGetValue("keyword", out var keyword) || keyword is null) + return; + + var title = $"Use "; + var codeAction = CodeAction.Create( + title, + cancellationToken => Fix(context.Document, nodeToFix, keyword, cancellationToken), + equivalenceKey: title); + + context.RegisterCodeFix(codeAction, context.Diagnostics); + } + + private static async Task Fix(Document document, SyntaxNode nodeToFix, string keyword, CancellationToken cancellationToken) + { + var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); + + var newNode = XmlNullKeywordElement() + .WithLessThanToken(Token(SyntaxKind.LessThanToken)) + .WithAttributes( + SingletonList( + XmlTextAttribute("langword", keyword))); + + editor.ReplaceNode(nodeToFix, newNode); + return editor.GetChangedDocument(); + } +} diff --git a/src/Meziantou.Analyzer/Rules/UseLangwordInXmlCommentAnalyzer.cs b/src/Meziantou.Analyzer/Rules/UseLangwordInXmlCommentAnalyzer.cs index 15147b4db..998e9b0e9 100644 --- a/src/Meziantou.Analyzer/Rules/UseLangwordInXmlCommentAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/UseLangwordInXmlCommentAnalyzer.cs @@ -159,7 +159,8 @@ private static void AnalyzeSymbol(SymbolAnalysisContext context) var item = elementSyntax.Content.SingleOrDefaultIfMultiple(); if (item is XmlTextSyntax { TextTokens: [var codeText] } && CSharpKeywords.Contains(codeText.Text)) { - context.ReportDiagnostic(Rule, elementSyntax); + var properties = ImmutableDictionary.Empty.Add("keyword", codeText.Text); + context.ReportDiagnostic(Rule, properties, elementSyntax); } } else diff --git a/tests/Meziantou.Analyzer.Test/Rules/UseLangwordInXmlCommentAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/UseLangwordInXmlCommentAnalyzerTests.cs index e5ee34e8d..cfcf9bbfd 100644 --- a/tests/Meziantou.Analyzer.Test/Rules/UseLangwordInXmlCommentAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/UseLangwordInXmlCommentAnalyzerTests.cs @@ -10,17 +10,33 @@ private static ProjectBuilder CreateProjectBuilder() { return new ProjectBuilder() .WithAnalyzer() + .WithCodeFixProvider() .WithTargetFramework(TargetFramework.NetLatest); } [Theory] - [InlineData("[|void|]")] - [InlineData("[|void|]")] - [InlineData("[|null|]")] + [InlineData("[|void|]", "")] + [InlineData("[|void|]", "")] + [InlineData("[|null|]", "")] + public async Task ValidateSummary_Invalid(string comment, string fix) + { + await CreateProjectBuilder() + .WithSourceCode($$""" +/// {{comment}} +class Sample { } +""") + .ShouldFixCodeWith($$""" +/// {{fix}} +class Sample { } +""") + .ValidateAsync(); + } + + [Theory] [InlineData("in")] [InlineData("null")] [InlineData("this is null")] - public async Task ValidateSummary(string comment) + public async Task ValidateSummary_Valid(string comment) { await CreateProjectBuilder() .WithSourceCode($$"""