Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java": [
371,
380
],
"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java": [
370
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java": [
371,
380
],
"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java": [
370
],
"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/RolloverFileOutputStream.java": [
410,
411,
412
],
"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslSelectionDump.java": [
46
],
"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java": [
99
]
}
5 changes: 5 additions & 0 deletions its/ruling/src/test/resources/expected/guava/java-S9434.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"guava:src/com/google/common/collect/TreeRangeMap.java": [
547
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package checks;

import java.util.function.Supplier;

public class RedundantQualifiedThisCheckSample {

private final String id = "id";
private int counter;

String redundantInMethod() {
return RedundantQualifiedThisCheckSample.this.id; // Noncompliant {{Remove this redundant qualifier.}}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

void redundantOnMethodCall() {
RedundantQualifiedThisCheckSample.this.increment(); // Noncompliant
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

RedundantQualifiedThisCheckSample redundantWithoutMemberAccess() {
return RedundantQualifiedThisCheckSample.this; // Noncompliant
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

Supplier<String> redundantInLambda() {
return () -> RedundantQualifiedThisCheckSample.this.id; // Noncompliant
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

Supplier<Supplier<String>> redundantInNestedLambda() {
return () -> () -> RedundantQualifiedThisCheckSample.this.id; // Noncompliant
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

void increment() {
counter++;
}

String compliantExplicitThis() {
return this.id;
}

String compliantImplicitThis() {
return id;
}

class Inner {
private final String id = "inner";

String outerQualifierIsRequired() {
return RedundantQualifiedThisCheckSample.this.id;
}

String ownQualifierIsRedundant() {
return Inner.this.id; // Noncompliant
// ^^^^^^^^^^
}

Supplier<String> outerQualifierIsRequiredInLambda() {
return () -> RedundantQualifiedThisCheckSample.this.id;
}

Supplier<String> ownQualifierIsRedundantInLambda() {
return () -> Inner.this.id; // Noncompliant
// ^^^^^^^^^^
}

class Innermost {
String enclosingQualifiersAreRequired() {
return RedundantQualifiedThisCheckSample.this.id + Inner.this.id;
}

String ownQualifierIsRedundant() {
return Innermost.this.toString(); // Noncompliant
// ^^^^^^^^^^^^^^
}
}
}

Supplier<String> anonymousClassNeedsQualifier() {
return new Supplier<String>() {
@Override
public String get() {
return RedundantQualifiedThisCheckSample.this.id;
}
};
}

Supplier<String> lambdaInAnonymousClassNeedsQualifier() {
return new Supplier<String>() {
@Override
public String get() {
Supplier<String> nested = () -> RedundantQualifiedThisCheckSample.this.id;
return nested.get();
}
};
}

String localClassNeedsQualifier() {
class Local {
String get() {
return RedundantQualifiedThisCheckSample.this.id;
}
}
return new Local().get();
}

String localClassOwnQualifierIsRedundant() {
class Local {
String get() {
return Local.this.toString(); // Noncompliant
// ^^^^^^^^^^
}
}
return new Local().get();
}

static class StaticNested {
private final String id = "nested";

String ownQualifierIsRedundant() {
return StaticNested.this.id; // Noncompliant
// ^^^^^^^^^^^^^^^^^
}

Supplier<String> ownQualifierIsRedundantInLambda() {
return () -> StaticNested.this.id; // Noncompliant
// ^^^^^^^^^^^^^^^^^
}
}

enum Status {
OPEN;

String ownQualifierIsRedundant() {
return Status.this.name(); // Noncompliant
// ^^^^^^^^^^^
}
}

record Point(int x, int y) {
int ownQualifierIsRedundant() {
return Point.this.x; // Noncompliant
// ^^^^^^^^^^
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;

import java.util.List;
import javax.annotation.CheckForNull;
import org.sonar.check.Rule;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.Tree;

@Rule(key = "S9434")
public class RedundantQualifiedThisCheck extends IssuableSubscriptionVisitor {

private static final String MESSAGE = "Remove this redundant qualifier.";

@Override
public List<Tree.Kind> nodesToVisit() {
return List.of(Tree.Kind.MEMBER_SELECT);
}

@Override
public void visitNode(Tree tree) {
MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) tree;
if (!ExpressionUtils.isThis(memberSelect.identifier())) {
return;
}
Symbol qualifier = memberSelect.expression().symbolType().symbol();
if (!qualifier.isUnknown() && qualifier.equals(innermostEnclosingClass(memberSelect))) {
reportIssue(memberSelect, MESSAGE);
}
}

/**
* Returns the symbol of the class whose instance an unqualified {@code this} would denote at the given position.
* Lambda bodies are transparent because a lambda does not introduce its own {@code this}, while anonymous and
* local classes do and are therefore returned like any other class.
*/
@CheckForNull
private static Symbol innermostEnclosingClass(Tree tree) {
Tree parent = tree.parent();
while (parent != null) {
if (parent instanceof ClassTree classTree) {
return classTree.symbol();
}
parent = parent.parent();
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;

import org.junit.jupiter.api.Test;
import org.sonar.java.checks.verifier.CheckVerifier;

import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;

class RedundantQualifiedThisCheckTest {

private static final String SAMPLE = "checks/RedundantQualifiedThisCheckSample.java";
private static final RedundantQualifiedThisCheck CHECK = new RedundantQualifiedThisCheck();

@Test
void test() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath(SAMPLE))
.withCheck(CHECK)
.verifyIssues();
}

@Test
void test_without_semantic() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath(SAMPLE))
.withCheck(CHECK)
.withoutSemantic()
.verifyIssues();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<h2>Why is this an issue?</h2>
<p>A qualified <code>this</code> expression, written <code>Outer.this</code>, is the Java syntax for reaching the instance of an enclosing class from
inside an inner class. It is only needed when a plain <code>this</code> would refer to something else.</p>
<p>When the qualifier names the class that the expression is already lexically inside, and no inner class sits between the expression and that class,
<code>Outer.this</code> and <code>this</code> denote the very same object. The qualifier therefore adds no information.</p>
<p>This hurts readability in two ways. The expression is longer than it needs to be, and, more importantly, it tells the reader that an enclosing
instance is involved when none is. Anyone reading the code has to look for a surrounding class to understand which object is being accessed, only to
discover that it is the obvious one.</p>
<p>Lambdas are the case developers most often get wrong. A lambda body does not introduce its own <code>this</code>: inside a lambda,
<code>this</code> is still the instance of the class where the lambda is written. A qualified <code>this</code> in a lambda declared directly in the
class it names is just as redundant as one written in a method body.</p>
<p>To fix an issue, delete the qualifier and keep the plain <code>this</code>. The expression keeps referring to the same object, so the change cannot
alter behavior.</p>
<h3>Exceptions</h3>
<p>No issue is raised when the qualifier is required to disambiguate. Inside an anonymous class or a named inner class, plain <code>this</code> refers
to the instance of that inner class, so the qualified form is the only way to reach the enclosing instance:</p>
<pre>
public class Invoice {
private String id;

public Runnable renamer(String newId) {
return new Runnable() {
@Override
public void run() {
Invoice.this.id = newId; // Compliant: "this" would be the Runnable
}
};
}
}
</pre>
<p>Removing the qualifier is therefore safe only when the enclosing class is the innermost class around the expression. If an issue is raised on a
qualified <code>this</code> that does resolve to a different object than a plain <code>this</code> would, the finding is a false positive and worth
reporting rather than silencing.</p>
<h2>How to fix it</h2>
<p>Remove the class name and the dot in front of <code>this</code>, leaving a plain <code>this</code>. If the member access is not ambiguous either,
for example because no local variable or parameter shadows the field, you may drop <code>this</code> as well, but keeping it is enough to resolve the
issue.</p>
<h3>Code examples</h3>
<h4>Noncompliant code example</h4>
<pre data-diff-id="1" data-diff-type="noncompliant">
public class Invoice {
private String id;
private List&lt;String&gt; lines = new ArrayList&lt;&gt;();

public String describe() {
return "Invoice " + Invoice.this.id; // Noncompliant: "Invoice.this" is the same object as "this"
}

public List&lt;String&gt; nonEmptyLines() {
return Invoice.this.lines.stream() // Noncompliant: "Invoice.this" is the same object as "this"
.filter(line -&gt; !line.isEmpty() &amp;&amp; line.startsWith(Invoice.this.id)) // Noncompliant: a lambda does not introduce its own "this"
.collect(Collectors.toList());
}
}
</pre>
<h4>Compliant solution</h4>
<pre data-diff-id="1" data-diff-type="compliant">
public class Invoice {
private String id;
private List&lt;String&gt; lines = new ArrayList&lt;&gt;();

public String describe() {
return "Invoice " + this.id;
}

public List&lt;String&gt; nonEmptyLines() {
return this.lines.stream()
.filter(line -&gt; !line.isEmpty() &amp;&amp; line.startsWith(this.id))
.collect(Collectors.toList());
}
}
</pre>
<h2>Resources</h2>
<h3>Documentation</h3>
<ul>
<li>Java Documentation - <a href="https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.8.4">Qualified This</a></li>
</ul>

Loading
Loading