Skip to content

Instantly share code, notes, and snippets.

@Meir017
Last active January 5, 2018 09:02
Show Gist options
  • Save Meir017/48eef45a5b2525ce21ff12de0bae5dc0 to your computer and use it in GitHub Desktop.
Save Meir017/48eef45a5b2525ce21ff12de0bae5dc0 to your computer and use it in GitHub Desktop.
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