private static (SyntaxNode source, SyntaxNode destination) GetFluentContractsReplacements(
            IInvocationOperation operation,
            ContractMethodNames contractMethod,
            SemanticModel semanticModel,
            CancellationToken token)
        {
            var invocationExpression = operation.Syntax;

            // Getting the original predicate.
            var predicateArgumentOperation = operation.Arguments[0];
            var predicateArgument          = (ArgumentSyntax)predicateArgumentOperation.Syntax;

            ArgumentSyntax?extraForAllArgument  = null;
            int            messageArgumentIndex = 1;

            // We need to mutate it for the cases like RequiresNotNull and RequiresNotNullOrEmpty

            if (contractMethod.IsNullCheck())
            {
                predicateArgument =
                    Argument(
                        // Changing NotNull(x) to x != null
                        BinaryExpression(
                            SyntaxKind.NotEqualsExpression,
                            predicateArgument.Expression,
                            LiteralExpression(SyntaxKind.NullLiteralExpression)))
                    .NormalizeWhitespace();
            }
            else if (contractMethod.IsNotNullOrEmpty() || contractMethod.IsNotNullOrWhiteSpace())
            {
                var stringMethodName = contractMethod.IsNotNullOrEmpty() ? nameof(string.IsNullOrEmpty) : nameof(string.IsNullOrWhiteSpace);

                // Targeting a full framework can cause an issue for null-ness analysis
                // because string.IsNullOrEmpty and string.IsNullOrWhiteSpace is not annotated with any attributes.
                // It means that the compiler can't recognize that the following code is correct and still emit the warning:
                // if (!string.IsNullOrEmpty(str)) return str.Length;
                predicateArgument =
                    Argument(
                        PrefixUnaryExpression(
                            SyntaxKind.LogicalNotExpression,
                            InvocationExpression(
                                MemberAccessExpression(
                                    SyntaxKind.SimpleMemberAccessExpression,
                                    PredefinedType(Token(SyntaxKind.StringKeyword)),
                                    IdentifierName(stringMethodName)))
                            .WithArgumentList(
                                ArgumentList(SingletonSeparatedList(predicateArgument))))
                        ).NormalizeWhitespace();
            }
            else if (contractMethod.IsForAll())
            {
                extraForAllArgument = (ArgumentSyntax)operation.Arguments[1].Syntax;
                messageArgumentIndex++;
            }

            // Detecting the following case:
            // if (predicate is false) {Contract.Assert(false, complicatedMessage);}
            var sourceNode = invocationExpression.Parent;

            if (predicateArgumentOperation.Value is ILiteralOperation lo &&
                lo.ConstantValue.HasValue && lo.ConstantValue.Value.Equals(false))
            {
                // this is Assert(false) case.
                if (operation.Parent?.Parent?.Parent is IConditionalOperation conditional &&
                    conditional.WhenFalse == null &&
                    conditional.WhenTrue.Children.Count() == 1)
                {
                    // The contract is inside the if block with a single statement.
                    sourceNode = conditional.Syntax;

                    var negatedCondition = (ExpressionSyntax?)SyntaxGeneratorExtensions.Negate(conditional.Condition.Syntax, semanticModel, token);
                    if (negatedCondition != null)
                    {
                        predicateArgument = Argument(negatedCondition);
                    }
                }
            }

            var           originalMessageArgument = operation.Arguments[messageArgumentIndex];
            Func <string> nonDefaultArgument      =
                () => contractMethod.IsForAll()
                    ? operation.Arguments[1].Syntax.ToFullString()
                    : predicateArgument.ToFullString();

            // Using an original message if provided.
            // Otherwise using a predicate as the new message.
            var messageArgument =
                originalMessageArgument.IsImplicit == false
                    ? (ArgumentSyntax)originalMessageArgument.Syntax
                    : Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(nonDefaultArgument())));

            var arguments =
                new SeparatedSyntaxList <ArgumentSyntax>()
                .Add(predicateArgument)
                .AddIfNotNull(extraForAllArgument);

            // Generating Contract.Check(predicate)?.Requires/Assert(message)

            var targetMethodName = GetTargetMethod(contractMethod);
            var checkMethodName  = GetCheckMethod(contractMethod);
            var contractCall     =
                ExpressionStatement(
                    ConditionalAccessExpression(
                        // Contract.Requires(
                        InvocationExpression(
                            MemberAccessExpression(
                                SyntaxKind.SimpleMemberAccessExpression,
                                IdentifierName(FluentContractNames.ContractClassName),
                                IdentifierName(checkMethodName)))
                        // (predicate)
                        .WithArgumentList(
                            ArgumentList(arguments)),
                        // ?.IsTrue(message)
                        InvocationExpression(
                            MemberBindingExpression(
                                IdentifierName(targetMethodName)))
                        .WithArgumentList(
                            ArgumentList(
                                SingletonSeparatedList(messageArgument)))
                        )
                    );

            var trivia    = sourceNode.GetLeadingTrivia();
            var finalNode = contractCall.WithLeadingTrivia(trivia);

            return(sourceNode, finalNode);
        }
