-
Notifications
You must be signed in to change notification settings - Fork 720
SONARJAVA-6084 Validation logic should be placed in constructor prologue when possible #5446
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2e72dc7
2535857
73db80a
b361bf8
70c684f
f1e642d
2becb3b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "ruleKey": "S8433", | ||
| "hasTruePositives": true, | ||
| "falseNegatives": 0, | ||
| "falsePositives": 0 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| package checks; | ||
|
|
||
| import java.util.Objects; | ||
|
|
||
| class FlexibleConstructorBodyValidationCheckSample { | ||
|
|
||
| static class Coffee { | ||
| Coffee() {} | ||
| Coffee(int water, int milk) {} | ||
| } | ||
|
|
||
| static class SmallCoffee extends Coffee { | ||
| private String topping; | ||
| private int maxVolume = 100; | ||
|
|
||
| public SmallCoffee(int water, int milk, String topping) { | ||
| super(water, milk); | ||
| int maxCupCoffee = 90; | ||
| int totalVolume = water + milk; | ||
| if (totalVolume > maxCupCoffee) { // Noncompliant {{Move this validation logic before the super() or this() call.}} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice to have |
||
| //^[el=+3;ec=7] | ||
| throw new IllegalArgumentException(); | ||
| } | ||
| this.topping = topping; | ||
| } | ||
|
|
||
| public SmallCoffee(int water, int milk) { | ||
| int totalVolume = water + milk; | ||
| int maxCupCoffee = 90; | ||
rombirli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (totalVolume > maxCupCoffee) { // Compliant: validation before super() | ||
| throw new IllegalArgumentException(); | ||
| } | ||
| maxCupCoffee++; | ||
| super(water, milk); | ||
| } | ||
|
|
||
| public SmallCoffee(int water) { | ||
| super(water, 0); | ||
| if (water > maxVolume) { // Compliant: Uses instance field | ||
rombirli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| throw new IllegalArgumentException(); | ||
| } | ||
| } | ||
|
|
||
| public SmallCoffee() { | ||
| super(0, 0); // Compliant: no validation | ||
| } | ||
| } | ||
|
|
||
| static class MediumCoffee extends Coffee { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we add a comment to explain that the purpose of SmallCoffee is to test validation before super() and that MediumCoffee is to test validation before this() to make things a bit clearer
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see that in many test samples we don't add explaining comments. Personally I don't think it's necessary.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree we should avoid redundant comments. However, given the length of this specific sample (>150 lines), the purpose of SmallCoffee vs MediumCoffee isn't immediately obvious from the names alone. I think a quick comment adds a lot of 'scannability' here that we usually get from explicit filenames in our other tests. |
||
| private String flavor; | ||
|
|
||
| public MediumCoffee(int water, int milk) { | ||
| this.isValidFlavor(""); | ||
| super(water, milk); | ||
| if (water + milk > MAX_SIZE) { // Noncompliant | ||
| throw new IllegalArgumentException(); | ||
| } | ||
| this.isValidFlavor(""); | ||
| this.flavor = "Vanilla"; | ||
| } | ||
|
|
||
| public MediumCoffee(int water) { | ||
| if (water < 0) { // Compliant: throw before this() | ||
| throw new IllegalArgumentException("Water cannot be negative"); | ||
| } | ||
| this(water, 0); | ||
| } | ||
|
|
||
| public MediumCoffee(String flavor) { | ||
| this(100, 50); | ||
| if (!isValidFlavor(flavor)) { // Compliant: validation uses instance method, cannot be moved | ||
| throw new IllegalArgumentException(); | ||
| } | ||
| this.flavor = flavor; | ||
| } | ||
|
|
||
| private boolean isValidFlavor(String flavor) { | ||
| return flavor != null && !flavor.isEmpty(); | ||
| } | ||
| } | ||
|
|
||
| // Test using static members | ||
| static class LargeCoffee extends Coffee { | ||
| private String name; | ||
| private static final int MAX_SIZE = 500; | ||
|
|
||
| public LargeCoffee(int water, int milk) { | ||
| super(water, milk); | ||
| if (water + milk > MAX_SIZE) { // Noncompliant | ||
| throw new IllegalArgumentException(); | ||
| } | ||
| } | ||
|
|
||
| public LargeCoffee(String name) { | ||
| super(100, 100); | ||
| this.name = name; | ||
| if (!isValidName(name)) { // NonCompliant | ||
| throw new IllegalArgumentException(); | ||
| } | ||
| } | ||
|
|
||
| private static boolean isValidName(String name) { | ||
| return name != null && !name.isEmpty(); | ||
| } | ||
| } | ||
|
|
||
| // Test with different validation libraries | ||
| static class GuavaCoffee extends Coffee { | ||
| public GuavaCoffee(String name) { | ||
| super(100, 50); | ||
| com.google.common.base.Preconditions.checkNotNull(name); // Noncompliant {{Move this validation logic before the super() or this() call.}} | ||
| } | ||
|
|
||
| public GuavaCoffee(String name) { | ||
| com.google.common.base.Preconditions.checkNotNull(name); // Compliant | ||
| super(100, 50); | ||
| } | ||
| } | ||
|
|
||
| static class SpringCoffee extends Coffee { | ||
| public SpringCoffee(String name) { | ||
| super(100, 50); | ||
| org.springframework.util.Assert.notNull(name, "Name must not be null"); // Noncompliant {{Move this validation logic before the super() or this() call.}} | ||
| } | ||
|
|
||
| public SpringCoffee(String name) { | ||
| org.springframework.util.Assert.notNull(name, "Name must not be null"); // Compliant | ||
| super(100, 50); | ||
| } | ||
| } | ||
rombirli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Test with implicit super() | ||
| static class ImplicitSuperCoffee extends Coffee { | ||
| public ImplicitSuperCoffee(int water) { | ||
| // Implicit super() call here | ||
| if (water < 0) { // Noncompliant | ||
| throw new IllegalArgumentException(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Test multiple validations | ||
| static class MultiValidationCoffee extends Coffee { | ||
| public MultiValidationCoffee(int water, int milk, String name) { | ||
| super(water, milk); | ||
| if (water < 0) { // Noncompliant | ||
| throw new IllegalArgumentException("Invalid water"); | ||
| } | ||
| if (milk < 0) { // Noncompliant | ||
| throw new IllegalArgumentException("Invalid milk"); | ||
| } | ||
| Objects.requireNonNull(name); // Noncompliant | ||
| } | ||
| } | ||
|
|
||
| // Test nested if statements | ||
| static class NestedIfCoffee extends Coffee { | ||
| public NestedIfCoffee(int water, int milk) { | ||
| super(water, milk); | ||
| if (water > 0) { // Noncompliant | ||
| if (milk > 0) { | ||
| if (water + milk > 200) { | ||
| throw new IllegalArgumentException(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.