WrongStringComparisonFixCore.java

/*******************************************************************************
 * Copyright (c) 2026 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;

import java.util.Set;

import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFixCore.CompilationUnitRewriteOperation;
import org.eclipse.jdt.internal.corext.fix.LinkedProposalModelCore;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.text.edits.TextEditGroup;

/**
 * Fix core for wrong string comparison detection.
 *
 * <p>Detects {@code str == "literal"} and replaces with {@code "literal".equals(str)},
 * and {@code str != "literal"} with {@code !"literal".equals(str)}.</p>
 *
 */
public class WrongStringComparisonFixCore {

	/**
	 * Finds wrong string comparison operations in the compilation unit.
	 *
	 * @param compilationUnit the compilation unit to search
	 * @param operations the set to add found operations to
	 */
	public static void findOperations(CompilationUnit compilationUnit,
			Set<CompilationUnitRewriteOperation> operations) {

		compilationUnit.accept(new ASTVisitor() {
			@Override
			public boolean visit(InfixExpression node) {
				InfixExpression.Operator op = node.getOperator();
				if (op != InfixExpression.Operator.EQUALS && op != InfixExpression.Operator.NOT_EQUALS) {
					return true;
				}

				Expression left = node.getLeftOperand();
				Expression right = node.getRightOperand();

				StringLiteral literal = null;
				Expression other = null;

				if (left instanceof StringLiteral sl) {
					literal = sl;
					other = right;
				} else if (right instanceof StringLiteral sl) {
					literal = sl;
					other = left;
				}

				if (literal != null && other != null) {
					operations.add(new WrongStringComparisonOperation(node, literal, other,
							op == InfixExpression.Operator.NOT_EQUALS));
				}
				return true;
			}
		});
	}

	private static class WrongStringComparisonOperation extends CompilationUnitRewriteOperation {

		private final InfixExpression originalExpr;
		private final StringLiteral literal;
		private final Expression other;
		private final boolean negated;

		WrongStringComparisonOperation(InfixExpression originalExpr,
				StringLiteral literal, Expression other, boolean negated) {
			this.originalExpr = originalExpr;
			this.literal = literal;
			this.other = other;
			this.negated = negated;
		}

		@Override
		public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModelCore linkedModel) {
			ASTRewrite rewrite = cuRewrite.getASTRewrite();
			AST ast = cuRewrite.getRoot().getAST();
			TextEditGroup group = createTextEditGroup(
					"Replace string reference comparison with .equals()", cuRewrite); //$NON-NLS-1$

			MethodInvocation equalsCall = ast.newMethodInvocation();
			equalsCall.setExpression((Expression) ASTNode.copySubtree(ast, literal));
			equalsCall.setName(ast.newSimpleName("equals")); //$NON-NLS-1$
			equalsCall.arguments().add(ASTNode.copySubtree(ast, other));

			Expression replacement;
			if (negated) {
				PrefixExpression not = ast.newPrefixExpression();
				not.setOperator(PrefixExpression.Operator.NOT);
				ParenthesizedExpression paren = ast.newParenthesizedExpression();
				paren.setExpression(equalsCall);
				not.setOperand(paren);
				replacement = not;
			} else {
				replacement = equalsCall;
			}

			rewrite.replace(originalExpr, replacement, group);
		}
	}
}