in src/tools/Avalonia.Analyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs [116:292]
private void RegisterAvaloniaPropertySymbols(Compilation compilation, CancellationToken cancellationToken)
{
var namespaceStack = new Stack<INamespaceSymbol>();
namespaceStack.Push(compilation.GlobalNamespace);
var types = new List<INamedTypeSymbol>();
while (namespaceStack.Count > 0)
{
cancellationToken.ThrowIfCancellationRequested();
var current = namespaceStack.Pop();
types.AddRange(current.GetTypeMembers());
foreach (var child in current.GetNamespaceMembers())
{
namespaceStack.Push(child);
}
}
var avaloniaPropertyStorageSymbols = new ConcurrentBag<ISymbol>();
var propertyDescriptions = new ConcurrentDictionary<ISymbol, AvaloniaPropertyDescription>(SymbolEqualityComparer.Default);
// key initializes value
var fieldInitializations = new ConcurrentDictionary<ISymbol, ISymbol>(SymbolEqualityComparer.Default);
var parallelOptions = new ParallelOptions() { CancellationToken = cancellationToken };
var semanticModels = new ConcurrentDictionary<SyntaxTree, SemanticModel>();
Parallel.ForEach(types, parallelOptions, type =>
{
try
{
foreach (var member in type.GetMembers())
{
switch (member)
{
case IFieldSymbol fieldSymbol when IsAvaloniaPropertyStorage(fieldSymbol):
avaloniaPropertyStorageSymbols.Add(fieldSymbol);
break;
case IPropertySymbol propertySymbol when IsAvaloniaPropertyStorage(propertySymbol):
avaloniaPropertyStorageSymbols.Add(propertySymbol);
break;
}
}
foreach (var constructor in type.StaticConstructors)
{
foreach (var syntaxRef in constructor.DeclaringSyntaxReferences.Where(sr => compilation.ContainsSyntaxTree(sr.SyntaxTree)))
{
var (node, model) = GetNodeAndModel(syntaxRef);
foreach (var descendant in node.DescendantNodes().Where(n => n.IsKind(SyntaxKind.SimpleAssignmentExpression)))
{
if (model.GetOperation(descendant, cancellationToken) is IAssignmentOperation assignmentOperation &&
GetReferencedFieldOrProperty(assignmentOperation.Target, cancellationToken) is { } target)
{
RegisterAssignment(target, assignmentOperation.Value);
}
}
}
}
}
catch (Exception ex)
{
WrapAndThrowIfNotCancellation(ex, $"Failed to find AvaloniaProperty objects in {type}.", cancellationToken);
throw;
}
});
Parallel.ForEach(avaloniaPropertyStorageSymbols, parallelOptions, symbol =>
{
foreach (var syntaxRef in symbol.DeclaringSyntaxReferences.Where(sr => compilation.ContainsSyntaxTree(sr.SyntaxTree)))
{
var (node, model) = GetNodeAndModel(syntaxRef);
var operation = node.ChildNodes().Select(n => model.GetOperation(n, cancellationToken)).OfType<ISymbolInitializerOperation>().FirstOrDefault();
if (operation == null)
{
return;
}
RegisterAssignment(symbol, operation.Value);
}
});
// we have recorded every Register and AddOwner call. Now follow assignment chains.
Parallel.ForEach(fieldInitializations.Keys.Intersect(propertyDescriptions.Keys, SymbolEqualityComparer.Default).ToArray(), parallelOptions, root =>
{
var propertyDescription = propertyDescriptions[root];
var owner = propertyDescription.AssignedTo[root];
var seen = new HashSet<ISymbol>(SymbolEqualityComparer.Default);
var current = root;
do
{
cancellationToken.ThrowIfCancellationRequested();
if (!seen.Add(current))
{
break; // self-assignment, just stop processing if this happens
}
var target = fieldInitializations[current];
propertyDescription.SetAssignment(target, new(owner.Type, target.Locations[0])); // This loop handles simple assignment operations, so do NOT change the owner type
propertyDescriptions[target] = propertyDescription;
fieldInitializations.TryGetValue(target, out current);
}
while (current != null);
});
var clrPropertyWrapCandidates = new ConcurrentBag<(IPropertySymbol, AvaloniaPropertyDescription)>();
var propertyDescriptionsByName = propertyDescriptions.Values.ToLookup(p => p.Name, p => (property: p, owners: p.OwnerTypes.Select(t => t.Type).ToImmutableHashSet(SymbolEqualityComparer.Default)));
// Detect CLR properties that provide syntatic wrapping around an AvaloniaProperty (or potentially multiple, which leads to a warning diagnostic)
Parallel.ForEach(propertyDescriptions.Values, parallelOptions, propertyDescription =>
{
var nameMatches = propertyDescriptionsByName[propertyDescription.Name];
foreach (var ownerType in propertyDescription.OwnerTypes.Select(o => o.Type).Distinct(SymbolEqualityComparer<ITypeSymbol>.Default))
{
if (ownerType.GetMembers(propertyDescription.Name).OfType<IPropertySymbol>().SingleOrDefault() is not { IsStatic: false } clrProperty)
{
continue;
}
propertyDescription.AddPropertyWrapper(clrProperty);
clrPropertyWrapCandidates.Add((clrProperty, propertyDescription));
var current = ownerType.BaseType;
while (current != null)
{
cancellationToken.ThrowIfCancellationRequested();
foreach (var otherProp in nameMatches.Where(t => t.owners.Contains(current)).Select(t => t.property))
{
clrPropertyWrapCandidates.Add((clrProperty, otherProp));
}
current = current.BaseType;
}
}
});
// convert our dictionaries to immutable form
_clrPropertyToAvaloniaProperties = clrPropertyWrapCandidates.ToLookup(t => t.Item1, t => t.Item2, SymbolEqualityComparer<IPropertySymbol>.Default)
.ToImmutableDictionary(g => g.Key, g => g.Distinct().ToImmutableArray(), SymbolEqualityComparer<IPropertySymbol>.Default);
_avaloniaPropertyDescriptions = propertyDescriptions.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value.Seal(), SymbolEqualityComparer.Default);
void RegisterAssignment(ISymbol target, IOperation value)
{
switch (ResolveOperationSource(value, cancellationToken))
{
case IInvocationOperation invocation:
RegisterInitializer_Invocation(invocation, target, propertyDescriptions, cancellationToken);
break;
case IFieldReferenceOperation fieldRef when IsAvaloniaPropertyStorage(fieldRef.Field):
fieldInitializations[fieldRef.Field] = target;
break;
case IPropertyReferenceOperation propRef when IsAvaloniaPropertyStorage(propRef.Property):
fieldInitializations[propRef.Property] = target;
break;
}
}
(SyntaxNode, SemanticModel) GetNodeAndModel(SyntaxReference syntaxRef) =>
(syntaxRef.GetSyntax(cancellationToken), semanticModels.GetOrAdd(syntaxRef.SyntaxTree, st => compilation.GetSemanticModel(st)));
}