diff --git a/src/Features/CSharp/Portable/QualifyMemberAccess/CSharpQualifyMemberAccessDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/QualifyMemberAccess/CSharpQualifyMemberAccessDiagnosticAnalyzer.cs index 8172276d2cc7be319326d6279574e969511628fc..26d0a6019f9c24b189e3f07b387916e27a5ae1d0 100644 --- a/src/Features/CSharp/Portable/QualifyMemberAccess/CSharpQualifyMemberAccessDiagnosticAnalyzer.cs +++ b/src/Features/CSharp/Portable/QualifyMemberAccess/CSharpQualifyMemberAccessDiagnosticAnalyzer.cs @@ -2,18 +2,20 @@ using System.Linq; using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.QualifyMemberAccess; namespace Microsoft.CodeAnalysis.CSharp.QualifyMemberAccess { [DiagnosticAnalyzer(LanguageNames.CSharp)] - internal sealed class CSharpQualifyMemberAccessDiagnosticAnalyzer : AbstractQualifyMemberAccessDiagnosticAnalyzer + internal sealed class CSharpQualifyMemberAccessDiagnosticAnalyzer + : AbstractQualifyMemberAccessDiagnosticAnalyzer { protected override string GetLanguageName() => LanguageNames.CSharp; - protected override bool IsAlreadyQualifiedMemberAccess(SyntaxNode node) + protected override bool IsAlreadyQualifiedMemberAccess(ExpressionSyntax node) => node.IsKind(SyntaxKind.ThisExpression); // If the member is already qualified with `base.`, diff --git a/src/Features/Core/Portable/Diagnostics/Analyzers/SimplifyTypeNamesDiagnosticAnalyzerBase.cs b/src/Features/Core/Portable/Diagnostics/Analyzers/SimplifyTypeNamesDiagnosticAnalyzerBase.cs index 874006a02e1aba40021cfa52630efe04c118cac7..bf2cdbc74bd840b0da3beea18424857c74a00757 100644 --- a/src/Features/Core/Portable/Diagnostics/Analyzers/SimplifyTypeNamesDiagnosticAnalyzerBase.cs +++ b/src/Features/Core/Portable/Diagnostics/Analyzers/SimplifyTypeNamesDiagnosticAnalyzerBase.cs @@ -192,7 +192,7 @@ private DiagnosticDescriptor GetRemoveQualificationDiagnosticDescriptor(Semantic return null; } - var applicableOption = AbstractQualifyMemberAccessDiagnosticAnalyzer.GetApplicableOptionFromSymbolKind(symbolInfo.Symbol.Kind); + var applicableOption = QualifyMembersHelpers.GetApplicableOptionFromSymbolKind(symbolInfo.Symbol.Kind); var optionValue = optionSet.GetOption(applicableOption, GetLanguageName()); var severity = optionValue.Notification.Value; diff --git a/src/Features/Core/Portable/QualifyMemberAccess/AbstractQualifyMemberAccessCodeFixprovider.cs b/src/Features/Core/Portable/QualifyMemberAccess/AbstractQualifyMemberAccessCodeFixprovider.cs index bd3c84702e345e6dd971282e3264816df06cc05f..fe7fffa4dd2fb1468899a63c5ae22c00d0312d18 100644 --- a/src/Features/Core/Portable/QualifyMemberAccess/AbstractQualifyMemberAccessCodeFixprovider.cs +++ b/src/Features/Core/Portable/QualifyMemberAccess/AbstractQualifyMemberAccessCodeFixprovider.cs @@ -12,51 +12,46 @@ namespace Microsoft.CodeAnalysis.QualifyMemberAccess { - internal abstract class AbstractQualifyMemberAccessCodeFixprovider : CodeFixProvider where TSyntaxNode : SyntaxNode + internal abstract class AbstractQualifyMemberAccessCodeFixprovider + : SyntaxEditorBasedCodeFixProvider + where TSimpleNameSyntax : SyntaxNode { + protected abstract string GetTitle(); + public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(IDEDiagnosticIds.AddQualificationDiagnosticId); - public override async Task RegisterCodeFixesAsync(CodeFixContext context) + public override Task RegisterCodeFixesAsync(CodeFixContext context) { - var document = context.Document; - var span = context.Span; - var cancellationToken = context.CancellationToken; + context.RegisterCodeFix(new MyCodeAction( + this.GetTitle(), + c => FixAsync(context.Document, context.Diagnostics[0], c)), + context.Diagnostics); + return Task.CompletedTask; + } + protected override async Task FixAllAsync( + Document document, ImmutableArray diagnostics, + SyntaxEditor editor, CancellationToken cancellationToken) + { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var generator = document.GetLanguageService(); - var token = root.FindToken(span.Start); - if (!token.Span.IntersectsWith(span)) + foreach (var diagnostic in diagnostics) { - return; - } + var node = diagnostic.Location.FindNode(getInnermostNodeForTie: true, cancellationToken) as TSimpleNameSyntax; + if (node != null) + { + var qualifiedAccess = + generator.MemberAccessExpression( + generator.ThisExpression(), + node.WithLeadingTrivia()) + .WithLeadingTrivia(node.GetLeadingTrivia()); - var node = token.GetAncestor(); - if (node == null) - { - return; + editor.ReplaceNode(node, qualifiedAccess); + } } - - var generator = document.GetLanguageService(); - var title = this.GetTitle(); - var codeAction = new MyCodeAction( - title, c => document.ReplaceNodeAsync(node, GetReplacementSyntax(node, generator), c)); - context.RegisterCodeFix(codeAction, context.Diagnostics); - } - - protected abstract string GetTitle(); - - public override FixAllProvider GetFixAllProvider() => BatchFixAllProvider.Instance; - - private static SyntaxNode GetReplacementSyntax(SyntaxNode node, SyntaxGenerator generator) - { - var qualifiedAccess = - generator.MemberAccessExpression( - generator.ThisExpression(), - node.WithLeadingTrivia()) - .WithLeadingTrivia(node.GetLeadingTrivia()); - return qualifiedAccess; } private class MyCodeAction : CodeAction.DocumentChangeAction diff --git a/src/Features/Core/Portable/QualifyMemberAccess/AbstractQualifyMemberAccessDiagnosticAnalyzer.cs b/src/Features/Core/Portable/QualifyMemberAccess/AbstractQualifyMemberAccessDiagnosticAnalyzer.cs index df24efce2066388f7b99209355a916ada5b0adc7..1c84b38a73f414b71c2c521374c9b2bed27dd106 100644 --- a/src/Features/Core/Portable/QualifyMemberAccess/AbstractQualifyMemberAccessDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/QualifyMemberAccess/AbstractQualifyMemberAccessDiagnosticAnalyzer.cs @@ -1,19 +1,21 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Immutable; -using System.Reflection; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Operations; +using Microsoft.CodeAnalysis.Options; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.QualifyMemberAccess { - internal abstract class AbstractQualifyMemberAccessDiagnosticAnalyzer : - AbstractCodeStyleDiagnosticAnalyzer + internal abstract class AbstractQualifyMemberAccessDiagnosticAnalyzer< + TLanguageKindEnum, + TExpressionSyntax, + TSimpleNameSyntax> + : AbstractCodeStyleDiagnosticAnalyzer where TLanguageKindEnum : struct + where TExpressionSyntax : SyntaxNode + where TSimpleNameSyntax : TExpressionSyntax { protected AbstractQualifyMemberAccessDiagnosticAnalyzer() : base(IDEDiagnosticIds.AddQualificationDiagnosticId, @@ -45,7 +47,7 @@ public override bool OpenFileOnly(Workspace workspace) /// True if the member access can be qualified; otherwise, False. protected abstract bool CanMemberAccessBeQualified(ISymbol containingSymbol, SyntaxNode node); - protected abstract bool IsAlreadyQualifiedMemberAccess(SyntaxNode node); + protected abstract bool IsAlreadyQualifiedMemberAccess(TExpressionSyntax node); protected override void InitializeWorker(AnalysisContext context) => context.RegisterOperationAction(AnalyzeOperation, OperationKind.FieldReference, OperationKind.PropertyReference, OperationKind.MethodReference); @@ -74,7 +76,8 @@ private void AnalyzeOperation(OperationAnalysisContext context) } // If we can't be qualified (e.g., because we're already qualified with `base.`), we're done. - if (!CanMemberAccessBeQualified(context.ContainingSymbol, memberReference.Instance.Syntax)) + var instanceSyntaxOpt = memberReference.Instance.Syntax as TExpressionSyntax; + if (!CanMemberAccessBeQualified(context.ContainingSymbol, instanceSyntaxOpt)) { return; } @@ -87,7 +90,13 @@ private void AnalyzeOperation(OperationAnalysisContext context) return; } - var syntaxTree = context.Operation.Syntax.SyntaxTree; + var simpleName = memberReference.Syntax as TSimpleNameSyntax; + if (simpleName == null) + { + return; + } + + var syntaxTree = simpleName.SyntaxTree; var cancellationToken = context.CancellationToken; var optionSet = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult(); if (optionSet == null) @@ -95,12 +104,12 @@ private void AnalyzeOperation(OperationAnalysisContext context) return; } - var language = context.Operation.Syntax.Language; - var applicableOption = GetApplicableOptionFromSymbolKind(memberReference.Member.Kind); - var optionValue = optionSet.GetOption(applicableOption, language); + + var applicableOption = QualifyMembersHelpers.GetApplicableOptionFromSymbolKind(memberReference.Member.Kind); + var optionValue = optionSet.GetOption(applicableOption, simpleName.Language); var shouldOptionBePresent = optionValue.Value; - var isQualificationPresent = IsAlreadyQualifiedMemberAccess(memberReference.Instance.Syntax); + var isQualificationPresent = IsAlreadyQualifiedMemberAccess(instanceSyntaxOpt); if (shouldOptionBePresent && !isQualificationPresent) { var severity = optionValue.Notification.Value; @@ -108,12 +117,15 @@ private void AnalyzeOperation(OperationAnalysisContext context) { context.ReportDiagnostic(Diagnostic.Create( GetDescriptorWithSeverity(severity), - context.Operation.Syntax.GetLocation())); + simpleName.GetLocation())); } } } + } - internal static PerLanguageOption> GetApplicableOptionFromSymbolKind(SymbolKind symbolKind) + internal static class QualifyMembersHelpers + { + public static PerLanguageOption> GetApplicableOptionFromSymbolKind(SymbolKind symbolKind) { switch (symbolKind) { diff --git a/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicQualifyMemberAccessDiagnosticAnalyzer.vb b/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicQualifyMemberAccessDiagnosticAnalyzer.vb index e6aaea65ff37ebb48dda475222cc27e87a12b0fc..ce54e3a08d0d27d9240a22f97d87c3fed3488912 100644 --- a/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicQualifyMemberAccessDiagnosticAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicQualifyMemberAccessDiagnosticAnalyzer.vb @@ -2,17 +2,18 @@ Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.QualifyMemberAccess +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.QualifyMemberAccess Friend NotInheritable Class VisualBasicQualifyMemberAccessDiagnosticAnalyzer - Inherits AbstractQualifyMemberAccessDiagnosticAnalyzer(Of SyntaxKind) + Inherits AbstractQualifyMemberAccessDiagnosticAnalyzer(Of SyntaxKind, ExpressionSyntax, SimpleNameSyntax) Protected Overrides Function GetLanguageName() As String Return LanguageNames.VisualBasic End Function - Protected Overrides Function IsAlreadyQualifiedMemberAccess(node As SyntaxNode) As Boolean + Protected Overrides Function IsAlreadyQualifiedMemberAccess(node As ExpressionSyntax) As Boolean Return node.IsKind(SyntaxKind.MeExpression) End Function