JUnit3AssertionInventory.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.multifile;

import static org.sandbox.jdt.internal.corext.fix.helper.lib.JUnit3SemanticSupport.ASSERTION_METHODS;
import static org.sandbox.jdt.internal.corext.fix.helper.lib.JUnit3SemanticSupport.JUNIT3_ASSERT;
import static org.sandbox.jdt.internal.corext.fix.helper.lib.JUnit3SemanticSupport.JUNIT3_TEST_CASE;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.TypeDeclaration;

import org.sandbox.jdt.internal.corext.fix.helper.lib.JUnit3SemanticSupport;
import org.sandbox.jdt.internal.corext.fix.multifile.JUnit3HierarchyMigration.InvocationKind;
import org.sandbox.jdt.internal.corext.fix.multifile.JUnit3HierarchyMigration.InvocationMigration;

/** Collects exact assertion call sites and rejects custom JUnit 3 API usage. */
final class JUnit3AssertionInventory {

	private static final Set<String> CUSTOM_EXECUTION_METHODS= Set.of(
			"suite", "runTest", "runBare", "createResult", "countTestCases", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
			"getName", "setName", "run"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

	record Result(String rejectionReason, List<InvocationMigration> invocations) {
		Result {
			invocations= List.copyOf(invocations);
		}

		boolean supported() {
			return rejectionReason == null;
		}
	}

	private JUnit3AssertionInventory() {
	}

	static Result analyze(TypeDeclaration declaration) {
		String[] rejection= new String[1];
		List<InvocationMigration> invocations= new ArrayList<>();
		declaration.accept(new ASTVisitor() {
			@Override
			public boolean visit(SuperMethodInvocation node) {
				rejection[0]= "Explicit superclass method calls can change lifecycle semantics under Jupiter."; //$NON-NLS-1$
				return false;
			}

			@Override
			public boolean visit(MethodInvocation node) {
				if (rejection[0] != null) {
					return false;
				}
				IMethodBinding binding= node.resolveMethodBinding();
				String name= node.getName().getIdentifier();
				if (binding == null) {
					if (CUSTOM_EXECUTION_METHODS.contains(name) || ASSERTION_METHODS.contains(name)) {
						rejection[0]= "A potentially JUnit-related invocation has no resolved method binding."; //$NON-NLS-1$
					}
					return rejection[0] == null;
				}
				ITypeBinding declaringClass= binding.getDeclaringClass();
				String owner= declaringClass == null ? "" : declaringClass.getErasure().getQualifiedName(); //$NON-NLS-1$
				if (!JUNIT3_TEST_CASE.equals(owner) && !JUNIT3_ASSERT.equals(owner)) {
					return true;
				}
				if (!ASSERTION_METHODS.contains(name)) {
					rejection[0]= "The hierarchy calls inherited JUnit 3 API " + owner + "." + name + "()."; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					return false;
				}
				String key= binding.getMethodDeclaration().getKey();
				if (key == null) {
					rejection[0]= "A JUnit 3 assertion call has no stable method binding key."; //$NON-NLS-1$
					return false;
				}
				InvocationKind kind= JUnit3SemanticSupport.hasLeadingMessage(node, binding)
						? InvocationKind.MESSAGE_FIRST
						: InvocationKind.QUALIFY;
				invocations.add(new InvocationMigration(key, node.getStartPosition(), node.getLength(), kind));
				return true;
			}
		});
		return new Result(rejection[0], invocations);
	}
}