diff --git a/src/Orleans.Analyzers/IdClashAttributeAnalyzer.cs b/src/Orleans.Analyzers/IdClashAttributeAnalyzer.cs index 96a3df2bb5..d7e425f785 100644 --- a/src/Orleans.Analyzers/IdClashAttributeAnalyzer.cs +++ b/src/Orleans.Analyzers/IdClashAttributeAnalyzer.cs @@ -45,7 +45,7 @@ private void CheckSyntaxNode(SyntaxNodeAnalysisContext context) return; } - List> bags = new(); + List> bags = []; foreach (var memberDeclaration in typeDeclaration.Members.OfType()) { var attributes = memberDeclaration.AttributeLists.GetAttributeSyntaxes(Constants.IdAttributeName); @@ -78,11 +78,16 @@ private void CheckSyntaxNode(SyntaxNodeAnalysisContext context) { foreach (var bag in filteredBags) { + var builder = ImmutableDictionary.CreateBuilder(); + + builder.Add("IdValue", bag.Value.ToString()); + context.ReportDiagnostic(Diagnostic.Create( descriptor: Rule, - location: bag.Location)); + location: bag.Location, + properties: builder.ToImmutable())); } } } } -} \ No newline at end of file +} diff --git a/src/Orleans.Analyzers/IdClashAttributeCodeFix.cs b/src/Orleans.Analyzers/IdClashAttributeCodeFix.cs new file mode 100644 index 0000000000..021e27b9d8 --- /dev/null +++ b/src/Orleans.Analyzers/IdClashAttributeCodeFix.cs @@ -0,0 +1,56 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace Orleans.Analyzers; + +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(IdClashAttributeCodeFix)), Shared] +public class IdClashAttributeCodeFix : CodeFixProvider +{ + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(IdClashAttributeAnalyzer.RuleId); + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var diagnostic = context.Diagnostics.First(); + if (root.FindNode(diagnostic.Location.SourceSpan) is not AttributeSyntax attribute) + { + return; + } + + var idValue = diagnostic.Properties["IdValue"]; + + context.RegisterCodeFix( + CodeAction.Create( + Resources.IdClashDetectedTitle, + createChangedDocument: _ => + { + var newIdValue = root + .DescendantNodes() + .OfType() + .Where(a => a.IsAttribute(Constants.IdAttributeName)) + .Select(a => int.Parse(a.ArgumentList.Arguments.Single().ToString())) + .ToList() + .Max() + 1; + + var newAttribute = attribute.ReplaceNode( + attribute.ArgumentList.Arguments[0].Expression, + LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(newIdValue))); + + var newRoot = root.ReplaceNode(attribute, newAttribute); + var newDocument = context.Document.WithSyntaxRoot(newRoot); + + return Task.FromResult(newDocument); + }, + equivalenceKey: IdClashAttributeAnalyzer.RuleId), + diagnostic); + } +} \ No newline at end of file diff --git a/src/Orleans.Analyzers/Resources.Designer.cs b/src/Orleans.Analyzers/Resources.Designer.cs index 4393a35353..08fdb8cb68 100644 --- a/src/Orleans.Analyzers/Resources.Designer.cs +++ b/src/Orleans.Analyzers/Resources.Designer.cs @@ -214,7 +214,7 @@ internal static string IdClashDetectedDescription { } /// - /// Looks up a localized string similar to Substitute duplicated [Id] value with the correct unique identity of this member. + /// Looks up a localized string similar to Change duplicated [Id]. /// internal static string IdClashDetectedMessageFormat { get { @@ -223,7 +223,7 @@ internal static string IdClashDetectedMessageFormat { } /// - /// Looks up a localized string similar to Substitute duplicated [Id] value with the correct unique identity of this member. + /// Looks up a localized string similar to Change duplicated [Id]. /// internal static string IdClashDetectedTitle { get { diff --git a/src/Orleans.Analyzers/Resources.resx b/src/Orleans.Analyzers/Resources.resx index 90d80ddfcc..09e12ca19e 100644 --- a/src/Orleans.Analyzers/Resources.resx +++ b/src/Orleans.Analyzers/Resources.resx @@ -169,10 +169,10 @@ The [Id] attribute must be unique to each members of the declaring type. - Substitute duplicated [Id] value with the correct unique identity of this member + Change duplicated [Id] - Substitute duplicated [Id] value with the correct unique identity of this member + Change duplicated [Id] Remove attribute [{0}]