Last active
January 5, 2018 09:02
-
-
Save Meir017/48eef45a5b2525ce21ff12de0bae5dc0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.CodeAnalysis; | |
using Microsoft.CodeAnalysis.Diagnostics; | |
using Microsoft.CodeAnalysis.CSharp.Syntax; | |
using Microsoft.CodeAnalysis.CSharp; | |
using Microsoft.CodeAnalysis.CodeFixes; | |
using System.Collections.Generic; | |
using System.Collections.Immutable; | |
using System.Threading.Tasks; | |
using System.Threading; | |
using System.Linq; | |
using System; | |
namespace FluentAssertions.BestPractices | |
{ | |
public class PocCodeFix : CodeFixProvider | |
{ | |
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(PocAnalyzer.DiagnosticId); | |
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; | |
public async override Task RegisterCodeFixesAsync(CodeFixContext context) | |
{ | |
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | |
foreach (var diagnostic in context.Diagnostics) | |
{ | |
var statement = (ExpressionStatementSyntax)root.FindNode(diagnostic.Location.SourceSpan); | |
context.RegisterCodeFix(CodeAction.Create(PocAnalyzer.Title, c => PrettifyStatement(context.Document, statement, c)), diagnostic); | |
} | |
} | |
protected IEnumerable<NodeReplacement> Replacements | |
{ | |
get | |
{ | |
yield return new MethodBReplacement(); | |
yield return new MethodDReplacement(); | |
} | |
} | |
private async Task<Document> PrettifyStatement(Document document, ExpressionStatementSyntax statement, CancellationToken c) | |
{ | |
var newStatement = statement; | |
void UpdateRoot(NodeReplacement replacement) | |
{ | |
var members = new LinkedList<MemberAccessExpressionSyntax>(newStatement.DescendantNodes().OfType<MemberAccessExpressionSyntax>()); | |
var current = members.Last; | |
while (current != null) | |
{ | |
if (replacement.IsValidNode(current.Value)) | |
{ | |
newStatement = newStatement.ReplaceNode(replacement.ComputeOldNew(current), replacement.ComputeNewNew(current)); | |
return; | |
} | |
current = current.Previous; | |
} | |
} | |
foreach (var replacement in Replacements) | |
{ | |
UpdateRoot(replacement); | |
} | |
var root = await document.GetSyntaxRootAsync(c).ConfigureAwait(false); | |
root = root.ReplaceNode(statement, newStatement); | |
return document.WithSyntaxRoot(root); | |
} | |
private class MethodDReplacement : NodeReplacement | |
{ | |
public override bool IsValidNode(MemberAccessExpressionSyntax node) => node.Name.Identifier.Text == "D"; | |
public override SyntaxNode ComputeOldNew(LinkedListNode<MemberAccessExpressionSyntax> listNode) => listNode.Previous.Value; | |
public override SyntaxNode ComputeNewNew(LinkedListNode<MemberAccessExpressionSyntax> listNode) | |
{ | |
var previous = listNode.Previous.Value; | |
var next = listNode.Next.Value; | |
return previous.WithExpression(next); | |
} | |
} | |
private class MethodBReplacement : NodeReplacement | |
{ | |
public override bool IsValidNode(MemberAccessExpressionSyntax node) => node.Name.Identifier.Text == "B"; | |
public override SyntaxNode ComputeOldNew(LinkedListNode<MemberAccessExpressionSyntax> listNode) => listNode.Value; | |
public override SyntaxNode ComputeNewNew(LinkedListNode<MemberAccessExpressionSyntax> listNode) => listNode.Value.WithName(SyntaxFactory.IdentifierName("H")); | |
} | |
} | |
public abstract class NodeReplacement | |
{ | |
public abstract bool IsValidNode(MemberAccessExpressionSyntax node); | |
public abstract SyntaxNode ComputeOldNew(LinkedListNode<MemberAccessExpressionSyntax> listNode); | |
public abstract SyntaxNode ComputeNewNew(LinkedListNode<MemberAccessExpressionSyntax> listNode); | |
} | |
[DiagnosticAnalyzer(LanguageNames.CSharp)] | |
public class PocAnalyzer : DiagnosticAnalyzer | |
{ | |
public const string DiagnosticId = "PocAnalyzer"; | |
public const string Category = "POC"; | |
public const string Title = "POC analyzer"; | |
public const string Message = "Use {0} .Should() followed by .BeEmpty() instead."; | |
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); | |
private readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, Message, Category, DiagnosticSeverity.Info, true); | |
public override void Initialize(AnalysisContext context) | |
{ | |
context.RegisterCodeBlockAction(AnalyzeCodeBlock); | |
} | |
private void AnalyzeCodeBlock(CodeBlockAnalysisContext context) | |
{ | |
var statements = (context.CodeBlock as MethodDeclarationSyntax)?.Body?.Statements; | |
if (statements == null) return; | |
foreach (var statement in statements.OfType<ExpressionStatementSyntax>()) | |
{ | |
var diagnostic = AnalyzeExpressionStatement(statement); | |
if (diagnostic != null) | |
{ | |
context.ReportDiagnostic(diagnostic); | |
} | |
} | |
} | |
private Diagnostic AnalyzeExpressionStatement(ExpressionStatementSyntax statement) | |
{ | |
var walker = new PocCSharpSyntaxWalker("B", "D"); | |
statement.Accept(walker); | |
if (walker.Members.Count == 0) | |
{ | |
return Diagnostic.Create(Rule, statement.GetLocation()); | |
} | |
return null; | |
} | |
private class PocCSharpSyntaxWalker : CSharpSyntaxWalker | |
{ | |
public Stack<string> Members { get; } | |
public PocCSharpSyntaxWalker(params string[] members) | |
{ | |
Members = new Stack<string>(members); | |
} | |
public override void VisitMemberAccessExpression(MemberAccessExpressionSyntax node) | |
{ | |
var name = node.Name.Identifier.Text; | |
if (Members.Count > 0 && Members.Peek() == name) | |
{ | |
Members.Pop(); | |
} | |
Visit(node.Expression); | |
} | |
/* | |
private int _indent = 0; | |
public override void Visit(SyntaxNode node) | |
{ | |
_indent++; | |
var indent = new string(' ', _indent * 2); | |
Console.WriteLine($"{indent}{node.GetType().Name}"); | |
base.Visit(node); | |
--_indent; | |
} | |
*/ | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment