Browse Source

Added InappropriatePropertyRegistration and UnexpectedPropertyAccess

Fixed AttachedProperty registrations in non-AvaloniaObject types not being detected
Optimised parameter lookup
pull/10244/head
Tom Edwards 3 years ago
parent
commit
2b68e42e31
  1. 259
      src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs
  2. 98
      src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.cs

259
src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs

@ -31,6 +31,7 @@ public partial class AvaloniaPropertyAnalyzer
private readonly INamedTypeSymbol _avaloniaObjectType;
private readonly ImmutableHashSet<IMethodSymbol> _getValueMethods;
private readonly ImmutableHashSet<IMethodSymbol> _setValueMethods;
private readonly ImmutableHashSet<IMethodSymbol> _allGetSetMethods;
private readonly INamedTypeSymbol _avaloniaPropertyType;
private readonly INamedTypeSymbol _styledPropertyType;
private readonly INamedTypeSymbol _attachedPropertyType;
@ -38,6 +39,11 @@ public partial class AvaloniaPropertyAnalyzer
private readonly ImmutableArray<INamedTypeSymbol> _allAvaloniaPropertyTypes;
private readonly ImmutableHashSet<IMethodSymbol> _avaloniaPropertyRegisterMethods;
private readonly ImmutableHashSet<IMethodSymbol> _avaloniaPropertyAddOwnerMethods;
private readonly ImmutableHashSet<IMethodSymbol> _allAvaloniaPropertyMethods;
private readonly ImmutableDictionary<IMethodSymbol, ITypeParameterSymbol> _ownerTypeParams;
private readonly ImmutableDictionary<IMethodSymbol, ITypeParameterSymbol> _valueTypeParams;
private readonly ImmutableDictionary<IMethodSymbol, ITypeParameterSymbol> _hostTypeParams;
private readonly ImmutableDictionary<IMethodSymbol, IParameterSymbol> _inheritsParams;
public CompileAnalyzer(CompilationStartAnalysisContext context, INamedTypeSymbol avaloniaObjectType)
{
@ -47,6 +53,7 @@ public partial class AvaloniaPropertyAnalyzer
_avaloniaObjectType = avaloniaObjectType;
_getValueMethods = _avaloniaObjectType.GetMembers("GetValue").OfType<IMethodSymbol>().ToImmutableHashSet(methodComparer);
_setValueMethods = _avaloniaObjectType.GetMembers("SetValue").OfType<IMethodSymbol>().ToImmutableHashSet(methodComparer);
_allGetSetMethods = _getValueMethods.Concat(_setValueMethods).ToImmutableHashSet(methodComparer);
_avaloniaPropertyType = GetTypeOrThrow("Avalonia.AvaloniaProperty");
_styledPropertyType = GetTypeOrThrow("Avalonia.StyledProperty`1");
@ -61,11 +68,19 @@ public partial class AvaloniaPropertyAnalyzer
_avaloniaPropertyAddOwnerMethods = _allAvaloniaPropertyTypes
.SelectMany(t => t.GetMembers("AddOwner").OfType<IMethodSymbol>()).ToImmutableHashSet(methodComparer);
_allAvaloniaPropertyMethods = _avaloniaPropertyRegisterMethods.Concat(_avaloniaPropertyAddOwnerMethods).ToImmutableHashSet(SymbolEqualityComparer<IMethodSymbol>.Default);
_ownerTypeParams = GetParamDictionary("TOwner", m => m.TypeParameters);
_valueTypeParams = GetParamDictionary("TValue", m => m.TypeParameters);
_hostTypeParams = GetParamDictionary("THost", m => m.TypeParameters);
_inheritsParams = GetParamDictionary("inherits", m => m.Parameters);
RegisterAvaloniaPropertySymbols(context.Compilation, context.CancellationToken);
context.RegisterOperationAction(AnalyzeFieldInitializer, OperationKind.FieldInitializer);
context.RegisterOperationAction(AnalyzePropertyInitializer, OperationKind.PropertyInitializer);
context.RegisterOperationAction(AnalyzeAssignment, OperationKind.SimpleAssignment);
context.RegisterOperationAction(AnalyzeMethodInvocation, OperationKind.Invocation);
context.RegisterSymbolStartAction(StartPropertySymbolAnalysis, SymbolKind.Property);
@ -75,6 +90,11 @@ public partial class AvaloniaPropertyAnalyzer
}
INamedTypeSymbol GetTypeOrThrow(string name) => context.Compilation.GetTypeByMetadataName(name) ?? throw new KeyNotFoundException($"Could not locate {name} in the compilation context.");
ImmutableDictionary<IMethodSymbol, TSymbol> GetParamDictionary<TSymbol>(string name, Func<IMethodSymbol, IEnumerable<TSymbol>> methodSymbolSelector) where TSymbol : ISymbol => _allAvaloniaPropertyMethods
.Select(m => methodSymbolSelector(m).SingleOrDefault(p => p.Name == name))
.Where(p => p != null).Cast<TSymbol>()
.ToImmutableDictionary(p => (IMethodSymbol)p.ContainingSymbol, SymbolEqualityComparer<IMethodSymbol>.Default);
}
private bool IsAvaloniaPropertyStorage(IFieldSymbol symbol) => symbol.Type is INamedTypeSymbol namedType && IsAvaloniaPropertyType(namedType, _allAvaloniaPropertyTypes);
@ -91,13 +111,7 @@ public partial class AvaloniaPropertyAnalyzer
{
var current = namespaceStack.Pop();
foreach (var type in current.GetTypeMembers())
{
if (DerivesFrom(type, _avaloniaObjectType))
{
types.Add(type);
}
}
types.AddRange(current.GetTypeMembers());
foreach (var child in current.GetNamespaceMembers())
{
@ -114,6 +128,8 @@ public partial class AvaloniaPropertyAnalyzer
var parallelOptions = new ParallelOptions() { CancellationToken = cancellationToken };
var semanticModels = new ConcurrentDictionary<SyntaxTree, SemanticModel>();
Parallel.ForEach(types, parallelOptions, type =>
{
try
@ -133,38 +149,17 @@ public partial class AvaloniaPropertyAnalyzer
foreach (var constructor in type.StaticConstructors)
{
foreach (var syntaxRef in constructor.DeclaringSyntaxReferences)
foreach (var syntaxRef in constructor.DeclaringSyntaxReferences.Where(sr => compilation.ContainsSyntaxTree(sr.SyntaxTree)))
{
var node = syntaxRef.GetSyntax(cancellationToken);
if (!compilation.ContainsSyntaxTree(node.SyntaxTree))
{
continue;
}
var model = compilation.GetSemanticModel(node.SyntaxTree);
var (node, model) = GetNodeAndModel(syntaxRef);
foreach (var descendant in node.DescendantNodes())
foreach (var descendant in node.DescendantNodes().Where(n => n.IsKind(SyntaxKind.SimpleAssignmentExpression)))
{
switch (descendant.Kind())
{
case SyntaxKind.SimpleAssignmentExpression:
var assignmentOperation = (IAssignmentOperation)model.GetOperation(descendant, cancellationToken)!;
var target = assignmentOperation.Target switch
{
IFieldReferenceOperation fieldRef => fieldRef.Field,
IPropertyReferenceOperation propertyRef => propertyRef.Property,
_ => default(ISymbol),
};
if (target == null)
{
break;
}
var assignmentOperation = (IAssignmentOperation)model.GetOperation(descendant, cancellationToken)!;
RegisterAssignment(target, assignmentOperation.Value);
break;
if (GetReferencedFieldOrProperty(assignmentOperation.Target) is { } target)
{
RegisterAssignment(target, assignmentOperation.Value);
}
}
}
@ -178,15 +173,10 @@ public partial class AvaloniaPropertyAnalyzer
Parallel.ForEach(avaloniaPropertyStorageSymbols, parallelOptions, symbol =>
{
foreach (var syntaxRef in symbol.DeclaringSyntaxReferences)
foreach (var syntaxRef in symbol.DeclaringSyntaxReferences.Where(sr => compilation.ContainsSyntaxTree(sr.SyntaxTree)))
{
var node = syntaxRef.GetSyntax(cancellationToken);
if (!compilation.ContainsSyntaxTree(node.SyntaxTree))
{
continue;
}
var (node, model) = GetNodeAndModel(syntaxRef);
var model = compilation.GetSemanticModel(node.SyntaxTree);
var operation = node.ChildNodes().Select(n => model.GetOperation(n, cancellationToken)).OfType<ISymbolInitializerOperation>().FirstOrDefault();
if (operation == null)
@ -199,7 +189,7 @@ public partial class AvaloniaPropertyAnalyzer
});
// we have recorded every Register and AddOwner call. Now follow assignment chains.
foreach (var root in fieldInitializations.Keys.Intersect(propertyDescriptions.Keys, SymbolEqualityComparer.Default).ToArray())
Parallel.ForEach(fieldInitializations.Keys.Intersect(propertyDescriptions.Keys, SymbolEqualityComparer.Default).ToArray(), root =>
{
var propertyDescription = propertyDescriptions[root];
var owner = propertyDescription.AssignedTo[root];
@ -209,13 +199,13 @@ public partial class AvaloniaPropertyAnalyzer
{
var target = fieldInitializations[current];
propertyDescription.AddAssignment(target, owner); // This loop handles simple assignment operations, so do NOT change the owner
propertyDescription.AddAssignment(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)>();
@ -251,7 +241,7 @@ public partial class AvaloniaPropertyAnalyzer
// convert our dictionaries to immutable form
_clrPropertyToAvaloniaProperties = clrPropertyWrapCandidates.ToLookup(t => t.Item1, t => t.Item2, SymbolEqualityComparer<IPropertySymbol>.Default)
.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.ToImmutableArray(), 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)
@ -269,8 +259,12 @@ public partial class AvaloniaPropertyAnalyzer
break;
}
}
(SyntaxNode, SemanticModel) GetNodeAndModel(SyntaxReference syntaxRef) =>
(syntaxRef.GetSyntax(cancellationToken), semanticModels.GetOrAdd(syntaxRef.SyntaxTree, st => compilation.GetSemanticModel(st)));
}
// This method handles registration of a new AvaloniaProperty, and calls to AddOwner.
private void RegisterInitializer_Invocation(IInvocationOperation invocation, ISymbol target, ConcurrentDictionary<ISymbol, AvaloniaPropertyDescription> propertyDescriptions)
{
try
@ -280,49 +274,38 @@ public partial class AvaloniaPropertyAnalyzer
return;
}
if (_avaloniaPropertyRegisterMethods.Contains(invocation.TargetMethod.OriginalDefinition)) // This is a call to one of the AvaloniaProperty.Register* methods
var originalMethod = invocation.TargetMethod.OriginalDefinition;
if (_avaloniaPropertyRegisterMethods.Contains(originalMethod)) // This is a call to one of the AvaloniaProperty.Register* methods
{
if (!invocation.TargetMethod.IsGenericMethod)
TypeReference ownerTypeRef;
if (_ownerTypeParams.TryGetValue(originalMethod, out var ownerTypeParam))
{
return;
ownerTypeRef = TypeReference.FromInvocationTypeParameter(invocation, ownerTypeParam);
}
var typeParamLookup = invocation.TargetMethod.TypeParameters.Select((s, i) => (param: s, index: i))
.ToDictionary(t => t.param.Name, t =>
{
var argument = invocation.TargetMethod.TypeArguments[t.index];
var typeArgumentSyntax = invocation.Syntax;
if (invocation.Language == LanguageNames.CSharp) // type arguments do not appear in the invocation, so search the code for them
{
try
{
typeArgumentSyntax = invocation.Syntax.DescendantNodes()
.First(n => n.IsKind(SyntaxKind.TypeArgumentList))
.DescendantNodes().ElementAt(t.index);
}
catch
{
// ignore, this is unimportant
}
}
return new TypeReference((INamedTypeSymbol)argument, typeArgumentSyntax.GetLocation());
});
if (!typeParamLookup.TryGetValue("TOwner", out var ownerTypeRef) && // if it's NOT a generic parameter, try to work out the runtime value
invocation.TargetMethod.Parameters.FirstOrDefault(p => p.Name == "ownerType") is INamedTypeSymbol ownerTypeParam &&
invocation.Arguments.FirstOrDefault(a => SymbolEquals(a.Parameter, ownerTypeParam)) is IArgumentOperation argument)
else if (invocation.TargetMethod.Parameters.FirstOrDefault(p => p.Name == "ownerType") is { } ownerParam) // try extracting the runtime argument
{
switch (ResolveOperationSource(argument.Value))
switch (ResolveOperationSource(invocation.Arguments[ownerParam.Ordinal].Value))
{
case ITypeOfOperation typeOf when typeOf.Type is INamedTypeSymbol type:
case ITypeOfOperation { Type: INamedTypeSymbol type } typeOf:
ownerTypeRef = new TypeReference(type, typeOf.Syntax.GetLocation());
break;
default:
return;
}
}
else
{
return;
}
if (ownerTypeRef.Type == null || !typeParamLookup.TryGetValue("TValue", out var propertyValueTypeRef))
TypeReference valueTypeRef;
if (_valueTypeParams.TryGetValue(originalMethod, out var valueTypeParam))
{
valueTypeRef = TypeReference.FromInvocationTypeParameter(invocation, valueTypeParam);
}
else
{
return;
}
@ -343,47 +326,69 @@ public partial class AvaloniaPropertyAnalyzer
return;
}
var description = propertyDescriptions.GetOrAdd(target, s => new AvaloniaPropertyDescription(name, propertyType, propertyValueTypeRef.Type));
var inherits = false;
if (_inheritsParams.TryGetValue(originalMethod, out var inheritsParam) &&
invocation.Arguments[inheritsParam.Ordinal].Value is ILiteralOperation literalOp &&
literalOp.ConstantValue.Value is bool constValue)
{
inherits = constValue;
}
TypeReference? hostTypeRef = null;
if (SymbolEquals(propertyType.OriginalDefinition, _attachedPropertyType))
{
if (_hostTypeParams.TryGetValue(originalMethod, out var hostTypeParam))
{
hostTypeRef = TypeReference.FromInvocationTypeParameter(invocation, hostTypeParam);
}
else
{
hostTypeRef = new(_avaloniaObjectType, Location.None);
}
}
var description = propertyDescriptions.GetOrAdd(target, s => new AvaloniaPropertyDescription(name, propertyType, valueTypeRef.Type));
description.Name = name;
description.HostType = hostTypeRef;
description.Inherits = inherits;
description.AddAssignment(target, ownerTypeRef);
description.AddOwner(ownerTypeRef);
}
else if (_avaloniaPropertyAddOwnerMethods.Contains(invocation.TargetMethod.OriginalDefinition)) // This is a call to one of the AddOwner methods
{
if (invocation.TargetMethod.TypeArguments[0] is not INamedTypeSymbol ownerType)
if (!_ownerTypeParams.TryGetValue(invocation.TargetMethod.OriginalDefinition, out var ownerTypeParam) ||
invocation.TargetMethod.TypeArguments[ownerTypeParam.Ordinal] is not INamedTypeSymbol)
{
return;
}
var ownerTypeRef = new TypeReference(ownerType, invocation.TargetMethod.TypeArguments[0].Locations[0]);
ISymbol sourceSymbol;
switch (invocation.Instance)
if (GetReferencedFieldOrProperty(invocation.Instance) is not { } sourceSymbol)
{
case IFieldReferenceOperation fieldReference:
sourceSymbol = fieldReference.Field;
break;
case IPropertyReferenceOperation propertyReference:
sourceSymbol = propertyReference.Property;
break;
default:
return;
return;
}
var propertyValueType = AvaloniaPropertyType_GetValueType(propertyType);
var description = propertyDescriptions.GetOrAdd(target, s =>
var description = propertyDescriptions.GetOrAdd(sourceSymbol, s =>
{
string inferredName = target.Name;
string inferredName = s.Name;
var match = Regex.Match(target.Name, "(?<name>.*)Property$");
var match = Regex.Match(s.Name, "(?<name>.*)Property$");
if (match.Success)
{
inferredName = match.Groups["name"].Value;
}
return new AvaloniaPropertyDescription(inferredName, (INamedTypeSymbol)invocation.TargetMethod.ReturnType, propertyValueType);
var propertyValueType = AvaloniaPropertyType_GetValueType(propertyType);
TypeReference? hostTypeRef = null;
if (SymbolEquals(propertyType.OriginalDefinition, _attachedPropertyType))
{
hostTypeRef = new(_avaloniaObjectType, Location.None); // assume that an attached property applies everywhere until we find its registration
}
return new AvaloniaPropertyDescription(inferredName, propertyType, propertyValueType) { HostType = hostTypeRef };
});
var ownerTypeRef = TypeReference.FromInvocationTypeParameter(invocation, ownerTypeParam);
description.AddAssignment(target, ownerTypeRef);
description.AddOwner(ownerTypeRef);
}
@ -458,7 +463,7 @@ public partial class AvaloniaPropertyAnalyzer
try
{
var (target, isValid) = operation.Target switch
var (target, isValid) = ResolveOperationSource(operation.Target) switch
{
IFieldReferenceOperation fieldRef => (fieldRef.Field, IsValidAvaloniaPropertyStorage(fieldRef.Field)),
IPropertyReferenceOperation propertyRef => (propertyRef.Property, IsValidAvaloniaPropertyStorage(propertyRef.Property)),
@ -508,6 +513,58 @@ public partial class AvaloniaPropertyAnalyzer
}
}
/// <seealso cref="UnexpectedPropertyAccess"/>
/// <seealso cref="InappropriatePropertyRegistration"/>
private void AnalyzeMethodInvocation(OperationAnalysisContext context)
{
var invocation = (IInvocationOperation)context.Operation;
var originalMethod = invocation.TargetMethod.OriginalDefinition;
if (_allGetSetMethods.Contains(originalMethod))
{
var avaloniaPropertyOperation = invocation.Arguments[0].Value;
var propertyStorageSymbol = GetReferencedFieldOrProperty(ResolveOperationSource(avaloniaPropertyOperation));
if (propertyStorageSymbol == null || !_avaloniaPropertyDescriptions.TryGetValue(propertyStorageSymbol, out var propertyDescription))
{
return;
}
TypeReference ownerOrHostType;
if (SymbolEquals(propertyDescription.PropertyType.OriginalDefinition, _attachedPropertyType))
{
ownerOrHostType = propertyDescription.HostType!.Value;
}
else if (!propertyDescription.AssignedTo.TryGetValue(propertyStorageSymbol, out ownerOrHostType))
{
return;
}
if (invocation.Instance is IInstanceReferenceOperation { ReferenceKind: InstanceReferenceKind.ContainingTypeInstance } &&
!DerivesFrom(context.ContainingSymbol.ContainingType, ownerOrHostType.Type))
{
context.ReportDiagnostic(Diagnostic.Create(UnexpectedPropertyAccess, invocation.Arguments[0].Syntax.GetLocation(),
GetReferencedFieldOrProperty(avaloniaPropertyOperation), context.ContainingSymbol.ContainingType));
}
}
else if (!IsStaticConstructorOrInitializer() && _allAvaloniaPropertyMethods.Contains(originalMethod))
{
context.ReportDiagnostic(Diagnostic.Create(InappropriatePropertyRegistration, invocation.Syntax.GetLocation(),
originalMethod.ToDisplayString(TypeQualifiedName)));
}
bool IsStaticConstructorOrInitializer() =>
context.ContainingSymbol is IMethodSymbol { MethodKind: MethodKind.StaticConstructor } ||
ResolveOperationTarget(invocation.Parent!) switch
{
IFieldInitializerOperation fieldInit when fieldInit.InitializedFields.All(f => f.IsStatic) => true,
IPropertyInitializerOperation propInit when propInit.InitializedProperties.All(p => p.IsStatic) => true,
_ => false,
};
}
/// <seealso cref="AmbiguousPropertyName"/>
/// <seealso cref="PropertyTypeMismatch"/>
/// <seealso cref="AssociatedAvaloniaProperty"/>
@ -520,7 +577,7 @@ public partial class AvaloniaPropertyAnalyzer
{
if (!_clrPropertyToAvaloniaProperties.TryGetValue(property, out var candidateTargetProperties))
{
return; // does not refer to an AvaloniaProperty
return; // does not refer to an AvaloniaProperty
}
context.RegisterSymbolEndAction(context =>

98
src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.cs

@ -6,6 +6,7 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.Serialization;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
@ -18,7 +19,8 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer
private const string TypeMismatchTag = "TypeMismatch";
private const string NameCollisionTag = "NameCollision";
private const string AssociatedPropertyTag = "AssociatedProperty";
private const string AssociatedClrPropertyTag = "AssociatedClrProperty";
private const string InappropriateReadWriteTag = "InappropriateReadWrite";
private static readonly DiagnosticDescriptor AssociatedAvaloniaProperty = new(
"AVP0001",
@ -28,7 +30,7 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer
DiagnosticSeverity.Info,
isEnabledByDefault: false,
"This informational diagnostic identifies which AvaloniaProperty a CLR property is associated with.",
AssociatedPropertyTag);
AssociatedClrPropertyTag);
private static readonly DiagnosticDescriptor InappropriatePropertyAssignment = new(
"AVP1000",
@ -39,9 +41,18 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer
isEnabledByDefault: true,
"AvaloniaProperty objects have static lifetimes and should be stored accordingly. Do not multiply construct the same property.");
private static readonly DiagnosticDescriptor InappropriatePropertyRegistration = new(
"AVP1001",
"Ensure that the same AvaloniaProperty cannot be registered twice",
"Unsafe registration: {0} should be called only in static constructors or static initializers",
Category,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
"AvaloniaProperty objects have static lifetimes and should be created only once. To ensure this, only call Register or AddOwner in static constructors or static initializers.");
private static readonly DiagnosticDescriptor OwnerDoesNotMatchOuterType = new(
"AVP1010",
"Avaloniaproperty objects should declare their owner to be the type in which they are stored",
"AvaloniaProperty objects should be owned be the type in which they are stored",
"Type mismatch: AvaloniaProperty owner is {0}, which is not the containing type",
Category,
DiagnosticSeverity.Warning,
@ -49,6 +60,16 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer
"The owner of an AvaloniaProperty should generally be the containing type. This ensures that the property can be used as expected in XAML.",
TypeMismatchTag);
private static readonly DiagnosticDescriptor UnexpectedPropertyAccess = new(
"AVP1011",
"An AvaloniaObject should be the owner of each AvaloniaProperty it reads or writes on itself",
"Unexpected property use: {0} is neither owned by nor attached to {1}",
Category,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
"It is possible to use any AvaloniaProperty with any AvaloniaObject. However, each AvaloniaProperty an object uses on itself should be either owned by that object, or attached to that object.",
InappropriateReadWriteTag);
private static readonly DiagnosticDescriptor DuplicatePropertyName = new(
"AVP1020",
"AvaloniaProperty names should be unique within each class",
@ -87,7 +108,7 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
"The AvaloniaObject.GetValue and AvaloniaObject.SetValue methods are public, and do not call any user CLR properties. To execute code before or after the property is set, create a Coerce method or a PropertyChanged subscriber.",
AssociatedPropertyTag);
AssociatedClrPropertyTag);
private static readonly DiagnosticDescriptor MissingAccessor = new(
"AVP1031",
@ -97,7 +118,7 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
"The AvaloniaObject.GetValue and AvaloniaObject.SetValue methods are public, and do not call CLR properties on the owning type. Not providing both CLR property accessors is ineffective.",
AssociatedPropertyTag);
AssociatedClrPropertyTag);
private static readonly DiagnosticDescriptor InconsistentAccessibility = new(
"AVP1032",
@ -107,7 +128,7 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
"The AvaloniaObject.GetValue and AvaloniaObject.SetValue methods are public, and do not call CLR properties on the owning type. Defining a CLR property with different acessibility from its associated AvaloniaProperty is ineffective.",
AssociatedPropertyTag);
AssociatedClrPropertyTag);
private static readonly DiagnosticDescriptor PropertyTypeMismatch = new(
"AVP1040",
@ -117,12 +138,18 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
"The AvaloniaObject.GetValue and AvaloniaObject.SetValue methods are public, and do not call CLR properties on the owning type. A CLR property changing the value type (even when an implicit cast is possible) is ineffective and can lead to InvalidCastException to be thrown.",
TypeMismatchTag, AssociatedPropertyTag);
TypeMismatchTag, AssociatedClrPropertyTag);
private static readonly SymbolDisplayFormat TypeQualifiedName = new(
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes,
memberOptions: SymbolDisplayMemberOptions.IncludeContainingType);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(
AssociatedAvaloniaProperty,
InappropriatePropertyAssignment,
InappropriatePropertyRegistration,
OwnerDoesNotMatchOuterType,
UnexpectedPropertyAccess,
DuplicatePropertyName,
AmbiguousPropertyName,
PropertyNameMismatch,
@ -193,6 +220,31 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer
}
}
private static IOperation ResolveOperationTarget(IOperation operation)
{
while (true)
{
switch (operation)
{
case IConversionOperation conversion:
operation = conversion.Parent!;
break;
case ISimpleAssignmentOperation assignment:
operation = assignment.Target;
break;
default:
return operation;
}
}
}
private static ISymbol? GetReferencedFieldOrProperty(IOperation? operation) => operation == null ? null : ResolveOperationSource(operation) switch
{
IFieldReferenceOperation fieldRef => fieldRef.Field,
IPropertyReferenceOperation propertyRef => propertyRef.Property,
_ => null,
};
private static bool IsValidAvaloniaPropertyStorage(IFieldSymbol field) => field.IsStatic && field.IsReadOnly;
private static bool IsValidAvaloniaPropertyStorage(IPropertySymbol field) => field.IsStatic && field.IsReadOnly;
@ -228,12 +280,22 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer
/// </summary>
public INamedTypeSymbol ValueType { get; }
/// <summary>
/// Gets whether the value of this property is inherited from the parent AvaloniaObject.
/// </summary>
public bool Inherits { get; set; }
/// <summary>
/// Gets the type which registered the property, and all types which have added themselves as owners.
/// </summary>
public IReadOnlyCollection<TypeReference> OwnerTypes { get; private set; }
private ConcurrentBag<TypeReference>? _ownerTypes = new();
/// <summary>
/// Gets the type to which an AttachedProperty is attached, or null if the property is StyledProperty or DirectProperty.
/// </summary>
public TypeReference? HostType { get; set; }
/// <summary>
/// Gets a dictionary which maps fields and properties which were initialized with this AvaloniaProperty to the TOwner specified at each assignment.
/// </summary>
@ -314,6 +376,28 @@ public partial class AvaloniaPropertyAnalyzer : DiagnosticAnalyzer
Type = type;
Location = location;
}
public static TypeReference FromInvocationTypeParameter(IInvocationOperation invocation, ITypeParameterSymbol typeParameter)
{
var argument = invocation.TargetMethod.TypeArguments[typeParameter.Ordinal];
var typeArgumentSyntax = invocation.Syntax;
if (invocation.Language == LanguageNames.CSharp) // type arguments do not appear in the invocation, so search the code for them
{
try
{
typeArgumentSyntax = invocation.Syntax.DescendantNodes()
.First(n => n.IsKind(SyntaxKind.TypeArgumentList))
.DescendantNodes().ElementAt(typeParameter.Ordinal);
}
catch
{
// ignore, this is just a nicety
}
}
return new TypeReference((INamedTypeSymbol)argument, typeArgumentSyntax.GetLocation());
}
}
private class SymbolEqualityComparer<T> : IEqualityComparer<T> where T : ISymbol

Loading…
Cancel
Save