提交 cc473cf7 编写于 作者: C Cyrus Najmabadi

Move 'use auto prop' over to a semantic model analyzer.

上级 a604cefe
// 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.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.UseAutoProperty;
......@@ -12,21 +14,74 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UseAutoProperty
{
[Export]
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class UseAutoPropertyAnalyzer : AbstractUseAutoPropertyAnalyzer<PropertyDeclarationSyntax, FieldDeclarationSyntax, VariableDeclaratorSyntax, ExpressionSyntax>
internal class CSharpUseAutoPropertyAnalyzer : AbstractUseAutoPropertyAnalyzer<PropertyDeclarationSyntax, FieldDeclarationSyntax, VariableDeclaratorSyntax, ExpressionSyntax>
{
protected override bool SupportsReadOnlyProperties(Compilation compilation)
=> ((CSharpCompilation)compilation).LanguageVersion >= LanguageVersion.CSharp6;
protected override bool SupportsPropertyInitializer(Compilation compilation)
=> ((CSharpCompilation)compilation).LanguageVersion >= LanguageVersion.CSharp6;
protected override void AnalyzeCompilationUnit(
SemanticModelAnalysisContext context, SyntaxNode root, List<AnalysisResult> analysisResults)
=> AnalyzeMembers(context, ((CompilationUnitSyntax)root).Members, analysisResults);
private void AnalyzeMembers(
SemanticModelAnalysisContext context,
SyntaxList<MemberDeclarationSyntax> members,
List<AnalysisResult> analysisResults)
{
return ((CSharpCompilation)compilation).LanguageVersion >= LanguageVersion.CSharp6;
foreach (var memberDeclaration in members)
{
AnalyzeMemberDeclaration(context, memberDeclaration, analysisResults);
}
}
protected override bool SupportsPropertyInitializer(Compilation compilation)
private void AnalyzeMemberDeclaration(
SemanticModelAnalysisContext context,
MemberDeclarationSyntax member,
List<AnalysisResult> analysisResults)
{
return ((CSharpCompilation)compilation).LanguageVersion >= LanguageVersion.CSharp6;
if (member.IsKind(SyntaxKind.NamespaceDeclaration, out NamespaceDeclarationSyntax namespaceDeclaration))
{
AnalyzeMembers(context, namespaceDeclaration.Members, analysisResults);
}
// If we have a class or struct, recurse inwards.
if (member.IsKind(SyntaxKind.ClassDeclaration, out TypeDeclarationSyntax typeDeclaration) ||
member.IsKind(SyntaxKind.StructDeclaration, out typeDeclaration))
{
AnalyzeMembers(context, typeDeclaration.Members, analysisResults);
}
if (member is PropertyDeclarationSyntax propertyDeclaration)
{
var property = (IPropertySymbol)context.SemanticModel.GetDeclaredSymbol(propertyDeclaration, context.CancellationToken);
AnalyzeProperty(context, property, analysisResults);
}
}
protected override void RegisterIneligibleFieldsAction(CompilationStartAnalysisContext context, ConcurrentBag<IFieldSymbol> ineligibleFields)
protected override void RegisterIneligibleFieldsAction(
List<AnalysisResult> analysisResults, HashSet<IFieldSymbol> ineligibleFields,
Compilation compilation, CancellationToken cancellationToken)
{
context.RegisterSyntaxNodeAction(snac => AnalyzeArgument(ineligibleFields, snac), SyntaxKind.Argument);
var groups = analysisResults.Select(r => (TypeDeclarationSyntax)r.PropertyDeclaration.Parent)
.Distinct()
.GroupBy(n => n.SyntaxTree);
foreach (var group in groups)
{
var tree = group.Key;
var semanticModel = compilation.GetSemanticModel(tree);
foreach (var typeDeclaration in group)
{
foreach (var argument in typeDeclaration.DescendantNodesAndSelf().OfType<ArgumentSyntax>())
{
AnalyzeArgument(semanticModel, argument, ineligibleFields, cancellationToken);
}
}
}
}
protected override ExpressionSyntax GetFieldInitializer(VariableDeclaratorSyntax variable, CancellationToken cancellationToken)
......@@ -34,18 +89,18 @@ protected override ExpressionSyntax GetFieldInitializer(VariableDeclaratorSyntax
return variable.Initializer?.Value;
}
private void AnalyzeArgument(ConcurrentBag<IFieldSymbol> ineligibleFields, SyntaxNodeAnalysisContext context)
private void AnalyzeArgument(
SemanticModel semanticModel, ArgumentSyntax argument,
HashSet<IFieldSymbol> ineligibleFields, CancellationToken cancellationToken)
{
// An argument will disqualify a field if that field is used in a ref/out position.
// We can't change such field references to be property references in C#.
var argument = (ArgumentSyntax)context.Node;
if (argument.RefOrOutKeyword.Kind() == SyntaxKind.None)
{
return;
}
var cancellationToken = context.CancellationToken;
var symbolInfo = context.SemanticModel.GetSymbolInfo(argument.Expression, cancellationToken);
var symbolInfo = semanticModel.GetSymbolInfo(argument.Expression, cancellationToken);
AddIneligibleField(symbolInfo.Symbol, ineligibleFields);
foreach (var symbol in symbolInfo.CandidateSymbols)
{
......@@ -53,7 +108,7 @@ private void AnalyzeArgument(ConcurrentBag<IFieldSymbol> ineligibleFields, Synta
}
}
private static void AddIneligibleField(ISymbol symbol, ConcurrentBag<IFieldSymbol> ineligibleFields)
private static void AddIneligibleField(ISymbol symbol, HashSet<IFieldSymbol> ineligibleFields)
{
if (symbol is IFieldSymbol field)
{
......
......@@ -18,8 +18,8 @@
namespace Microsoft.CodeAnalysis.Editor.CSharp.UseAutoProperty
{
[Shared]
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseAutoPropertyCodeFixProvider))]
internal class UseAutoPropertyCodeFixProvider : AbstractUseAutoPropertyCodeFixProvider<PropertyDeclarationSyntax, FieldDeclarationSyntax, VariableDeclaratorSyntax, ConstructorDeclarationSyntax, ExpressionSyntax>
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CSharpUseAutoPropertyCodeFixProvider))]
internal class CSharpUseAutoPropertyCodeFixProvider : AbstractUseAutoPropertyCodeFixProvider<PropertyDeclarationSyntax, FieldDeclarationSyntax, VariableDeclaratorSyntax, ConstructorDeclarationSyntax, ExpressionSyntax>
{
protected override SyntaxNode GetNodeToRemove(VariableDeclaratorSyntax declarator)
{
......
......@@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.UseAutoProp
public class UseAutoPropertyTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest
{
internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace)
=> (new UseAutoPropertyAnalyzer(), new UseAutoPropertyCodeFixProvider());
=> (new CSharpUseAutoPropertyAnalyzer(), new CSharpUseAutoPropertyCodeFixProvider());
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)]
public async Task TestSingleGetterFromField()
......
......@@ -10,7 +10,7 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UseAutoProperty
<Export>
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
Friend Class UseAutoPropertyAnalyzer
Friend Class VisualBasicUseAutoPropertyAnalyzer
Inherits AbstractUseAutoPropertyAnalyzer(Of PropertyBlockSyntax, FieldDeclarationSyntax, ModifiedIdentifierSyntax, ExpressionSyntax)
Protected Overrides Function SupportsReadOnlyProperties(compilation As Compilation) As Boolean
......@@ -21,7 +21,44 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UseAutoProperty
Return DirectCast(compilation, VisualBasicCompilation).LanguageVersion >= LanguageVersion.VisualBasic10
End Function
Protected Overrides Sub RegisterIneligibleFieldsAction(context As CompilationStartAnalysisContext, ineligibleFields As ConcurrentBag(Of IFieldSymbol))
Protected Overrides Sub AnalyzeCompilationUnit(context As SemanticModelAnalysisContext, root As SyntaxNode, analysisResults As List(Of AnalysisResult))
AnalyzeMembers(context, DirectCast(root, CompilationUnitSyntax).Members, analysisResults)
End Sub
Private Sub AnalyzeMembers(context As SemanticModelAnalysisContext,
members As SyntaxList(Of StatementSyntax),
analysisResults As List(Of AnalysisResult))
For Each member In members
AnalyzeMember(context, member, analysisResults)
Next
End Sub
Private Sub AnalyzeMember(context As SemanticModelAnalysisContext,
member As StatementSyntax,
analysisResults As List(Of AnalysisResult))
If member.Kind() = SyntaxKind.NamespaceBlock Then
Dim namespaceBlock = DirectCast(member, NamespaceBlockSyntax)
AnalyzeMembers(context, namespaceBlock.Members, analysisResults)
End If
' If we have a class or struct or module, recurse inwards.
If member.IsKind(SyntaxKind.ClassBlock) OrElse
member.IsKind(SyntaxKind.StructureBlock) OrElse
member.IsKind(SyntaxKind.ModuleBlock) Then
Dim typeBlock = DirectCast(member, TypeBlockSyntax)
AnalyzeMembers(context, typeBlock.Members, analysisResults)
End If
Dim propertyDeclaration = TryCast(member, PropertyBlockSyntax)
If propertyDeclaration IsNot Nothing Then
Dim [property] = context.SemanticModel.GetDeclaredSymbol(propertyDeclaration, context.CancellationToken)
AnalyzeProperty(context, [property], analysisResults)
End If
End Sub
Protected Overrides Sub RegisterIneligibleFieldsAction(analysisResults As List(Of AnalysisResult), ineligibleFields As HashSet(Of IFieldSymbol), compilation As Compilation, cancellationToken As CancellationToken)
' There are no syntactic constructs that make a field ineligible to be replaced with
' a property. In C# you can't use a property in a ref/out position. But that restriction
' doesn't apply to VB.
......@@ -37,7 +74,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UseAutoProperty
Dim memberAccessExpression = DirectCast(expression, MemberAccessExpressionSyntax)
Return memberAccessExpression.Expression.Kind() = SyntaxKind.MeExpression AndAlso
memberAccessExpression.Name.Kind() = SyntaxKind.IdentifierName
ElseIf expression.IsKind(SyntaxKind.IdentifierName)
ElseIf expression.IsKind(SyntaxKind.IdentifierName) Then
Return True
End If
......
......@@ -33,7 +33,9 @@ protected AbstractUseAutoPropertyAnalyzer()
public override bool OpenFileOnly(Workspace workspace) => false;
public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.ProjectAnalysis;
protected abstract void RegisterIneligibleFieldsAction(CompilationStartAnalysisContext context, ConcurrentBag<IFieldSymbol> ineligibleFields);
protected abstract void RegisterIneligibleFieldsAction(
List<AnalysisResult> analysisResults, HashSet<IFieldSymbol> ineligibleFields,
Compilation compilation, CancellationToken cancellationToken);
protected abstract bool SupportsReadOnlyProperties(Compilation compilation);
protected abstract bool SupportsPropertyInitializer(Compilation compilation);
protected abstract TExpression GetFieldInitializer(TVariableDeclarator variable, CancellationToken cancellationToken);
......@@ -42,20 +44,26 @@ protected AbstractUseAutoPropertyAnalyzer()
protected abstract SyntaxNode GetNodeToFade(TFieldDeclaration fieldDeclaration, TVariableDeclarator variableDeclarator);
protected sealed override void InitializeWorker(AnalysisContext context)
=> context.RegisterCompilationStartAction(csac =>
{
var analysisResults = new ConcurrentBag<AnalysisResult>();
var ineligibleFields = new ConcurrentBag<IFieldSymbol>();
=> context.RegisterSemanticModelAction(AnalyzeSemanticModel);
csac.RegisterSymbolAction(sac => AnalyzeProperty(analysisResults, sac), SymbolKind.Property);
RegisterIneligibleFieldsAction(csac, ineligibleFields);
private void AnalyzeSemanticModel(SemanticModelAnalysisContext context)
{
var analysisResults = new List<AnalysisResult>();
var ineligibleFields = new HashSet<IFieldSymbol>();
var root = context.SemanticModel.SyntaxTree.GetRoot(context.CancellationToken);
AnalyzeCompilationUnit(context, root, analysisResults);
csac.RegisterCompilationEndAction(cac => Process(analysisResults, ineligibleFields, cac));
});
RegisterIneligibleFieldsAction(
analysisResults, ineligibleFields,
context.SemanticModel.Compilation, context.CancellationToken);
Process(analysisResults, ineligibleFields, context);
}
private void AnalyzeProperty(ConcurrentBag<AnalysisResult> analysisResults, SymbolAnalysisContext symbolContext)
protected abstract void AnalyzeCompilationUnit(SemanticModelAnalysisContext context, SyntaxNode root, List<AnalysisResult> analysisResults);
protected void AnalyzeProperty(SemanticModelAnalysisContext context, IPropertySymbol property, List<AnalysisResult> analysisResults)
{
var property = (IPropertySymbol)symbolContext.Symbol;
if (property.IsIndexer)
{
return;
......@@ -96,23 +104,30 @@ private void AnalyzeProperty(ConcurrentBag<AnalysisResult> analysisResults, Symb
return;
}
var cancellationToken = symbolContext.CancellationToken;
var cancellationToken = context.CancellationToken;
var propertyDeclaration = property.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken).FirstAncestorOrSelf<TPropertyDeclaration>();
if (propertyDeclaration == null)
{
return;
}
var semanticModel = symbolContext.Compilation.GetSemanticModel(propertyDeclaration.SyntaxTree);
var semanticModel = context.SemanticModel;
var getterField = GetGetterField(semanticModel, property.GetMethod, cancellationToken);
if (getterField == null)
{
return;
}
if (getterField.DeclaredAccessibility != Accessibility.Private)
{
// Only support this for private fields. It limits the scope of hte program
// we have to analyze to make sure this is safe to do.
return;
}
// If the user made the field readonly, we only want to convert it to a property if we
// can keep it readonly.
if (getterField.IsReadOnly && !SupportsReadOnlyProperties(symbolContext.Compilation))
if (getterField.IsReadOnly && !SupportsReadOnlyProperties(semanticModel.Compilation))
{
return;
}
......@@ -160,14 +175,14 @@ private void AnalyzeProperty(ConcurrentBag<AnalysisResult> analysisResults, Symb
}
var fieldReference = getterField.DeclaringSyntaxReferences[0];
var variableDeclarator = fieldReference.GetSyntax(symbolContext.CancellationToken) as TVariableDeclarator;
var variableDeclarator = fieldReference.GetSyntax(cancellationToken) as TVariableDeclarator;
if (variableDeclarator == null)
{
return;
}
var initializer = GetFieldInitializer(variableDeclarator, cancellationToken);
if (initializer != null && !SupportsPropertyInitializer(symbolContext.Compilation))
if (initializer != null && !SupportsPropertyInitializer(semanticModel.Compilation))
{
return;
}
......@@ -223,9 +238,9 @@ private IFieldSymbol CheckFieldAccessExpression(SemanticModel semanticModel, TEx
}
private void Process(
ConcurrentBag<AnalysisResult> analysisResults,
ConcurrentBag<IFieldSymbol> ineligibleFields,
CompilationAnalysisContext compilationContext)
List<AnalysisResult> analysisResults,
HashSet<IFieldSymbol> ineligibleFields,
SemanticModelAnalysisContext context)
{
var ineligibleFieldsSet = new HashSet<IFieldSymbol>(ineligibleFields);
foreach (var result in analysisResults)
......@@ -236,15 +251,19 @@ private IFieldSymbol CheckFieldAccessExpression(SemanticModel semanticModel, TEx
continue;
}
Process(result, compilationContext);
Process(result, context);
}
}
private void Process(AnalysisResult result, CompilationAnalysisContext compilationContext)
private void Process(AnalysisResult result, SemanticModelAnalysisContext context)
{
// Check if there are additional reasons we think this field might be ineligible for
// replacing with an auto prop.
if (!IsEligibleHeuristic(result.Field, result.PropertyDeclaration, compilationContext.Compilation, compilationContext.CancellationToken))
var cancellationToken = context.CancellationToken;
var semanticModel = context.SemanticModel;
var compilation = semanticModel.Compilation;
if (!IsEligibleHeuristic(result.Field, result.PropertyDeclaration, compilation, cancellationToken))
{
return;
}
......@@ -258,7 +277,7 @@ private void Process(AnalysisResult result, CompilationAnalysisContext compilati
// Fade out the field/variable we are going to remove.
var diagnostic1 = Diagnostic.Create(UnnecessaryWithoutSuggestionDescriptor, nodeToFade.GetLocation());
compilationContext.ReportDiagnostic(diagnostic1);
context.ReportDiagnostic(diagnostic1);
// Now add diagnostics to both the field and the property saying we can convert it to
// an auto property. For each diagnostic store both location so we can easily retrieve
......@@ -266,10 +285,10 @@ private void Process(AnalysisResult result, CompilationAnalysisContext compilati
IEnumerable<Location> additionalLocations = new Location[] { propertyDeclaration.GetLocation(), variableDeclarator.GetLocation() };
var diagnostic2 = Diagnostic.Create(HiddenDescriptor, propertyDeclaration.GetLocation(), additionalLocations, properties);
compilationContext.ReportDiagnostic(diagnostic2);
context.ReportDiagnostic(diagnostic2);
var diagnostic3 = Diagnostic.Create(HiddenDescriptor, nodeToFade.GetLocation(), additionalLocations, properties);
compilationContext.ReportDiagnostic(diagnostic3);
context.ReportDiagnostic(diagnostic3);
}
protected virtual bool IsEligibleHeuristic(IFieldSymbol field, TPropertyDeclaration propertyDeclaration, Compilation compilation, CancellationToken cancellationToken)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册