@@ -297,6 +297,16 @@ private ExpressionSyntax ConvertConstraintToTUnitWithMessage(ExpressionSyntax ac
297297 _ => memberAccess . Name . ToString ( )
298298 } ;
299299
300+ // Handle chained constraint modifiers like .Within(delta) on Is.EqualTo(x).Within(delta)
301+ if ( methodName == "Within" && memberAccess . Expression is InvocationExpressionSyntax innerConstraint )
302+ {
303+ // Get the base assertion (e.g., IsEqualTo(5)) first
304+ var baseAssertion = ConvertConstraintToTUnitWithMessage ( actualValue , innerConstraint , message ) ;
305+
306+ // Now chain .Within(delta) to it
307+ return ChainMethodCall ( baseAssertion , "Within" , constraint . ArgumentList . Arguments . ToArray ( ) ) ;
308+ }
309+
300310 // Handle generic type constraints: Is.TypeOf<T>(), Is.InstanceOf<T>(), Is.AssignableFrom<T>()
301311 if ( memberAccess . Name is GenericNameSyntax genericMethodName )
302312 {
@@ -453,6 +463,16 @@ private ExpressionSyntax ConvertConstraintToTUnit(ExpressionSyntax actualValue,
453463 _ => memberAccess . Name . ToString ( )
454464 } ;
455465
466+ // Handle chained constraint modifiers like .Within(delta) on Is.EqualTo(x).Within(delta)
467+ if ( methodName == "Within" && memberAccess . Expression is InvocationExpressionSyntax innerConstraint )
468+ {
469+ // Get the base assertion (e.g., IsEqualTo(5)) first
470+ var baseAssertion = ConvertConstraintToTUnit ( actualValue , innerConstraint ) ;
471+
472+ // Now chain .Within(delta) to it
473+ return ChainMethodCall ( baseAssertion , "Within" , constraint . ArgumentList . Arguments . ToArray ( ) ) ;
474+ }
475+
456476 // Handle generic type constraints: Is.TypeOf<T>(), Is.InstanceOf<T>(), Is.AssignableFrom<T>()
457477 if ( memberAccess . Name is GenericNameSyntax genericMethodName )
458478 {
@@ -613,6 +633,43 @@ private ExpressionSyntax CreateInRangeAssertion(ExpressionSyntax actualValue, Se
613633 }
614634 return CreateTUnitAssertion ( "IsInRange" , actualValue ) ;
615635 }
636+
637+ /// <summary>
638+ /// Chains a method call onto an existing await expression.
639+ /// For example: await Assert.That(x).IsEqualTo(5) becomes await Assert.That(x).IsEqualTo(5).Within(2)
640+ /// </summary>
641+ private ExpressionSyntax ChainMethodCall ( ExpressionSyntax baseExpression , string methodName , params ArgumentSyntax [ ] arguments )
642+ {
643+ // The base expression is an AwaitExpression like: await Assert.That(x).IsEqualTo(5)
644+ // We need to extract the invocation, add .Within(2) to it, and re-wrap in await
645+ if ( baseExpression is AwaitExpressionSyntax awaitExpr )
646+ {
647+ var innerInvocation = awaitExpr . Expression ;
648+
649+ // Create the chained method access: Assert.That(x).IsEqualTo(5).Within
650+ var chainedAccess = SyntaxFactory . MemberAccessExpression (
651+ SyntaxKind . SimpleMemberAccessExpression ,
652+ innerInvocation ,
653+ SyntaxFactory . IdentifierName ( methodName )
654+ ) ;
655+
656+ // Create the invocation: Assert.That(x).IsEqualTo(5).Within(2)
657+ var chainedInvocation = SyntaxFactory . InvocationExpression (
658+ chainedAccess ,
659+ arguments . Length > 0
660+ ? SyntaxFactory . ArgumentList ( SyntaxFactory . SeparatedList ( arguments ) )
661+ : SyntaxFactory . ArgumentList ( )
662+ ) ;
663+
664+ // Re-wrap in await
665+ var awaitKeyword = SyntaxFactory . Token ( SyntaxKind . AwaitKeyword )
666+ . WithTrailingTrivia ( SyntaxFactory . Space ) ;
667+ return SyntaxFactory . AwaitExpression ( awaitKeyword , chainedInvocation ) ;
668+ }
669+
670+ // Fallback: just return the base expression if it's not the expected shape
671+ return baseExpression ;
672+ }
616673
617674 private ExpressionSyntax ? ConvertClassicAssertion ( InvocationExpressionSyntax invocation , string methodName )
618675 {
@@ -624,6 +681,12 @@ private ExpressionSyntax CreateInRangeAssertion(ExpressionSyntax actualValue, Se
624681 return ConvertNUnitThrows ( invocation ) ;
625682 }
626683
684+ // Handle Assert.DoesNotThrow and Assert.DoesNotThrowAsync
685+ if ( methodName is "DoesNotThrow" or "DoesNotThrowAsync" )
686+ {
687+ return ConvertDoesNotThrow ( arguments ) ;
688+ }
689+
627690 // Handle special assertions (Pass, Inconclusive, Fail, Warn)
628691 return methodName switch
629692 {
@@ -812,6 +875,53 @@ private ExpressionSyntax ConvertNUnitThrows(InvocationExpressionSyntax invocatio
812875 : invocation . ArgumentList . Arguments [ 0 ] . Expression ;
813876 return CreateTUnitAssertion ( "Throws" , fallbackArg ) ;
814877 }
878+
879+ private ExpressionSyntax ConvertDoesNotThrow ( SeparatedSyntaxList < ArgumentSyntax > arguments )
880+ {
881+ // Assert.DoesNotThrow(() => action) -> await Assert.That(() => action).ThrowsNothing()
882+ if ( arguments . Count == 0 )
883+ {
884+ // Fallback - shouldn't happen but handle gracefully
885+ return SyntaxFactory . InvocationExpression (
886+ SyntaxFactory . MemberAccessExpression (
887+ SyntaxKind . SimpleMemberAccessExpression ,
888+ SyntaxFactory . IdentifierName ( "Assert" ) ,
889+ SyntaxFactory . IdentifierName ( "Pass" )
890+ )
891+ ) ;
892+ }
893+
894+ var action = arguments [ 0 ] . Expression ;
895+
896+ // Create Assert.That(() => action)
897+ var assertThatInvocation = SyntaxFactory . InvocationExpression (
898+ SyntaxFactory . MemberAccessExpression (
899+ SyntaxKind . SimpleMemberAccessExpression ,
900+ SyntaxFactory . IdentifierName ( "Assert" ) ,
901+ SyntaxFactory . IdentifierName ( "That" )
902+ ) ,
903+ SyntaxFactory . ArgumentList (
904+ SyntaxFactory . SingletonSeparatedList (
905+ SyntaxFactory . Argument ( action )
906+ )
907+ )
908+ ) ;
909+
910+ // Chain .ThrowsNothing()
911+ var throwsNothingInvocation = SyntaxFactory . InvocationExpression (
912+ SyntaxFactory . MemberAccessExpression (
913+ SyntaxKind . SimpleMemberAccessExpression ,
914+ assertThatInvocation ,
915+ SyntaxFactory . IdentifierName ( "ThrowsNothing" )
916+ ) ,
917+ SyntaxFactory . ArgumentList ( )
918+ ) ;
919+
920+ // Wrap in await
921+ var awaitKeyword = SyntaxFactory . Token ( SyntaxKind . AwaitKeyword )
922+ . WithTrailingTrivia ( SyntaxFactory . Space ) ;
923+ return SyntaxFactory . AwaitExpression ( awaitKeyword , throwsNothingInvocation ) ;
924+ }
815925
816926 /// <summary>
817927 /// Attempts to extract the exception type from NUnit constraint expressions like Is.TypeOf(typeof(T)).
@@ -875,34 +985,46 @@ private ExpressionSyntax CreatePassAssertion(SeparatedSyntaxList<ArgumentSyntax>
875985
876986 private ExpressionSyntax CreateFailAssertion ( SeparatedSyntaxList < ArgumentSyntax > arguments )
877987 {
988+ // TUnit: Fail.Test("reason") - not awaited, throws synchronously
989+ var reasonArg = arguments . Count > 0
990+ ? arguments [ 0 ]
991+ : SyntaxFactory . Argument (
992+ SyntaxFactory . LiteralExpression (
993+ SyntaxKind . StringLiteralExpression ,
994+ SyntaxFactory . Literal ( "Test failed" ) ) ) ;
995+
878996 var failInvocation = SyntaxFactory . InvocationExpression (
879997 SyntaxFactory . MemberAccessExpression (
880998 SyntaxKind . SimpleMemberAccessExpression ,
881- SyntaxFactory . IdentifierName ( "Assert " ) ,
882- SyntaxFactory . IdentifierName ( "Fail " )
999+ SyntaxFactory . IdentifierName ( "Fail " ) ,
1000+ SyntaxFactory . IdentifierName ( "Test " )
8831001 ) ,
884- arguments . Count > 0
885- ? SyntaxFactory . ArgumentList ( SyntaxFactory . SingletonSeparatedList ( arguments [ 0 ] ) )
886- : SyntaxFactory . ArgumentList ( )
1002+ SyntaxFactory . ArgumentList ( SyntaxFactory . SingletonSeparatedList ( reasonArg ) )
8871003 ) ;
8881004
889- return SyntaxFactory . AwaitExpression ( failInvocation ) ;
1005+ return failInvocation ;
8901006 }
8911007
8921008 private ExpressionSyntax CreateSkipAssertion ( SeparatedSyntaxList < ArgumentSyntax > arguments )
8931009 {
1010+ // TUnit: Skip.Test("reason") - not awaited, throws SkipTestException
1011+ var reasonArg = arguments . Count > 0
1012+ ? arguments [ 0 ]
1013+ : SyntaxFactory . Argument (
1014+ SyntaxFactory . LiteralExpression (
1015+ SyntaxKind . StringLiteralExpression ,
1016+ SyntaxFactory . Literal ( "Test skipped" ) ) ) ;
1017+
8941018 var skipInvocation = SyntaxFactory . InvocationExpression (
8951019 SyntaxFactory . MemberAccessExpression (
8961020 SyntaxKind . SimpleMemberAccessExpression ,
897- SyntaxFactory . IdentifierName ( "Assert " ) ,
898- SyntaxFactory . IdentifierName ( "Skip " )
1021+ SyntaxFactory . IdentifierName ( "Skip " ) ,
1022+ SyntaxFactory . IdentifierName ( "Test " )
8991023 ) ,
900- arguments . Count > 0
901- ? SyntaxFactory . ArgumentList ( SyntaxFactory . SingletonSeparatedList ( arguments [ 0 ] ) )
902- : SyntaxFactory . ArgumentList ( )
1024+ SyntaxFactory . ArgumentList ( SyntaxFactory . SingletonSeparatedList ( reasonArg ) )
9031025 ) ;
9041026
905- return SyntaxFactory . AwaitExpression ( skipInvocation ) ;
1027+ return skipInvocation ;
9061028 }
9071029}
9081030
0 commit comments