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);
        }
Example #3
0
        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);
                    }