diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/ILLink.CodeFix/DAMCodeFixProvider.cs | 134 | ||||
-rw-r--r-- | src/ILLink.CodeFix/Resources.resx | 3 | ||||
-rw-r--r-- | src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs | 10 |
3 files changed, 143 insertions, 4 deletions
diff --git a/src/ILLink.CodeFix/DAMCodeFixProvider.cs b/src/ILLink.CodeFix/DAMCodeFixProvider.cs new file mode 100644 index 000000000..60de66cea --- /dev/null +++ b/src/ILLink.CodeFix/DAMCodeFixProvider.cs @@ -0,0 +1,134 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ILLink.CodeFixProvider; +using ILLink.RoslynAnalyzer; +using ILLink.Shared; +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 Microsoft.CodeAnalysis.Simplification; + +namespace ILLink.CodeFix +{ + public class DAMCodeFixProvider : Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider + { + public static ImmutableArray<DiagnosticDescriptor> GetSupportedDiagnostics () + { + var diagDescriptorsArrayBuilder = ImmutableArray.CreateBuilder<DiagnosticDescriptor> (); + diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersMismatchParameterTargetsThisParameter)); + diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersMismatchFieldTargetsThisParameter)); + return diagDescriptorsArrayBuilder.ToImmutable (); + } + + public static ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => GetSupportedDiagnostics (); + + public sealed override ImmutableArray<string> FixableDiagnosticIds => SupportedDiagnostics.Select (dd => dd.Id).ToImmutableArray (); + + private protected static LocalizableString CodeFixTitle => new LocalizableResourceString (nameof (Resources.DynamicallyAccessedMembersCodeFixTitle), Resources.ResourceManager, typeof (Resources)); + + private protected static string FullyQualifiedAttributeName => DynamicallyAccessedMembersAnalyzer.FullyQualifiedDynamicallyAccessedMembersAttribute; + + protected static SyntaxNode[] GetAttributeArguments (ISymbol targetSymbol, SyntaxGenerator syntaxGenerator, Diagnostic diagnostic) + { + object id = Enum.Parse (typeof (DiagnosticId), diagnostic.Id.Substring (2)); + switch (id) { + case DiagnosticId.DynamicallyAccessedMembersMismatchParameterTargetsThisParameter: + return new[] { syntaxGenerator.AttributeArgument (syntaxGenerator.TypedConstantExpression (targetSymbol.GetAttributes ().First (attr => attr.AttributeClass?.ToDisplayString () == DynamicallyAccessedMembersAnalyzer.FullyQualifiedDynamicallyAccessedMembersAttribute).ConstructorArguments[0])) }; + case DiagnosticId.DynamicallyAccessedMembersMismatchFieldTargetsThisParameter: + return new[] { syntaxGenerator.AttributeArgument (syntaxGenerator.TypedConstantExpression (targetSymbol.GetAttributes ().First (attr => attr.AttributeClass?.ToDisplayString () == DynamicallyAccessedMembersAnalyzer.FullyQualifiedDynamicallyAccessedMembersAttribute).ConstructorArguments[0])) }; + default: + return Array.Empty<SyntaxNode> (); + } + } + + public sealed override FixAllProvider GetFixAllProvider () + { + // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers + return WellKnownFixAllProviders.BatchFixer; + } + + public override async Task RegisterCodeFixesAsync (CodeFixContext context) + { + var document = context.Document; + if (await document.GetSyntaxRootAsync (context.CancellationToken).ConfigureAwait (false) is not { } root) + return; + var diagnostic = context.Diagnostics.First (); + SyntaxNode diagnosticNode = root.FindNode (diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); + if (await document.GetSemanticModelAsync (context.CancellationToken).ConfigureAwait (false) is not { } model) + return; + // Note: We get the target symbol from the diagnostic location. + // This works when the diagnostic location is a method call, because the target symbol will be the called method with annotations, but won't work in general for other kinds of diagnostics. + if (model.GetSymbolInfo (diagnosticNode).Symbol is not { } targetSymbol) + return; + if (model.Compilation.GetTypeByMetadataName (FullyQualifiedAttributeName) is not { } attributeSymbol) + return; + + if (diagnosticNode is not InvocationExpressionSyntax invocationExpression) + return; + + var arguments = invocationExpression.ArgumentList.Arguments; + + if (arguments.Count > 1) + return; + + if (arguments.Count == 1) { + if (arguments[0].Expression is not LiteralExpressionSyntax literalSyntax + || literalSyntax.Kind () is not SyntaxKind.StringLiteralExpression) { + return; + } + } + + // N.B. May be null for FieldDeclaration, since field declarations can declare multiple variables + var attributableSymbol = (invocationExpression.Expression is MemberAccessExpressionSyntax simpleMember + && simpleMember.Expression is IdentifierNameSyntax name) ? model.GetSymbolInfo (name).Symbol : null; + + + if (attributableSymbol is null) + return; + + var attributableNodeList = attributableSymbol.DeclaringSyntaxReferences; + + if (attributableNodeList.Length != 1) + return; + + var attributableNode = attributableNodeList[0].GetSyntax (); + + if (attributableNode is null) return; + + var attributeArguments = GetAttributeArguments (targetSymbol, SyntaxGenerator.GetGenerator (document), diagnostic); + var codeFixTitle = CodeFixTitle.ToString (); + + context.RegisterCodeFix (CodeAction.Create ( + title: codeFixTitle, + createChangedDocument: ct => AddAttributeAsync ( + document, attributableNode, attributeArguments, attributeSymbol, ct), + equivalenceKey: codeFixTitle), diagnostic); + } + + private static async Task<Document> AddAttributeAsync ( + Document document, + SyntaxNode targetNode, + SyntaxNode[] attributeArguments, + ITypeSymbol attributeSymbol, + CancellationToken cancellationToken) + { + var editor = await DocumentEditor.CreateAsync (document, cancellationToken).ConfigureAwait (false); + var generator = editor.Generator; + var attribute = generator.Attribute ( + generator.TypeExpression (attributeSymbol), attributeArguments) + .WithAdditionalAnnotations (Simplifier.Annotation, Simplifier.AddImportsAnnotation); + + editor.AddAttribute (targetNode, attribute); + return editor.GetChangedDocument (); + } + } +} diff --git a/src/ILLink.CodeFix/Resources.resx b/src/ILLink.CodeFix/Resources.resx index 6105f1c1e..1e674b631 100644 --- a/src/ILLink.CodeFix/Resources.resx +++ b/src/ILLink.CodeFix/Resources.resx @@ -129,4 +129,7 @@ <data name="UconditionalSuppressMessageCodeFixTitle" xml:space="preserve"> <value>Add UnconditionalSuppressMessage attribute to parent method</value> </data> + <data name="DynamicallyAccessedMembersCodeFixTitle" xml:space="preserve"> + <value>Add DynamicallyAccessedMembers attribute to source of warning</value> + </data> </root>
\ No newline at end of file diff --git a/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs b/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs index 0c93e3f62..0e095dbc5 100644 --- a/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs +++ b/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs @@ -22,8 +22,9 @@ namespace ILLink.RoslynAnalyzer { internal const string DynamicallyAccessedMembers = nameof (DynamicallyAccessedMembers); internal const string DynamicallyAccessedMembersAttribute = nameof (DynamicallyAccessedMembersAttribute); + public const string FullyQualifiedDynamicallyAccessedMembersAttribute = "System.Diagnostics.CodeAnalysis." + DynamicallyAccessedMembersAttribute; - static ImmutableArray<DiagnosticDescriptor> GetSupportedDiagnostics () + public static ImmutableArray<DiagnosticDescriptor> GetSupportedDiagnostics () { var diagDescriptorsArrayBuilder = ImmutableArray.CreateBuilder<DiagnosticDescriptor> (26); diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.RequiresUnreferencedCode)); @@ -114,12 +115,12 @@ namespace ILLink.RoslynAnalyzer var symbol = context.SemanticModel.GetSymbolInfo (context.Node).Symbol; - // Avoid unnecesary execution if not NamedType or Method + // Avoid unnecessary execution if not NamedType or Method if (symbol is not INamedTypeSymbol && symbol is not IMethodSymbol) return; // Members inside nameof or cref comments, commonly used to access the string value of a variable, type, or a memeber, - // can generate diagnostics warnings, which can be noisy and unhelpful. + // can generate diagnostics warnings, which can be noisy and unhelpful. // Walking the node heirarchy to check if the member is inside a nameof/cref to not generate diagnostics var parentNode = context.Node; while (parentNode != null) { @@ -210,11 +211,12 @@ namespace ILLink.RoslynAnalyzer method.Locations[0], method.GetDisplayName (), overriddenMethod.GetDisplayName ())); for (int i = 0; i < method.Parameters.Length; i++) { - if (FlowAnnotations.GetMethodParameterAnnotation (method.Parameters[i]) != FlowAnnotations.GetMethodParameterAnnotation (overriddenMethod.Parameters[i])) + if (FlowAnnotations.GetMethodParameterAnnotation (method.Parameters[i]) != FlowAnnotations.GetMethodParameterAnnotation (overriddenMethod.Parameters[i])) { context.ReportDiagnostic (Diagnostic.Create ( DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodParameterBetweenOverrides), method.Parameters[i].Locations[0], method.Parameters[i].GetDisplayName (), method.GetDisplayName (), overriddenMethod.Parameters[i].GetDisplayName (), overriddenMethod.GetDisplayName ())); + } } for (int i = 0; i < method.TypeParameters.Length; i++) { |