|
9 | 9 | using System.Threading; |
10 | 10 | using System.Threading.Tasks; |
11 | 11 | using ILLink.CodeFixProvider; |
12 | | -using ILLink.RoslynAnalyzer; |
13 | | -using ILLink.Shared; |
14 | 12 | using Microsoft.CodeAnalysis; |
15 | 13 | using Microsoft.CodeAnalysis.CodeActions; |
16 | 14 | using Microsoft.CodeAnalysis.CodeFixes; |
|
21 | 19 | namespace ILLink.CodeFix |
22 | 20 | { |
23 | 21 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(RequiresUnsafeCodeFixProvider)), Shared] |
24 | | - public sealed class RequiresUnsafeCodeFixProvider : BaseAttributeCodeFixProvider |
| 22 | + public sealed class RequiresUnsafeCodeFixProvider : Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider |
25 | 23 | { |
26 | 24 | private const string WrapInUnsafeBlockTitle = "Wrap in unsafe block"; |
27 | 25 |
|
28 | | - public static ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresUnsafe)); |
| 26 | + public const string UnsafeMemberOperationDiagnosticId = "CS9362"; |
29 | 27 |
|
30 | | - public sealed override ImmutableArray<string> FixableDiagnosticIds => SupportedDiagnostics.Select(dd => dd.Id).ToImmutableArray(); |
| 28 | + public sealed override ImmutableArray<string> FixableDiagnosticIds => [UnsafeMemberOperationDiagnosticId]; |
31 | 29 |
|
32 | | - private protected override LocalizableString CodeFixTitle => new LocalizableResourceString(nameof(Resources.RequiresUnsafeCodeFixTitle), Resources.ResourceManager, typeof(Resources)); |
33 | | - |
34 | | - private protected override string FullyQualifiedAttributeName => RequiresUnsafeAnalyzer.FullyQualifiedRequiresUnsafeAttribute; |
35 | | - |
36 | | - private protected override AttributeableParentTargets AttributableParentTargets => AttributeableParentTargets.MethodOrConstructor; |
| 30 | + private static LocalizableString CodeFixTitle => new LocalizableResourceString(nameof(Resources.RequiresUnsafeCodeFixTitle), Resources.ResourceManager, typeof(Resources)); |
37 | 31 |
|
38 | 32 | public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) |
39 | 33 | { |
40 | | - // Register the base code fix (add RequiresUnsafe attribute) |
41 | | - await BaseRegisterCodeFixesAsync(context).ConfigureAwait(false); |
42 | | - |
43 | | - // Register the "wrap in unsafe block" code fix |
44 | 34 | var document = context.Document; |
45 | 35 | var diagnostic = context.Diagnostics.First(); |
| 36 | + var codeFixTitle = CodeFixTitle.ToString(); |
46 | 37 |
|
47 | 38 | if (await document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false) is not { } root) |
48 | 39 | return; |
49 | 40 |
|
50 | 41 | SyntaxNode targetNode = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); |
51 | 42 |
|
| 43 | + // Register "add unsafe modifier" code fix |
| 44 | + var unsafeModifierTarget = GetUnsafeModifierTarget(targetNode); |
| 45 | + if (unsafeModifierTarget is not null && !HasUnsafeModifier(unsafeModifierTarget)) |
| 46 | + { |
| 47 | + context.RegisterCodeFix(CodeAction.Create( |
| 48 | + title: codeFixTitle, |
| 49 | + createChangedDocument: ct => AddUnsafeModifierAsync(document, unsafeModifierTarget, ct), |
| 50 | + equivalenceKey: codeFixTitle), diagnostic); |
| 51 | + } |
| 52 | + |
| 53 | + // Register the "wrap in unsafe block" code fix |
| 54 | + |
52 | 55 | // Find the statement containing the unsafe call |
53 | 56 | var containingStatement = targetNode.AncestorsAndSelf().OfType<StatementSyntax>().FirstOrDefault(); |
54 | 57 |
|
@@ -127,6 +130,39 @@ private static bool IsEmbeddedStatement(StatementSyntax statement) |
127 | 130 | || statement.Parent is FixedStatementSyntax; |
128 | 131 | } |
129 | 132 |
|
| 133 | + private static async Task<Document> AddUnsafeModifierAsync( |
| 134 | + Document document, |
| 135 | + SyntaxNode declaration, |
| 136 | + CancellationToken cancellationToken) |
| 137 | + { |
| 138 | + var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); |
| 139 | + var modifiers = editor.Generator.GetModifiers(declaration); |
| 140 | + editor.SetModifiers(declaration, modifiers.WithIsUnsafe(true)); |
| 141 | + return editor.GetChangedDocument(); |
| 142 | + } |
| 143 | + |
| 144 | + private static SyntaxNode? GetUnsafeModifierTarget(SyntaxNode targetNode) |
| 145 | + => targetNode.AncestorsAndSelf().FirstOrDefault(static node => node is MethodDeclarationSyntax |
| 146 | + or ConstructorDeclarationSyntax |
| 147 | + or DestructorDeclarationSyntax |
| 148 | + or LocalFunctionStatementSyntax |
| 149 | + or PropertyDeclarationSyntax |
| 150 | + or IndexerDeclarationSyntax |
| 151 | + or AccessorDeclarationSyntax); |
| 152 | + |
| 153 | + private static bool HasUnsafeModifier(SyntaxNode declaration) |
| 154 | + => declaration switch |
| 155 | + { |
| 156 | + MethodDeclarationSyntax method => method.Modifiers.Any(SyntaxKind.UnsafeKeyword), |
| 157 | + ConstructorDeclarationSyntax constructor => constructor.Modifiers.Any(SyntaxKind.UnsafeKeyword), |
| 158 | + DestructorDeclarationSyntax destructor => destructor.Modifiers.Any(SyntaxKind.UnsafeKeyword), |
| 159 | + LocalFunctionStatementSyntax localFunction => localFunction.Modifiers.Any(SyntaxKind.UnsafeKeyword), |
| 160 | + PropertyDeclarationSyntax property => property.Modifiers.Any(SyntaxKind.UnsafeKeyword), |
| 161 | + IndexerDeclarationSyntax indexer => indexer.Modifiers.Any(SyntaxKind.UnsafeKeyword), |
| 162 | + AccessorDeclarationSyntax accessor => accessor.Modifiers.Any(SyntaxKind.UnsafeKeyword), |
| 163 | + _ => false |
| 164 | + }; |
| 165 | + |
130 | 166 | private static async Task<Document> WrapStatementsInUnsafeBlockAsync( |
131 | 167 | Document document, |
132 | 168 | BlockSyntax parentBlock, |
@@ -480,9 +516,6 @@ private static async Task<Document> ConvertExpressionBodyToUnsafeBlockAsync( |
480 | 516 | return editor.GetChangedDocument(); |
481 | 517 | } |
482 | 518 |
|
483 | | - protected override SyntaxNode[] GetAttributeArguments(ISymbol? attributableSymbol, ISymbol targetSymbol, SyntaxGenerator syntaxGenerator, Diagnostic diagnostic) => |
484 | | - RequiresHelpers.GetAttributeArgumentsForRequires(targetSymbol, syntaxGenerator, HasPublicAccessibility(attributableSymbol)); |
485 | | - |
486 | 519 | /// <summary> |
487 | 520 | /// Checks if the arrow expression clause or its expression has preprocessor directive trivia. |
488 | 521 | /// Converting expression bodies with directives to block bodies is error-prone, so we skip the fix. |
|
0 commit comments