From 4addef899c5c95ce94c0bd3b42bbc10a6bb8c1a4 Mon Sep 17 00:00:00 2001 From: Irina Batinic <117161143+irina-batinic-sonarsource@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:24:49 +0100 Subject: [PATCH] SONARJAVA-4662 Update Rules Metadata and External Linters Metadata (#4527) --- .../java/rules/spotbugs/spotbugs-rules.json | 3 +- .../org/sonar/l10n/java/rules/java/S113.html | 2 +- .../org/sonar/l10n/java/rules/java/S113.json | 2 +- .../org/sonar/l10n/java/rules/java/S1144.html | 31 ++-- .../org/sonar/l10n/java/rules/java/S1144.json | 2 +- .../org/sonar/l10n/java/rules/java/S1860.html | 58 +++++-- .../org/sonar/l10n/java/rules/java/S1871.html | 42 ++--- .../org/sonar/l10n/java/rules/java/S1905.html | 2 +- .../org/sonar/l10n/java/rules/java/S2259.html | 144 +++++++++++++--- .../org/sonar/l10n/java/rules/java/S3776.html | 161 +++++++++++++++++- .../org/sonar/l10n/java/rules/java/S3776.json | 2 +- .../org/sonar/l10n/java/rules/java/S6806.json | 2 +- .../org/sonar/l10n/java/rules/java/S6813.html | 12 +- .../org/sonar/l10n/java/rules/java/S6813.json | 2 +- .../org/sonar/l10n/java/rules/java/S6818.json | 2 +- sonarpedia.json | 2 +- 16 files changed, 374 insertions(+), 95 deletions(-) diff --git a/external-reports/src/main/resources/org/sonar/l10n/java/rules/spotbugs/spotbugs-rules.json b/external-reports/src/main/resources/org/sonar/l10n/java/rules/spotbugs/spotbugs-rules.json index 56505a8f06a..4a269907bf9 100644 --- a/external-reports/src/main/resources/org/sonar/l10n/java/rules/spotbugs/spotbugs-rules.json +++ b/external-reports/src/main/resources/org/sonar/l10n/java/rules/spotbugs/spotbugs-rules.json @@ -2276,7 +2276,8 @@ }, { "key": "PI_DO_NOT_REUSE_PUBLIC_IDENTIFIERS_INNER_CLASS_NAMES", - "name": "Do not reuse public identifiers from JSL as inner name", + "name": "Bad practice - Do not reuse public identifiers from JSL as inner name", + "type": "CODE_SMELL", "severity": "MAJOR", "url": "https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html#pi-do-not-reuse-public-identifiers-inner-class-names" }, diff --git a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S113.html b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S113.html index 33ec564a16a..65ddd8f677d 100644 --- a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S113.html +++ b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S113.html @@ -1,5 +1,5 @@

Why is this an issue?

-

Some tools work better when files end with an empty line.

+

Some tools work better when files end with a newline.

This rule simply generates an issue if it is missing.

For example, a Git diff looks like this if the empty line is missing at the end of the file:

diff --git a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S113.json b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S113.json
index b5acb1513c0..1d0fcfdd81e 100644
--- a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S113.json
+++ b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S113.json
@@ -1,5 +1,5 @@
 {
-  "title": "Files should contain an empty newline at the end",
+  "title": "Files should end with a newline",
   "type": "CODE_SMELL",
   "code": {
     "impacts": {
diff --git a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1144.html b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1144.html
index 678681fdd3a..c5b43b81fef 100644
--- a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1144.html
+++ b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1144.html
@@ -1,35 +1,34 @@
+

This rule raises an issue when a private method is never referenced in the code.

Why is this an issue?

-

private methods that are never executed are dead code: unnecessary, inoperative code that should be removed. Cleaning out dead code -decreases the size of the maintained codebase, making it easier to understand the program and preventing bugs from being introduced.

-

Note that this rule does not take reflection into account, which means that issues will be raised on private methods that are only -accessed using the reflection API.

+

A method that is never called is dead code, and should be removed. Cleaning out dead code decreases the size of the maintained codebase, making it +easier to understand the program and preventing bugs from being introduced.

+

This rule detects methods that are never referenced from inside a translation unit, and cannot be referenced from the outside.

+

Code examples

Noncompliant code example

-
+
 public class Foo implements Serializable
 {
-  private Foo(){}     //Compliant, private empty constructor intentionally used to prevent any direct instantiation of a class.
-  public static void doSomething(){
+  public static void doSomething() {
     Foo foo = new Foo();
     ...
   }
-  private void unusedPrivateMethod(){...}
-  private void writeObject(ObjectOutputStream s){...}  //Compliant, relates to the java serialization mechanism
-  private void readObject(ObjectInputStream in){...}  //Compliant, relates to the java serialization mechanism
+
+  private void unusedPrivateMethod() {...}
+  private void writeObject(ObjectOutputStream s) {...}  //Compliant, relates to the java serialization mechanism
+  private void readObject(ObjectInputStream in) {...}  //Compliant, relates to the java serialization mechanism
 }
 

Compliant solution

-
+
 public class Foo implements Serializable
 {
-  private Foo(){}     //Compliant, private empty constructor intentionally used to prevent any direct instantiation of a class.
   public static void doSomething(){
     Foo foo = new Foo();
     ...
   }
 
-  private void writeObject(ObjectOutputStream s){...}  //Compliant, relates to the java serialization mechanism
-
-  private void readObject(ObjectInputStream in){...}  //Compliant, relates to the java serialization mechanism
+  private void writeObject(ObjectOutputStream s) {...}  //Compliant, relates to the java serialization mechanism
+  private void readObject(ObjectInputStream in) {...}  //Compliant, relates to the java serialization mechanism
 }
 

Exceptions

@@ -38,4 +37,6 @@

Exceptions

  • annotated methods
  • methods with parameters that are annotated with @javax.enterprise.event.Observes
  • +

    The rule does not take reflection into account, which means that issues will be raised on private methods that are only accessed using +the reflection API.

    diff --git a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1144.json b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1144.json index 21c14cac34d..ec6423802c8 100644 --- a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1144.json +++ b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1144.json @@ -10,7 +10,7 @@ "status": "ready", "remediation": { "func": "Constant\/Issue", - "constantCost": "5min" + "constantCost": "2min" }, "tags": [ "unused" diff --git a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1860.html b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1860.html index 634d18d517c..98015ed1cdb 100644 --- a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1860.html +++ b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1860.html @@ -1,6 +1,34 @@

    Why is this an issue?

    -

    Instances of value-based classes, which are pooled and potentially reused, should not be used for synchronization. If they are, it can cause -unrelated threads to deadlock with unhelpful stacktraces.

    +

    In Java, value-based classes are those for which instances are final and immutable, like String, Integer and so on, and +their identity relies on their value and not their reference. When a variable of one of these types is instantiated, the JVM caches its value, and the +variable is just a reference to that value. For example, multiple String variables with the same value "Hello world!" will refer to the +same cached string literal in memory.

    +

    The synchronized keyword tells the JVM to only allow the execution of the code contained in the following block to one +Thread at a time. This mechanism relies on the identity of the object that is being synchronized between threads, to prevent that if +object X is locked, it will still be possible to lock another object Y.

    +

    It means that the JVM will fail to correctly synchronize threads on instances of the aforementioned value-based classes, for instance:

    +
    +// These variables "a" and "b" will effectively reference the same object in memory
    +Integer a = 0;
    +Integer b = 0;
    +
    +// This means that in the following code, the JVM could try to lock and execute
    +// on the variable "a" because "b" was notified to be released, as the two Integer variables
    +// are the same object to the JVM
    +void syncMethod(int x) {
    +    synchronized (a) {
    +        if (a == x) {
    +        // ... do something here
    +        }
    +    }
    +    synchronized (b) {
    +        if (b == x) {
    +        // ... do something else
    +        }
    +    }
    +}
    +
    +

    This behavior can cause unrelated threads to deadlock with unclear stacktraces.

    Within the JDK, types which should not be used for synchronization include:

    • String literals
    • @@ -21,8 +49,8 @@

      Why is this an issue?

    How to fix it

    Replace instances of value-based classes with a new object instance to synchronize on.

    -

    Code examples

    -

    Noncompliant code example

    +

    Code examples

    +

    Noncompliant code example

     private static final Boolean bLock = Boolean.FALSE;
     private static final Integer iLock = Integer.valueOf(0);
    @@ -44,33 +72,33 @@ 

    Noncompliant code example

    ... }
    -

    Compliant solution

    +

    Compliant solution

     private static final Object lock1 = new Object();
    -private static final Object lock2 = new Object();
    -private static final Object lock3 = new Object();
    -private static final Object lock4 = new Object();
    +private static final Integer iLock = new Integer(42);
    +private static final String sLock = new String("A brand new string in memory!");
    +private static final List<String> listLock = new ArrayList<>();
     
     public void doSomething() {
     
       synchronized(lock1) { // Compliant
           ...
       }
    -  synchronized(lock2) { // Compliant
    +  synchronized(iLock) { // Compliant
           ...
       }
    -  synchronized(lock3) { // Compliant
    +  synchronized(sLock) { // Compliant
           ...
       }
    -  synchronized(lock4) { // Compliant
    +  synchronized(listLock) { // Compliant
           ...
       }
     

    Resources

    diff --git a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1871.html b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1871.html index 7118f52a0b4..f299e820b01 100644 --- a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1871.html +++ b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1871.html @@ -1,7 +1,25 @@

    Why is this an issue?

    +

    When the same code is duplicated in two or more separate branches of a conditional, it can make the code harder to understand, maintain, and can +potentially introduce bugs if one instance of the code is changed but others are not.

    Having two cases in a switch statement or two branches in an if chain with the same implementation is at best duplicate code, and at worst a coding error.

    +if (a >= 0 && a < 10) {
    +  doFirstThing();
    +  doTheThing();
    +}
    +else if (a >= 10 && a < 20) {
    +  doTheOtherThing();
    +}
    +else if (a >= 20 && a < 50) {
    +  doFirstThing();
    +  doTheThing();  // Noncompliant; duplicates first condition
    +}
    +else {
    +  doTheRest();
    +}
    +
    +
     switch (i) {
       case 1:
         doFirstThing();
    @@ -18,27 +36,11 @@ 

    Why is this an issue?

    doTheRest(); }
    -
    -if (a >= 0 && a < 10) {
    -  doFirstThing();
    -  doTheThing();
    -}
    -else if (a >= 10 && a < 20) {
    -  doTheOtherThing();
    -}
    -else if (a >= 20 && a < 50) {
    -  doFirstThing();
    -  doTheThing();  // Noncompliant; duplicates first condition
    -}
    -else {
    -  doTheRest();
    -}
    -

    If the same logic is truly needed for both instances, then:

    • in an if chain they should be combined
    -
    +
     if ((a >= 0 && a < 10) || (a >= 20 && a < 50)) { // Compliant
       doFirstThing();
       doTheThing();
    @@ -51,9 +53,9 @@ 

    Why is this an issue?

    }
      -
    • for a switch, one should fall through to the other.
    • +
    • for a switch, one should fall through to the other
    -
    +
     switch (i) {
       case 1:
       case 3: // Compliant
    @@ -74,7 +76,7 @@ 

    Exceptions

    switch statement that contains a single line of code with or without a following break.

     if (a == 1) {
    -  doSomething();  //no issue, usually this is done on purpose to increase the readability
    +  doSomething();  // Compliant, usually this is done on purpose to increase the readability
     } else if (a == 2) {
       doSomethingElse();
     } else {
    diff --git a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1905.html b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1905.html
    index ded3c006f5d..b017dfa35a0 100644
    --- a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1905.html
    +++ b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S1905.html
    @@ -1,6 +1,6 @@
     

    Why is this an issue?

    Casting expressions are utilized to convert one data type to another, such as transforming an integer into a string. This is especially crucial in -strongly typed languages like C, C++, C#, Java, Python, and others.

    +strongly typed languages like C, C++, C#, Java, Python, and others.

    However, there are instances where casting expressions are not needed. These include situations like:

    • casting a variable to its own type
    • diff --git a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S2259.html b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S2259.html index 5db5b20f8e2..27ffe34c081 100644 --- a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S2259.html +++ b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S2259.html @@ -2,53 +2,143 @@

      Why is this an issue?

      A reference to null should never be dereferenced/accessed. Doing so will cause a NullPointerException to be thrown. At best, such an exception will cause abrupt program termination. At worst, it could expose debugging information that would be useful to an attacker, or it could allow an attacker to bypass security measures.

      -

      Note that when they are present, this rule takes advantage of @CheckForNull and @Nonnull annotations defined in JSR-305 to understand which values are and are not nullable except when @Nonnull is used -on the parameter to equals, which by contract should always work with null.

      -

      Noncompliant code example

      -
      +

      Note that when they are present, this rule takes advantage of nullability annotations, like @CheckForNull or @Nonnull, +defined in JSR-305 to understand which values can be null or not. @Nonnull will be +ignored if used on the parameter of the equals method, which by contract should always work with null.

      +

      How to fix it

      +

      Code examples

      +

      Noncompliant code example

      +

      The variable myObject is equal to null, meaning it has no value:

      +
      +public void method() {
      +  Object myObject = null;
      +  System.out.println(myObject.toString()); // Noncompliant: myObject is null
      +}
      +
      +

      The parameter input might be null as suggested by the if condition:

      +
      +public void method(Object input)
      +{
      +  if (input == null)
      +  {
      +    // ...
      +  }
      +  System.out.println(input.toString()); // Noncompliant
      +}
      +
      +

      The unboxing triggered in the return statement will throw a NullPointerException:

      +
      +public boolean method() {
      +  Boolean boxed = null;
      +  return boxed; // Noncompliant
      +}
      +
      +

      Both conn and stmt might be null in case an exception was thrown in the try{} block:

      +
      +Connection conn = null;
      +Statement stmt = null;
      +try {
      +  conn = DriverManager.getConnection(DB_URL,USER,PASS);
      +  stmt = conn.createStatement();
      +  // ...
      +} catch(Exception e) {
      +  e.printStackTrace();
      +} finally {
      +  stmt.close();  // Noncompliant
      +  conn.close();  // Noncompliant
      +}
      +
      +

      As getName() is annotated with @CheckForNull, there is a risk of NullPointerException here:

      +
       @CheckForNull
      -String getName(){...}
      +String getName() {...}
       
       public boolean isNameEmpty() {
      -  return getName().length() == 0; // Noncompliant; the result of getName() could be null, but isn't null-checked
      +  return getName().length() == 0; // Noncompliant
      +}
      +
      +

      As merge(…​) parameter is annotated with @Nonnull, passing an identified potential null value (thanks to @CheckForNull) +is not safe:

      +
      +private void merge(@Nonnull Color firstColor, @Nonnull Color secondColor) {...}
      +
      +public void append(@CheckForNull Color color) {
      +  merge(currentColor, color);  // Noncompliant: color should be null-checked because merge(...) doesn't accept nullable parameters
      +}
      +
      +

      Compliant solution

      +

      Ensuring the variable myObject has a value resolves the issue:

      +
      +public void method() {
      +  Object myObject = new Object();
      +  System.out.println(myObject.toString()); // Compliant: myObject is not null
      +}
      +
      +

      Preventing the non-compliant code to be executed by returning early:

      +
      +public void method(Object input)
      +{
      +  if (input == null)
      +  {
      +    return;
      +  }
      +  System.out.println(input.toString()); // Compliant: if 'input' is null, this is unreachable
      +}
      +
      +

      Ensuring that no unboxing of null value can happen resolves the issue

      +
      +public boolean method() {
      +  Boolean boxed = true;
      +  return boxed; // Compliant
       }
       
      -
      +

      Ensuring that both conn and stmt are not null resolves the issue:

      +
       Connection conn = null;
       Statement stmt = null;
      -try{
      +try {
         conn = DriverManager.getConnection(DB_URL,USER,PASS);
         stmt = conn.createStatement();
         // ...
      -
      -}catch(Exception e){
      +} catch(Exception e) {
         e.printStackTrace();
      -}finally{
      -  stmt.close();   // Noncompliant; stmt could be null if an exception was thrown in the try{} block
      -  conn.close();  // Noncompliant; conn could be null if an exception was thrown
      +} finally {
      +  if (stmt != null) {
      +    stmt.close();  // Compliant
      +  }
      +  if (conn != null) {
      +    conn.close();  // Compliant
      +  }
       }
       
      -
      -private void merge(@Nonnull Color firstColor, @Nonnull Color secondColor){...}
      +

      Checking the returned value of getName() resolves the issue:

      +
      +@CheckForNull
      +String getName() {...}
       
      -public  void append(@CheckForNull Color color) {
      -    merge(currentColor, color);  // Noncompliant; color should be null-checked because merge(...) doesn't accept nullable parameters
      +public boolean isNameEmpty() {
      +  String name = getName();
      +  if (name != null) {
      +    return name.length() == 0; // Compliant
      +  } else {
      +    // ...
      +  }
       }
       
      -
      -void paint(Color color) {
      -  if(color == null) {
      -    System.out.println("Unable to apply color " + color.toString());  // Noncompliant; NullPointerException will be thrown
      -    return;
      +

      Ensuring that the provided color is not null resolves the issue:

      +
      +private void merge(@Nonnull Color firstColor, @Nonnull Color secondColor) {...}
      +
      +public void append(@CheckForNull Color color) {
      +  if (color != null) {
      +    merge(currentColor, color);  // Compliant
         }
      -  ...
       }
       

      Resources

      diff --git a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S3776.html b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S3776.html index f43326affc6..8fdc8af66fe 100644 --- a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S3776.html +++ b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S3776.html @@ -1,17 +1,168 @@ +

      This rule raises an issue when the code cognitive complexity of a function is above a certain threshold.

      Why is this an issue?

      -

      Cognitive Complexity is a measure of how hard the control flow of a method is to understand. Methods with high Cognitive Complexity will be -difficult to maintain.

      +

      Cognitive Complexity is a measure of how hard it is to understand the control flow of a unit of code. Code with high cognitive complexity is hard +to read, understand, test, and modify.

      +

      As a rule of thumb, high cognitive complexity is a sign that the code should be refactored into smaller, easier-to-manage pieces.

      +

      Which syntax in code does impact cognitive complexity score?

      +

      Here are the core concepts:

      +
        +
      • Cognitive complexity is incremented each time the code breaks the normal linear reading flow.
        This concerns, for example: + Loop structures, Conditionals, Catches, Switches, Jumps to label and mixed operators in condition.
      • +
      • Each nesting level adds a malus to the breaking call.
        During code reading, the deeper you go through nested layers, the + harder it becomes to keep the context in mind.
      • +
      • Method calls are free
        A well-picked method name is a summary of multiple lines of code. A reader can first explore a + high-level view of what the code is performing then go deeper and deeper by looking at called functions content.
        Note: This does not + apply to recursive calls, those will increment cognitive score.
      • +
      +

      The method of computation is fully detailed in the pdf linked in the resources.

      Exceptions

      equals and hashCode methods are ignored because they might be automatically generated and might end up being difficult to understand, especially in the presence of many fields.

      +

      How to fix it

      +

      Reducing cognitive complexity can be challenging.
      Here are a few suggestions:

      +
        +
      • Extract complex conditions in a new function.
        Mixed operators in condition will increase complexity. Extracting the + condition in a new function with an appropriate name will reduce cognitive load.
      • +
      • Break down large functions.
        Large functions can be hard to understand and maintain. If a function is doing too many + things, consider breaking it down into smaller, more manageable functions. Each function should have a single responsibility.
      • +
      • Avoid deep nesting by returning early.
        To avoid the nesting of conditions, process exceptional cases first and return + early.
      • +
      +

      Code examples

      +

      Extraction of a complex condition in a new function.

      +

      Noncompliant code example

      +

      The code is using a complex condition and has a cognitive cost of 3.

      +
      +double calculateFinalPrice(User user, Cart cart) {
      +  double total = calculateTotal(cart);
      +  if (user.hasMembership()                            // +1 (if)
      +    && user.ordersCount() > 10                        // +1 (more than one condition)
      +    && user.isAccountActive()
      +    && !user.hasDiscount()
      +    || user.ordersCount() == 1) {                    // +1 (change of operator in condition)
      +    total = applyDiscount(user, total);
      +  }
      +  return total;
      +}
      +
      +

      Compliant solution

      +

      Even if the cognitive complexity of the whole program did not change, it is easier for a reader to understand the code of the +calculateFinalPrice function, which now only has a cognitive cost of 1.

      +
      +double calculateFinalPrice(User user, Cart cart) {
      +  double total = calculateTotal(cart);
      +  if (isEligibleForDiscount(user)) {                  // +1 (if)
      +    total = applyDiscount(user, total);
      +  }
      +  return total;
      +}
      +
      +boolean isEligibleForDiscount(User user) {
      +  return user.hasMembership()
      +    && user.ordersCount() > 10                        // +1 (more than one condition)
      +    && user.isAccountActive()
      +    && !user.hasDiscount()
      +    || user.ordersCount() == 1;                       // +1 (change of operator in condition)
      +}
      +
      +

      Break down large functions.

      +

      Noncompliant code example

      +

      For example, consider a function that calculates the total price of a shopping cart, including sales tax and shipping.
      Note: The code +is simplified here, to illustrate the purpose. Please imagine there is more happening in the for loops.

      +
      +double calculateTotal(Cart cart) {
      +  double total = 0;
      +  for (Item item : cart.items()) {       // +1 (for)
      +    total += item.price;
      +  }
      +
      +  // calculateSalesTax
      +  for (Item item : cart.items()) {       // +1 (for)
      +    total += 0.2 * item.price;
      +  }
      +
      +  //calculateShipping
      +  total += 5 * cart.items().size();
      +
      +  return total;
      +}
      +
      +

      This function could be refactored into smaller functions: The complexity is spread over multiple functions and the complex +calculateTotal has now a complexity score of zero.

      +

      Compliant solution

      +
      +double calculateTotal(Cart cart) {
      +  double total = 0;
      +  total = calculateSubtotal(cart, total);
      +  total += calculateSalesTax(cart, total);
      +  total += calculateShipping(cart, total);
      +
      +  return total;
      +}
      +
      +double calculateShipping(Cart cart, double total) {
      +  total += 5 * cart.items().size();
      +  return total;
      +}
      +
      +double calculateSalesTax(Cart cart, double total) {
      +  for (Item item : cart.items()) {       // +1 (for)
      +    total += 0.2 * item.price;
      +  }
      +  return total;
      +}
      +
      +double calculateSubtotal(Cart cart, double total) {
      +  for (Item item : cart.items()) {       // +1 (for)
      +    total += item.price;
      +  }
      +  return total;
      +}
      +
      +

      Avoid deep nesting by returning early.

      +

      Noncompliant code example

      +

      The below code has a cognitive complexity of 6.

      +
      +double calculateDiscount(double price, User user) {
      +  if (isEligibleForDiscount(user)) {      // +1 ( if )
      +    if (user.hasMembership()) {           // +2 ( nested if )
      +      return price * 0.9;
      +    } else if (user.ordersCount() == 1) { // +1 ( else )
      +      return price * 0.95;
      +    } else {                              // +1 ( else )
      +      return price;
      +    }
      +  } else {                                // +1 ( else )
      +    return price;
      +  }
      +}
      +
      +

      Compliant solution

      +

      Checking for the edge case first flattens the if statements and reduces the cognitive complexity to 3.

      +
      +double calculateDiscount(double price, User user) {
      +  if (!isEligibleForDiscount(user)) {     // +1 ( if )
      +    return price;
      +  }
      +  if (user.hasMembership()) {             // +1
      +    return price * 0.9;
      +  }
      +  if (user.ordersCount() == 1) {          // +1 ( if )
      +    return price * 0.95;
      +  }
      +  return price;
      +}
      +
      +

      Pitfalls

      +

      As this code is complex, ensure that you have unit tests that cover the code before refactoring.

      Resources

      Documentation

      Articles & blog posts

      diff --git a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S3776.json b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S3776.json index 30b6c0e2578..268a1030414 100644 --- a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S3776.json +++ b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S3776.json @@ -21,5 +21,5 @@ "ruleSpecification": "RSPEC-3776", "sqKey": "S3776", "scope": "All", - "quickfix": "unknown" + "quickfix": "infeasible" } diff --git a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S6806.json b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S6806.json index ee12a770706..74f9dbf5d06 100644 --- a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S6806.json +++ b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S6806.json @@ -1,5 +1,5 @@ { - "title": "Model attribute naming convention for Spring Expression Language (SpEL)", + "title": "Model attributes should follow the Java identifier naming convention", "type": "BUG", "status": "ready", "remediation": { diff --git a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S6813.html b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S6813.html index 9bb439cadcf..f56d8e48622 100644 --- a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S6813.html +++ b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S6813.html @@ -1,10 +1,12 @@

      Why is this an issue?

      Dependency injection frameworks such as Spring support dependency injection by using annotations such as @Inject and -@Autowired. These annotation can be used to inject beans via constructor, setter, and field injection.

      +@Autowired. These annotations can be used to inject beans via constructor, setter, and field injection.

      Generally speaking, field injection is discouraged. It allows the creation of objects in an invalid state and makes testing more difficult. The dependencies are not explicit when instantiating a class that uses field injection.

      -

      Apart from that, field injection is not compatible with final fields. Keeping dependencies immutable where possible makes the code easier to +

      In addition, field injection is not compatible with final fields. Keeping dependencies immutable where possible makes the code easier to understand, easing development and maintenance.

      +

      Finally, because values are injected into fields after the object has been constructed, they cannot be used to initialize other non-injected fields +inline.

      This rule raises an issue when the @Autowired or @Inject annotations are used on a field.

      How to fix it

      Use constructor injection instead.

      @@ -16,17 +18,21 @@

      Noncompliant code example

       public class SomeService {
           @Autowired
      -    private SomeDependency someDependency;
      +    private SomeDependency someDependency; // Noncompliant
      +
      +    private String name = someDependency.getName(); // Will throw a NullPointerException
       }
       

      Compliant solution

       public class SomeService {
           private final SomeDependency someDependency;
      +    private final String name;
       
           @Autowired
           public SomeService(SomeDependency someDependency) {
               this.someDependency = someDependency;
      +        name = someDependency.getName();
           }
       }
       
      diff --git a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S6813.json b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S6813.json index 2bcf98f6b5a..1ca158fc7ba 100644 --- a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S6813.json +++ b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S6813.json @@ -1,5 +1,5 @@ { - "title": "Avoid field dependency injection", + "title": "Field dependency injection should be avoided", "type": "CODE_SMELL", "status": "ready", "remediation": { diff --git a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S6818.json b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S6818.json index 4f57b81bea5..640596b3f8a 100644 --- a/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S6818.json +++ b/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/S6818.json @@ -1,5 +1,5 @@ { - "title": "Avoid Using \"@Autowired\" on Multiple Constructors in a Spring Component", + "title": "\"@Autowired\" should only be used on a single constructor", "type": "BUG", "status": "ready", "remediation": { diff --git a/sonarpedia.json b/sonarpedia.json index 09651911a16..7a72ecacce3 100644 --- a/sonarpedia.json +++ b/sonarpedia.json @@ -3,7 +3,7 @@ "languages": [ "JAVA" ], - "latest-update": "2023-10-20T09:15:09.502876096Z", + "latest-update": "2023-11-07T15:40:45.973420Z", "options": { "no-language-in-filenames": true, "preserve-filenames": false