提交 5e36ef9f 编写于 作者: C Cyrus Najmabadi

Provide feature to simplify delegate invocations with C# 6 syntax.

上级 817ad74b
......@@ -319,6 +319,8 @@
<Compile Include="IntroduceVariable\CSharpIntroduceVariableService_IntroduceField.cs" />
<Compile Include="IntroduceVariable\CSharpIntroduceVariableService_IntroduceLocal.cs" />
<Compile Include="IntroduceVariable\CSharpIntroduceVariableService_IntroduceQueryLocal.cs" />
<Compile Include="InvokeDelegateWithConditionalAccess\InvokeDelegateWithConditionalAccessAnalyzer.cs" />
<Compile Include="InvokeDelegateWithConditionalAccess\InvokeDelegateWithConditionalAccessCodeFixProvider.cs" />
<Compile Include="LanguageServices\CSharpAnonymousTypeDisplayService.cs" />
<Compile Include="LanguageServices\CSharpSymbolDisplayService.cs" />
<Compile Include="LanguageServices\CSharpSymbolDisplayService.SymbolDescriptionBuilder.cs" />
......
......@@ -232,6 +232,15 @@ internal class CSharpFeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Delegate invocation can be simplified..
/// </summary>
internal static string DelegateInvocationCanBeSimplified {
get {
return ResourceManager.GetString("DelegateInvocationCanBeSimplified", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to deprecated.
/// </summary>
......
......@@ -437,4 +437,7 @@
<data name="HideBase" xml:space="preserve">
<value>Hide base member</value>
</data>
<data name="DelegateInvocationCanBeSimplified" xml:space="preserve">
<value>Delegate invocation can be simplified.</value>
</data>
</root>
\ No newline at end of file
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.CSharp.InvokeDelegateWithConditionalAccess
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class InvokeDelegateWithConditionalAccessAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor descriptor = new DiagnosticDescriptor(
IDEDiagnosticIds.InvokeDelegateWithConditionalAccessId,
CSharpFeaturesResources.DelegateInvocationCanBeSimplified,
CSharpFeaturesResources.DelegateInvocationCanBeSimplified,
DiagnosticCategory.Style,
DiagnosticSeverity.Hidden,
isEnabledByDefault: true,
customTags: DiagnosticCustomTags.Unnecessary);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(descriptor);
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(SyntaxNodeAction, SyntaxKind.IfStatement);
}
private void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext)
{
// look for the form "if (a != null)" or "if (null != a)"
var ifStatement = (IfStatementSyntax)syntaxContext.Node;
if (!ifStatement.Condition.IsKind(SyntaxKind.NotEqualsExpression))
{
return;
}
if (ifStatement.Else != null)
{
return;
}
var binaryExpression = (BinaryExpressionSyntax)ifStatement.Condition;
if (!IsNotEqualsTest(binaryExpression.Left, binaryExpression.Right) &&
!IsNotEqualsTest(binaryExpression.Right, binaryExpression.Left))
{
return;
}
// Check for both: "if (...) { a(); }" and "if (...) a();"
var statement = ifStatement.Statement;
if (statement.IsKind(SyntaxKind.Block))
{
var block = (BlockSyntax)statement;
if (block.Statements.Count != 1)
{
return;
}
statement = block.Statements[0];
}
if (!statement.Parent.IsKind(SyntaxKind.Block))
{
return;
}
if (!statement.IsKind(SyntaxKind.ExpressionStatement))
{
return;
}
var expressionStatement = (ExpressionStatementSyntax)statement;
// Check that it's of the form: "if (a != null) { a(); }}
var invocationExpression = ((ExpressionStatementSyntax)statement).Expression;
if (!invocationExpression.IsKind(SyntaxKind.InvocationExpression))
{
return;
}
var expression = ((InvocationExpressionSyntax)invocationExpression).Expression;
if (!expression.IsKind(SyntaxKind.IdentifierName))
{
return;
}
var conditionName = binaryExpression.Left is IdentifierNameSyntax
? (IdentifierNameSyntax)binaryExpression.Left
: (IdentifierNameSyntax)binaryExpression.Right;
var invocationName = (IdentifierNameSyntax)expression;
if (!Equals(conditionName.Identifier.ValueText, invocationName.Identifier.ValueText))
{
return;
}
// Now make sure the previous statement is "var a = ..."
var parentBlock = (BlockSyntax)ifStatement.Parent;
var ifIndex = parentBlock.Statements.IndexOf(ifStatement);
if (ifIndex == 0)
{
return;
}
var previousStatement = parentBlock.Statements[ifIndex - 1];
if (!previousStatement.IsKind(SyntaxKind.LocalDeclarationStatement))
{
return;
}
var localDeclarationStatement = (LocalDeclarationStatementSyntax)previousStatement;
var variableDeclaration = localDeclarationStatement.Declaration;
if (variableDeclaration.Variables.Count != 1)
{
return;
}
var declarator = variableDeclaration.Variables[0];
if (declarator.Initializer == null)
{
return;
}
if (!Equals(declarator.Identifier.ValueText, conditionName.Identifier.ValueText))
{
return;
}
// Syntactically this looks good. Now make sure that the local is a delegate type.
var semanticModel = syntaxContext.SemanticModel;
var localSymbol = (ILocalSymbol)semanticModel.GetDeclaredSymbol(declarator);
if (localSymbol.Type.TypeKind != TypeKind.Delegate)
{
return;
}
// Ok, we made a local just to check it for null and invoke it. Looks like something
// we can suggest an improvement for!
// But first make sure we're only using the local only within the body of this if statement.
var analysis = semanticModel.AnalyzeDataFlow(localDeclarationStatement, ifStatement);
if (analysis.ReadOutside.Contains(localSymbol) || analysis.WrittenOutside.Contains(localSymbol))
{
return;
}
// Looks good!
var tree = semanticModel.SyntaxTree;
var additionalLocations = new List<Location>
{
Location.Create(tree, localDeclarationStatement.Span),
Location.Create(tree, ifStatement.Span),
Location.Create(tree, expressionStatement.Span)
};
syntaxContext.ReportDiagnostic(Diagnostic.Create(descriptor,
Location.Create(tree, TextSpan.FromBounds(localDeclarationStatement.SpanStart, invocationExpression.SpanStart)),
additionalLocations));
if (localDeclarationStatement.Span.End != ifStatement.Span.End)
{
syntaxContext.ReportDiagnostic(Diagnostic.Create(descriptor,
Location.Create(tree, TextSpan.FromBounds(expressionStatement.Span.End, ifStatement.Span.End)),
additionalLocations));
}
}
private bool IsNotEqualsTest(ExpressionSyntax left, ExpressionSyntax right) =>
left.IsKind(SyntaxKind.IdentifierName) && right.IsKind(SyntaxKind.NullLiteralExpression);
}
}
\ No newline at end of file
using System;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Formatting;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.InvokeDelegateWithConditionalAccess
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(InvokeDelegateWithConditionalAccessCodeFixProvider)), Shared]
internal class InvokeDelegateWithConditionalAccessCodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(IDEDiagnosticIds.InvokeDelegateWithConditionalAccessId);
public override FixAllProvider GetFixAllProvider() => BatchFixAllProvider.Instance;
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
context.RegisterCodeFix(new MyCodeAction(
CSharpFeaturesResources.DelegateInvocationCanBeSimplified,
async c => await UpdateDocumentAsync(context).ConfigureAwait(false),
equivalenceKey: nameof(InvokeDelegateWithConditionalAccessCodeFixProvider)),
context.Diagnostics);
return Task.FromResult(false);
}
private async Task<Document> UpdateDocumentAsync(CodeFixContext context)
{
var document = context.Document;
var cancellationToken = context.CancellationToken;
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var diagnostic = context.Diagnostics.First();
var localDeclarationLocation = diagnostic.AdditionalLocations[0];
var ifStatementLocation = diagnostic.AdditionalLocations[1];
var expressionStatementLocation = diagnostic.AdditionalLocations[2];
var localDeclarationStatement = (LocalDeclarationStatementSyntax)root.FindNode(localDeclarationLocation.SourceSpan);
var expressionStatement = (ExpressionStatementSyntax)root.FindNode(expressionStatementLocation.SourceSpan);
var ifStatement = (IfStatementSyntax)root.FindNode(ifStatementLocation.SourceSpan);
var invocationExpression = (InvocationExpressionSyntax)expressionStatement.Expression;
var parentBlock = (BlockSyntax)localDeclarationStatement.Parent;
var newStatement = expressionStatement.WithExpression(
SyntaxFactory.ConditionalAccessExpression(
localDeclarationStatement.Declaration.Variables[0].Initializer.Value.Parenthesize(),
SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberBindingExpression(SyntaxFactory.IdentifierName("Invoke")), invocationExpression.ArgumentList)));
newStatement = newStatement.WithAdditionalAnnotations(Formatter.Annotation);
var newStatements = parentBlock.Statements.TakeWhile(s => s != localDeclarationStatement)
.Concat(newStatement)
.Concat(parentBlock.Statements.SkipWhile(s => s != ifStatement).Skip(1));
var newBlock = parentBlock.WithStatements(SyntaxFactory.List(newStatements));
return document.WithSyntaxRoot(root.ReplaceNode(parentBlock, newBlock));
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(string title, Func<CancellationToken, Task<Document>> createChangedDocument, string equivalenceKey)
: base(title, createChangedDocument, equivalenceKey)
{
}
}
}
}
......@@ -15,5 +15,6 @@ internal static class IDEDiagnosticIds
public const string AnalyzerDependencyConflictId = "IDE1002";
public const string MissingAnalyzerReferenceId = "IDE1003";
public const string ErrorReadingRulesetId = "IDE1004";
public const string InvokeDelegateWithConditionalAccessId = "IDE1005";
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册