Merge pull request #4050 from oowekyala:violation-decorators
[core] Implement violation decorators #4050
This commit is contained in:
39 files changed
+723
-746
No files matched your search
@@ -247,6 +247,8 @@ The following previously deprecated rules have been finally removed:
|
||||
* [#4080](https://github.com/pmd/pmd/issues/4080): \[ant] Split off Ant integration into a new submodule
|
||||
* core
|
||||
* [#2234](https://github.com/pmd/pmd/issues/2234): \[core] Consolidate PMD CLI into a single command
|
||||
* [#3203](https://github.com/pmd/pmd/issues/3203): \[core] Replace RuleViolationFactory implementations with ViolationDecorator
|
||||
* [#3902](https://github.com/pmd/pmd/issues/3902): \[core] Violation decorators
|
||||
* [#4035](https://github.com/pmd/pmd/issues/4035): \[core] ConcurrentModificationException in DefaultRuleViolationFactory
|
||||
* cli
|
||||
* [#3828](https://github.com/pmd/pmd/issues/3828): \[core] Progress reporting
|
||||
|
||||
@@ -9,6 +9,7 @@ import static net.sourceforge.pmd.util.CollectionUtil.listOf;
|
||||
import net.sourceforge.pmd.lang.BaseLanguageModule;
|
||||
import net.sourceforge.pmd.lang.Language;
|
||||
import net.sourceforge.pmd.lang.LanguageRegistry;
|
||||
import net.sourceforge.pmd.lang.apex.internal.ApexHandler;
|
||||
|
||||
import apex.jorje.services.Version;
|
||||
|
||||
|
||||
+7
-10
@@ -1,35 +1,32 @@
|
||||
/**
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex;
|
||||
package net.sourceforge.pmd.lang.apex.internal;
|
||||
|
||||
import static net.sourceforge.pmd.util.CollectionUtil.setOf;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import net.sourceforge.pmd.annotation.InternalApi;
|
||||
import net.sourceforge.pmd.ViolationSuppressor;
|
||||
import net.sourceforge.pmd.lang.AbstractPmdLanguageVersionHandler;
|
||||
import net.sourceforge.pmd.lang.apex.ApexLanguageModule;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ApexParser;
|
||||
import net.sourceforge.pmd.lang.apex.internal.ApexDesignerBindings;
|
||||
import net.sourceforge.pmd.lang.apex.metrics.ApexMetrics;
|
||||
import net.sourceforge.pmd.lang.apex.rule.internal.ApexRuleViolationFactory;
|
||||
import net.sourceforge.pmd.lang.ast.Parser;
|
||||
import net.sourceforge.pmd.lang.metrics.LanguageMetricsProvider;
|
||||
import net.sourceforge.pmd.lang.metrics.Metric;
|
||||
import net.sourceforge.pmd.lang.rule.RuleViolationFactory;
|
||||
import net.sourceforge.pmd.properties.PropertySource;
|
||||
import net.sourceforge.pmd.util.designerbindings.DesignerBindings;
|
||||
|
||||
@InternalApi
|
||||
public class ApexHandler extends AbstractPmdLanguageVersionHandler {
|
||||
|
||||
private final ApexMetricsProvider myMetricsProvider = new ApexMetricsProvider();
|
||||
|
||||
|
||||
@Override
|
||||
public RuleViolationFactory getRuleViolationFactory() {
|
||||
return ApexRuleViolationFactory.INSTANCE;
|
||||
public List<ViolationSuppressor> getExtraViolationSuppressors() {
|
||||
return ApexViolationSuppressors.ALL_APEX_SUPPRESSORS;
|
||||
}
|
||||
|
||||
@Override
|
||||
+6
-10
@@ -2,10 +2,11 @@
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.rule.internal;
|
||||
package net.sourceforge.pmd.lang.apex.internal;
|
||||
|
||||
import static net.sourceforge.pmd.util.CollectionUtil.listOf;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
@@ -29,11 +30,9 @@ import net.sourceforge.pmd.lang.apex.ast.ASTUserEnum;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclarationStatements;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ApexNode;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.rule.impl.DefaultRuleViolationFactory;
|
||||
|
||||
public final class ApexRuleViolationFactory extends DefaultRuleViolationFactory {
|
||||
public final class ApexViolationSuppressors {
|
||||
|
||||
public static final ApexRuleViolationFactory INSTANCE = new ApexRuleViolationFactory();
|
||||
private static final ViolationSuppressor APEX_ANNOT_SUPPRESSOR = new ViolationSuppressor() {
|
||||
@Override
|
||||
public String getId() {
|
||||
@@ -49,12 +48,9 @@ public final class ApexRuleViolationFactory extends DefaultRuleViolationFactory
|
||||
}
|
||||
};
|
||||
|
||||
private ApexRuleViolationFactory() {
|
||||
}
|
||||
static final List<ViolationSuppressor> ALL_APEX_SUPPRESSORS = listOf(APEX_ANNOT_SUPPRESSOR);
|
||||
|
||||
@Override
|
||||
protected List<ViolationSuppressor> getSuppressors() {
|
||||
return Collections.singletonList(APEX_ANNOT_SUPPRESSOR);
|
||||
private ApexViolationSuppressors() {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4,21 +4,29 @@
|
||||
|
||||
package net.sourceforge.pmd;
|
||||
|
||||
import static net.sourceforge.pmd.util.CollectionUtil.listOf;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import net.sourceforge.pmd.Report.SuppressedViolation;
|
||||
import net.sourceforge.pmd.annotation.InternalApi;
|
||||
import net.sourceforge.pmd.lang.LanguageVersionHandler;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
import net.sourceforge.pmd.lang.document.TextRange2d;
|
||||
import net.sourceforge.pmd.lang.rule.AbstractRule;
|
||||
import net.sourceforge.pmd.lang.rule.RuleViolationFactory;
|
||||
import net.sourceforge.pmd.lang.rule.ParametricRuleViolation;
|
||||
import net.sourceforge.pmd.processor.AbstractPMDProcessor;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.reporting.FileAnalysisListener;
|
||||
import net.sourceforge.pmd.reporting.ViolationDecorator;
|
||||
|
||||
/**
|
||||
* The API for rules to report violations or errors during analysis.
|
||||
@@ -37,6 +45,9 @@ public final class RuleContext {
|
||||
// they are stack-local
|
||||
|
||||
private static final Object[] NO_ARGS = new Object[0];
|
||||
private static final List<ViolationSuppressor> DEFAULT_SUPPRESSORS = listOf(ViolationSuppressor.NOPMD_COMMENT_SUPPRESSOR,
|
||||
ViolationSuppressor.REGEX_SUPPRESSOR,
|
||||
ViolationSuppressor.XPATH_SUPPRESSOR);
|
||||
|
||||
private final FileAnalysisListener listener;
|
||||
private final Rule rule;
|
||||
@@ -118,7 +129,7 @@ public final class RuleContext {
|
||||
* as a format string for a {@link MessageFormat} and should hence use
|
||||
* appropriate escapes. The given formatting arguments are used.
|
||||
*
|
||||
* @param location Location of the violation
|
||||
* @param node Location of the violation
|
||||
* @param message Violation message
|
||||
* @param formatArgs Format arguments for the message
|
||||
*/
|
||||
@@ -127,17 +138,18 @@ public final class RuleContext {
|
||||
Objects.requireNonNull(message, "Message was null");
|
||||
Objects.requireNonNull(formatArgs, "Format arguments were null, use an empty array");
|
||||
|
||||
RuleViolationFactory fact = node.getTextDocument().getLanguageVersion().getLanguageVersionHandler().getRuleViolationFactory();
|
||||
|
||||
LanguageVersionHandler handler = node.getTextDocument().getLanguageVersion().getLanguageVersionHandler();
|
||||
|
||||
FileLocation location = node.getReportLocation();
|
||||
if (beginLine != -1 && endLine != -1) {
|
||||
location = FileLocation.range(location.getFileName(), TextRange2d.range2d(beginLine, 1, endLine, 1));
|
||||
}
|
||||
|
||||
RuleViolation violation = fact.createViolation(rule, node, location, makeMessage(message, formatArgs));
|
||||
final Map<String, String> extraVariables = ViolationDecorator.apply(handler.getViolationDecorator(), node);
|
||||
final String description = makeMessage(message, formatArgs, extraVariables);
|
||||
final RuleViolation violation = new ParametricRuleViolation(rule, location, description, extraVariables);
|
||||
|
||||
SuppressedViolation suppressed = fact.suppressOrNull(node, violation);
|
||||
final SuppressedViolation suppressed = suppressOrNull(node, violation, handler);
|
||||
|
||||
if (suppressed != null) {
|
||||
listener.onSuppressedRuleViolation(suppressed);
|
||||
@@ -146,10 +158,18 @@ public final class RuleContext {
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable SuppressedViolation suppressOrNull(Node location, RuleViolation rv, LanguageVersionHandler handler) {
|
||||
SuppressedViolation suppressed = ViolationSuppressor.suppressOrNull(handler.getExtraViolationSuppressors(), rv, location);
|
||||
if (suppressed == null) {
|
||||
suppressed = ViolationSuppressor.suppressOrNull(DEFAULT_SUPPRESSORS, rv, location);
|
||||
}
|
||||
return suppressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the recording of a violation, ignoring the violation
|
||||
* suppression mechanism ({@link ViolationSuppressor}).
|
||||
*
|
||||
*
|
||||
* @param rv A violation
|
||||
*/
|
||||
@InternalApi
|
||||
@@ -157,13 +177,46 @@ public final class RuleContext {
|
||||
listener.onRuleViolation(rv);
|
||||
}
|
||||
|
||||
private static String makeMessage(@NonNull String message, Object[] args) {
|
||||
private String makeMessage(@NonNull String message, Object[] args, Map<String, String> extraVars) {
|
||||
// Escape PMD specific variable message format, specifically the {
|
||||
// in the ${, so MessageFormat doesn't bitch.
|
||||
final String escapedMessage = StringUtils.replace(message, "${", "$'{'");
|
||||
return MessageFormat.format(escapedMessage, args);
|
||||
String formatted = MessageFormat.format(escapedMessage, args);
|
||||
return expandVariables(formatted, extraVars);
|
||||
}
|
||||
|
||||
|
||||
private String expandVariables(String message, Map<String, String> extraVars) {
|
||||
|
||||
if (!message.contains("${")) {
|
||||
return message;
|
||||
}
|
||||
|
||||
StringBuilder buf = new StringBuilder(message);
|
||||
int startIndex = -1;
|
||||
while ((startIndex = buf.indexOf("${", startIndex + 1)) >= 0) {
|
||||
final int endIndex = buf.indexOf("}", startIndex);
|
||||
if (endIndex >= 0) {
|
||||
final String name = buf.substring(startIndex + 2, endIndex);
|
||||
String variableValue = getVariableValue(name, extraVars);
|
||||
if (variableValue != null) {
|
||||
buf.replace(startIndex, endIndex + 1, variableValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private String getVariableValue(String name, Map<String, String> extraVars) {
|
||||
String value = extraVars.get(name);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
final PropertyDescriptor<?> propertyDescriptor = rule.getPropertyDescriptor(name);
|
||||
return propertyDescriptor == null ? null : String.valueOf(rule.getProperty(propertyDescriptor));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new RuleContext. This is internal API owned by {@link AbstractPMDProcessor}
|
||||
* (can likely be hidden when everything relevant is moved into rule package).
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
package net.sourceforge.pmd;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
|
||||
import net.sourceforge.pmd.annotation.DeprecatedUntil700;
|
||||
import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
|
||||
/**
|
||||
@@ -35,6 +37,28 @@ public interface RuleViolation {
|
||||
.thenComparingInt(RuleViolation::getEndColumn)
|
||||
.thenComparing(rv -> rv.getRule().getName());
|
||||
|
||||
|
||||
/**
|
||||
* Key in {@link #getAdditionalInfo()} for the name of the class in
|
||||
* which the violation was identified.
|
||||
*/
|
||||
String CLASS_NAME = "className";
|
||||
/**
|
||||
* Key in {@link #getAdditionalInfo()} for the name of the variable
|
||||
* related to the violation.
|
||||
*/
|
||||
String VARIABLE_NAME = "variableName";
|
||||
/**
|
||||
* Key in {@link #getAdditionalInfo()} for the name of the method in
|
||||
* which the violation was identified.
|
||||
*/
|
||||
String METHOD_NAME = "methodName";
|
||||
/**
|
||||
* Key in {@link #getAdditionalInfo()} for the name of the package in
|
||||
* which the violation was identified.
|
||||
*/
|
||||
String PACKAGE_NAME = "packageName";
|
||||
|
||||
/**
|
||||
* Get the Rule which identified this violation.
|
||||
*
|
||||
@@ -104,34 +128,63 @@ public interface RuleViolation {
|
||||
return getLocation().getEndPos().getColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* A map of additional key-value pairs known about this violation.
|
||||
* What data is in there is language specific. Common keys supported
|
||||
* by several languages are defined as constants on this interface.
|
||||
* The map is unmodifiable.
|
||||
*/
|
||||
Map<String, String> getAdditionalInfo();
|
||||
|
||||
|
||||
/**
|
||||
* Get the package name of the Class in which this violation was identified.
|
||||
*
|
||||
* @return The package name.
|
||||
*
|
||||
* @deprecated Use {@link #PACKAGE_NAME}
|
||||
*/
|
||||
// TODO Isn't this Java specific?
|
||||
String getPackageName();
|
||||
@Deprecated
|
||||
@DeprecatedUntil700
|
||||
default String getPackageName() {
|
||||
return getAdditionalInfo().get(PACKAGE_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the Class in which this violation was identified.
|
||||
*
|
||||
* @return The Class name.
|
||||
* @deprecated Use {@link #CLASS_NAME}
|
||||
*/
|
||||
// TODO Isn't this Java specific?
|
||||
String getClassName();
|
||||
@Deprecated
|
||||
@DeprecatedUntil700
|
||||
default String getClassName() {
|
||||
return getAdditionalInfo().get(CLASS_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the method name in which this violation was identified.
|
||||
*
|
||||
* @return The method name.
|
||||
* @deprecated Use {@link #METHOD_NAME}
|
||||
*/
|
||||
// TODO Isn't this Java specific?
|
||||
String getMethodName();
|
||||
@Deprecated
|
||||
@DeprecatedUntil700
|
||||
default String getMethodName() {
|
||||
return getAdditionalInfo().get(METHOD_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the variable name on which this violation was identified.
|
||||
*
|
||||
* @return The variable name.
|
||||
* @deprecated Use {@link #VARIABLE_NAME}
|
||||
*/
|
||||
String getVariableName();
|
||||
@Deprecated
|
||||
@DeprecatedUntil700
|
||||
default String getVariableName() {
|
||||
return getAdditionalInfo().get(VARIABLE_NAME);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
package net.sourceforge.pmd;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -13,14 +14,13 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import net.sourceforge.pmd.Report.SuppressedViolation;
|
||||
import net.sourceforge.pmd.lang.ast.AstInfo;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.rule.RuleViolationFactory;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.XPathVersion;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.internal.DeprecatedAttrLogger;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.internal.SaxonXPathRuleQuery;
|
||||
|
||||
/**
|
||||
* An object that suppresses rule violations. Suppressors are used by
|
||||
* {@link RuleViolationFactory} to filter out violations. In PMD 6.0.x,
|
||||
* {@link RuleContext} to filter out violations. In PMD 6.0.x,
|
||||
* the {@link Report} object filtered violations itself - but it has
|
||||
* no knowledge of language-specific suppressors.
|
||||
*/
|
||||
@@ -121,4 +121,20 @@ public interface ViolationSuppressor {
|
||||
SuppressedViolation suppressOrNull(RuleViolation rv, @NonNull Node node);
|
||||
|
||||
|
||||
/**
|
||||
* Apply a list of suppressors on the violation. Returns the violation
|
||||
* of the first suppressor that matches the input violation. If no
|
||||
* suppressor matches, then returns null.
|
||||
*/
|
||||
static @Nullable SuppressedViolation suppressOrNull(List<ViolationSuppressor> suppressorList,
|
||||
RuleViolation rv,
|
||||
Node node) {
|
||||
for (ViolationSuppressor suppressor : suppressorList) {
|
||||
SuppressedViolation suppressed = suppressor.suppressOrNull(rv, node);
|
||||
if (suppressed != null) {
|
||||
return suppressed;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+44
-48
@@ -7,12 +7,19 @@ package net.sourceforge.pmd.cache;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import net.sourceforge.pmd.Rule;
|
||||
import net.sourceforge.pmd.RuleViolation;
|
||||
import net.sourceforge.pmd.annotation.InternalApi;
|
||||
import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
import net.sourceforge.pmd.lang.document.TextRange2d;
|
||||
import net.sourceforge.pmd.util.StringUtil;
|
||||
|
||||
/**
|
||||
* A {@link RuleViolation} implementation that is immutable, and therefore cache friendly
|
||||
@@ -30,26 +37,20 @@ public final class CachedRuleViolation implements RuleViolation {
|
||||
private final String ruleClassName;
|
||||
private final String ruleName;
|
||||
private final String ruleTargetLanguage;
|
||||
private final String packageName;
|
||||
private final String className;
|
||||
private final String methodName;
|
||||
private final String variableName;
|
||||
private final Map<String, String> additionalInfo;
|
||||
|
||||
private CachedRuleViolation(final CachedRuleMapper mapper, final String description,
|
||||
final String fileName, final String ruleClassName, final String ruleName,
|
||||
final String ruleTargetLanguage, final int beginLine, final int beginColumn,
|
||||
final int endLine, final int endColumn, final String packageName,
|
||||
final String className, final String methodName, final String variableName) {
|
||||
final String fileName, final String ruleClassName, final String ruleName,
|
||||
final String ruleTargetLanguage, final int beginLine, final int beginColumn,
|
||||
final int endLine, final int endColumn,
|
||||
final Map<String, String> additionalInfo) {
|
||||
this.mapper = mapper;
|
||||
this.description = description;
|
||||
this.location = FileLocation.range(fileName, TextRange2d.range2d(beginLine, beginColumn, endLine, endColumn));
|
||||
this.ruleClassName = ruleClassName;
|
||||
this.ruleName = ruleName;
|
||||
this.ruleTargetLanguage = ruleTargetLanguage;
|
||||
this.packageName = packageName;
|
||||
this.className = className;
|
||||
this.methodName = methodName;
|
||||
this.variableName = variableName;
|
||||
this.additionalInfo = additionalInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,23 +70,8 @@ public final class CachedRuleViolation implements RuleViolation {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPackageName() {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return className;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodName() {
|
||||
return methodName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVariableName() {
|
||||
return variableName;
|
||||
public Map<String, String> getAdditionalInfo() {
|
||||
return additionalInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,41 +93,51 @@ public final class CachedRuleViolation implements RuleViolation {
|
||||
final int beginColumn = stream.readInt();
|
||||
final int endLine = stream.readInt();
|
||||
final int endColumn = stream.readInt();
|
||||
final String packageName = stream.readUTF();
|
||||
final String className = stream.readUTF();
|
||||
final String methodName = stream.readUTF();
|
||||
final String variableName = stream.readUTF();
|
||||
|
||||
final Map<String, String> additionalInfo = readAdditionalInfo(stream);
|
||||
return new CachedRuleViolation(mapper, description, fileName, ruleClassName, ruleName, ruleTargetLanguage,
|
||||
beginLine, beginColumn, endLine, endColumn, packageName, className, methodName, variableName);
|
||||
beginLine, beginColumn, endLine, endColumn, additionalInfo);
|
||||
}
|
||||
|
||||
private static @NonNull Map<String, String> readAdditionalInfo(DataInputStream stream) throws IOException {
|
||||
int numAdditionalInfoKeyValuePairs = stream.readInt();
|
||||
if (numAdditionalInfoKeyValuePairs == 0) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<String, String> additionalInfo = new LinkedHashMap<>();
|
||||
while (numAdditionalInfoKeyValuePairs-- > 0) {
|
||||
final String key = stream.readUTF();
|
||||
final String value = stream.readUTF();
|
||||
additionalInfo.put(key, value);
|
||||
}
|
||||
return Collections.unmodifiableMap(additionalInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to store a {@link RuleViolation} in an output stream to be later
|
||||
* retrieved as a {@link CachedRuleViolation}
|
||||
*
|
||||
* @param stream The stream on which to store the violation.
|
||||
* @param stream The stream on which to store the violation.
|
||||
* @param violation The rule violation to cache.
|
||||
* @throws IOException
|
||||
*/
|
||||
/* package */ static void storeToStream(final DataOutputStream stream,
|
||||
final RuleViolation violation) throws IOException {
|
||||
stream.writeUTF(getValueOrEmpty(violation.getDescription()));
|
||||
stream.writeUTF(getValueOrEmpty(violation.getRule().getRuleClass()));
|
||||
stream.writeUTF(getValueOrEmpty(violation.getRule().getName()));
|
||||
stream.writeUTF(getValueOrEmpty(violation.getRule().getLanguage().getTerseName()));
|
||||
stream.writeUTF(StringUtil.nullToEmpty(violation.getDescription()));
|
||||
stream.writeUTF(StringUtil.nullToEmpty(violation.getRule().getRuleClass()));
|
||||
stream.writeUTF(StringUtil.nullToEmpty(violation.getRule().getName()));
|
||||
stream.writeUTF(StringUtil.nullToEmpty(violation.getRule().getLanguage().getTerseName()));
|
||||
FileLocation location = violation.getLocation();
|
||||
stream.writeInt(location.getStartPos().getLine());
|
||||
stream.writeInt(location.getStartPos().getColumn());
|
||||
stream.writeInt(location.getEndPos().getColumn());
|
||||
stream.writeInt(location.getEndPos().getColumn());
|
||||
stream.writeUTF(getValueOrEmpty(violation.getPackageName()));
|
||||
stream.writeUTF(getValueOrEmpty(violation.getClassName()));
|
||||
stream.writeUTF(getValueOrEmpty(violation.getMethodName()));
|
||||
stream.writeUTF(getValueOrEmpty(violation.getVariableName()));
|
||||
Map<String, String> additionalInfo = violation.getAdditionalInfo();
|
||||
stream.writeInt(additionalInfo.size());
|
||||
for (Entry<String, String> entry : additionalInfo.entrySet()) {
|
||||
stream.writeUTF(entry.getKey());
|
||||
stream.writeUTF(StringUtil.nullToEmpty(entry.getValue()));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static String getValueOrEmpty(final String value) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,16 @@
|
||||
|
||||
package net.sourceforge.pmd.lang;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import net.sourceforge.pmd.ViolationSuppressor;
|
||||
import net.sourceforge.pmd.annotation.Experimental;
|
||||
import net.sourceforge.pmd.lang.ast.Parser;
|
||||
import net.sourceforge.pmd.lang.metrics.LanguageMetricsProvider;
|
||||
import net.sourceforge.pmd.lang.rule.RuleViolationFactory;
|
||||
import net.sourceforge.pmd.lang.rule.impl.DefaultRuleViolationFactory;
|
||||
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathHandler;
|
||||
import net.sourceforge.pmd.properties.PropertySource;
|
||||
import net.sourceforge.pmd.reporting.ViolationDecorator;
|
||||
import net.sourceforge.pmd.util.designerbindings.DesignerBindings;
|
||||
import net.sourceforge.pmd.util.designerbindings.DesignerBindings.DefaultDesignerBindings;
|
||||
|
||||
@@ -42,19 +45,24 @@ public interface LanguageVersionHandler {
|
||||
|
||||
|
||||
/**
|
||||
* Get the Parser.
|
||||
*
|
||||
* @return Parser
|
||||
* Returns the parser instance.
|
||||
*/
|
||||
Parser getParser();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the language-specific violation decorator.
|
||||
*/
|
||||
default ViolationDecorator getViolationDecorator() {
|
||||
return ViolationDecorator.noop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the RuleViolationFactory.
|
||||
* Returns additional language-specific violation suppressors.
|
||||
* These take precedence over the default suppressors (eg nopmd comment),
|
||||
* but do not replace them.
|
||||
*/
|
||||
default RuleViolationFactory getRuleViolationFactory() {
|
||||
return DefaultRuleViolationFactory.defaultInstance();
|
||||
default List<ViolationSuppressor> getExtraViolationSuppressors() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.rule;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import net.sourceforge.pmd.Rule;
|
||||
import net.sourceforge.pmd.RuleViolation;
|
||||
import net.sourceforge.pmd.annotation.InternalApi;
|
||||
import net.sourceforge.pmd.internal.util.AssertionUtil;
|
||||
import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.reporting.Reportable;
|
||||
|
||||
/**
|
||||
@@ -25,60 +27,43 @@ public class ParametricRuleViolation implements RuleViolation {
|
||||
|
||||
private final FileLocation location;
|
||||
|
||||
protected String packageName = "";
|
||||
protected String className = "";
|
||||
protected String methodName = "";
|
||||
protected String variableName = "";
|
||||
private final Map<String, String> additionalInfo;
|
||||
|
||||
// todo add factory methods on the interface and hide the class.
|
||||
|
||||
/**
|
||||
* @deprecated Update tests that use this not to call the ctor directly.
|
||||
*/
|
||||
@Deprecated
|
||||
public ParametricRuleViolation(Rule theRule, Reportable node, String message) {
|
||||
this(theRule, node.getReportLocation(), message);
|
||||
this(theRule, node.getReportLocation(), message, Collections.emptyMap());
|
||||
}
|
||||
|
||||
public ParametricRuleViolation(Rule theRule, FileLocation location, String message) {
|
||||
this(theRule, location, message, Collections.emptyMap());
|
||||
}
|
||||
|
||||
public ParametricRuleViolation(Rule theRule, Reportable node, String message, Map<String, String> additionalInfo) {
|
||||
this(theRule, node.getReportLocation(), message, additionalInfo);
|
||||
}
|
||||
|
||||
public ParametricRuleViolation(Rule theRule, FileLocation location, String message, Map<String, String> additionalInfo) {
|
||||
this.rule = AssertionUtil.requireParamNotNull("rule", theRule);
|
||||
this.description = AssertionUtil.requireParamNotNull("message", message);
|
||||
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
protected String expandVariables(String message) {
|
||||
// TODO move that to RuleContext with the rest of the formatting logic
|
||||
|
||||
if (!message.contains("${")) {
|
||||
return message;
|
||||
}
|
||||
|
||||
StringBuilder buf = new StringBuilder(message);
|
||||
int startIndex = -1;
|
||||
while ((startIndex = buf.indexOf("${", startIndex + 1)) >= 0) {
|
||||
final int endIndex = buf.indexOf("}", startIndex);
|
||||
if (endIndex >= 0) {
|
||||
final String name = buf.substring(startIndex + 2, endIndex);
|
||||
String variableValue = getVariableValue(name);
|
||||
if (variableValue != null) {
|
||||
buf.replace(startIndex, endIndex + 1, variableValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
protected String getVariableValue(String name) {
|
||||
if ("variableName".equals(name)) {
|
||||
return variableName;
|
||||
} else if ("methodName".equals(name)) {
|
||||
return methodName;
|
||||
} else if ("className".equals(name)) {
|
||||
return className;
|
||||
} else if ("packageName".equals(name)) {
|
||||
return packageName;
|
||||
if (!additionalInfo.isEmpty()) {
|
||||
this.additionalInfo = Collections.unmodifiableMap(additionalInfo);
|
||||
} else {
|
||||
final PropertyDescriptor<?> propertyDescriptor = rule.getPropertyDescriptor(name);
|
||||
return propertyDescriptor == null ? null : String.valueOf(rule.getProperty(propertyDescriptor));
|
||||
this.additionalInfo = Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getAdditionalInfo() {
|
||||
return additionalInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rule getRule() {
|
||||
return rule;
|
||||
@@ -86,7 +71,7 @@ public class ParametricRuleViolation implements RuleViolation {
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return expandVariables(description);
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,26 +79,6 @@ public class ParametricRuleViolation implements RuleViolation {
|
||||
return location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPackageName() {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClassName() {
|
||||
return className;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodName() {
|
||||
return methodName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVariableName() {
|
||||
return variableName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getFilename() + ':' + getRule() + ':' + getDescription() + ':' + getLocation().startPosToString();
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.rule;
|
||||
|
||||
import net.sourceforge.pmd.Report.SuppressedViolation;
|
||||
import net.sourceforge.pmd.Rule;
|
||||
import net.sourceforge.pmd.RuleContext;
|
||||
import net.sourceforge.pmd.RuleViolation;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
|
||||
/**
|
||||
* Creates violations and controls suppression behavior for a language.
|
||||
*
|
||||
* TODO split this into violation decorators + violation suppressors.
|
||||
* There is no need to have language-specific violation classes.
|
||||
*
|
||||
* <p>Since PMD 6.43.0, {@link RuleContext} has been enriched with methods that should
|
||||
* be strongly preferred to using this interface directly. The interface will change a
|
||||
* lot in PMD 7.
|
||||
*/
|
||||
public interface RuleViolationFactory {
|
||||
// todo move to package reporting
|
||||
|
||||
|
||||
default RuleViolation createViolation(Rule rule, Node node, FileLocation location, String formattedMessage) {
|
||||
return new ParametricRuleViolation(rule, location, formattedMessage);
|
||||
}
|
||||
|
||||
|
||||
SuppressedViolation suppressOrNull(Node location, RuleViolation violation);
|
||||
|
||||
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.rule.impl;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import net.sourceforge.pmd.Report.SuppressedViolation;
|
||||
import net.sourceforge.pmd.RuleViolation;
|
||||
import net.sourceforge.pmd.ViolationSuppressor;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.rule.RuleViolationFactory;
|
||||
|
||||
/**
|
||||
* This is a functional implementation of {@link RuleViolationFactory}.
|
||||
* It uses only the standard {@link ViolationSuppressor}s (constants in the interface).
|
||||
* It may be extended to add more suppression options.
|
||||
*
|
||||
* <p>Implementations should be internal. Only the interface should be exposed.
|
||||
*/
|
||||
public class DefaultRuleViolationFactory implements RuleViolationFactory {
|
||||
// todo move to package reporting
|
||||
|
||||
private static final DefaultRuleViolationFactory DEFAULT = new DefaultRuleViolationFactory();
|
||||
|
||||
// volatile for lazy init - see #getAllSuppressors
|
||||
private volatile Set<ViolationSuppressor> allSuppressors; // NOPMD volatile needed for lazy init
|
||||
|
||||
@Override
|
||||
public SuppressedViolation suppressOrNull(Node location, RuleViolation violation) {
|
||||
for (ViolationSuppressor suppressor : getAllSuppressors()) {
|
||||
SuppressedViolation suppressed = suppressor.suppressOrNull(violation, location);
|
||||
if (suppressed != null) {
|
||||
return suppressed;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of additional suppressors for this language. These
|
||||
* are added to regular //NOPMD, regex and XPath suppression.
|
||||
*/
|
||||
protected List<ViolationSuppressor> getSuppressors() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private Set<ViolationSuppressor> getAllSuppressors() {
|
||||
Set<ViolationSuppressor> result = allSuppressors;
|
||||
if (result == null) {
|
||||
// lazy loaded because calling getSuppressors in constructor
|
||||
// is not safe wrt initialization of static constants
|
||||
// (order dependent otherwise)
|
||||
result = new LinkedHashSet<>(getSuppressors());
|
||||
result.add(ViolationSuppressor.NOPMD_COMMENT_SUPPRESSOR);
|
||||
result.add(ViolationSuppressor.REGEX_SUPPRESSOR);
|
||||
result.add(ViolationSuppressor.XPATH_SUPPRESSOR);
|
||||
|
||||
// note 1: allSuppressors must be volatile to avoid other threads seeing the HashSet under construction
|
||||
// note 2: multiple threads might create their own HashSets and the last HashSet is stored and overwrites
|
||||
// previously created HashSets. This is ok, because this method is supposed to be idempotent.
|
||||
allSuppressors = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Returns the default instance (no additional suppressors, creates a ParametricRuleViolation). */
|
||||
public static RuleViolationFactory defaultInstance() {
|
||||
return DEFAULT;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import net.sourceforge.pmd.PMD;
|
||||
import net.sourceforge.pmd.RuleViolation;
|
||||
@@ -28,8 +29,8 @@ import net.sourceforge.pmd.renderers.ColumnDescriptor.Accessor;
|
||||
*/
|
||||
public class CSVRenderer extends AbstractIncrementingRenderer {
|
||||
|
||||
private String separator;
|
||||
private String cr;
|
||||
private final String separator;
|
||||
private final String cr;
|
||||
|
||||
private CSVWriter<RuleViolation> csvWriter;
|
||||
|
||||
@@ -41,47 +42,19 @@ public class CSVRenderer extends AbstractIncrementingRenderer {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private final ColumnDescriptor<RuleViolation>[] allColumns = new ColumnDescriptor[] {
|
||||
new ColumnDescriptor<>("problem", "Problem", new Accessor<RuleViolation>() {
|
||||
@Override
|
||||
public String get(int idx, RuleViolation rv, String cr) {
|
||||
return Integer.toString(idx);
|
||||
}
|
||||
}), new ColumnDescriptor<>("package", "Package", new Accessor<RuleViolation>() {
|
||||
@Override
|
||||
public String get(int idx, RuleViolation rv, String cr) {
|
||||
return rv.getPackageName();
|
||||
}
|
||||
}), new ColumnDescriptor<>("file", "File", new Accessor<RuleViolation>() {
|
||||
@Override
|
||||
public String get(int idx, RuleViolation rv, String cr) {
|
||||
return CSVRenderer.this.determineFileName(rv.getFilename());
|
||||
}
|
||||
}), new ColumnDescriptor<>("priority", "Priority", new Accessor<RuleViolation>() {
|
||||
@Override
|
||||
public String get(int idx, RuleViolation rv, String cr) {
|
||||
return Integer.toString(rv.getRule().getPriority().getPriority());
|
||||
}
|
||||
}), new ColumnDescriptor<>("line", "Line", new Accessor<RuleViolation>() {
|
||||
@Override
|
||||
public String get(int idx, RuleViolation rv, String cr) {
|
||||
return Integer.toString(rv.getBeginLine());
|
||||
}
|
||||
}), new ColumnDescriptor<>("desc", "Description", new Accessor<RuleViolation>() {
|
||||
@Override
|
||||
public String get(int idx, RuleViolation rv, String cr) {
|
||||
return StringUtils.replaceChars(rv.getDescription(), '\"', '\'');
|
||||
}
|
||||
}), new ColumnDescriptor<>("ruleSet", "Rule set", new Accessor<RuleViolation>() {
|
||||
@Override
|
||||
public String get(int idx, RuleViolation rv, String cr) {
|
||||
return rv.getRule().getRuleSetName();
|
||||
}
|
||||
}), new ColumnDescriptor<>("rule", "Rule", new Accessor<RuleViolation>() {
|
||||
@Override
|
||||
public String get(int idx, RuleViolation rv, String cr) {
|
||||
return rv.getRule().getName();
|
||||
}
|
||||
}), };
|
||||
newColDescriptor("problem", "Problem", (idx, rv, cr) -> Integer.toString(idx)),
|
||||
newColDescriptor("package", "Package", (idx, rv, cr) -> rv.getAdditionalInfo().getOrDefault(RuleViolation.PACKAGE_NAME, "")),
|
||||
newColDescriptor("file", "File", (idx, rv, cr) -> determineFileName(rv.getFilename())),
|
||||
newColDescriptor("priority", "Priority", (idx, rv, cr) -> Integer.toString(rv.getRule().getPriority().getPriority())),
|
||||
newColDescriptor("line", "Line", (idx, rv, cr) -> Integer.toString(rv.getBeginLine())),
|
||||
newColDescriptor("desc", "Description", (idx, rv, cr) -> StringUtils.replaceChars(rv.getDescription(), '\"', '\'')),
|
||||
newColDescriptor("ruleSet", "Rule set", (idx, rv, cr) -> rv.getRule().getRuleSetName()),
|
||||
newColDescriptor("rule", "Rule", (idx, rv, cr) -> rv.getRule().getName()),
|
||||
};
|
||||
|
||||
private static @NonNull ColumnDescriptor<RuleViolation> newColDescriptor(String id, String title, Accessor<RuleViolation> accessor) {
|
||||
return new ColumnDescriptor<>(id, title, accessor);
|
||||
}
|
||||
|
||||
public CSVRenderer(ColumnDescriptor<RuleViolation>[] columns, String theSeparator, String theCR) {
|
||||
super(NAME, "Comma-separated values tabular format.");
|
||||
|
||||
@@ -16,13 +16,13 @@ import java.util.List;
|
||||
* @author Brian Remedios
|
||||
* @param <T>
|
||||
*/
|
||||
public class CSVWriter<T extends Object> {
|
||||
class CSVWriter<T> {
|
||||
|
||||
private final String separator; // e.g., the comma
|
||||
private final String lineSeparator; // cr
|
||||
private final List<ColumnDescriptor<T>> columns;
|
||||
|
||||
public CSVWriter(List<ColumnDescriptor<T>> theColumns, String theSeparator, String theLineSeparator) {
|
||||
CSVWriter(List<ColumnDescriptor<T>> theColumns, String theSeparator, String theLineSeparator) {
|
||||
columns = theColumns;
|
||||
separator = theSeparator;
|
||||
lineSeparator = theLineSeparator;
|
||||
@@ -67,7 +67,7 @@ public class CSVWriter<T extends Object> {
|
||||
|
||||
private void quote(StringBuilder buffer, String s) {
|
||||
if (s == null) {
|
||||
return;
|
||||
s = "";
|
||||
}
|
||||
buffer.append('"').append(s).append('"');
|
||||
}
|
||||
|
||||
@@ -4,28 +4,21 @@
|
||||
|
||||
package net.sourceforge.pmd.renderers;
|
||||
|
||||
import net.sourceforge.pmd.annotation.InternalApi;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Brian Remedios
|
||||
*
|
||||
* @param <T>
|
||||
* @deprecated Is internal API
|
||||
*/
|
||||
@InternalApi
|
||||
@Deprecated
|
||||
public class ColumnDescriptor<T extends Object> {
|
||||
final class ColumnDescriptor<T> {
|
||||
|
||||
public final String id;
|
||||
public final String title;
|
||||
public final Accessor<T> accessor;
|
||||
|
||||
public interface Accessor<T extends Object> {
|
||||
public interface Accessor<T> {
|
||||
|
||||
String get(int idx, T violation, String lineSeparator);
|
||||
}
|
||||
|
||||
public ColumnDescriptor(String theId, String theTitle, Accessor<T> theAccessor) {
|
||||
ColumnDescriptor(String theId, String theTitle, Accessor<T> theAccessor) {
|
||||
id = theId;
|
||||
title = theTitle;
|
||||
accessor = theAccessor;
|
||||
|
||||
@@ -259,7 +259,9 @@ public class TextColorRenderer extends AbstractAccumulatingRenderer {
|
||||
}
|
||||
|
||||
private static String keyFor(RuleViolation rv) {
|
||||
return StringUtils.isNotBlank(rv.getPackageName()) ? rv.getPackageName() + '.' + rv.getClassName() : "";
|
||||
String packageName = rv.getAdditionalInfo().getOrDefault(RuleViolation.PACKAGE_NAME, "");
|
||||
String className = rv.getAdditionalInfo().getOrDefault(RuleViolation.CLASS_NAME, "");
|
||||
return StringUtils.isNotBlank(packageName) ? packageName + '.' + className : "";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -166,10 +166,11 @@ public class XMLRenderer extends AbstractIncrementingRenderer {
|
||||
xmlWriter.writeAttribute("endcolumn", String.valueOf(rv.getEndColumn()));
|
||||
xmlWriter.writeAttribute("rule", rv.getRule().getName());
|
||||
xmlWriter.writeAttribute("ruleset", rv.getRule().getRuleSetName());
|
||||
maybeAdd("package", rv.getPackageName());
|
||||
maybeAdd("class", rv.getClassName());
|
||||
maybeAdd("method", rv.getMethodName());
|
||||
maybeAdd("variable", rv.getVariableName());
|
||||
maybeAdd("package", rv.getAdditionalInfo().get(RuleViolation.PACKAGE_NAME));
|
||||
maybeAdd("class", rv.getAdditionalInfo().get(RuleViolation.CLASS_NAME));
|
||||
maybeAdd("method", rv.getAdditionalInfo().get(RuleViolation.METHOD_NAME));
|
||||
maybeAdd("variable", rv.getAdditionalInfo().get(RuleViolation.VARIABLE_NAME));
|
||||
// todo other additional info keys are not rendered
|
||||
maybeAdd("externalInfoUrl", rv.getRule().getExternalInfoUrl());
|
||||
xmlWriter.writeAttribute("priority", String.valueOf(rv.getRule().getPriority().getPriority()));
|
||||
writeNewLine();
|
||||
|
||||
@@ -21,6 +21,7 @@ import net.sourceforge.pmd.Report;
|
||||
import net.sourceforge.pmd.RuleViolation;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.properties.PropertyFactory;
|
||||
import net.sourceforge.pmd.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Renderer to another HTML format.
|
||||
@@ -49,7 +50,8 @@ public class YAHTMLRenderer extends AbstractAccumulatingRenderer {
|
||||
}
|
||||
|
||||
private void addViolation(RuleViolation violation) {
|
||||
String packageName = violation.getPackageName();
|
||||
String packageName = violation.getAdditionalInfo().getOrDefault(RuleViolation.PACKAGE_NAME, "");
|
||||
String className = violation.getAdditionalInfo().getOrDefault(RuleViolation.CLASS_NAME, "");
|
||||
|
||||
// report each part of the package name: e.g. net.sf.pmd.test will create nodes for
|
||||
// net, net.sf, net.sf.pmd, and net.sf.pmd.test
|
||||
@@ -71,10 +73,10 @@ public class YAHTMLRenderer extends AbstractAccumulatingRenderer {
|
||||
}
|
||||
|
||||
// add one node per class collecting the actual violations
|
||||
String fqClassName = packageName + "." + violation.getClassName();
|
||||
String fqClassName = packageName + "." + className;
|
||||
ReportNode classNode = reportNodesByPackage.get(fqClassName);
|
||||
if (classNode == null) {
|
||||
classNode = new ReportNode(packageName, violation.getClassName());
|
||||
classNode = new ReportNode(packageName, className);
|
||||
reportNodesByPackage.put(fqClassName, classNode);
|
||||
}
|
||||
classNode.addRuleViolation(violation);
|
||||
@@ -163,15 +165,17 @@ public class YAHTMLRenderer extends AbstractAccumulatingRenderer {
|
||||
out.println(" <tr><th>Method</th><th>Violation</th></tr>");
|
||||
for (RuleViolation violation : node.getViolations()) {
|
||||
out.print(" <tr><td>");
|
||||
out.print(violation.getMethodName());
|
||||
String methodName = violation.getAdditionalInfo().get(RuleViolation.METHOD_NAME);
|
||||
out.print(StringUtil.nullToEmpty(methodName));
|
||||
out.print("</td><td>");
|
||||
out.print("<table border=\"0\">");
|
||||
|
||||
out.print(renderViolationRow("Rule:", violation.getRule().getName()));
|
||||
out.print(renderViolationRow("Description:", violation.getDescription()));
|
||||
|
||||
if (StringUtils.isNotBlank(violation.getVariableName())) {
|
||||
out.print(renderViolationRow("Variable:", violation.getVariableName()));
|
||||
String variableName = violation.getAdditionalInfo().get(RuleViolation.VARIABLE_NAME);
|
||||
if (StringUtils.isNotBlank(variableName)) {
|
||||
out.print(renderViolationRow("Variable:", variableName));
|
||||
}
|
||||
|
||||
out.print(renderViolationRow("Line:", violation.getEndLine() > 0
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.reporting;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.sourceforge.pmd.RuleViolation;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
|
||||
/**
|
||||
* Adds additional key/value pairs to a violation in a language-specific manner.
|
||||
* The keys are completely free. {@link RuleViolation} defines some of these keys.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ViolationDecorator {
|
||||
|
||||
/**
|
||||
* Compute additional key/value pairs about the violation that should be
|
||||
* reflected in {@link RuleViolation#getAdditionalInfo()}. This additional
|
||||
* info should be accumulated into the {@code additionalInfo} parameter.
|
||||
*
|
||||
* @param violationNode The node on which the violation was reported
|
||||
* @param additionalInfo Accumulator
|
||||
*/
|
||||
void decorate(Node violationNode, Map<String, String> additionalInfo);
|
||||
|
||||
static Map<String, String> apply(ViolationDecorator decorator, Node violationNode) {
|
||||
Map<String, String> additionalInfo = new HashMap<>();
|
||||
decorator.decorate(violationNode, additionalInfo);
|
||||
if (!additionalInfo.isEmpty()) {
|
||||
return Collections.unmodifiableMap(additionalInfo);
|
||||
} else {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply several decorators in a chain.
|
||||
*/
|
||||
static ViolationDecorator chain(List<? extends ViolationDecorator> list) {
|
||||
return (node, map) -> {
|
||||
for (ViolationDecorator decorator : list) {
|
||||
decorator.decorate(node, map);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static ViolationDecorator noop() {
|
||||
return (node, map) -> { };
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* Logic about reporting: violations, suppression etc.
|
||||
*
|
||||
* <p>TODO move {@link net.sourceforge.pmd.Report}, {@link net.sourceforge.pmd.RuleViolation},
|
||||
* {@link net.sourceforge.pmd.RuleContext}, {@link net.sourceforge.pmd.ViolationSuppressor},
|
||||
* {@link net.sourceforge.pmd.lang.rule.RuleViolationFactory} into this package
|
||||
* {@link net.sourceforge.pmd.RuleContext}, {@link net.sourceforge.pmd.ViolationSuppressor}
|
||||
* into this package
|
||||
*/
|
||||
package net.sourceforge.pmd.reporting;
|
||||
@@ -280,6 +280,13 @@ public final class CollectionUtil {
|
||||
return Collections.singletonMap(k0, v0);
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> mapOf(K k1, V v1, K k2, V v2) {
|
||||
Map<K, V> map = new LinkedHashMap<>();
|
||||
map.put(k1, v1);
|
||||
map.put(k2, v2);
|
||||
return Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> buildMap(Consumer<Map<K, V>> effect) {
|
||||
Map<K, V> map = new LinkedHashMap<>();
|
||||
effect.accept(map);
|
||||
|
||||
@@ -48,6 +48,10 @@ public final class StringUtil {
|
||||
return "'" + s + "'";
|
||||
}
|
||||
|
||||
public static @NonNull String inDoubleQuotes(String expected) {
|
||||
return "\"" + expected + "\"";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the (1-based) line number of the character at the given index.
|
||||
@@ -551,8 +555,10 @@ public final class StringUtil {
|
||||
return str.replaceAll("'", "''");
|
||||
}
|
||||
|
||||
public static @NonNull String inDoubleQuotes(String expected) {
|
||||
return "\"" + expected + "\"";
|
||||
|
||||
/** Return the empty string if the parameter is null. */
|
||||
public static String nullToEmpty(final String value) {
|
||||
return value == null ? "" : value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,21 +7,24 @@ package net.sourceforge.pmd;
|
||||
import static net.sourceforge.pmd.properties.constraints.NumericConstraints.inRange;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import net.sourceforge.pmd.Report.SuppressedViolation;
|
||||
import net.sourceforge.pmd.lang.DummyLanguageModule;
|
||||
import net.sourceforge.pmd.lang.ast.DummyNode.DummyRootNode;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.rule.AbstractRule;
|
||||
import net.sourceforge.pmd.lang.rule.ParametricRuleViolation;
|
||||
import net.sourceforge.pmd.lang.rule.impl.DefaultRuleViolationFactory;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.properties.PropertyFactory;
|
||||
import net.sourceforge.pmd.reporting.FileAnalysisListener;
|
||||
|
||||
|
||||
public class AbstractRuleTest {
|
||||
@@ -105,17 +108,20 @@ public class AbstractRuleTest {
|
||||
DummyRootNode s = DummyLanguageModule.parse("abc()", "filename");
|
||||
|
||||
RuleViolation rv = RuleContextTest.getReportForRuleApply(r, s).getViolations().get(0);
|
||||
assertEquals("Message foo 10 ${noSuchProperty}", rv.getDescription());
|
||||
assertEquals("Message foo ${className} ${methodName} ${variableName} 10 ${noSuchProperty}", rv.getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRuleSuppress() {
|
||||
DummyRootNode n = DummyLanguageModule.parse("abc()", "filename")
|
||||
.withNoPmdComments(Collections.singletonMap(1, "ohio"));
|
||||
RuleViolation violation = DefaultRuleViolationFactory.defaultInstance().createViolation(new MyRule(), n, n.getReportLocation(), "specificdescription");
|
||||
SuppressedViolation suppressed = DefaultRuleViolationFactory.defaultInstance().suppressOrNull(n, violation);
|
||||
|
||||
assertNotNull(suppressed);
|
||||
FileAnalysisListener listener = mock(FileAnalysisListener.class);
|
||||
RuleContext ctx = RuleContext.create(listener, new MyRule());
|
||||
ctx.addViolationWithMessage(n, "message");
|
||||
|
||||
verify(listener, never()).onRuleViolation(any());
|
||||
verify(listener, times(1)).onSuppressedRuleViolation(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -103,11 +103,11 @@ public class ReportTest {
|
||||
assertEquals(2, union.getViolations().size());
|
||||
}
|
||||
|
||||
private @NonNull RuleViolation violation(Rule rule, FileLocation loc2) {
|
||||
public static @NonNull RuleViolation violation(Rule rule, FileLocation loc2) {
|
||||
return violation(rule, loc2, rule.getMessage());
|
||||
}
|
||||
|
||||
private @NonNull RuleViolation violation(Rule rule, FileLocation loc1, String rule1) {
|
||||
public static @NonNull RuleViolation violation(Rule rule, FileLocation loc1, String rule1) {
|
||||
return new ParametricRuleViolation(rule, loc1, rule1);
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,6 @@ class RuleViolationComparatorTest {
|
||||
private RuleViolation createJavaRuleViolation(Rule rule, String fileName, int beginLine, String description,
|
||||
int beginColumn, int endLine, int endColumn) {
|
||||
FileLocation loc = FileLocation.range(fileName, TextRange2d.range2d(beginLine, beginColumn, endLine, endColumn));
|
||||
return new ParametricRuleViolation(rule, loc, description);
|
||||
return new ParametricRuleViolation(rule, loc, description, Collections.emptyMap());
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
package net.sourceforge.pmd;
|
||||
|
||||
import static net.sourceforge.pmd.ReportTest.violation;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@@ -76,8 +77,8 @@ class RuleViolationTest {
|
||||
|
||||
|
||||
FileLocation loc = FileLocation.range(filename, TextRange2d.range2d(10, 1, 15, 10));
|
||||
RuleViolation r1 = new ParametricRuleViolation(rule, loc, "description");
|
||||
RuleViolation r2 = new ParametricRuleViolation(rule, loc, "description");
|
||||
RuleViolation r1 = violation(rule, loc, "description");
|
||||
RuleViolation r2 = violation(rule, loc, "description");
|
||||
|
||||
assertEquals(0, comp.compare(r1, r2));
|
||||
assertEquals(0, comp.compare(r2, r1));
|
||||
|
||||
@@ -6,25 +6,19 @@ package net.sourceforge.pmd.lang;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import net.sourceforge.pmd.Rule;
|
||||
import net.sourceforge.pmd.RuleViolation;
|
||||
import net.sourceforge.pmd.lang.ast.DummyNode;
|
||||
import net.sourceforge.pmd.lang.ast.DummyNode.DummyRootNode;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.ast.ParseException;
|
||||
import net.sourceforge.pmd.lang.ast.Parser;
|
||||
import net.sourceforge.pmd.lang.ast.Parser.ParserTask;
|
||||
import net.sourceforge.pmd.lang.ast.SemanticErrorReporter;
|
||||
import net.sourceforge.pmd.lang.document.Chars;
|
||||
import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
import net.sourceforge.pmd.lang.document.TextDocument;
|
||||
import net.sourceforge.pmd.lang.document.TextFile;
|
||||
import net.sourceforge.pmd.lang.document.TextRegion;
|
||||
import net.sourceforge.pmd.lang.rule.ParametricRuleViolation;
|
||||
import net.sourceforge.pmd.lang.rule.impl.DefaultRuleViolationFactory;
|
||||
import net.sourceforge.pmd.processor.PmdRunnableTest;
|
||||
import net.sourceforge.pmd.reporting.ViolationDecorator;
|
||||
|
||||
/**
|
||||
* Dummy language used for testing PMD.
|
||||
@@ -68,13 +62,13 @@ public class DummyLanguageModule extends BaseLanguageModule {
|
||||
public static class Handler extends AbstractPmdLanguageVersionHandler {
|
||||
|
||||
@Override
|
||||
public RuleViolationFactory getRuleViolationFactory() {
|
||||
return new RuleViolationFactory();
|
||||
public Parser getParser() {
|
||||
return DummyLanguageModule::readLispNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parser getParser() {
|
||||
return DummyLanguageModule::readLispNode;
|
||||
public ViolationDecorator getViolationDecorator() {
|
||||
return (node, data) -> data.put(RuleViolation.PACKAGE_NAME, "foo");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,15 +132,4 @@ public class DummyLanguageModule extends BaseLanguageModule {
|
||||
return root;
|
||||
}
|
||||
|
||||
public static class RuleViolationFactory extends DefaultRuleViolationFactory {
|
||||
|
||||
@Override
|
||||
public RuleViolation createViolation(Rule rule, @NonNull Node node, FileLocation location, @NonNull String formattedMessage) {
|
||||
return new ParametricRuleViolation(rule, location, formattedMessage) {
|
||||
{
|
||||
this.packageName = "foo"; // just for testing variable expansion
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -99,7 +100,7 @@ abstract class AbstractRendererTest {
|
||||
|
||||
protected RuleViolation newRuleViolation(int beginLine, int beginColumn, int endLine, int endColumn, Rule rule) {
|
||||
FileLocation loc = createLocation(beginLine, beginColumn, endLine, endColumn);
|
||||
return new ParametricRuleViolation(rule, loc, "blah");
|
||||
return new ParametricRuleViolation(rule, loc, "blah", Collections.emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,11 +9,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -28,6 +28,7 @@ import net.sourceforge.pmd.Rule;
|
||||
import net.sourceforge.pmd.RuleViolation;
|
||||
import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
import net.sourceforge.pmd.lang.rule.ParametricRuleViolation;
|
||||
import net.sourceforge.pmd.util.CollectionUtil;
|
||||
import net.sourceforge.pmd.util.IOUtil;
|
||||
|
||||
class YAHTMLRendererTest extends AbstractRendererTest {
|
||||
@@ -38,19 +39,16 @@ class YAHTMLRendererTest extends AbstractRendererTest {
|
||||
private Path folder;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
void setUp() {
|
||||
outputDir = folder.resolve("pmdtest").toFile();
|
||||
assertTrue(outputDir.mkdir());
|
||||
}
|
||||
|
||||
private RuleViolation newRuleViolation(int beginLine, int beginColumn, int endLine, int endColumn, final String packageNameArg, final String classNameArg) {
|
||||
FileLocation loc = createLocation(beginLine, beginColumn, endLine, endColumn);
|
||||
return new ParametricRuleViolation(new FooRule(), loc, "blah") {
|
||||
{
|
||||
packageName = packageNameArg;
|
||||
className = classNameArg;
|
||||
}
|
||||
};
|
||||
Map<String, String> additionalInfo = CollectionUtil.mapOf(RuleViolation.PACKAGE_NAME, packageNameArg,
|
||||
RuleViolation.CLASS_NAME, classNameArg);
|
||||
return new ParametricRuleViolation(new FooRule(), loc, "blah", additionalInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+27
-2
@@ -2,15 +2,23 @@
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.rule.internal;
|
||||
package net.sourceforge.pmd.lang.java.internal;
|
||||
|
||||
import static net.sourceforge.pmd.util.CollectionUtil.listOf;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import net.sourceforge.pmd.Report;
|
||||
import net.sourceforge.pmd.Report.SuppressedViolation;
|
||||
import net.sourceforge.pmd.Rule;
|
||||
import net.sourceforge.pmd.RuleViolation;
|
||||
import net.sourceforge.pmd.ViolationSuppressor;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
|
||||
@@ -48,10 +56,27 @@ final class AnnotationSuppressionUtil {
|
||||
|
||||
private static final Set<String> UNUSED_RULES
|
||||
= new HashSet<>(Arrays.asList("UnusedPrivateField", "UnusedLocalVariable", "UnusedPrivateMethod",
|
||||
"UnusedFormalParameter", "UnusedAssignment", "SingularField"));
|
||||
"UnusedFormalParameter", "UnusedAssignment", "SingularField"));
|
||||
private static final Set<String> SERIAL_RULES =
|
||||
new HashSet<>(Arrays.asList("BeanMembersShouldSerialize", "NonSerializableClass", "MissingSerialVersionUID"));
|
||||
|
||||
static final ViolationSuppressor JAVA_ANNOT_SUPPRESSOR = new ViolationSuppressor() {
|
||||
@Override
|
||||
public String getId() {
|
||||
return "@SuppressWarnings";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Report.SuppressedViolation suppressOrNull(RuleViolation rv, @NonNull Node node) {
|
||||
if (contextSuppresses(node, rv.getRule())) {
|
||||
return new SuppressedViolation(rv, this, null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
static final List<ViolationSuppressor> ALL_JAVA_SUPPRESSORS = listOf(JAVA_ANNOT_SUPPRESSOR);
|
||||
|
||||
private AnnotationSuppressionUtil() {
|
||||
|
||||
}
|
||||
Loaded 30 of 39 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user