forked from fluentassertions/fluentassertions
-
Notifications
You must be signed in to change notification settings - Fork 0
/
MemberPath.cs
131 lines (107 loc) · 4.69 KB
/
MemberPath.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions.Equivalency;
namespace FluentAssertions.Common
{
/// <summary>
/// Encapsulates a dotted candidate to a (nested) member of a type as well as the
/// declaring type of the deepest member.
/// </summary>
internal class MemberPath
{
private readonly string dottedPath;
private readonly Type reflectedType;
private readonly Type declaringType;
private string[] segments;
private static readonly MemberPathSegmentEqualityComparer MemberPathSegmentEqualityComparer = new();
public MemberPath(IMember member, string parentPath)
: this(member.ReflectedType, member.DeclaringType, parentPath.Combine(member.Name))
{
}
public MemberPath(Type reflectedType, Type declaringType, string dottedPath)
: this(dottedPath)
{
this.reflectedType = reflectedType;
this.declaringType = declaringType;
}
public MemberPath(string dottedPath)
{
Guard.ThrowIfArgumentIsNull(
dottedPath, nameof(dottedPath),
"A member path cannot be null");
this.dottedPath = dottedPath;
}
/// <summary>
/// Gets a value indicating whether the current object represents a child member of the <paramref name="candidate"/>
/// or that it is the parent of that candidate.
/// </summary>
public bool IsParentOrChildOf(MemberPath candidate)
{
return IsParentOf(candidate) || IsChildOf(candidate);
}
public bool IsSameAs(MemberPath candidate)
{
if ((declaringType == candidate.declaringType) || declaringType?.IsAssignableFrom(candidate.reflectedType) == true)
{
string[] candidateSegments = candidate.Segments;
return candidateSegments.SequenceEqual(Segments, MemberPathSegmentEqualityComparer);
}
return false;
}
private bool IsParentOf(MemberPath candidate)
{
string[] candidateSegments = candidate.Segments;
return candidateSegments.Length > Segments.Length &&
candidateSegments.Take(Segments.Length).SequenceEqual(Segments, MemberPathSegmentEqualityComparer);
}
private bool IsChildOf(MemberPath candidate)
{
string[] candidateSegments = candidate.Segments;
return candidateSegments.Length < Segments.Length
&& candidateSegments.SequenceEqual(Segments.Take(candidateSegments.Length),
MemberPathSegmentEqualityComparer);
}
public MemberPath AsParentCollectionOf(MemberPath nextPath)
{
var extendedDottedPath = dottedPath.Combine(nextPath.dottedPath, "[]");
return new MemberPath(declaringType, nextPath.reflectedType, extendedDottedPath);
}
/// <summary>
/// Determines whether the current path is the same as <paramref name="path"/> when ignoring any specific indexes.
/// </summary>
public bool IsEquivalentTo(string path)
{
return path.WithoutSpecificCollectionIndices() == dottedPath.WithoutSpecificCollectionIndices();
}
public bool HasSameParentAs(MemberPath path)
{
return Segments.Length == path.Segments.Length
&& GetParentSegments().SequenceEqual(path.GetParentSegments(), MemberPathSegmentEqualityComparer);
}
private IEnumerable<string> GetParentSegments() => Segments.Take(Segments.Length - 1);
/// <summary>
/// Gets a value indicating whether the current path contains an indexer like `[1]` instead of `[]`.
/// </summary>
public bool GetContainsSpecificCollectionIndex() => dottedPath.ContainsSpecificCollectionIndex();
private string[] Segments =>
segments ??= dottedPath
.Replace("[]", "[*]", StringComparison.Ordinal)
.Split(new[] { '.', '[', ']' }, StringSplitOptions.RemoveEmptyEntries);
/// <summary>
/// Returns a copy of the current object as if it represented an un-indexed item in a collection.
/// </summary>
public MemberPath WithCollectionAsRoot()
{
return new MemberPath(reflectedType, declaringType, "[]." + dottedPath);
}
/// <summary>
/// Returns the name of the member the current path points to without its parent path.
/// </summary>
public string MemberName => Segments.Last();
public override string ToString()
{
return dottedPath;
}
}
}