![JAR search and dependency download from the Maven repository](/logo.png)
org.codenarc.rule.groovyism.UseCollectNestedRule.groovy Maven / Gradle / Ivy
/*
* Copyright 2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codenarc.rule.groovyism
import org.codehaus.groovy.ast.stmt.ExpressionStatement
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.codehaus.groovy.ast.ClassHelper
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.Parameter
import org.codehaus.groovy.ast.expr.ClosureExpression
import org.codehaus.groovy.ast.expr.Expression
import org.codehaus.groovy.ast.expr.MethodCallExpression
import org.codehaus.groovy.ast.expr.VariableExpression
import org.codenarc.rule.AbstractAstVisitor
import org.codenarc.rule.AbstractAstVisitorRule
import org.codenarc.util.AstUtil
/**
* Instead of nested collect{}-calls use collectNested{}
*
* @author Joachim Baumann
* @author Chris Mair
*/
class UseCollectNestedRule extends AbstractAstVisitorRule {
protected static final String MESSAGE = 'Instead of nested collect{}-calls use collectNested{}'
String name = 'UseCollectNested'
int priority = 2
Class astVisitorClass = UseCollectNestedAstVisitor
}
class UseCollectNestedAstVisitor extends AbstractAstVisitor {
private static final Logger LOG = LoggerFactory.getLogger(UseCollectNestedAstVisitor)
private final Stack parameterStack = []
private boolean withinOuterCollectCall = false
private boolean withinCollectCallStatement = false
@Override
protected void visitClassComplete(ClassNode cn) {
if (parameterStack.size() != 0) {
LOG.warn("Internal Error for ${cn.name}: Visits are unbalanced")
}
}
@Override
void visitExpressionStatement(ExpressionStatement statement) {
boolean originalValue = withinCollectCallStatement
// We only care about MethodCallStatements for collect() calls if they occur within the context of an outer collect() call
if (isCollectMethodCall(statement.expression) && withinOuterCollectCall) {
withinCollectCallStatement = true
}
super.visitExpressionStatement(statement)
withinCollectCallStatement = originalValue
}
@Override
void visitMethodCallExpression(MethodCallExpression call) {
boolean isCollectCall = false
Parameter parameter
Parameter it = new Parameter(ClassHelper.OBJECT_TYPE, 'it')
boolean originalWithinOuterCollectCall = withinOuterCollectCall
// The idea for this rule is to add the parameter of the
// closure used in the collect call to the stack of parameters,
// and check for each collect expression whether it is called on
// the parameter on the top of the stack
if(isCollectMethodCall(call)) {
Expression expression = getMethodCallParameterThatIsAClosure(call)
isCollectCall = expression instanceof ClosureExpression
if (isCollectCall) {
withinOuterCollectCall = true
parameter = getClosureParameter(expression, it)
checkForCallToClosureParameter(call)
}
}
addArgumentList(isCollectCall, parameter)
super.visitMethodCallExpression(call)
removeArgumentList(isCollectCall)
withinOuterCollectCall = originalWithinOuterCollectCall
}
private boolean isCollectMethodCall(Expression call) {
return AstUtil.isMethodCall(call, 'collect', 1..2)
}
private Parameter getClosureParameter(ClosureExpression expression, Parameter it) {
Parameter param
if (expression.parameters.size() != 0) {
// we assume correct syntax and thus only one parameter
param = expression.parameters[0]
}
else {
// implicit parameter, we use our own parameter object as placeholder
param = it
}
return param
}
private Expression getMethodCallParameterThatIsAClosure(MethodCallExpression call) {
int arity = AstUtil.getMethodArguments(call).size()
Expression expression
if (arity == 1) {
// closure is the first parameter
expression = call.arguments.expressions[0]
} else {
// closure is second parameter
expression = call.arguments.expressions[1]
}
return expression
}
private void checkForCallToClosureParameter(MethodCallExpression call) {
// Only check for collect() calls that are standalone (i.e., statements).
// If the collect() calls are part of larger expressions then we cannot make assumptions about the result types.
if (withinCollectCallStatement
// Now if the call is to the parameter of the closure then the node on
// which collect is called has to be a VariableExpression.
&& call.objectExpression instanceof VariableExpression
&& !parameterStack.empty()
&& parameterStack.peek().name == call.objectExpression.name) {
addViolation(call, UseCollectNestedRule.MESSAGE)
}
}
private void addArgumentList(boolean isCollectCall, Parameter param) {
if (isCollectCall) {
parameterStack.push(param)
}
}
private void removeArgumentList(boolean isCollectCall) {
if (isCollectCall) {
parameterStack.pop()
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy