ProhibitPlainJunitAssertionsRule.java

/*
 * Copyright (c) 2011-2024 Qulice.com
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: 1) Redistributions of source code must retain the above
 * copyright notice, this list of conditions and the following
 * disclaimer. 2) Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided
 * with the distribution. 3) Neither the name of the Qulice.com nor
 * the names of its contributors may be used to endorse or promote
 * products derived from this software without specific prior written
 * permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.qulice.pmd.rules;

import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTName;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
import net.sourceforge.pmd.lang.java.rule.AbstractJUnitRule;

/**
 * Rule to check plain assertions in JUnit tests.
 * @since 0.17
 */
public final class ProhibitPlainJunitAssertionsRule extends AbstractJUnitRule {

    /**
     * Mask of prohibited imports.
     */
    private static final String[] PROHIBITED = {
        "org.junit.Assert.assert",
        "junit.framework.Assert.assert",
    };

    @Override
    public Object visit(final ASTMethodDeclaration method, final Object data) {
        if (this.isJUnitMethod(method, data)
            && this.containsPlainJunitAssert(method.getBlock())) {
            this.addViolation(data, method);
        }
        return data;
    }

    @Override
    public Object visit(final ASTImportDeclaration imp, final Object data) {
        for (final String element : ProhibitPlainJunitAssertionsRule
            .PROHIBITED) {
            if (imp.getImportedName().contains(element)) {
                this.addViolation(data, imp);
                break;
            }
        }
        return super.visit(imp, data);
    }

    /**
     * Recursively verifies if node contains plain JUnit assert statements.
     * @param node Root statement node to search
     * @return True if statement contains plain JUnit assertions, false
     *  otherwise
     */
    private boolean containsPlainJunitAssert(final Node node) {
        boolean found = false;
        if (node instanceof ASTStatementExpression
            && ProhibitPlainJunitAssertionsRule.isPlainJunitAssert(node)) {
            found = true;
        }
        if (!found) {
            for (int iter = 0; iter < node.jjtGetNumChildren(); iter += 1) {
                final Node child = node.jjtGetChild(iter);
                if (this.containsPlainJunitAssert(child)) {
                    found = true;
                    break;
                }
            }
        }
        return found;
    }

    /**
     * Tells if the statement is an assert statement or not.
     * @param statement Root node to search assert statements
     * @return True is statement is assert, false otherwise
     */
    private static boolean isPlainJunitAssert(final Node statement) {
        final ASTPrimaryExpression expression =
            ProhibitPlainJunitAssertionsRule.getChildNodeWithType(
                statement, ASTPrimaryExpression.class
            );
        final ASTPrimaryPrefix prefix =
            ProhibitPlainJunitAssertionsRule.getChildNodeWithType(
                expression, ASTPrimaryPrefix.class
            );
        final ASTName name = ProhibitPlainJunitAssertionsRule
            .getChildNodeWithType(prefix, ASTName.class);
        boolean assrt = false;
        if (name != null) {
            final String img = name.getImage();
            assrt = img != null && (img.startsWith("assert")
                || img.startsWith("Assert.assert"));
        }
        return assrt;
    }

    /**
     * Gets child node with specified type.
     * @param node Parent node
     * @param clazz Specified class
     * @param <T> Node type
     * @return Child node if exists, null otherwise
     */
    private static <T extends Node> T getChildNodeWithType(final Node node,
        final Class<T> clazz) {
        T expression = null;
        if (node != null && node.jjtGetNumChildren() > 0
            && clazz.isInstance(node.jjtGetChild(0))) {
            expression = clazz.cast(node.jjtGetChild(0));
        }
        return expression;
    }

}