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); }
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); }