private static void Handle(SyntaxNodeAnalysisContext context) { if (!context.IsExcludedFromAnalysis() && context.Node is InvocationExpressionSyntax invocation && PropertyChanged.TryGetName(invocation, context.SemanticModel, context.CancellationToken, out _) != AnalysisResult.No) { if (invocation.TryFirstAncestor(out AccessorDeclarationSyntax? setter) && setter.IsKind(SyntaxKind.SetAccessorDeclaration)) { if (Setter.TryFindSingleAssignment(setter, out var assignment)) { if (IsFirstCall(invocation) && IncorrectOrMissingCheckIfDifferent(context, setter, invocation, assignment)) { context.ReportDiagnostic(Diagnostic.Create(Descriptors.INPC005CheckIfDifferentBeforeNotifying, GetLocation())); } } else if (Setter.TryFindSingleTrySet(setter, context.SemanticModel, context.CancellationToken, out var trySet)) { if (IsFirstCall(invocation) && IncorrectOrMissingCheckIfDifferent(trySet, invocation)) { context.ReportDiagnostic(Diagnostic.Create(Descriptors.INPC005CheckIfDifferentBeforeNotifying, GetLocation())); } } }
#pragma warning restore GU0073 // Member of non-public type should be internal. internal static bool Uses(ExpressionSyntax assigned, ExpressionSyntax returned, SyntaxNodeAnalysisContext context, PooledSet <SyntaxNode>?visited = null) { if (assigned is null || returned is null) { return(false); } using (var assignedPath = MemberPath.PathWalker.Borrow(assigned)) { var containingType = context.ContainingSymbol.ContainingType; if (UsedMemberWalker.Uses(returned, assignedPath, Search.TopLevel, containingType, context.SemanticModel, context.CancellationToken)) { return(true); } if (assignedPath.Tokens.TrySingle(out var candidate) && containingType.TryFindPropertyRecursive(candidate.ValueText, out var property) && property.TrySingleDeclaration(context.CancellationToken, out var declaration) && declaration.TryGetSetter(out var setter) && Setter.TryFindSingleAssignment(setter, out var assignment)) { using var set = visited.IncrementUsage(); if (set.Add(candidate.Parent)) { return(Uses(assignment.Left, returned, context, set)); } } } return(false); }
protected override async Task RegisterCodeFixesAsync(DocumentEditorCodeFixContext context) { var syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken) .ConfigureAwait(false); var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken) .ConfigureAwait(false); foreach (var diagnostic in context.Diagnostics) { if (syntaxRoot.TryFindNodeOrAncestor(diagnostic, out ExpressionStatementSyntax? onPropertyChangedStatement) && onPropertyChangedStatement.TryFirstAncestor(out AccessorDeclarationSyntax? setter) && setter.IsKind(SyntaxKind.SetAccessorDeclaration) && setter.Body is { } body) { if (Setter.TryFindSingleAssignment(setter, out var assignment) && assignment.Parent is ExpressionStatementSyntax assignmentStatement && body.Statements.IndexOf(assignmentStatement) == 0) { if (semanticModel.TryGetSymbol(assignment.Left, CancellationToken.None, out var assignedSymbol) && assignedSymbol.Kind == SymbolKind.Field && semanticModel.TryGetSymbol(setter, context.CancellationToken, out IMethodSymbol? setterSymbol) && TrySet.TryFind(setterSymbol.ContainingType, semanticModel, context.CancellationToken, out var trySetMethod) && TrySet.CanCreateInvocation(trySetMethod, out _) && setter.TryFirstAncestor(out PropertyDeclarationSyntax? property)) { if (setter.Body.Statements.Count == 2) { context.RegisterCodeFix( trySetMethod.DisplaySignature(), async(editor, cancellationToken) => { var trySet = await editor.TrySetInvocationAsync(trySetMethod, assignment.Left, assignment.Right, property, cancellationToken) .ConfigureAwait(false); _ = editor.ReplaceNode( setter, x => x.AsExpressionBody(trySet)); }, trySetMethod.MetadataName, diagnostic); } } context.RegisterCodeFix( "Check that value is different before notifying.", (editor, cancellationToken) => editor.InsertBefore( assignmentStatement, InpcFactory.IfReturn( InpcFactory.Equals( assignment.Right, assignment.Left, editor.SemanticModel, cancellationToken))), nameof(CheckIfDifferentBeforeNotifyFix), diagnostic); }