FixMethodOrderJUnitPlugin.java

/*******************************************************************************
 * Copyright (c) 2026 Carsten Hammer and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *******************************************************************************/
package org.sandbox.jdt.internal.corext.fix.helper;

import static org.sandbox.jdt.internal.corext.fix.helper.lib.JUnitConstants.ORG_JUNIT_FIX_METHOD_ORDER;
import static org.sandbox.jdt.internal.corext.fix.helper.lib.JUnitConstants.ORG_JUNIT_JUPITER_API_METHOD_ORDERER;
import static org.sandbox.jdt.internal.corext.fix.helper.lib.JUnitConstants.ORG_JUNIT_JUPITER_API_TEST_METHOD_ORDER;
import static org.sandbox.jdt.internal.corext.fix.helper.lib.JUnitConstants.ORG_JUNIT_RUNNERS_METHOD_SORTERS;

import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.TypeLiteral;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
import org.eclipse.text.edits.TextEditGroup;

import org.sandbox.jdt.internal.corext.fix.helper.lib.JunitHolder;
import org.sandbox.jdt.internal.corext.fix.helper.lib.TriggerPatternCleanupPlugin;
import org.sandbox.jdt.triggerpattern.api.CleanupPattern;
import org.sandbox.jdt.triggerpattern.api.Match;
import org.sandbox.jdt.triggerpattern.api.PatternKind;

/** Migrates JUnit 4 method ordering to the corresponding Jupiter API. */
@CleanupPattern(value = "@FixMethodOrder($sorter)", kind = PatternKind.ANNOTATION,
		qualifiedType = ORG_JUNIT_FIX_METHOD_ORDER, cleanupId = "cleanup.junit.fixmethodorder",
		description = "Migrate @FixMethodOrder to @TestMethodOrder",
		displayName = "JUnit 4 @FixMethodOrder → Jupiter @TestMethodOrder")
public class FixMethodOrderJUnitPlugin extends TriggerPatternCleanupPlugin {

	@Override
	protected JunitHolder createHolder(Match match) {
		JunitHolder holder= super.createHolder(match);
		Object sorterBinding= match.getBindings().get("$sorter"); //$NON-NLS-1$
		if (sorterBinding instanceof QualifiedName qn) {
			holder.setAdditionalInfo(qn.getName().getIdentifier());
			return holder;
		}
		return null;
	}

	@Override
	protected void process2Rewrite(TextEditGroup group, ASTRewrite rewriter, AST ast,
			ImportRewrite importRewriter, JunitHolder junitHolder) {
		Annotation oldAnnotation= junitHolder.getAnnotation();
		String methodSorter= (String) junitHolder.getAdditionalInfo();
		if (methodSorter == null) {
			return;
		}

		if ("DEFAULT".equals(methodSorter)) { //$NON-NLS-1$
			rewriter.remove(oldAnnotation, group);
		} else if ("NAME_ASCENDING".equals(methodSorter) || "JVM".equals(methodSorter)) { //$NON-NLS-1$ //$NON-NLS-2$
			ImportRewriteContext importContext= new ContextSensitiveImportRewriteContext(
					(CompilationUnit) oldAnnotation.getRoot(), oldAnnotation.getStartPosition(), importRewriter);
			SingleMemberAnnotation newAnnotation= ast.newSingleMemberAnnotation();
			newAnnotation.setTypeName(ast.newName(
					importRewriter.addImport(ORG_JUNIT_JUPITER_API_TEST_METHOD_ORDER, importContext)));

			Name methodOrderer= ast.newName(
					importRewriter.addImport(ORG_JUNIT_JUPITER_API_METHOD_ORDERER, importContext));
			TypeLiteral typeLiteral= ast.newTypeLiteral();
			String targetOrderer= "NAME_ASCENDING".equals(methodSorter) //$NON-NLS-1$
					? "MethodName" : "Random"; //$NON-NLS-1$ //$NON-NLS-2$
			typeLiteral.setType(ast.newSimpleType(ast.newQualifiedName(
					methodOrderer, ast.newSimpleName(targetOrderer))));
			newAnnotation.setValue(typeLiteral);
			rewriter.replace(oldAnnotation, newAnnotation, group);
		} else {
			// Unknown custom sorters are not semantics-preserving migrations.
			return;
		}

		importRewriter.removeImport(ORG_JUNIT_FIX_METHOD_ORDER);
		importRewriter.removeImport(ORG_JUNIT_RUNNERS_METHOD_SORTERS);
	}

	@Override
	public String getPreview(boolean afterRefactoring) {
		if (afterRefactoring) {
			return """
					import org.junit.jupiter.api.MethodOrderer;
					import org.junit.jupiter.api.TestMethodOrder;

					@TestMethodOrder(MethodOrderer.MethodName.class)
					public class OrderedTest {
					}
					"""; //$NON-NLS-1$
		}
		return """
				import org.junit.FixMethodOrder;
				import org.junit.runners.MethodSorters;

				@FixMethodOrder(MethodSorters.NAME_ASCENDING)
				public class OrderedTest {
				}
				"""; //$NON-NLS-1$
	}

	@Override
	public String toString() {
		return "FixMethodOrder"; //$NON-NLS-1$
	}
}