PMD language module for static analysis of Apache Velocity Template Language (VTL) files
—
Production-ready rules for detecting common issues in Velocity templates including best practices violations, design problems, and error-prone patterns. These rules are organized by category and ready for use in PMD rulesets.
Rules that enforce Velocity template best practices and coding standards.
Detects reassignment of macro parameters, which can lead to confusion and unexpected behavior.
/**
* Rule that detects reassignment of macro parameters.
* Parameter reassignment can make templates harder to understand and debug.
* Location: net.sourceforge.pmd.lang.vm.rule.bestpractices.AvoidReassigningParametersRule
*/
public class AvoidReassigningParametersRule extends AbstractVmRule {
// Implementation analyzes #set directives and macro parameter usage
// Reports violations when macro parameters are reassigned
}What it detects:
#set directiveExample violations:
#macro(processUser $userName $userAge)
#set($userName = "Modified") ## Violation: reassigning parameter
#set($userAge = $userAge + 1) ## Violation: reassigning parameter
<p>User: $userName, Age: $userAge</p>
#endRecommended fix:
#macro(processUser $userName $userAge)
#set($displayName = "Modified") ## Use different variable name
#set($calculatedAge = $userAge + 1) ## Use different variable name
<p>User: $displayName, Age: $calculatedAge</p>
#endIdentifies macro parameters that are declared but never used, indicating potential dead code or missing implementation.
/**
* Rule that detects unused macro parameters.
* Unused parameters suggest incomplete implementation or unnecessary complexity.
* Location: net.sourceforge.pmd.lang.vm.rule.bestpractices.UnusedMacroParameterRule
*/
public class UnusedMacroParameterRule extends AbstractVmRule {
// Implementation tracks macro parameter declarations and usage
// Reports violations for parameters that are never referenced
}What it detects:
Example violations:
#macro(displayUser $userName $userAge $userEmail)
<p>Welcome $userName!</p>
<p>Age: $userAge</p>
## $userEmail is never used - violation
#endRecommended fix:
#macro(displayUser $userName $userAge) ## Remove unused parameter
<p>Welcome $userName!</p>
<p>Age: $userAge</p>
#endRules that identify design issues and structural problems in Velocity templates.
Detects excessive nesting of if statements, which reduces readability and maintainability.
/**
* Statistical rule that detects deeply nested if statements.
* Deep nesting makes templates difficult to read and maintain.
* Location: net.sourceforge.pmd.lang.vm.rule.design.AvoidDeeplyNestedIfStmtsRule
*/
public class AvoidDeeplyNestedIfStmtsRule extends AbstractStatisticalVmRule {
// Implementation tracks nesting depth of if statements
// Default threshold typically set to 3-4 levels
}What it detects:
Example violations:
#if($user)
#if($user.isActive)
#if($user.hasPermission)
#if($user.inGroup) ## Violation: 4th level nesting
<p>User has access</p>
#end
#end
#end
#endRecommended fix:
#if($user && $user.isActive && $user.hasPermission && $user.inGroup)
<p>User has access</p>
#end
## Or use a macro to encapsulate complex logic
#macro(hasFullAccess $user)
#if($user && $user.isActive && $user.hasPermission && $user.inGroup)
true
#else
false
#end
#endIdentifies adjacent if statements that can be combined into a single conditional.
/**
* Rule that detects if statements that can be collapsed into a single condition.
* Collapsible if statements indicate opportunities for simplification.
* Location: net.sourceforge.pmd.lang.vm.rule.design.CollapsibleIfStatementsRule
*/
public class CollapsibleIfStatementsRule extends AbstractVmRule {
// Implementation analyzes nested if statements with same conditional logic
// Reports violations when inner if can be combined with outer if
}What it detects:
Example violations:
#if($user)
#if($user.isActive) ## Violation: can be collapsed
<p>Active user: $user.name</p>
#end
#endRecommended fix:
#if($user && $user.isActive)
<p>Active user: $user.name</p>
#endMeasures template length and flags templates that exceed reasonable size limits.
/**
* Statistical rule that detects excessively long templates.
* Long templates are difficult to maintain and may indicate design issues.
* Location: net.sourceforge.pmd.lang.vm.rule.design.ExcessiveTemplateLengthRule
*/
public class ExcessiveTemplateLengthRule extends AbstractStatisticalVmRule {
// Implementation counts lines or AST nodes in template
// Default threshold typically set to 500-1000 lines
}What it detects:
Configuration:
maxLines: Maximum allowed lines (default: 500)countBlankLines: Whether to include blank lines in countRecommended fixes:
Detects inline JavaScript code within Velocity templates, promoting separation of concerns.
/**
* Rule that detects inline JavaScript in Velocity templates.
* Inline JavaScript violates separation of concerns and complicates maintenance.
* Location: net.sourceforge.pmd.lang.vm.rule.design.NoInlineJavaScriptRule
*/
public class NoInlineJavaScriptRule extends AbstractVmRule {
// Implementation analyzes text content for JavaScript patterns
// Reports violations when <script> tags or JavaScript code detected
}What it detects:
<script> tags with JavaScript contentonclick, onload, etc.)Example violations:
<div onclick="alert('Hello')">Click me</div> ## Violation: inline JS
<script>
function doSomething() { ## Violation: inline script block
alert('Action performed');
}
</script>Recommended fix:
<div class="clickable-element">Click me</div>
## Move JavaScript to separate .js files
## Use CSS classes and external event handlersDetects inline CSS styles within Velocity templates, promoting separation of concerns and maintainable styling.
/**
* XPath-based rule that detects inline CSS styles in Velocity templates.
* Inline styles violate separation of concerns and complicate maintenance.
* Location: Implemented as XPath rule in category/vm/design.xml
*/
// XPath Rule: //Text[matches(@literal, "<[^>]+\s[sS][tT][yY][lL][eE]\s*=")]
// Rule Class: net.sourceforge.pmd.lang.rule.XPathRuleWhat it detects:
style attributes on HTML elementsExample violations:
<div style="color: red; font-size: 14px;">Styled text</div> ## Violation: inline style
<p style="margin: 10px;">Content</p> ## Violation: inline style
<span style="background-color: #fff;">Text</span> ## Violation: inline styleRecommended fix:
<div class="error-text">Styled text</div>
<p class="content-paragraph">Content</p>
<span class="highlighted-text">Text</span>
## Move styles to separate .css files
## Use CSS classes and external stylesheetsRules that detect patterns likely to cause runtime errors or unexpected behavior.
Detects foreach loops with empty bodies, which may indicate incomplete implementation.
/**
* Rule that detects empty foreach statements.
* Empty foreach loops suggest incomplete implementation or missing content.
* Location: net.sourceforge.pmd.lang.vm.rule.errorprone.EmptyForeachStmtRule
*/
public class EmptyForeachStmtRule extends AbstractVmRule {
// Implementation analyzes foreach statement bodies
// Reports violations when body contains no meaningful content
}What it detects:
Example violations:
#foreach($item in $items)
## Empty loop body - violation
#end
#foreach($user in $users)
<!-- TODO: implement user display -->
## Only comments - violation
#endRecommended fix:
#foreach($item in $items)
<p>Item: $item</p>
#end
## Or remove the loop if not neededIdentifies if statements with empty bodies, indicating incomplete conditional logic.
/**
* Rule that detects empty if statements.
* Empty if statements suggest incomplete implementation or unnecessary conditions.
* Location: net.sourceforge.pmd.lang.vm.rule.errorprone.EmptyIfStmtRule
*/
public class EmptyIfStmtRule extends AbstractVmRule {
// Implementation analyzes if statement bodies
// Reports violations when body contains no meaningful content
}What it detects:
Example violations:
#if($user.isAdmin)
## Empty if body - violation
#end
#if($showSpecialContent)
<!-- Content will be added later -->
## Only comments - violation
#endRecommended fix:
#if($user.isAdmin)
<p>Admin panel access granted</p>
#end
## Or remove the condition if not needed<?xml version="1.0"?>
<ruleset name="velocity-rules"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0
http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
<description>Custom Velocity template rules</description>
<!-- Best Practices Rules -->
<rule ref="category/vm/bestpractices.xml/AvoidReassigningParameters"/>
<rule ref="category/vm/bestpractices.xml/UnusedMacroParameter"/>
<!-- Design Rules with Custom Configuration -->
<rule ref="category/vm/design.xml/ExcessiveTemplateLength">
<properties>
<property name="maxLines" value="300"/>
</properties>
</rule>
<rule ref="category/vm/design.xml/AvoidDeeplyNestedIfStmts">
<properties>
<property name="maxNesting" value="3"/>
</properties>
</rule>
<rule ref="category/vm/design.xml/CollapsibleIfStatements"/>
<rule ref="category/vm/design.xml/NoInlineJavaScript"/>
<!-- Error-Prone Rules -->
<rule ref="category/vm/errorprone.xml/EmptyIfStmt"/>
<rule ref="category/vm/errorprone.xml/EmptyForeachStmt"/>
</ruleset>import net.sourceforge.pmd.PMD;
import net.sourceforge.pmd.PMDConfiguration;
import net.sourceforge.pmd.RuleSet;
import net.sourceforge.pmd.RuleSetFactory;
// Configure PMD for Velocity analysis
PMDConfiguration config = new PMDConfiguration();
config.setInputPaths("src/main/resources/templates");
config.setReportFormat("text");
// Load built-in Velocity rules
RuleSetFactory factory = new RuleSetFactory();
RuleSet ruleSet = factory.createRuleSet("category/vm/bestpractices.xml");
// Run analysis
PMD.runPMD(config);import net.sourceforge.pmd.lang.vm.rule.AbstractVmRule;
// Custom rule extending built-in functionality
public class EnhancedUnusedParameterRule extends UnusedMacroParameterRule {
@Override
public Object visit(ASTReference node, Object data) {
// Call parent implementation
Object result = super.visit(node, data);
// Add custom analysis
analyzeReferencePattern(node, data);
return result;
}
private void analyzeReferencePattern(ASTReference node, Object data) {
// Custom pattern analysis
String refName = node.getRootString();
if (refName != null && refName.matches("^unused.*")) {
addViolation(data, node, "Reference suggests unused parameter: " + refName);
}
}
}## Suppress specific rule for next directive
## NOPMD: AvoidReassigningParameters
#set($userName = "Modified")
## Suppress multiple rules
## NOPMD: EmptyIfStmt, UnusedMacroParameter
#if($condition)
#end<!-- PMD suppression file -->
<suppressions>
<suppress files="legacy-template.vm"
checks="ExcessiveTemplateLength"/>
<suppress files="generated-.*\.vm"
checks=".*"/>
</suppressions>Install with Tessl CLI
npx tessl i tessl/maven-net-sourceforge-pmd--pmd-vm