From b9e71a99f3b1abdde5d67f3cf5a391c16c0ed729 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 14 Jul 2026 10:43:59 +0200 Subject: [PATCH 1/3] SONARJAVA-4926 Use secondary locations for class-level @Transactional issues When a @Transactional annotation is on a class and multiple methods throw checked exceptions without rollback configuration, instead of raising multiple issues on the same annotation, we now: - Report the issue on each affected method name (primary location) - Show the class-level annotation as a secondary location - Provide a clearer message indicating the configuration needs to be added to the class-level annotation This makes it clearer which methods are affected and where the fix needs to be applied. Co-Authored-By: Claude Sonnet 4.5 --- ...eckedExceptionCheckSampleNonCompiling.java | 4 +- ...onalMethodCheckedExceptionCheckSample.java | 90 +++++++++---------- ...nsactionalMethodCheckedExceptionCheck.java | 34 +++++-- 3 files changed, 76 insertions(+), 52 deletions(-) diff --git a/java-checks-test-sources/default/src/main/files/non-compiling/checks/spring/TransactionalMethodCheckedExceptionCheckSampleNonCompiling.java b/java-checks-test-sources/default/src/main/files/non-compiling/checks/spring/TransactionalMethodCheckedExceptionCheckSampleNonCompiling.java index 114af2f93ea..258f412171f 100644 --- a/java-checks-test-sources/default/src/main/files/non-compiling/checks/spring/TransactionalMethodCheckedExceptionCheckSampleNonCompiling.java +++ b/java-checks-test-sources/default/src/main/files/non-compiling/checks/spring/TransactionalMethodCheckedExceptionCheckSampleNonCompiling.java @@ -9,7 +9,7 @@ class TransactionalMethodCheckedExceptionCheckSampleNonCompiling { public void unknownException() throws UnresolvedCheckedException { // No issue - type is unknown } - @Transactional // Noncompliant - public void knownException() throws java.io.IOException { + @Transactional + public void knownException() throws java.io.IOException { // Noncompliant } } diff --git a/java-checks-test-sources/default/src/main/java/checks/spring/TransactionalMethodCheckedExceptionCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/spring/TransactionalMethodCheckedExceptionCheckSample.java index 27b7c44d3f5..f835d58ac30 100644 --- a/java-checks-test-sources/default/src/main/java/checks/spring/TransactionalMethodCheckedExceptionCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/spring/TransactionalMethodCheckedExceptionCheckSample.java @@ -18,43 +18,43 @@ class CustomCheckedException extends Exception {} public class TransactionalMethodCheckedExceptionCheckSample { - @Transactional // Noncompliant {{Specify rollback behavior for checked exceptions using "rollbackFor" or "noRollbackFor" attributes.}} [[quickfixes=qf1,qf2]] -//^^^^^^^^^^^^^^ + @Transactional + public void processOrder(Order order) throws IOException, SQLException { // Noncompliant [[quickfixes=qf1,qf2]] +// ^^^^^^^^^^^^ // fix@qf1 {{Add rollbackFor attribute}} - // edit@qf1 [[sc=3;ec=17]] {{@Transactional(rollbackFor = {java.io.IOException.class, java.sql.SQLException.class})}} + // edit@qf1 [[sl=-1;sc=3;el=-1;ec=17]] {{@Transactional(rollbackFor = {java.io.IOException.class, java.sql.SQLException.class})}} // fix@qf2 {{Add rollbackFor = Exception.class}} - // edit@qf2 [[sc=3;ec=17]] {{@Transactional(rollbackFor = java.lang.Exception.class)}} - public void processOrder(Order order) throws IOException, SQLException { + // edit@qf2 [[sl=-1;sc=3;el=-1;ec=17]] {{@Transactional(rollbackFor = java.lang.Exception.class)}} } - @Transactional // Noncompliant [[quickfixes=qf3]] -//^^^^^^^^^^^^^^ + @Transactional + public void importData() throws Exception { // Noncompliant [[quickfixes=qf3]] +// ^^^^^^^^^^ // fix@qf3 {{Add rollbackFor = Exception.class}} - // edit@qf3 [[sc=3;ec=17]] {{@Transactional(rollbackFor = java.lang.Exception.class)}} - public void importData() throws Exception { + // edit@qf3 [[sl=-1;sc=3;el=-1;ec=17]] {{@Transactional(rollbackFor = java.lang.Exception.class)}} } - @Transactional(timeout = 30) // Noncompliant -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - public void withOtherAttributes() throws SQLException { + @Transactional(timeout = 30) + public void withOtherAttributes() throws SQLException { // Noncompliant +// ^^^^^^^^^^^^^^^^^^^ } - @Transactional // Noncompliant [[quickfixes=qf7,qf8]] -//^^^^^^^^^^^^^^ + @Transactional + public void customException() throws CustomCheckedException { // Noncompliant [[quickfixes=qf7,qf8]] +// ^^^^^^^^^^^^^^^ // fix@qf7 {{Add rollbackFor attribute}} - // edit@qf7 [[sc=3;ec=17]] {{@Transactional(rollbackFor = checks.spring.CustomCheckedException.class)}} + // edit@qf7 [[sl=-1;sc=3;el=-1;ec=17]] {{@Transactional(rollbackFor = checks.spring.CustomCheckedException.class)}} // fix@qf8 {{Add rollbackFor = Exception.class}} - // edit@qf8 [[sc=3;ec=17]] {{@Transactional(rollbackFor = java.lang.Exception.class)}} - public void customException() throws CustomCheckedException { + // edit@qf8 [[sl=-1;sc=3;el=-1;ec=17]] {{@Transactional(rollbackFor = java.lang.Exception.class)}} } - @Transactional // Noncompliant [[quickfixes=qf9,qf10]] -//^^^^^^^^^^^^^^ + @Transactional + public void mixedExceptions() throws IOException, RuntimeException { // Noncompliant [[quickfixes=qf9,qf10]] +// ^^^^^^^^^^^^^^^ // fix@qf9 {{Add rollbackFor attribute}} - // edit@qf9 [[sc=3;ec=17]] {{@Transactional(rollbackFor = java.io.IOException.class)}} + // edit@qf9 [[sl=-1;sc=3;el=-1;ec=17]] {{@Transactional(rollbackFor = java.io.IOException.class)}} // fix@qf10 {{Add rollbackFor = Exception.class}} - // edit@qf10 [[sc=3;ec=17]] {{@Transactional(rollbackFor = java.lang.Exception.class)}} - public void mixedExceptions() throws IOException, RuntimeException { + // edit@qf10 [[sl=-1;sc=3;el=-1;ec=17]] {{@Transactional(rollbackFor = java.lang.Exception.class)}} } @Transactional(rollbackFor = IOException.class) @@ -102,14 +102,10 @@ public void inherited() throws IOException { } } - @Transactional // Noncompliant [[quickfixes=qf11,qf12]] -//^^^^^^^^^^^^^^ - // fix@qf11 {{Add rollbackFor attribute}} - // edit@qf11 [[sc=3;ec=17]] {{@Transactional(rollbackFor = java.io.IOException.class)}} - // fix@qf12 {{Add rollbackFor = Exception.class}} - // edit@qf12 [[sc=3;ec=17]] {{@Transactional(rollbackFor = java.lang.Exception.class)}} + @Transactional // [[secondary=+2]] static class ClassLevelNoConfig { - public void noConfig() throws IOException { + public void noConfig() throws IOException { // Noncompliant {{Specify rollback behavior for checked exceptions using "rollbackFor" or "noRollbackFor" attributes on the class-level @Transactional.}} +// ^^^^^^^^ } @Transactional(rollbackFor = IOException.class) @@ -121,29 +117,30 @@ public void methodOverrides() throws IOException { public void errorNotChecked() throws Error { } - @org.springframework.transaction.annotation.Transactional // Noncompliant [[quickfixes=qf13,qf14]] -//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>^^^^^^^^^^^^^^^^^^^^^^^^ + @org.springframework.transaction.annotation.Transactional + public void fullyQualified() throws IOException { // Noncompliant [[quickfixes=qf13,qf14]] +// ^^^^^^^^^^^^^^ // fix@qf13 {{Add rollbackFor attribute}} - // edit@qf13 [[sc=3;ec=60]] {{@org.springframework.transaction.annotation.Transactional(rollbackFor = java.io.IOException.class)}} + // edit@qf13 [[sl=-1;sc=3;el=-1;ec=60]] {{@org.springframework.transaction.annotation.Transactional(rollbackFor = java.io.IOException.class)}} // fix@qf14 {{Add rollbackFor = Exception.class}} - // edit@qf14 [[sc=3;ec=60]] {{@org.springframework.transaction.annotation.Transactional(rollbackFor = java.lang.Exception.class)}} - public void fullyQualified() throws IOException { + // edit@qf14 [[sl=-1;sc=3;el=-1;ec=60]] {{@org.springframework.transaction.annotation.Transactional(rollbackFor = java.lang.Exception.class)}} } @Transactional(rollbackFor = IOException.class) public void partialConfig() throws SQLException { } - @Transactional(value = "txManager") // Noncompliant -//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - public void withValueAttribute() throws IOException { + @Transactional(value = "txManager") + public void withValueAttribute() throws IOException { // Noncompliant +// ^^^^^^^^^^^^^^^^^^ } // Test nested structure to ensure parent traversal works static class OuterClass { - @Transactional // Noncompliant + @Transactional // [[secondary=+2]] static class InnerClassWithAnnotation { - public void nestedMethod() throws IOException { + public void nestedMethod() throws IOException { // Noncompliant {{Specify rollback behavior for checked exceptions using "rollbackFor" or "noRollbackFor" attributes on the class-level @Transactional.}} +// ^^^^^^^^^^^^ } } @@ -154,19 +151,22 @@ public void nestedMethodNoAnnotation() throws IOException { } } - @Transactional // Noncompliant + @Transactional // [[secondary=+2]] interface TransactionalInterface { - void interfaceMethod() throws IOException; + void interfaceMethod() throws IOException; // Noncompliant {{Specify rollback behavior for checked exceptions using "rollbackFor" or "noRollbackFor" attributes on the class-level @Transactional.}} +// ^^^^^^^^^^^^^^^ } // Test meta-annotated (composed) annotation - @MyTransactional // Noncompliant - public void metaAnnotated() throws IOException { + @MyTransactional + public void metaAnnotated() throws IOException { // Noncompliant +// ^^^^^^^^^^^^^ } // Test annotation with value attribute (transaction manager name) - @Transactional("txManager") // Noncompliant - public void valueShorthand() throws IOException { + @Transactional("txManager") + public void valueShorthand() throws IOException { // Noncompliant +// ^^^^^^^^^^^^^^ // Has value attribute but no rollback configuration } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/spring/TransactionalMethodCheckedExceptionCheck.java b/java-checks/src/main/java/org/sonar/java/checks/spring/TransactionalMethodCheckedExceptionCheck.java index 2326574ffb8..946877cfe76 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/spring/TransactionalMethodCheckedExceptionCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/spring/TransactionalMethodCheckedExceptionCheck.java @@ -29,6 +29,7 @@ import org.sonar.java.reporting.JavaTextEdit; import org.sonar.plugins.java.api.DependencyVersionAware; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.Version; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.AnnotationTree; @@ -75,12 +76,24 @@ public void visitNode(Tree tree) { return; } - QuickFixHelper.newIssue(context) + boolean isClassLevel = isClassLevelAnnotation(method, transactionalAnnotation); + + var issueBuilder = QuickFixHelper.newIssue(context) .forRule(this) - .onTree(transactionalAnnotation) - .withMessage("Specify rollback behavior for checked exceptions using \"rollbackFor\" or \"noRollbackFor\" attributes.") - .withQuickFixes(() -> computeQuickFixes(transactionalAnnotation, checkedExceptions)) - .report(); + .onTree(method.simpleName()); + + if (isClassLevel) { + issueBuilder + .withMessage("Specify rollback behavior for checked exceptions using \"rollbackFor\" or \"noRollbackFor\" attributes on the class-level @Transactional.") + .withSecondaries(new JavaFileScannerContext.Location("Class-level @Transactional annotation", transactionalAnnotation)) + .withQuickFixes(() -> computeQuickFixes(transactionalAnnotation, checkedExceptions)); + } else { + issueBuilder + .withMessage("Specify rollback behavior for checked exceptions using \"rollbackFor\" or \"noRollbackFor\" attributes.") + .withQuickFixes(() -> computeQuickFixes(transactionalAnnotation, checkedExceptions)); + } + + issueBuilder.report(); } private static AnnotationTree getTransactionalAnnotation(MethodTree method) { @@ -108,6 +121,17 @@ private static AnnotationTree getTransactionalAnnotation(MethodTree method) { return null; } + private static boolean isClassLevelAnnotation(MethodTree method, AnnotationTree annotation) { + // Check if the annotation is on the method itself + for (AnnotationTree methodAnnotation : method.modifiers().annotations()) { + if (methodAnnotation == annotation) { + return false; + } + } + // If not on the method, it must be class-level + return true; + } + private static boolean isTransactionalAnnotation(AnnotationTree annotation) { // Check if the annotation itself is @Transactional if (annotation.symbolType().is(SpringUtils.TRANSACTIONAL_ANNOTATION)) { From 7543849dd823cfbe3d8b3398b8b4d7a541630228 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 14 Jul 2026 10:54:53 +0200 Subject: [PATCH 2/3] Add secondary locations for method-level annotations as well Now all issues have a secondary location pointing to the @Transactional annotation that needs to be configured, making it immediately clear where the fix should be applied. Co-Authored-By: Claude Sonnet 4.5 --- ...heckedExceptionCheckSampleNonCompiling.java | 3 ++- ...ionalMethodCheckedExceptionCheckSample.java | 18 +++++++++--------- ...ansactionalMethodCheckedExceptionCheck.java | 1 + 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/java-checks-test-sources/default/src/main/files/non-compiling/checks/spring/TransactionalMethodCheckedExceptionCheckSampleNonCompiling.java b/java-checks-test-sources/default/src/main/files/non-compiling/checks/spring/TransactionalMethodCheckedExceptionCheckSampleNonCompiling.java index 258f412171f..86f21082124 100644 --- a/java-checks-test-sources/default/src/main/files/non-compiling/checks/spring/TransactionalMethodCheckedExceptionCheckSampleNonCompiling.java +++ b/java-checks-test-sources/default/src/main/files/non-compiling/checks/spring/TransactionalMethodCheckedExceptionCheckSampleNonCompiling.java @@ -9,7 +9,8 @@ class TransactionalMethodCheckedExceptionCheckSampleNonCompiling { public void unknownException() throws UnresolvedCheckedException { // No issue - type is unknown } - @Transactional + @Transactional // [[secondary=+1]] public void knownException() throws java.io.IOException { // Noncompliant +// ^^^^^^^^^^^^^^ } } diff --git a/java-checks-test-sources/default/src/main/java/checks/spring/TransactionalMethodCheckedExceptionCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/spring/TransactionalMethodCheckedExceptionCheckSample.java index f835d58ac30..5b79ccb7f0d 100644 --- a/java-checks-test-sources/default/src/main/java/checks/spring/TransactionalMethodCheckedExceptionCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/spring/TransactionalMethodCheckedExceptionCheckSample.java @@ -18,7 +18,7 @@ class CustomCheckedException extends Exception {} public class TransactionalMethodCheckedExceptionCheckSample { - @Transactional + @Transactional // [[secondary=+1]] public void processOrder(Order order) throws IOException, SQLException { // Noncompliant [[quickfixes=qf1,qf2]] // ^^^^^^^^^^^^ // fix@qf1 {{Add rollbackFor attribute}} @@ -27,19 +27,19 @@ public void processOrder(Order order) throws IOException, SQLException { // Nonc // edit@qf2 [[sl=-1;sc=3;el=-1;ec=17]] {{@Transactional(rollbackFor = java.lang.Exception.class)}} } - @Transactional + @Transactional // [[secondary=+1]] public void importData() throws Exception { // Noncompliant [[quickfixes=qf3]] // ^^^^^^^^^^ // fix@qf3 {{Add rollbackFor = Exception.class}} // edit@qf3 [[sl=-1;sc=3;el=-1;ec=17]] {{@Transactional(rollbackFor = java.lang.Exception.class)}} } - @Transactional(timeout = 30) + @Transactional(timeout = 30) // [[secondary=+1]] public void withOtherAttributes() throws SQLException { // Noncompliant // ^^^^^^^^^^^^^^^^^^^ } - @Transactional + @Transactional // [[secondary=+1]] public void customException() throws CustomCheckedException { // Noncompliant [[quickfixes=qf7,qf8]] // ^^^^^^^^^^^^^^^ // fix@qf7 {{Add rollbackFor attribute}} @@ -48,7 +48,7 @@ public void customException() throws CustomCheckedException { // Noncompliant [[ // edit@qf8 [[sl=-1;sc=3;el=-1;ec=17]] {{@Transactional(rollbackFor = java.lang.Exception.class)}} } - @Transactional + @Transactional // [[secondary=+1]] public void mixedExceptions() throws IOException, RuntimeException { // Noncompliant [[quickfixes=qf9,qf10]] // ^^^^^^^^^^^^^^^ // fix@qf9 {{Add rollbackFor attribute}} @@ -117,7 +117,7 @@ public void methodOverrides() throws IOException { public void errorNotChecked() throws Error { } - @org.springframework.transaction.annotation.Transactional + @org.springframework.transaction.annotation.Transactional // [[secondary=+1]] public void fullyQualified() throws IOException { // Noncompliant [[quickfixes=qf13,qf14]] // ^^^^^^^^^^^^^^ // fix@qf13 {{Add rollbackFor attribute}} @@ -130,7 +130,7 @@ public void fullyQualified() throws IOException { // Noncompliant [[quickfixes=q public void partialConfig() throws SQLException { } - @Transactional(value = "txManager") + @Transactional(value = "txManager") // [[secondary=+1]] public void withValueAttribute() throws IOException { // Noncompliant // ^^^^^^^^^^^^^^^^^^ } @@ -158,13 +158,13 @@ interface TransactionalInterface { } // Test meta-annotated (composed) annotation - @MyTransactional + @MyTransactional // [[secondary=+1]] public void metaAnnotated() throws IOException { // Noncompliant // ^^^^^^^^^^^^^ } // Test annotation with value attribute (transaction manager name) - @Transactional("txManager") + @Transactional("txManager") // [[secondary=+1]] public void valueShorthand() throws IOException { // Noncompliant // ^^^^^^^^^^^^^^ // Has value attribute but no rollback configuration diff --git a/java-checks/src/main/java/org/sonar/java/checks/spring/TransactionalMethodCheckedExceptionCheck.java b/java-checks/src/main/java/org/sonar/java/checks/spring/TransactionalMethodCheckedExceptionCheck.java index 946877cfe76..d44f2ef38e7 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/spring/TransactionalMethodCheckedExceptionCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/spring/TransactionalMethodCheckedExceptionCheck.java @@ -90,6 +90,7 @@ public void visitNode(Tree tree) { } else { issueBuilder .withMessage("Specify rollback behavior for checked exceptions using \"rollbackFor\" or \"noRollbackFor\" attributes.") + .withSecondaries(new JavaFileScannerContext.Location("@Transactional annotation", transactionalAnnotation)) .withQuickFixes(() -> computeQuickFixes(transactionalAnnotation, checkedExceptions)); } From 2e65d042df844ba3413222d37753010bb851ec38 Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Tue, 14 Jul 2026 13:12:13 +0200 Subject: [PATCH 3/3] Fix test sample file format for secondary locations with custom messages The Noncompliant comment format requires custom messages to be on a separate line when used with secondary locations. Co-Authored-By: Claude Sonnet 4.5 --- ...eckedExceptionCheckSampleNonCompiling.java | 4 +- ...onalMethodCheckedExceptionCheckSample.java | 54 +++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/java-checks-test-sources/default/src/main/files/non-compiling/checks/spring/TransactionalMethodCheckedExceptionCheckSampleNonCompiling.java b/java-checks-test-sources/default/src/main/files/non-compiling/checks/spring/TransactionalMethodCheckedExceptionCheckSampleNonCompiling.java index 86f21082124..1a2e8bb8f96 100644 --- a/java-checks-test-sources/default/src/main/files/non-compiling/checks/spring/TransactionalMethodCheckedExceptionCheckSampleNonCompiling.java +++ b/java-checks-test-sources/default/src/main/files/non-compiling/checks/spring/TransactionalMethodCheckedExceptionCheckSampleNonCompiling.java @@ -9,8 +9,8 @@ class TransactionalMethodCheckedExceptionCheckSampleNonCompiling { public void unknownException() throws UnresolvedCheckedException { // No issue - type is unknown } - @Transactional // [[secondary=+1]] - public void knownException() throws java.io.IOException { // Noncompliant + @Transactional + public void knownException() throws java.io.IOException { // Noncompliant [[secondary=12]] // ^^^^^^^^^^^^^^ } } diff --git a/java-checks-test-sources/default/src/main/java/checks/spring/TransactionalMethodCheckedExceptionCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/spring/TransactionalMethodCheckedExceptionCheckSample.java index 5b79ccb7f0d..94501e6bb7c 100644 --- a/java-checks-test-sources/default/src/main/java/checks/spring/TransactionalMethodCheckedExceptionCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/spring/TransactionalMethodCheckedExceptionCheckSample.java @@ -18,8 +18,8 @@ class CustomCheckedException extends Exception {} public class TransactionalMethodCheckedExceptionCheckSample { - @Transactional // [[secondary=+1]] - public void processOrder(Order order) throws IOException, SQLException { // Noncompliant [[quickfixes=qf1,qf2]] + @Transactional + public void processOrder(Order order) throws IOException, SQLException { // Noncompliant [[secondary=21;quickfixes=qf1,qf2]] // ^^^^^^^^^^^^ // fix@qf1 {{Add rollbackFor attribute}} // edit@qf1 [[sl=-1;sc=3;el=-1;ec=17]] {{@Transactional(rollbackFor = {java.io.IOException.class, java.sql.SQLException.class})}} @@ -27,20 +27,20 @@ public void processOrder(Order order) throws IOException, SQLException { // Nonc // edit@qf2 [[sl=-1;sc=3;el=-1;ec=17]] {{@Transactional(rollbackFor = java.lang.Exception.class)}} } - @Transactional // [[secondary=+1]] - public void importData() throws Exception { // Noncompliant [[quickfixes=qf3]] + @Transactional + public void importData() throws Exception { // Noncompliant [[secondary=30;quickfixes=qf3]] // ^^^^^^^^^^ // fix@qf3 {{Add rollbackFor = Exception.class}} // edit@qf3 [[sl=-1;sc=3;el=-1;ec=17]] {{@Transactional(rollbackFor = java.lang.Exception.class)}} } - @Transactional(timeout = 30) // [[secondary=+1]] - public void withOtherAttributes() throws SQLException { // Noncompliant + @Transactional(timeout = 30) + public void withOtherAttributes() throws SQLException { // Noncompliant [[secondary=37]] // ^^^^^^^^^^^^^^^^^^^ } - @Transactional // [[secondary=+1]] - public void customException() throws CustomCheckedException { // Noncompliant [[quickfixes=qf7,qf8]] + @Transactional + public void customException() throws CustomCheckedException { // Noncompliant [[secondary=42;quickfixes=qf7,qf8]] // ^^^^^^^^^^^^^^^ // fix@qf7 {{Add rollbackFor attribute}} // edit@qf7 [[sl=-1;sc=3;el=-1;ec=17]] {{@Transactional(rollbackFor = checks.spring.CustomCheckedException.class)}} @@ -48,8 +48,8 @@ public void customException() throws CustomCheckedException { // Noncompliant [[ // edit@qf8 [[sl=-1;sc=3;el=-1;ec=17]] {{@Transactional(rollbackFor = java.lang.Exception.class)}} } - @Transactional // [[secondary=+1]] - public void mixedExceptions() throws IOException, RuntimeException { // Noncompliant [[quickfixes=qf9,qf10]] + @Transactional + public void mixedExceptions() throws IOException, RuntimeException { // Noncompliant [[secondary=51;quickfixes=qf9,qf10]] // ^^^^^^^^^^^^^^^ // fix@qf9 {{Add rollbackFor attribute}} // edit@qf9 [[sl=-1;sc=3;el=-1;ec=17]] {{@Transactional(rollbackFor = java.io.IOException.class)}} @@ -102,10 +102,10 @@ public void inherited() throws IOException { } } - @Transactional // [[secondary=+2]] + @Transactional static class ClassLevelNoConfig { - public void noConfig() throws IOException { // Noncompliant {{Specify rollback behavior for checked exceptions using "rollbackFor" or "noRollbackFor" attributes on the class-level @Transactional.}} -// ^^^^^^^^ + public void noConfig() throws IOException { // Noncompliant [[secondary=105]] +// ^^^^^^^^ {{Specify rollback behavior for checked exceptions using "rollbackFor" or "noRollbackFor" attributes on the class-level @Transactional.}} } @Transactional(rollbackFor = IOException.class) @@ -117,8 +117,8 @@ public void methodOverrides() throws IOException { public void errorNotChecked() throws Error { } - @org.springframework.transaction.annotation.Transactional // [[secondary=+1]] - public void fullyQualified() throws IOException { // Noncompliant [[quickfixes=qf13,qf14]] + @org.springframework.transaction.annotation.Transactional + public void fullyQualified() throws IOException { // Noncompliant [[secondary=120;quickfixes=qf13,qf14]] // ^^^^^^^^^^^^^^ // fix@qf13 {{Add rollbackFor attribute}} // edit@qf13 [[sl=-1;sc=3;el=-1;ec=60]] {{@org.springframework.transaction.annotation.Transactional(rollbackFor = java.io.IOException.class)}} @@ -130,17 +130,17 @@ public void fullyQualified() throws IOException { // Noncompliant [[quickfixes=q public void partialConfig() throws SQLException { } - @Transactional(value = "txManager") // [[secondary=+1]] - public void withValueAttribute() throws IOException { // Noncompliant + @Transactional(value = "txManager") + public void withValueAttribute() throws IOException { // Noncompliant [[secondary=133]] // ^^^^^^^^^^^^^^^^^^ } // Test nested structure to ensure parent traversal works static class OuterClass { - @Transactional // [[secondary=+2]] + @Transactional static class InnerClassWithAnnotation { - public void nestedMethod() throws IOException { // Noncompliant {{Specify rollback behavior for checked exceptions using "rollbackFor" or "noRollbackFor" attributes on the class-level @Transactional.}} -// ^^^^^^^^^^^^ + public void nestedMethod() throws IOException { // Noncompliant [[secondary=140]] +// ^^^^^^^^^^^^ {{Specify rollback behavior for checked exceptions using "rollbackFor" or "noRollbackFor" attributes on the class-level @Transactional.}} } } @@ -151,21 +151,21 @@ public void nestedMethodNoAnnotation() throws IOException { } } - @Transactional // [[secondary=+2]] + @Transactional interface TransactionalInterface { - void interfaceMethod() throws IOException; // Noncompliant {{Specify rollback behavior for checked exceptions using "rollbackFor" or "noRollbackFor" attributes on the class-level @Transactional.}} -// ^^^^^^^^^^^^^^^ + void interfaceMethod() throws IOException; // Noncompliant [[secondary=154]] +// ^^^^^^^^^^^^^^^ {{Specify rollback behavior for checked exceptions using "rollbackFor" or "noRollbackFor" attributes on the class-level @Transactional.}} } // Test meta-annotated (composed) annotation - @MyTransactional // [[secondary=+1]] - public void metaAnnotated() throws IOException { // Noncompliant + @MyTransactional + public void metaAnnotated() throws IOException { // Noncompliant [[secondary=161]] // ^^^^^^^^^^^^^ } // Test annotation with value attribute (transaction manager name) - @Transactional("txManager") // [[secondary=+1]] - public void valueShorthand() throws IOException { // Noncompliant + @Transactional("txManager") + public void valueShorthand() throws IOException { // Noncompliant [[secondary=167]] // ^^^^^^^^^^^^^^ // Has value attribute but no rollback configuration }