
org.codenarc.rule.formatting.SpaceAroundOperatorRule.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.formatting
import org.codehaus.groovy.ast.expr.BinaryExpression
import org.codehaus.groovy.ast.expr.CastExpression
import org.codenarc.rule.AbstractAstVisitor
import org.codenarc.rule.AbstractAstVisitorRule
import org.codehaus.groovy.ast.expr.TernaryExpression
import org.codehaus.groovy.ast.expr.ElvisOperatorExpression
import org.codehaus.groovy.ast.expr.MethodCallExpression
import org.codehaus.groovy.ast.expr.PropertyExpression
import org.codehaus.groovy.ast.expr.DeclarationExpression
import org.codehaus.groovy.ast.expr.BooleanExpression
/**
* Check that there is at least one space (blank) or whitespace around each binary operator,
* including: +, -, *, /, >>, <<, &&, ||, &, |, ?:, =, as.
*
* Do not check dot ('.') operator. Do not check unary operators (!, +, -, ++, --, ?.).
* Do not check array ('[') operator.
*
* Known limitation: Does not catch violations of missing space around equals operator (=) within a
* declaration expression, e.g. def x=23
*
* Known limitation: Does not catch violations of certain ternary expressions.
*
* @author Chris Mair
*/
class SpaceAroundOperatorRule extends AbstractAstVisitorRule {
String name = 'SpaceAroundOperator'
int priority = 3
Class astVisitorClass = SpaceAroundOperatorAstVisitor
}
class SpaceAroundOperatorAstVisitor extends AbstractAstVisitor {
private boolean withinDeclarationExpression
@Override
void visitDeclarationExpression(DeclarationExpression expression) {
withinDeclarationExpression = true
super.visitDeclarationExpression(expression)
withinDeclarationExpression = false
}
@Override
void visitTernaryExpression(TernaryExpression expression) {
if (isFirstVisit(expression)) {
if (expression instanceof ElvisOperatorExpression) {
processElvisExpression(expression)
}
else {
processTernaryExpression(expression)
}
}
super.visitTernaryExpression(expression)
}
private void processTernaryExpression(TernaryExpression expression) {
def opColumn = expression.columnNumber
def line = sourceLine(expression)
def beforeChar = line[opColumn - 2] as char
// Known limitation: Ternary expression column does not always indicate column of '?'
if (opColumn <= leftMostColumn(expression.booleanExpression)) {
checkForSpaceAroundTernaryOperator(expression, line)
return
}
if (!Character.isWhitespace(beforeChar)) {
addViolation(expression, "The operator \"?\" within class $currentClassName is not preceded by a space or whitespace")
}
if (opColumn < line.size() && !Character.isWhitespace(line[opColumn] as char)) {
addViolation(expression, "The operator \"?\" within class $currentClassName is not followed by a space or whitespace")
}
if (rightMostColumn(expression.trueExpression) + 1 == leftMostColumn(expression.falseExpression)) {
addViolation(expression, "The operator \":\" within class $currentClassName is not surrounded by a space or whitespace")
}
}
private checkForSpaceAroundTernaryOperator(TernaryExpression expression, String line) {
if (expression.lineNumber == expression.lastLineNumber) {
def hasWhitespaceAroundQuestionMark = (line =~ /\s\?\s/)
if (!hasWhitespaceAroundQuestionMark) {
addViolation(expression, "The operator \"?\" within class $currentClassName is not surrounded by a space or whitespace")
}
def hasWhitespaceAroundColon = (line =~ /\s\:\s/)
if (!hasWhitespaceAroundColon) {
addViolation(expression, "The operator \":\" within class $currentClassName is not surrounded by a space or whitespace")
}
}
}
private void processElvisExpression(ElvisOperatorExpression expression) {
def line = sourceCode.lines[expression.lineNumber - 1]
if (line.contains('?:')) {
if (!(line =~ /\s\?\:/)) {
addViolation(expression, "The operator \"?:\" within class $currentClassName is not preceded by a space or whitespace")
}
if (!(line =~ /\?\:\s/)) {
addViolation(expression, "The operator \"?:\" within class $currentClassName is not followed by a space or whitespace")
}
}
}
@Override
void visitBinaryExpression(BinaryExpression expression) {
if (!isFirstVisit(expression)) {
return
}
def op = expression.operation
def opText = op.text
def opEndColumn = op.startColumn + opText.size() - 1
def line = sourceCode.lines[op.startLine - 1]
boolean assignmentWithinDeclaration = (opText == '=') && withinDeclarationExpression
boolean arrayOperator = opText == '['
boolean isOperatorAtIndex = op.startColumn != -1 && (line[op.startColumn - 1] == opText[0])
boolean ignore = assignmentWithinDeclaration || arrayOperator || !isOperatorAtIndex
if (!ignore && op.startColumn > 1) {
def beforeChar = line[op.startColumn - 2] as char
if (!Character.isWhitespace(beforeChar)) {
addViolation(expression, "The operator \"${expression.operation.text}\" within class $currentClassName is not preceded by a space or whitespace")
}
}
if (!ignore && opEndColumn != -1 && opEndColumn < line.size()) {
def afterChar = line[opEndColumn] as char
if (!Character.isWhitespace(afterChar)) {
addViolation(expression, "The operator \"${expression.operation.text}\" within class $currentClassName is not followed by a space or whitespace")
}
}
super.visitBinaryExpression(expression)
}
@Override
void visitCastExpression(CastExpression expression) {
if (expression.coerce && expression.lineNumber != -1) {
boolean containsAsWithSpaces = (expression.lineNumber..expression.lastLineNumber).find { lineNumber ->
String line = sourceCode.lines[lineNumber - 1]
return line.find(/\sas\s/)
}
if (!containsAsWithSpaces) {
addViolation(expression, "The operator \"as\" within class $currentClassName is not surrounded by a space or whitespace")
}
}
super.visitCastExpression(expression)
}
private int rightMostColumn(expression) {
if (expression instanceof BinaryExpression) {
return rightMostColumn(expression.rightExpression)
}
if (expression instanceof MethodCallExpression) {
return expression.arguments.lastColumnNumber
}
if (expression instanceof PropertyExpression) {
return expression.property.lastColumnNumber
}
if (expression instanceof BooleanExpression) {
return rightMostColumn(expression.expression)
}
expression.lastColumnNumber
}
private int leftMostColumn(expression) {
return expression instanceof BinaryExpression ? leftMostColumn(expression.leftExpression) : expression.columnNumber
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy