@@ -63,6 +63,7 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
6363 private static class ExpectedExceptionToAssertThrowsVisitor extends JavaIsoVisitor <ExecutionContext > {
6464
6565 private static final String FIRST_EXPECTED_EXCEPTION_METHOD_INVOCATION = "firstExpectedExceptionMethodInvocation" ;
66+ private static final String STATEMENTS_BEFORE_EXPECT_EXCEPTION = "statementsBeforeExpectException" ;
6667 private static final String STATEMENTS_AFTER_EXPECT_EXCEPTION = "statementsAfterExpectException" ;
6768 private static final String HAS_MATCHER = "hasMatcher" ;
6869 private static final String EXCEPTION_CLASS = "exceptionClass" ;
@@ -100,13 +101,60 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex
100101 if (getCursor ().pollMessage ("hasExpectException" ) != null ) {
101102 List <NameTree > thrown = m .getThrows ();
102103 if (thrown != null && !thrown .isEmpty ()) {
104+ List <Statement > statementsBeforeExpect = getCursor ().pollMessage (STATEMENTS_BEFORE_EXPECT_EXCEPTION );
105+ if (statementsBeforeExpect != null && statementsBeforeExpect .stream ().anyMatch (this ::statementThrowsCheckedException )) {
106+ return m ;
107+ }
103108 assert m .getBody () != null ;
104109 return m .withBody (m .getBody ().withPrefix (thrown .get (0 ).getPrefix ())).withThrows (emptyList ());
105110 }
106111 }
107112 return m ;
108113 }
109114
115+ private boolean statementThrowsCheckedException (Statement statement ) {
116+ return new JavaIsoVisitor <AtomicBoolean >() {
117+ @ Override
118+ public J .MethodInvocation visitMethodInvocation (J .MethodInvocation method , AtomicBoolean found ) {
119+ if (found .get ()) {
120+ return method ;
121+ }
122+ JavaType .Method methodType = method .getMethodType ();
123+ if (methodType != null ) {
124+ for (JavaType thrownException : methodType .getThrownExceptions ()) {
125+ if (isCheckedException (thrownException )) {
126+ found .set (true );
127+ return method ;
128+ }
129+ }
130+ }
131+ return super .visitMethodInvocation (method , found );
132+ }
133+
134+ @ Override
135+ public J .NewClass visitNewClass (J .NewClass newClass , AtomicBoolean found ) {
136+ if (found .get ()) {
137+ return newClass ;
138+ }
139+ JavaType .Method constructorType = newClass .getConstructorType ();
140+ if (constructorType != null ) {
141+ for (JavaType thrownException : constructorType .getThrownExceptions ()) {
142+ if (isCheckedException (thrownException )) {
143+ found .set (true );
144+ return newClass ;
145+ }
146+ }
147+ }
148+ return super .visitNewClass (newClass , found );
149+ }
150+ }.reduce (statement , new AtomicBoolean (false )).get ();
151+ }
152+
153+ private boolean isCheckedException (JavaType exceptionType ) {
154+ return !TypeUtils .isAssignableTo ("java.lang.RuntimeException" , exceptionType ) &&
155+ !TypeUtils .isAssignableTo ("java.lang.Error" , exceptionType );
156+ }
157+
110158 @ Override
111159 public J .Block visitBlock (J .Block block , ExecutionContext ctx ) {
112160 J .Block b = super .visitBlock (block , ctx );
@@ -175,7 +223,13 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu
175223 return method ;
176224 }
177225 getCursor ().dropParentUntil (J .MethodDeclaration .class ::isInstance ).putMessage ("hasExpectException" , true );
178- getCursor ().dropParentUntil (J .Block .class ::isInstance ).computeMessageIfAbsent (FIRST_EXPECTED_EXCEPTION_METHOD_INVOCATION , k -> method );
226+ Cursor blockCursor = getCursor ().dropParentUntil (J .Block .class ::isInstance );
227+ blockCursor .computeMessageIfAbsent (FIRST_EXPECTED_EXCEPTION_METHOD_INVOCATION , k -> method );
228+
229+ List <Statement > predecessorStatements = findPredecessorStatements (getCursor ());
230+ getCursor ().dropParentUntil (J .MethodDeclaration .class ::isInstance )
231+ .computeMessageIfAbsent (STATEMENTS_BEFORE_EXPECT_EXCEPTION , k -> predecessorStatements );
232+
179233 List <Statement > successorStatements = findSuccessorStatements (getCursor ());
180234 getCursor ().putMessageOnFirstEnclosing (J .Block .class , STATEMENTS_AFTER_EXPECT_EXCEPTION , successorStatements );
181235 if (EXPECTED_EXCEPTION_CLASS_MATCHER .matches (method )) {
@@ -186,6 +240,25 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu
186240 return method ;
187241 }
188242
243+ /**
244+ * From the current cursor point find all preceding statements in the method body.
245+ */
246+ private List <Statement > findPredecessorStatements (Cursor cursor ) {
247+ J .MethodDeclaration methodDecl = cursor .firstEnclosing (J .MethodDeclaration .class );
248+ if (methodDecl == null || methodDecl .getBody () == null ) {
249+ return emptyList ();
250+ }
251+ List <Statement > predecessorStatements = new ArrayList <>();
252+ Statement currentStatement = cursor .firstEnclosing (Statement .class );
253+ for (Statement statement : methodDecl .getBody ().getStatements ()) {
254+ if (statement == currentStatement ) {
255+ break ;
256+ }
257+ predecessorStatements .add (statement );
258+ }
259+ return predecessorStatements ;
260+ }
261+
189262 /**
190263 * From the current cursor point find all the next statements that can be executed in the current path.
191264 */
0 commit comments