Skip to content
Merged
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,77 @@
/*
* Copyright 2026 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.staticanalysis;

import lombok.Getter;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.tree.J;

import java.time.Duration;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import static java.util.Collections.singleton;

@Getter
public class RemoveUnusedLabels extends Recipe {

final String displayName = "Remove unused labels";

final String description = "Remove labels that are not referenced by any `break` or `continue` statement.";

final Set<String> tags = singleton("RSPEC-S1065");

final Duration estimatedEffortPerOccurrence = Duration.ofMinutes(1);

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new JavaVisitor<ExecutionContext>() {
@Override
public J visitLabel(J.Label label, ExecutionContext ctx) {
J.Label l = (J.Label) super.visitLabel(label, ctx);
String labelName = l.getLabel().getSimpleName();

boolean used = new JavaVisitor<AtomicBoolean>() {
@Override
public J visitBreak(J.Break breakStatement, AtomicBoolean u) {
if (breakStatement.getLabel() != null &&
labelName.equals(breakStatement.getLabel().getSimpleName())) {
u.set(true);
}
return super.visitBreak(breakStatement, u);
}

@Override
public J visitContinue(J.Continue continueStatement, AtomicBoolean u) {
if (continueStatement.getLabel() != null &&
labelName.equals(continueStatement.getLabel().getSimpleName())) {
u.set(true);
}
return super.visitContinue(continueStatement, u);
}
}.reduce(l.getStatement(), new AtomicBoolean(false)).get();

if (used) {
return l;
}
return l.getStatement().withPrefix(l.getPrefix());
}
};
}
}
3 changes: 2 additions & 1 deletion src/main/resources/META-INF/rewrite/recipes.csv
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanaly
maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanalysis.RemoveToStringCallsFromArrayInstances,Remove `toString()` calls on arrays,"The result from `toString()` calls on arrays is largely useless. The output does not actually reflect the contents of the array. `Arrays.toString(array)` should be used instead as it gives the contents of the array. Since arrays do not override `toString()` from `Object`, calling it produces only the type name and memory address, which is rarely what was intended.",1,,Static analysis and remediation,,Remediations for issues identified by SAST tools.,
maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanalysis.RemoveUnneededAssertion,Remove unneeded assertions,"Remove unneeded assertions like `assert true`, `assertTrue(true)`, or `assertFalse(false)`.",1,,Static analysis and remediation,,Remediations for issues identified by SAST tools.,
maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanalysis.RemoveUnneededBlock,Remove unneeded block,"Flatten blocks into inline statements when possible. Unnecessary nested blocks add indentation and scope boundaries that obscure the control flow, often indicating code that should be extracted into its own method.",1,,Static analysis and remediation,,Remediations for issues identified by SAST tools.,
maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanalysis.RemoveUnusedLabels,Remove unused labels,Remove labels that are not referenced by any `break` or `continue` statement.,1,,Static analysis and remediation,,Remediations for issues identified by SAST tools.,
maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanalysis.RemoveUnusedLocalVariables,Remove unused local variables,"If a local variable is declared but not used, it is dead code and should be removed. Unused variables increase cognitive load for readers who must determine whether the variable matters, and they may signal incomplete implementations or missed refactoring.",1,,Static analysis and remediation,,Remediations for issues identified by SAST tools.,"[{""name"":""ignoreVariablesNamed"",""type"":""String[]"",""displayName"":""Ignore matching variable names"",""description"":""An array of variable identifier names for local variables to ignore, even if the local variable is unused."",""example"":""[unused, notUsed, IGNORE_ME]""},{""name"":""withType"",""type"":""String"",""displayName"":""Only remove variables of a given type"",""description"":""A fully qualified class name. Only unused local variables whose type matches this will be removed. If empty or not set, all unused local variables are considered for removal."",""example"":""java.lang.String""},{""name"":""withSideEffects"",""type"":""Boolean"",""displayName"":""Remove unused local variables with side effects in initializer"",""description"":""Whether to remove unused local variables despite side effects in the initializer. Default false.""}]"
maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanalysis.RemoveUnusedPrivateFields,Remove unused private fields,"If a private field is declared but not used in the program, it can be considered dead code and should therefore be removed. Dead fields clutter the class, increase its memory footprint, and can mislead developers into thinking they are part of the class's behavior.",1,,Static analysis and remediation,,Remediations for issues identified by SAST tools.,
maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanalysis.RemoveUnusedPrivateMethods,Remove unused private methods,`private` methods that are never executed are dead code and should be removed. Keeping unreachable methods around adds maintenance burden and can give a false impression of the class's capabilities.,1,,Static analysis and remediation,,Remediations for issues identified by SAST tools.,
Expand Down Expand Up @@ -198,7 +199,7 @@ maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanaly
maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanalysis.UseMapContainsKey,Use `Map#containsKey`,`map.keySet().contains(a)` can be simplified to `map.containsKey(a)`.,2,,Static analysis and remediation,,Remediations for issues identified by SAST tools.,
maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanalysis.ReplaceApacheCommonsLang3ValidateNotNullWithObjectsRequireNonNull,Replace `org.apache.commons.lang3.Validate#notNull` with `Objects#requireNonNull`,Replace `org.apache.commons.lang3.Validate.notNull(..)` with `Objects.requireNonNull(..)`.,5,,Static analysis and remediation,,Remediations for issues identified by SAST tools.,
maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanalysis.ReplaceValidateNotNullHavingSingleArgWithObjectsRequireNonNull,Replace `org.apache.commons.lang3.Validate#notNull` with `Objects#requireNonNull`,Replace `org.apache.commons.lang3.Validate.notNull(Object)` with `Objects.requireNonNull(Object)`.,3,,Static analysis and remediation,,Remediations for issues identified by SAST tools.,
maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanalysis.CodeCleanup,Code cleanup,"Automatically cleanup code, e.g. remove unnecessary parentheses, simplify expressions.",25,,Static analysis and remediation,,Remediations for issues identified by SAST tools.,
maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanalysis.CodeCleanup,Code cleanup,"Automatically cleanup code, e.g. remove unnecessary parentheses, simplify expressions.",26,,Static analysis and remediation,,Remediations for issues identified by SAST tools.,
maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanalysis.ReplaceThreadRunWithThreadStart,Replace calls to `Thread.run()` with `Thread.start()`,`Thread.run()` should not be called directly.,2,,Static analysis and remediation,,Remediations for issues identified by SAST tools.,
maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanalysis.CommonDeclarationSiteTypeVariances,Properly use declaration-site type variance for well-known types,"When using a method parameter like `Function<IN, OUT>`, it should rather be `Function<? super IN, ? extends OUT>`. This recipe checks for method parameters of well-known types.",2,,Static analysis and remediation,,Remediations for issues identified by SAST tools.,
maven,org.openrewrite.recipe:rewrite-static-analysis,org.openrewrite.staticanalysis.java.MoveFieldAnnotationToType,Move annotation to type instead of field,"Annotations that could be applied to either a field or a type are better applied to the type, because similar annotations may be more restrictive, leading to compile errors like 'scoping construct cannot be annotated with type-use annotation' when migrating later.",1,Java,Static analysis and remediation,,Remediations for issues identified by SAST tools.,"[{""name"":""annotationType"",""type"":""String"",""displayName"":""Annotation type"",""description"":""The type of annotation to move."",""example"":""org.openrewrite..*""}]"
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/META-INF/rewrite/static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ recipeList:
- org.openrewrite.java.ShortenFullyQualifiedTypeReferences
- org.openrewrite.java.SimplifySingleElementAnnotation
- org.openrewrite.java.OrderImports
- org.openrewrite.staticanalysis.RemoveUnusedLabels
---
type: specs.openrewrite.org/v1beta/recipe
name: org.openrewrite.staticanalysis.ReplaceThreadRunWithThreadStart
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* Copyright 2026 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.staticanalysis;

import org.junit.jupiter.api.Test;
import org.openrewrite.DocumentExample;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;

import static org.openrewrite.java.Assertions.java;

@SuppressWarnings({"UnusedLabel", "unused"})
class RemoveUnusedLabelsTest implements RewriteTest {

@Override
public void defaults(RecipeSpec spec) {
spec.recipe(new RemoveUnusedLabels());
}

@DocumentExample
@Test
void unusedLabelOnForLoop() {
rewriteRun(
//language=java
java(
"""
class A {
void foo() {
label: for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
""",
"""
class A {
void foo() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
"""
)
);
}

@Test
void unusedLabelOnWhileLoopWithUnlabeledBreak() {
rewriteRun(
//language=java
java(
"""
class A {
void foo() {
loop: while (true) {
break;
}
}
}
""",
"""
class A {
void foo() {
while (true) {
break;
}
}
}
"""
)
);
}

@Test
void doNotChangeUsedLabelWithBreak() {
rewriteRun(
//language=java
java(
"""
class A {
void foo() {
outer: for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (j == 5) break outer;
}
}
}
}
"""
)
);
}

@Test
void doNotChangeUsedLabelWithContinue() {
rewriteRun(
//language=java
java(
"""
class A {
void foo() {
outer: for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (j == 5) continue outer;
}
}
}
}
"""
)
);
}

@Test
void removeUnusedInnerLabelKeepUsedOuterLabel() {
rewriteRun(
//language=java
java(
"""
class A {
void foo() {
outer: for (int i = 0; i < 10; i++) {
inner: for (int j = 0; j < 10; j++) {
if (j == 5) break outer;
}
}
}
}
""",
"""
class A {
void foo() {
outer: for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (j == 5) break outer;
}
}
}
}
"""
)
);
}

@Test
void unusedLabelOnBlock() {
rewriteRun(
//language=java
java(
"""
class A {
void foo() {
block: {
System.out.println("hello");
}
}
}
""",
"""
class A {
void foo() {
{
System.out.println("hello");
}
}
}
"""
)
);
}
}
Loading