org.codenarc.rule.AbstractRule 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 2008 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;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ImportNode;
import org.codenarc.source.SourceCode;
import org.codenarc.source.SourceCodeCriteria;
import org.codenarc.util.ImportUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Abstract superclass for Rules.
*
* Each subclass must define an name
property (String) and a priority
property
* (integer 1..3).
*
* @author Chris Mair
* @author Hamlet D'Arcy
*/
public abstract class AbstractRule implements Rule {
private static final Logger LOG = LoggerFactory.getLogger(AbstractRule.class);
/**
* Flag indicating whether this rule should be enabled (applied). Defaults to true.
* If set to false, this rule will not produce any violations.
*/
private boolean enabled = true;
/**
* This rule is only applied to source code (file) pathnames matching this regular expression.
*/
private String applyToFilesMatching;
/**
* This rule is NOT applied to source code (file) pathnames matching this regular expression.
*/
private String doNotApplyToFilesMatching;
/**
* This rule is only applied to source code (file) names matching this value. The name may optionally
* contain a path. If a path is specified, then the source code path must match it. If no path is
* specified, then only the source code (file) name is compared (i.e., its path is ignored).
* The value may optionally be a comma-separated list of names, in which case one of the names must match.
* The name(s) may optionally include wildcard characters ('*' or '?').
*/
private String applyToFileNames;
/**
* This rule is NOT applied to source code (file) names matching this value. The name may optionally
* contain a path. If a path is specified, then the source code path must match it. If no path is
* specified, then only the source code (file) name is compared (i.e., its path is ignored).
* The value may optionally be a comma-separated list of names, in which case any one of the names can match.
* The name(s) may optionally include wildcard characters ('*' or '?').
*/
private String doNotApplyToFileNames;
/**
* If not null, this is used as the message for all violations of this rule, overriding any
* message generated by the concrete rule subclass. Defaults to null. Note that setting this
* to an empty string "hides" the message, if any, generated by the actual rule.
*/
private String violationMessage;
/**
* If not null, this is used as the description text for this rule, overriding any
* description text found in the i18n resource bundles. Defaults to null.
*/
private String description;
/**
* @return the unique name for this rule
*/
public abstract String getName();
/**
* Set the unique name for this rule
* @param name - the name for this rule; this should be unique
*/
public abstract void setName(String name);
/**
* @return the priority of this rule, between 1 (highest priority) and 3 (lowest priority), inclusive.
*/
public abstract int getPriority();
/**
* Set the priority for this rule
* @param priority - the priority of this rule, between 1 (highest priority) and 3 (lowest priority), inclusive.
*/
public abstract void setPriority(int priority);
/**
* @return the required compiler phase (as in {@link org.codehaus.groovy.control.Phases})
* of the AST of the {@link SourceCode}
* handed to the rule via {@link #applyTo(SourceCode sourceCode)}
*/
public int getCompilerPhase() {
return SourceCode.DEFAULT_COMPILER_PHASE;
}
/**
* Apply this rule to the specified source and return a list of violations (or an empty List)
* @param sourceCode - the source to apply this rule to
* @param violations - the List of violations to which new violations from this rule are to be added
*/
public abstract void applyTo(SourceCode sourceCode, List violations);
/**
* Apply this rule to the specified source and return a list of violations (or an empty List).
* This implementation delegates to the abstract applyCode(SourceCode,List), provided by
* concrete subclasses. This template method simplifies subclass implementations and also
* enables common handling of enablement logic.
* @param sourceCode - the source to apply this rule to
* @return the List of violations; may be empty
*/
public List applyTo(SourceCode sourceCode) throws Throwable {
try {
validateAstCompilerPhase(sourceCode);
validate();
List violations = new ArrayList();
if (shouldApplyThisRuleTo(sourceCode)) {
applyTo(sourceCode, violations);
}
overrideViolationMessageIfNecessary(violations);
return violations;
} catch(Throwable t) {
LOG.error("Error from [" + getClass().getName() + "] processing source file [" + sourceCode.getPath() + "]", t);
throw t;
}
}
private void validateAstCompilerPhase(SourceCode sourceCode) {
if (sourceCode.getAstCompilerPhase() != getCompilerPhase()) {
throw new IllegalArgumentException("This rule requires SourceCode with AST compiler phase '"
+ getCompilerPhase() + "', but was handed one with AST compiler phase '"
+ sourceCode.getAstCompilerPhase() + "'");
}
}
/**
* Allows rules to check whether preconditions are satisfied and short-circuit execution
* (i.e., do nothing) if those preconditions are not satisfied. Return true by default.
* This method is provided as a placeholder so subclasses can optionally override.
* @return true if all preconditions for this rule are satisfied
*/
public boolean isReady() {
return true;
}
/**
* Allows rules to perform validation. Do nothing by default.
* This method is provided as a placeholder so subclasses can optionally override.
* Subclasses will typically use assert
calls to verify required preconditions.
*/
public void validate() {
}
public String toString() {
return String.format(
"%s[name=%s, priority=%s]",
getClassNameNoPackage(),
getName(),
getPriority()
);
}
/**
* Create and return a new Violation for this rule and the specified values
* @param lineNumber - the line number for the violation; may be null
* @param sourceLine - the source line for the violation; may be null
* @param message - the message for the violation; may be null
* @return a new Violation object
*/
protected Violation createViolation(Integer lineNumber, String sourceLine, String message) {
Violation violation = new Violation();
violation.setRule(this);
violation.setSourceLine(sourceLine);
violation.setLineNumber(lineNumber);
violation.setMessage(message);
return violation;
}
/**
* Create a new Violation for the AST node.
* @param sourceCode - the SourceCode
* @param node - the Groovy AST Node
* @param message - the message for the violation; defaults to null
*/
protected Violation createViolation(SourceCode sourceCode, ASTNode node, String message) {
String sourceLine = sourceCode.line(node.getLineNumber()-1);
return createViolation(node.getLineNumber(), sourceLine, message);
}
/**
* Create and return a new Violation for this rule and the specified import
* @param sourceCode - the SourceCode
* @param importNode - the ImportNode for the import triggering the violation
* @return a new Violation object
*/
protected Violation createViolationForImport(SourceCode sourceCode, ImportNode importNode, String message) {
Map importInfo = ImportUtil.sourceLineAndNumberForImport(sourceCode, importNode);
Violation violation = new Violation();
violation.setRule(this);
violation.setSourceLine((String) importInfo.get("sourceLine"));
violation.setLineNumber((Integer) importInfo.get("lineNumber"));
violation.setMessage(message);
return violation;
}
/**
* Create and return a new Violation for this rule and the specified import className and alias
* @param sourceCode - the SourceCode
* @param className - the class name (as specified within the import statement)
* @param alias - the alias for the import statement
* @param violationMessage - the violation message; may be null
* @return a new Violation object
*/
protected Violation createViolationForImport(SourceCode sourceCode, String className, String alias, String violationMessage) {
Map importInfo = ImportUtil.sourceLineAndNumberForImport(sourceCode, className, alias);
Violation violation = new Violation();
violation.setRule(this);
violation.setSourceLine((String) importInfo.get("sourceLine"));
violation.setLineNumber((Integer) importInfo.get("lineNumber"));
violation.setMessage(violationMessage);
return violation;
}
private boolean shouldApplyThisRuleTo(SourceCode sourceCode) {
if (!enabled) return false;
if (!isReady()) return false;
SourceCodeCriteria criteria = new SourceCodeCriteria();
criteria.setApplyToFilesMatching(getApplyToFilesMatching());
criteria.setDoNotApplyToFilesMatching(getDoNotApplyToFilesMatching());
criteria.setApplyToFileNames(getApplyToFileNames());
criteria.setDoNotApplyToFileNames(getDoNotApplyToFileNames());
return criteria.matches(sourceCode);
}
private String getClassNameNoPackage() {
String className = getClass().getName();
int indexOfLastPeriod = className.lastIndexOf('.');
return (indexOfLastPeriod == -1) ? className : className.substring(indexOfLastPeriod + 1);
}
/**
* If the violationMessage property of this rule has been set, then use it to set the
* message within each violation, overriding the original message(s), if any.
*/
private void overrideViolationMessageIfNecessary(List violations) {
if (violationMessage != null && violations != null) {
for (Violation violation : violations) {
violation.setMessage(violationMessage);
}
}
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getApplyToFilesMatching() {
return applyToFilesMatching;
}
public void setApplyToFilesMatching(String applyToFilesMatching) {
validateRegularExpression(applyToFilesMatching);
this.applyToFilesMatching = applyToFilesMatching;
}
public String getDoNotApplyToFilesMatching() {
return doNotApplyToFilesMatching;
}
public void setDoNotApplyToFilesMatching(String doNotApplyToFilesMatching) {
validateRegularExpression(doNotApplyToFilesMatching);
this.doNotApplyToFilesMatching = doNotApplyToFilesMatching;
}
public String getApplyToFileNames() {
return applyToFileNames;
}
public void setApplyToFileNames(String applyToFileNames) {
this.applyToFileNames = applyToFileNames;
}
public String getDoNotApplyToFileNames() {
return doNotApplyToFileNames;
}
public void setDoNotApplyToFileNames(String doNotApplyToFileNames) {
this.doNotApplyToFileNames = doNotApplyToFileNames;
}
public String getViolationMessage() {
return violationMessage;
}
public void setViolationMessage(String violationMessage) {
this.violationMessage = violationMessage;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
private void validateRegularExpression(String regex) {
Pattern.compile(regex);
}
}