RuleExternalResourceJUnitPlugin.java

/*******************************************************************************
 * Copyright (c) 2021 Carsten Hammer.
 *
 * 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
 *
 * Contributors:
 *     Carsten Hammer
 *******************************************************************************/
package org.sandbox.jdt.internal.corext.fix.helper;

import static org.sandbox.jdt.internal.corext.fix.helper.lib.JUnitConstants.*;

import java.util.List;

import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.text.edits.TextEditGroup;
import org.sandbox.jdt.internal.corext.fix.helper.lib.ExternalResourceRefactorer;
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.Pattern;
import org.sandbox.jdt.triggerpattern.api.PatternKind;

/**
 * Plugin to migrate JUnit 4 ExternalResource rules to JUnit 5 extensions.
 *
 * @since 1.3.0
 */
@CleanupPattern(value = "@Rule public $type $name", kind = PatternKind.FIELD, qualifiedType = ORG_JUNIT_RULES_EXTERNAL_RESOURCE, cleanupId = "cleanup.junit.ruleexternalresource", description = "Migrate @Rule ExternalResource to JUnit 5 extension", displayName = "JUnit 4 @Rule ExternalResource \u2192 JUnit 5 Extension")
public class RuleExternalResourceJUnitPlugin extends TriggerPatternCleanupPlugin {

	/**
	 * Override getPatterns() to match both @Rule and @ClassRule variants.
	 */
	@Override
	protected List<Pattern> getPatterns() {
		return List.of(
				new Pattern("@Rule public $type $name", PatternKind.FIELD, null, null, ORG_JUNIT_RULES_EXTERNAL_RESOURCE, null, null),
				new Pattern("@ClassRule public $type $name", PatternKind.FIELD, null, null, ORG_JUNIT_RULES_EXTERNAL_RESOURCE, null, null),
				new Pattern("@ClassRule public static $type $name", PatternKind.FIELD, null, null, ORG_JUNIT_RULES_EXTERNAL_RESOURCE, null, null));
	}

	@Override
	protected JunitHolder createHolder(Match match) {
		FieldDeclaration fieldDecl = (FieldDeclaration) match.getMatchedNode();
		VariableDeclarationFragment fragment = (VariableDeclarationFragment) fieldDecl.fragments().get(0);
		if (fragment.resolveBinding() == null) {
			return null;
		}
		ITypeBinding binding = fragment.resolveBinding().getType();
		if (binding == null) {
			return null;
		}
		// Exclude specific rule types handled by dedicated plugins
		String qualifiedName = binding.getQualifiedName();
		if (ORG_JUNIT_RULES_TEST_NAME.equals(qualifiedName)
				|| ORG_JUNIT_RULES_TEMPORARY_FOLDER.equals(qualifiedName)) {
			return null;
		}
		JunitHolder holder = new JunitHolder();
		holder.setMinv(fieldDecl);
		return holder;
	}

	@Override
	protected void process2Rewrite(TextEditGroup group, ASTRewrite rewriter, AST ast, ImportRewrite importRewriter,
			JunitHolder junitHolder) {
		FieldDeclaration fieldDeclaration = junitHolder.getFieldDeclaration();
		boolean fieldStatic = isFieldAnnotatedWith(fieldDeclaration, ORG_JUNIT_CLASS_RULE);
		CompilationUnit cu = (CompilationUnit) fieldDeclaration.getRoot();

		ASTNode node2 = ExternalResourceRefactorer.getTypeDefinitionForField(fieldDeclaration, cu);

		if (node2 instanceof TypeDeclaration) {
			ExternalResourceRefactorer.modifyExternalResourceClass((TypeDeclaration) node2, fieldDeclaration,
					fieldStatic, rewriter, ast, group, importRewriter);
		} else if (node2 instanceof AnonymousClassDeclaration typeNode) {
			ExternalResourceRefactorer.refactorAnonymousClassToImplementCallbacks(typeNode, fieldDeclaration,
					fieldStatic, rewriter, ast, group, importRewriter);
		}
		// If no matching type definition found, no action needed
	}

	@Override
	public String getPreview(boolean afterRefactoring) {
		if (afterRefactoring) {
			return """
					ExtendWith(MyTest.MyExternalResource.class)
					public class MyTest {

						final class MyExternalResource implements BeforeEachCallback, AfterEachCallback {
							@Override
							protected void beforeEach() throws Throwable {
							}

							@Override
							protected void afterEach() {
							}
						}
					"""; //$NON-NLS-1$
		}
		return """
				public class MyTest {

					final class MyExternalResource extends ExternalResource {
						@Override
						protected void before() throws Throwable {
						}

						@Override
						protected void after() {
						}
					}

					@Rule
					public ExternalResource er= new MyExternalResource();
				"""; //$NON-NLS-1$
	}

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