Skip to content

Commit b05341e

Browse files
committed
first draft
1 parent e9e52b4 commit b05341e

File tree

6 files changed

+285
-5
lines changed

6 files changed

+285
-5
lines changed

src/FluentAssertions.BestPractices.Tests/Tips/CollectionTests.cs

Lines changed: 177 additions & 1 deletion
Large diffs are not rendered by default.

src/FluentAssertions.BestPractices/Constants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public static class DiagnosticProperties
1111
public const string UnexpectedItemString = nameof(UnexpectedItemString);
1212
public const string BecauseArgumentsString = nameof(BecauseArgumentsString);
1313
public const string ArgumentString = nameof(ArgumentString);
14+
15+
public const string VisitorName = nameof(VisitorName);
1416
}
1517

1618
public static class Tips

src/FluentAssertions.BestPractices/Utilities/FluentAssertionsCSharpSyntaxVisitor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using Microsoft.CodeAnalysis.CSharp.Syntax;
33
using System.Collections.Generic;
44
using System.Collections.Immutable;
5-
using Microsoft.CodeAnalysis;
65

76
namespace FluentAssertions.BestPractices
87
{
@@ -29,7 +28,8 @@ protected FluentAssertionsCSharpSyntaxVisitor(params string[] requiredMethods)
2928

3029
public virtual ImmutableDictionary<string, string> ToDiagnosticProperties() => new Dictionary<string, string>
3130
{
32-
[Constants.DiagnosticProperties.VariableName] = VariableName
31+
[Constants.DiagnosticProperties.VariableName] = VariableName,
32+
[Constants.DiagnosticProperties.VisitorName] = GetType().Name
3333
}.ToImmutableDictionary();
3434

3535
public override void VisitInvocationExpression(InvocationExpressionSyntax node)

src/FluentAssertions.BestPractices/Utilities/FluentAssertionsCodeFixProvider.cs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using Microsoft.CodeAnalysis;
22
using Microsoft.CodeAnalysis.CodeActions;
33
using Microsoft.CodeAnalysis.CodeFixes;
4+
using Microsoft.CodeAnalysis.CSharp;
45
using Microsoft.CodeAnalysis.CSharp.Syntax;
6+
using System.Collections.Generic;
57
using System.Collections.Immutable;
68
using System.Linq;
79
using System.Threading;
@@ -33,15 +35,57 @@ protected async Task<Document> RewriteAssertion(Document document, ExpressionSta
3335
{
3436
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
3537

36-
var newStatement = GetNewStatement(statement, new FluentAssertionsDiagnosticProperties(properties)).WithTriviaFrom(statement);
38+
var newStatement = GetNewStatement(statement, new FluentAssertionsDiagnosticProperties(properties));
3739

3840
root = root.ReplaceNode(statement, newStatement);
3941

4042
return document.WithSyntaxRoot(root);
4143
}
4244

45+
protected virtual StatementSyntax GetNewStatement(FluentAssertionsDiagnosticProperties properties) => throw new System.NotImplementedException();
4346
protected virtual StatementSyntax GetNewStatement(ExpressionStatementSyntax statement, FluentAssertionsDiagnosticProperties properties) => GetNewStatement(properties);
4447

45-
protected abstract StatementSyntax GetNewStatement(FluentAssertionsDiagnosticProperties properties);
48+
protected ExpressionStatementSyntax GetNewStatement(ExpressionStatementSyntax statement, params NodeReplacement[] replacements)
49+
{
50+
var newStatement = statement;
51+
void UpdateRoot(NodeReplacement replacement)
52+
{
53+
var visitor = new MemberAccessExpressionsCSharpSyntaxVisitor();
54+
newStatement.Accept(visitor);
55+
var members = new LinkedList<MemberAccessExpressionSyntax>(visitor.Members);
56+
57+
var current = members.Last;
58+
while (current != null)
59+
{
60+
if (replacement.IsValidNode(current.Value))
61+
{
62+
newStatement = newStatement.ReplaceNode(replacement.ComputeOld(current), replacement.ComputeNew(current));
63+
return;
64+
}
65+
current = current.Previous;
66+
}
67+
}
68+
69+
foreach (var replacement in replacements)
70+
{
71+
UpdateRoot(replacement);
72+
}
73+
return newStatement;
74+
}
75+
76+
private class MemberAccessExpressionsCSharpSyntaxVisitor : CSharpSyntaxVisitor
77+
{
78+
public List<MemberAccessExpressionSyntax> Members { get; } = new List<MemberAccessExpressionSyntax>();
79+
80+
public override void VisitInvocationExpression(InvocationExpressionSyntax node) => Visit(node.Expression);
81+
82+
public sealed override void VisitExpressionStatement(ExpressionStatementSyntax node) => Visit(node.Expression);
83+
84+
public sealed override void VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
85+
{
86+
Members.Add(node);
87+
Visit(node.Expression);
88+
}
89+
}
4690
}
4791
}

