public void TrySet(string trySetCode, AnalysisResult expected) { var testCode = @" namespace RoslynSandbox { using System.Collections.Generic; using System.ComponentModel; using System.Runtime.CompilerServices; public class Foo : INotifyPropertyChanged { private int value; public event PropertyChangedEventHandler PropertyChanged; public int Value { get => this.value; set => this.TrySet(ref this.value, value, nameof(Value)); } protected bool TrySet<T>(ref T field, T newValue, string propertyName) { if (EqualityComparer<T>.Default.Equals(field, newValue)) { return false; } field = newValue; this.OnPropertyChanged(propertyName); return true; } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }"; testCode = testCode.AssertReplace("this.TrySet(ref this.value, value, nameof(Value))", trySetCode); var syntaxTree = CSharpSyntaxTree.ParseText(testCode); var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); var semanticModel = compilation.GetSemanticModel(syntaxTree); var node = syntaxTree.FindArgument("ref this.value"); var property = semanticModel.GetDeclaredSymbol(syntaxTree.FindPropertyDeclaration("Value")); Assert.AreEqual(expected, PropertyChanged.InvokesPropertyChangedFor(node, property, semanticModel, CancellationToken.None)); }
public void WhenRecursive(string signature, string propertyName) { var syntaxTree = CSharpSyntaxTree.ParseText( @" namespace RoslynSandbox { using System; using System.ComponentModel; using System.Linq.Expressions; using System.Runtime.CompilerServices; public class Foo : INotifyPropertyChanged { private int _value1; private int _value2; private int _value3; public event PropertyChangedEventHandler PropertyChanged; public int Value1 { get { return _value1; } set { if (value == _value1) { return; } _value1 = value; OnPropertyChanged(); } } public int Value2 { get { return _value2; } set { if (value == _value2) { return; } _value2 = value; OnPropertyChanged(() => this.Value2); } } public int Value3 { get { return _value3; } set { TrySet(ref _value3, value); } } protected bool TrySet<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null) { if (TrySet<T>(ref field, newValue, propertyName)) { return false; } field = newValue; this.OnPropertyChanged(propertyName); return true; } protected virtual void OnPropertyChanged<T>(Expression<Func<T>> property) { this.OnPropertyChanged(property); } protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { this.OnPropertyChanged(e); } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { this.OnPropertyChanged(propertyName); } } }"); var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); var semanticModel = compilation.GetSemanticModel(syntaxTree); var node = syntaxTree.Find <SyntaxNode>(signature); var property = semanticModel.GetDeclaredSymbol(syntaxTree.FindPropertyDeclaration(propertyName)); Assert.AreEqual(AnalysisResult.No, PropertyChanged.InvokesPropertyChangedFor(node, property, semanticModel, CancellationToken.None)); }
private static void Handle(SyntaxNodeAnalysisContext context, IFieldSymbol assignedField) { if (context.IsExcludedFromAnalysis()) { return; } if (IsInIgnoredScope(context)) { return; } var typeDeclaration = context.Node.FirstAncestorOrSelf <TypeDeclarationSyntax>(); var typeSymbol = context.SemanticModel.GetDeclaredSymbolSafe(typeDeclaration, context.CancellationToken); if (!typeSymbol.Is(KnownSymbol.INotifyPropertyChanged)) { return; } if (!typeSymbol.Equals(assignedField.ContainingType)) { return; } var inProperty = context.Node.FirstAncestorOrSelf <PropertyDeclarationSyntax>(); if (inProperty != null) { if (Property.IsSimplePropertyWithBackingField(inProperty, context.SemanticModel, context.CancellationToken)) { return; } } using (var pooledSet = SetPool <IPropertySymbol> .Create()) { foreach (var member in typeDeclaration.Members) { var propertyDeclaration = member as PropertyDeclarationSyntax; if (propertyDeclaration == null || Property.IsLazy(propertyDeclaration, context.SemanticModel, context.CancellationToken)) { continue; } var property = context.SemanticModel.GetDeclaredSymbolSafe(propertyDeclaration, context.CancellationToken); var getter = GetterBody(propertyDeclaration); if (getter == null || property == null || property.DeclaredAccessibility != Accessibility.Public) { continue; } var accessor = context.Node.FirstAncestorOrSelf <AccessorDeclarationSyntax>(); if (accessor?.IsKind(SyntaxKind.GetAccessorDeclaration) == true && accessor.FirstAncestorOrSelf <PropertyDeclarationSyntax>() == propertyDeclaration) { continue; } var expressionBody = context.Node.FirstAncestorOrSelf <ArrowExpressionClauseSyntax>(); if (expressionBody?.FirstAncestorOrSelf <PropertyDeclarationSyntax>() == propertyDeclaration) { continue; } using (var pooled = TouchedFieldsWalker.Create(getter, context.SemanticModel, context.CancellationToken)) { if (pooled.Item.Contains(assignedField)) { if (PropertyChanged.InvokesPropertyChangedFor(context.Node, property, context.SemanticModel, context.CancellationToken) == AnalysisResult.No) { if (pooledSet.Item.Add(property)) { var properties = ImmutableDictionary.CreateRange(new[] { new KeyValuePair <string, string>(PropertyNameKey, property.Name), }); context.ReportDiagnostic(Diagnostic.Create(Descriptor, context.Node.GetLocation(), properties, property.Name)); } } } } } } }
private static void HandleAssignment(SyntaxNodeAnalysisContext context) { var assignment = (AssignmentExpressionSyntax)context.Node; if (assignment?.IsMissing != false || assignment.FirstAncestorOrSelf <ConstructorDeclarationSyntax>() != null || assignment.FirstAncestorOrSelf <InitializerExpressionSyntax>() != null) { return; } var block = assignment.FirstAncestorOrSelf <BlockSyntax>(); if (block == null) { return; } var typeDeclaration = assignment.FirstAncestorOrSelf <TypeDeclarationSyntax>(); var typeSymbol = context.SemanticModel.GetDeclaredSymbolSafe(typeDeclaration, context.CancellationToken); if (!typeSymbol.Is(KnownSymbol.INotifyPropertyChanged)) { return; } var field = context.SemanticModel.GetSymbolSafe(assignment.Left, context.CancellationToken) as IFieldSymbol; if (field == null || !typeSymbol.Equals(field.ContainingType)) { return; } foreach (var member in typeDeclaration.Members) { var propertyDeclaration = member as PropertyDeclarationSyntax; if (propertyDeclaration == null) { continue; } var property = context.SemanticModel.GetDeclaredSymbolSafe(propertyDeclaration, context.CancellationToken); var getter = Getter(propertyDeclaration); if (getter == null || property == null || property.DeclaredAccessibility != Accessibility.Public) { continue; } using (var pooled = IdentifierNameWalker.Create(getter)) { foreach (var identifierName in pooled.Item.IdentifierNames) { var component = context.SemanticModel.GetSymbolSafe(identifierName, context.CancellationToken); var componentField = component as IFieldSymbol; if (componentField == null) { var propertySymbol = component as IPropertySymbol; if (propertySymbol == null) { continue; } if (!Property.TryGetBackingField(propertySymbol, context.SemanticModel, context.CancellationToken, out componentField)) { continue; } } if (!field.Equals(componentField)) { continue; } if (PropertyChanged.InvokesPropertyChangedFor(assignment, property, context.SemanticModel, context.CancellationToken) == PropertyChanged.InvokesPropertyChanged.No) { var properties = ImmutableDictionary.CreateRange(new[] { new KeyValuePair <string, string>(PropertyNameKey, property.Name), }); context.ReportDiagnostic(Diagnostic.Create(Descriptor, assignment.GetLocation(), properties, property.Name)); } } } } }