Skip to content

Commit 6418e24

Browse files
committed
Revert RoslynExtensions to the version in the main branch; move enhancements/extensions into a new project-specific class, RoslynExtensionsAutomaticInterface
1 parent c97bc87 commit 6418e24

2 files changed

Lines changed: 271 additions & 211 deletions

File tree

Lines changed: 53 additions & 211 deletions
Original file line numberDiff line numberDiff line change
@@ -1,242 +1,84 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Collections.Immutable;
34
using System.Linq;
4-
using System.Text;
5-
using System.Text.RegularExpressions;
65
using Microsoft.CodeAnalysis;
76
using Microsoft.CodeAnalysis.CSharp.Syntax;
87

9-
namespace DotnetAutomaticInterface
8+
namespace DotnetAutomaticInterface;
9+
10+
/// <summary>
11+
/// Source: https://github.com/dominikjeske/Samples/blob/main/SourceGenerators/HomeCenter.SourceGenerators/Extensions/RoslynExtensions.cs
12+
/// </summary>
13+
/// <remarks>
14+
/// Enhancements or additional extension methods should be placed in <see cref="RoslynExtensionsAutomaticInterface"/>
15+
/// </remarks>
16+
public static class RoslynExtensions
1017
{
11-
/// <summary>
12-
/// Source: https://github.com/dominikjeske/Samples/blob/main/SourceGenerators/HomeCenter.SourceGenerators/Extensions/RoslynExtensions.cs
13-
/// </summary>
14-
public static class RoslynExtensions
18+
private static IEnumerable<ITypeSymbol> GetBaseTypesAndThis(this ITypeSymbol type)
1519
{
16-
private static IEnumerable<ITypeSymbol> GetBaseTypesAndThis(this ITypeSymbol type)
17-
{
18-
var current = type;
19-
while (current != null)
20-
{
21-
yield return current;
22-
current = current.BaseType;
23-
}
24-
}
25-
26-
public static IEnumerable<ISymbol> GetAllMembers(this ITypeSymbol type)
20+
var current = type;
21+
while (current != null)
2722
{
28-
return type.GetBaseTypesAndThis().SelectMany(n => n.GetMembers());
23+
yield return current;
24+
current = current.BaseType;
2925
}
26+
}
3027

31-
public static string GetClassName(this ClassDeclarationSyntax proxy)
32-
{
33-
return proxy.Identifier.Text;
34-
}
35-
36-
/// <summary>
37-
/// Thanks to https://www.codeproject.com/Articles/871704/Roslyn-Code-Analysis-in-Easy-Samples-Part-2
38-
/// </summary>
39-
public static string GetWhereStatement(
40-
this ITypeParameterSymbol typeParameterSymbol,
41-
SymbolDisplayFormat typeDisplayFormat,
42-
List<string> generatedInterfaceNames
43-
)
44-
{
45-
var result = $"where {typeParameterSymbol.Name} : ";
46-
47-
var constraints = new List<string>();
48-
49-
if (typeParameterSymbol.HasReferenceTypeConstraint)
50-
{
51-
constraints.Add("class");
52-
}
53-
54-
if (typeParameterSymbol.HasValueTypeConstraint)
55-
{
56-
constraints.Add("struct");
57-
}
58-
59-
if (typeParameterSymbol.HasNotNullConstraint)
60-
{
61-
constraints.Add("notnull");
62-
}
63-
64-
constraints.AddRange(
65-
typeParameterSymbol.ConstraintTypes.Select(t =>
66-
t.ToDisplayString(typeDisplayFormat, generatedInterfaceNames)
67-
)
68-
);
69-
70-
// The new() constraint must be last
71-
if (typeParameterSymbol.HasConstructorConstraint)
72-
{
73-
constraints.Add("new()");
74-
}
28+
public static IEnumerable<ISymbol> GetAllMembers(this ITypeSymbol type)
29+
{
30+
return type.GetBaseTypesAndThis().SelectMany(n => n.GetMembers());
31+
}
7532

76-
if (constraints.Count == 0)
77-
{
78-
return "";
79-
}
33+
public static string GetClassName(this ClassDeclarationSyntax proxy)
34+
{
35+
return proxy.Identifier.Text;
36+
}
8037

81-
result += string.Join(", ", constraints);
38+
/// <summary>
39+
/// Thanks to https://www.codeproject.com/Articles/871704/Roslyn-Code-Analysis-in-Easy-Samples-Part-2
40+
/// </summary>
41+
public static string GetWhereStatement(
42+
this ITypeParameterSymbol typeParameterSymbol,
43+
SymbolDisplayFormat typeDisplayFormat
44+
)
45+
{
46+
var result = $"where {typeParameterSymbol.Name} : ";
8247

83-
return result;
84-
}
48+
var constraints = new List<string>();
8549

86-
public static string ToDisplayString(
87-
this IParameterSymbol symbol,
88-
SymbolDisplayFormat displayFormat,
89-
bool nullableContextEnabled,
90-
List<string> generatedInterfaceNames
91-
)
50+
if (typeParameterSymbol.HasReferenceTypeConstraint)
9251
{
93-
string? RenderTypeSymbolWithNullableAnnotation(SymbolDisplayPart part) =>
94-
part.Symbol is ITypeSymbol typeSymbol
95-
? typeSymbol
96-
.WithNullableAnnotation(NullableAnnotation.Annotated)
97-
.ToDisplayString(displayFormat)
98-
: null;
99-
100-
// Special case for reference parameters with default value null (e.g. string x = null) - the nullable
101-
// context isn't applied automatically, so it must be forced explicitly
102-
var forceNullableAnnotation =
103-
nullableContextEnabled
104-
&& symbol
105-
is {
106-
Type.IsReferenceType: true,
107-
HasExplicitDefaultValue: true,
108-
ExplicitDefaultValue: null
109-
}
110-
&& symbol.NullableAnnotation != NullableAnnotation.Annotated;
111-
112-
return ToDisplayString(
113-
symbol,
114-
displayFormat,
115-
generatedInterfaceNames,
116-
forceNullableAnnotation ? RenderTypeSymbolWithNullableAnnotation : null
117-
);
52+
constraints.Add("class");
11853
}
11954

120-
public static string ToDisplayString(
121-
this ITypeSymbol symbol,
122-
SymbolDisplayFormat displayFormat,
123-
List<string> generatedInterfaceNames
124-
) => ToDisplayString((ISymbol)symbol, displayFormat, generatedInterfaceNames);
125-
126-
/// <summary>
127-
/// Wraps <see cref="ITypeSymbol.ToDisplayString(Microsoft.CodeAnalysis.SymbolDisplayFormat?)" /> with custom resolution for generated types
128-
/// </summary>
129-
private static string ToDisplayString(
130-
this ISymbol symbol,
131-
SymbolDisplayFormat displayFormat,
132-
List<string> generatedInterfaceNames,
133-
Func<SymbolDisplayPart, string?>? customRenderDisplayPart = null
134-
)
55+
if (typeParameterSymbol.HasValueTypeConstraint)
13556
{
136-
var displayStringBuilder = new StringBuilder();
137-
138-
var displayParts = GetDisplayParts(symbol, displayFormat);
139-
140-
foreach (var part in displayParts)
141-
{
142-
if (part.Kind == SymbolDisplayPartKind.ErrorTypeName)
143-
{
144-
var unrecognisedName = part.ToString();
145-
146-
var inferredName = ReplaceWithInferredInterfaceName(
147-
unrecognisedName,
148-
generatedInterfaceNames
149-
);
150-
151-
displayStringBuilder.Append(inferredName);
152-
}
153-
else
154-
{
155-
var customRender = customRenderDisplayPart?.Invoke(part);
156-
displayStringBuilder.Append(customRender ?? part.ToString());
157-
}
158-
}
159-
160-
return displayStringBuilder.ToString();
57+
constraints.Add("struct");
16158
}
16259

163-
/// <summary>
164-
/// The same as <see cref="ISymbol.ToDisplayParts"/> but with adjacent SymbolDisplayParts merged into qualified type references, e.g. [Parent, ., Child] => Parent.Child
165-
/// </summary>
166-
private static IEnumerable<SymbolDisplayPart> GetDisplayParts(
167-
ISymbol symbol,
168-
SymbolDisplayFormat displayFormat
169-
)
60+
if (typeParameterSymbol.HasNotNullConstraint)
17061
{
171-
var cache = new List<SymbolDisplayPart>();
172-
173-
foreach (var part in symbol.ToDisplayParts(displayFormat))
174-
{
175-
if (cache.Count == 0)
176-
{
177-
cache.Add(part);
178-
continue;
179-
}
180-
181-
var previousPart = cache.Last();
182-
183-
if (
184-
IsPartQualificationPunctuation(previousPart)
185-
^ IsPartQualificationPunctuation(part)
186-
)
187-
{
188-
cache.Add(part);
189-
}
190-
else
191-
{
192-
yield return CombineQualifiedTypeParts(cache);
193-
cache.Clear();
194-
cache.Add(part);
195-
}
196-
}
197-
198-
if (cache.Count > 0)
199-
{
200-
yield return CombineQualifiedTypeParts(cache);
201-
}
202-
203-
static SymbolDisplayPart CombineQualifiedTypeParts(
204-
ICollection<SymbolDisplayPart> qualifiedTypeParts
205-
)
206-
{
207-
var qualifiedType = qualifiedTypeParts.Last();
62+
constraints.Add("notnull");
63+
}
20864

209-
return qualifiedTypeParts.Count == 1
210-
? qualifiedType
211-
: new SymbolDisplayPart(
212-
qualifiedType.Kind,
213-
qualifiedType.Symbol,
214-
string.Join("", qualifiedTypeParts)
215-
);
216-
}
65+
constraints.AddRange(
66+
typeParameterSymbol.ConstraintTypes.Select(t => t.ToDisplayString(typeDisplayFormat))
67+
);
21768

218-
static bool IsPartQualificationPunctuation(SymbolDisplayPart part) =>
219-
part.ToString() is "." or "::";
69+
// The new() constraint must be last
70+
if (typeParameterSymbol.HasConstructorConstraint)
71+
{
72+
constraints.Add("new()");
22073
}
22174

222-
private static string ReplaceWithInferredInterfaceName(
223-
string unrecognisedName,
224-
List<string> generatedInterfaceNames
225-
)
75+
if (constraints.Count == 0)
22676
{
227-
var matches = generatedInterfaceNames
228-
.Where(name => Regex.IsMatch(name, $"[.:]{Regex.Escape(unrecognisedName)}$"))
229-
.ToList();
77+
return "";
78+
}
23079

231-
if (matches.Count != 1)
232-
{
233-
// Either there's no match or an ambiguous match - we can't safely infer the interface name.
234-
// This is very much a "best effort" approach - if there are two interfaces with the same name,
235-
// there's no obvious way to work out which one the symbol is referring to.
236-
return unrecognisedName;
237-
}
80+
result += string.Join(", ", constraints);
23881

239-
return matches[0];
240-
}
82+
return result;
24183
}
24284
}

0 commit comments

Comments
 (0)