Beispiel #2
0
        private static (SyntaxNode source, SyntaxNode destination) GetFluentContractsReplacements(
            IInvocationOperation operation,
            ContractMethodNames contractMethod,
            SemanticModel semanticModel,
            CancellationToken token)
        {
            var invocationExpression = operation.Syntax;

            // Getting the original predicate.
            var predicateArgumentOperation = operation.Arguments[0];
            var predicateArgument          = (ArgumentSyntax)predicateArgumentOperation.Syntax;

            ArgumentSyntax?extraForAllArgument  = null;
            int            messageArgumentIndex = 1;

            // We need to mutate it for the cases like RequiresNotNull and RequiresNotNullOrEmpty

            if (contractMethod.IsNullCheck())
            {
                predicateArgument =
                    Argument(
                        // Changing NotNull(x) to x != null
                        BinaryExpression(
                            SyntaxKind.NotEqualsExpression,
                            predicateArgument.Expression,
                            LiteralExpression(SyntaxKind.NullLiteralExpression)))
                    .NormalizeWhitespace();
            }
            else if (contractMethod.IsNotNullOrEmpty() || contractMethod.IsNotNullOrWhiteSpace())
            {
                var stringMethodName = contractMethod.IsNotNullOrEmpty() ? nameof(string.IsNullOrEmpty) : nameof(string.IsNullOrWhiteSpace);

                // Targeting a full framework can cause an issue for null-ness analysis
                // because string.IsNullOrEmpty and string.IsNullOrWhiteSpace is not annotated with any attributes.
                // It means that the compiler can't recognize that the following code is correct and still emit the warning:
                // if (!string.IsNullOrEmpty(str)) return str.Length;
                predicateArgument =
                    Argument(
                        PrefixUnaryExpression(
                            SyntaxKind.LogicalNotExpression,
                            InvocationExpression(
                                MemberAccessExpression(
                                    SyntaxKind.SimpleMemberAccessExpression,
                                    PredefinedType(Token(SyntaxKind.StringKeyword)),
                                    IdentifierName(stringMethodName)))
                            .WithArgumentList(
                                ArgumentList(SingletonSeparatedList(predicateArgument))))
                        ).NormalizeWhitespace();
            }

            // Detecting the following case:
            // if (predicate is false) {Contract.Assert(false, complicatedMessage);}
            var sourceNode = invocationExpression.Parent;

            var originalMessageArgument = operation.Arguments[messageArgumentIndex];

            // Using an original message if provided.
            // Otherwise using a predicate as the new message.
            var messageArgument =
                originalMessageArgument.IsImplicit == false
                    ? (ArgumentSyntax)originalMessageArgument.Syntax
                    : null;

            var arguments =
                new SeparatedSyntaxList <ArgumentSyntax>()
                .Add(predicateArgument)
                .AddIfNotNull(extraForAllArgument)
                .AddIfNotNull(messageArgument);

            // Generating Contract.Check(predicate)?.Requires/Assert(message)

            var targetMethodName = GetTargetMethod(contractMethod);
            var contractCall     =
                //ExpressionStatement(
                // Contract.Requires(
                InvocationExpression(
                    MemberAccessExpression(
                        SyntaxKind.SimpleMemberAccessExpression,
                        IdentifierName(FluentContractNames.ContractClassName),
                        IdentifierName(targetMethodName)))
                // (predicate, message)
                .WithArgumentList(ArgumentList(arguments)
                                  //)
                                  );

            var trivia    = sourceNode.GetLeadingTrivia();
            var finalNode = contractCall.WithLeadingTrivia(trivia);

            return(invocationExpression, finalNode);
        }