org.codenarc.rule.formatting.ClassEndsWithBlankLineRule.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of CodeNarc Show documentation
Show all versions of CodeNarc Show documentation
The CodeNarc project provides a static analysis tool for Groovy code.
/*
* Copyright 2018 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 groovy.transform.Memoized
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.ClassNode
import org.codenarc.rule.AbstractAstVisitorRule
import org.codenarc.rule.AbstractAstVisitor
import org.codenarc.rule.Violation
import org.codenarc.util.AstUtil
/**
* Check whether the class ends with a blank line. By default, it enforces that there must be a blank line before
* the closing class brace, except:
*
* - If the class is synthetic (generated)
* - If the class is empty and is written in a single line
* - If the class is a Script class
*
*
* A blank line is defined as any line that does not contain any visible characters.
*
* This rule can be configured with the following properties:
*
* - ignoreSingleLineClasses: a boolean property to forbid single line classes.
* If it is false, then single line classes are considered a violation. Default value is true
* - ignoreInnerClasses: a boolean property to ignore inner classes.
* If it is false, then inner classes can cause violations. Default value is false.
* - blankLineRequired: a boolean property to define if there may be a blank line before the closing
* class brace. If it is false, the last line before the brace must not be blank. Otherwise, it must be blank. Default
* value is true
*
*
* @author David Ausín
*/
class ClassEndsWithBlankLineRule extends AbstractAstVisitorRule {
String name = 'ClassEndsWithBlankLine'
int priority = 3
boolean ignoreSingleLineClasses = true
boolean ignoreInnerClasses = false
boolean blankLineRequired = true
Class astVisitorClass = ClassEndsWithBlankLineAstVisitor
}
class ClassEndsWithBlankLineAstVisitor extends AbstractAstVisitor {
private static final int PENULTIMATE_LINE_OFFSET = 2
private static final char CLOSE_BRACE_CHARACTER = '}'
@Override
protected void visitClassComplete(ClassNode classNode) {
if (classNode.lineNumber == -1) { return }
if (classNode.isScript()) { return }
if (classNode.outerClass && rule.ignoreInnerClasses) { return }
if (isSingleLineClassViolation() && isSingleLineClass(classNode)) { return }
if (rule.blankLineRequired) {
checkIfThereIsBlankLineBeforeClosingBrace(classNode)
} else {
checkIfThereIsNotBlankLineBeforeClosingBrace(classNode)
}
}
private boolean isSingleLineClassViolation() {
rule.ignoreSingleLineClasses || !rule.blankLineRequired
}
private void checkIfThereIsNotBlankLineBeforeClosingBrace(ClassNode classNode) {
String trimmedLineBeforeClosingBrace = getPenultimateLine(classNode).trim()
String trimmedLineOfClosingBrace = getLastLine(classNode).trim()
if (trimmedLineOfClosingBrace == CLOSE_BRACE_CHARACTER && trimmedLineBeforeClosingBrace.isEmpty()) {
addViolation(classNode, 'Class ends with an empty line before the closing brace', classNode.getLastLineNumber())
}
}
private void checkIfThereIsBlankLineBeforeClosingBrace(ClassNode classNode) {
if (isSingleLineClass(classNode)) {
addViolation(classNode, 'Single line classes are not allowed', classNode.getLastLineNumber())
return
}
String trimmedLineBeforeClosingBrace = getPenultimateLine(classNode).trim()
String trimmedLineOfClosingBrace = getLastLine(classNode).trim()
if (trimmedLineOfClosingBrace != CLOSE_BRACE_CHARACTER || !trimmedLineBeforeClosingBrace.isEmpty()) {
addViolation(classNode, 'Class does not end with a blank line before the closing brace', classNode.getLastLineNumber())
}
}
private String getPenultimateLine(ClassNode classNode) {
Integer penultimateLastLineNumber = classNode.lastLineNumber - PENULTIMATE_LINE_OFFSET
return AstUtil.getRawLine(sourceCode, penultimateLastLineNumber)
}
private String getLastLine(ClassNode classNode) {
return AstUtil.getLastLineOfNodeText(classNode, sourceCode)
}
@Memoized
private Boolean isSingleLineClass(ClassNode classNode) {
return AstUtil.getNodeText(classNode, sourceCode) == AstUtil.getLastLineOfNodeText(classNode, sourceCode)
}
private void addViolation(ASTNode node, String message, int lineNumber) {
if (lineNumber >= 0) {
String sourceLine = AstUtil.getLastLineOfNodeText(node, getSourceCode())
Violation violation = new Violation()
violation.rule = rule
violation.lineNumber = lineNumber
violation.sourceLine = sourceLine
violation.message = message
violations.add(violation)
}
}
}