Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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,160 @@
/*
* Copyright 2025 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.java.migrate.lang;

import org.openrewrite.ExecutionContext;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.search.SemanticallyEqual;
import org.openrewrite.java.search.UsesJavaVersion;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.java.tree.Space;
import org.openrewrite.java.tree.TypeUtils;
import org.openrewrite.staticanalysis.VariableReferences;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

public class MigrateMainMethodToInstanceMain extends Recipe {
@Override
public String getDisplayName() {
return "Migrate `public static void main(String[] args)` to instance `void main()`";
}

@Override
public String getDescription() {
return "Migrate `public static void main(String[] args)` method to instance `void main()` method when the `args` parameter is unused, as supported by JEP 512 in Java 25+.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new UsesJavaVersion<>(25), new JavaIsoVisitor<ExecutionContext>() {
@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
J.MethodDeclaration md = super.visitMethodDeclaration(method, ctx);

// Check if this is a main method: public static void main(String[] args)
if (!"main".equals(md.getSimpleName()) ||
md.getReturnTypeExpression() == null ||
!md.getReturnTypeExpression().toString().equals("void") ||
md.getParameters().size() != 1) {
return md;
}

// Check modifiers - must have public and static
boolean hasPublic = false;
boolean hasStatic = false;
for (J.Modifier modifier : md.getModifiers()) {
if (modifier.getType() == J.Modifier.Type.Public) {
hasPublic = true;
} else if (modifier.getType() == J.Modifier.Type.Static) {
hasStatic = true;
}
}

if (!hasPublic || !hasStatic) {
return md;
}

// Check parameter type
if (!(md.getParameters().get(0) instanceof J.VariableDeclarations)) {
return md;
}

J.VariableDeclarations param = (J.VariableDeclarations) md.getParameters().get(0);
if (param.getVariables().isEmpty()) {
return md;
}

J.VariableDeclarations.NamedVariable paramVar = param.getVariables().get(0);
String paramName = paramVar.getSimpleName();

// Check if parameter is String[] type
JavaType paramType = param.getType();
if (paramType == null || !TypeUtils.isOfClassType(paramType, "java.lang.String")) {
return md;
}

// Ensure it's an array
if (!(paramType instanceof JavaType.Array)) {
return md;
}

// Check if the parameter is used in the method body
if (md.getBody() == null) {
return md;
}

// Remove public and static modifiers, preserve spacing
List<J.Modifier> newModifiers = new ArrayList<>();
Space leadingSpace = null;

for (int i = 0; i < md.getModifiers().size(); i++) {
J.Modifier modifier = md.getModifiers().get(i);
if (modifier.getType() != J.Modifier.Type.Public &&
modifier.getType() != J.Modifier.Type.Static) {
if (!newModifiers.isEmpty() || leadingSpace == null) {
newModifiers.add(modifier);
} else {
// Apply the leading space to the first remaining modifier
newModifiers.add(modifier.withPrefix(leadingSpace));
}
} else if (leadingSpace == null) {
// Capture the leading space from the first public/static modifier
leadingSpace = modifier.getPrefix();
}
}

// If no modifiers remain and we have a return type, preserve the spacing
if (newModifiers.isEmpty() && leadingSpace != null && md.getReturnTypeExpression() != null) {
md = md.withReturnTypeExpression(md.getReturnTypeExpression().withPrefix(leadingSpace));
}

// Remove the parameter
if (argumentsUnused(paramVar, md.getBody())) {
md = md.withParameters(Collections.emptyList());
}

// Remove the modifiers
return md.withModifiers(newModifiers);
}

private boolean argumentsUnused(J.VariableDeclarations.NamedVariable variable, J context) {
return VariableReferences.findRhsReferences(context, variable.getName()).isEmpty() &&
!usedInModifyingUnary(variable.getName(), context);
}

private boolean usedInModifyingUnary(J.Identifier identifier, J context) {
return new JavaIsoVisitor<AtomicBoolean>() {
@Override
public J.Unary visitUnary(J.Unary unary, AtomicBoolean atomicBoolean) {
if (unary.getOperator().isModifying() &&
SemanticallyEqual.areEqual(identifier, unary.getExpression())) {
atomicBoolean.set(true);
}
return super.visitUnary(unary, atomicBoolean);
}
}.reduce(context, new AtomicBoolean(false)).get();
}
});
}

}
1 change: 1 addition & 0 deletions src/main/resources/META-INF/rewrite/java-version-25.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ recipeList:
- org.openrewrite.github.SetupJavaUpgradeJavaVersion:
minimumJavaMajorVersion: 25
- org.openrewrite.java.migrate.io.ReplaceSystemOutWithIOPrint
- org.openrewrite.java.migrate.lang.MigrateMainMethodToInstanceMain
- org.openrewrite.java.migrate.lang.MigrateProcessWaitForDuration
- org.openrewrite.java.migrate.lang.ReplaceUnusedVariablesWithUnderscore
- org.openrewrite.java.migrate.util.MigrateInflaterDeflaterToClose
Expand Down
Loading
Loading