src/FluentAssertions.BestPractices/Utilities/FluentAssertionsDiagnosticProperties.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ public FluentAssertionsDiagnosticProperties(ImmutableDictionary<string, string>
2020
public string UnexpectedItemString => GetPropertyOrDefault(Constants.DiagnosticProperties.UnexpectedItemString);
2121
public string ArgumentString => GetPropertyOrDefault(Constants.DiagnosticProperties.ArgumentString);
2222

23+
public string VisitorName => GetPropertyOrDefault(Constants.DiagnosticProperties.VisitorName);
24+
2325
public string CombineWithBecauseArgumentsString(string validArgument)
2426
{
2527
if (!string.IsNullOrWhiteSpace(BecauseArgumentsString) && !string.IsNullOrWhiteSpace(validArgument))
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using Microsoft.CodeAnalysis.CSharp.Syntax;
2+
using Microsoft.CodeAnalysis;
3+
using Microsoft.CodeAnalysis.CSharp;
4+
using System.Collections.Generic;
5+
6+
namespace FluentAssertions.BestPractices
7+
{
8+
public abstract class NodeReplacement
9+
{
10+
public abstract bool IsValidNode(MemberAccessExpressionSyntax node);
11+
public abstract SyntaxNode ComputeOld(LinkedListNode<MemberAccessExpressionSyntax> listNode);
12+
public abstract SyntaxNode ComputeNew(LinkedListNode<MemberAccessExpressionSyntax> listNode);
13+
public virtual void ExtractValues() { }
14+
15+
public class Rename : NodeReplacement
16+
{
17+
private readonly string _oldName;
18+
private readonly string _newName;
19+
20+
public Rename(string oldName, string newName)
21+
{
22+
_oldName = oldName;
23+
_newName = newName;
24+
}
25+
26+
public sealed override bool IsValidNode(MemberAccessExpressionSyntax node) => node.Name.Identifier.Text == _oldName;
27+
public sealed override SyntaxNode ComputeOld(LinkedListNode<MemberAccessExpressionSyntax> listNode) => listNode.Value;
28+
public override SyntaxNode ComputeNew(LinkedListNode<MemberAccessExpressionSyntax> listNode) => listNode.Value.WithName(SyntaxFactory.IdentifierName(_newName));
29+
}
30+
31+
public class Remove : NodeReplacement
32+
{
33+
private readonly string _name;
34+
35+
public Remove(string name)
36+
{
37+
_name = name;
38+
}
39+
40+
public sealed override bool IsValidNode(MemberAccessExpressionSyntax node) => node.Name.Identifier.Text == _name;
41+
public sealed override SyntaxNode ComputeOld(LinkedListNode<MemberAccessExpressionSyntax> listNode) => listNode.Previous.Value;
42+
public sealed override SyntaxNode ComputeNew(LinkedListNode<MemberAccessExpressionSyntax> listNode)
43+
{
44+
var previous = listNode.Previous.Value;
45+
var next = listNode.Next?.Value ?? listNode.Value.Expression;
46+
47+
if (next.Parent is InvocationExpressionSyntax invocation)
48+
{
49+
return previous.WithExpression(invocation);
50+
}
51+
52+
return previous.WithExpression(next);
53+
}
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)