Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/pmd/pmd into npath
Browse files Browse the repository at this point in the history
  • Loading branch information
oowekyala committed Aug 13, 2017
2 parents b86f0ae + 52d78d2 commit b24edf9
Show file tree
Hide file tree
Showing 134 changed files with 14,817 additions and 315 deletions.
20 changes: 20 additions & 0 deletions .travis/build-deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@ set -e

source .travis/common-functions.sh

function push_docs() {
if git diff --quiet docs; then
echo "No changes in docs..."
else
echo "Found changes in docs..."

if [ "$TRAVIS_BRANCH" == "master" ]; then
git config user.name "Travis CI (pmd-bot)"
git config user.email "adangel+pmd-bot@users.sourceforge.net"
git add -A docs
git commit -m "Update documentation"
git push git@github.com:pmd/pmd.git HEAD:master
else
echo "Not on master branch, won't commit+push"
fi
fi
}


VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec | tail -1)
echo "Building PMD ${VERSION} on branch ${TRAVIS_BRANCH}"

Expand All @@ -19,6 +38,7 @@ elif travis_isPush; then
elif [[ "$VERSION" == *-SNAPSHOT ]]; then
echo "This is a snapshot build"
./mvnw deploy -Possrh -B -V
push_docs
else
# other build. Can happen during release: the commit with a non snapshot version is built, but not from the tag.
echo "This is some other build, probably during release: commit with a non-snapshot version on branch master..."
Expand Down
14 changes: 10 additions & 4 deletions docs/_data/sidebars/pmd_sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

entries:
- title: sidebar
product: PMD Project
product: PMD
version: "!PMD_VERSION!"
folders:

Expand Down Expand Up @@ -79,27 +79,33 @@ entries:
url: /pmd_rules_java.html
output: web, pdf
- title: JavaScript Rules
url: /pmd_rules_javascript.html
url: /pmd_rules_ecmascript.html
output: web, pdf
- title: JSP Rules
url: /pmd_rules_jsp.html
output: web, pdf
- title: PLSQL Rules
url: /pmd_rules_plsql.html
output: web, pdf
- title: VisualForce Rules
url: /pmd_rules_vf.html
output: web, pdf
- title: Apache Velocity Rules
url: /pmd_rules_vm.html
output: web, pdf
- title: XML and XSL Rules
- title: XML Rules
url: /pmd_rules_xml.html
output: web, pdf
- title: XSL Rules
url: /pmd_rules_xsl.html
output: web, pdf
- title: Language Specific Documentation
output: web, pdf
folderitems:
- title: JSP Support
url: /pmd_languages_jsp.html
output: web, pdf
- title: Index of Java code metrics
- title: Java code metrics
url: /pmd_java_metrics_index.html
output: web, pdf
- title: Developer Documentation
Expand Down
2 changes: 1 addition & 1 deletion docs/_includes/sidebar.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
{% if folderitem.output contains "web" %}
{% if folderitem.external_url %}
<li><a href="{{folderitem.external_url}}" target="_blank">{{folderitem.title}}</a></li>
{% elsif page.url == folderitem.url %}
{% elsif page.url == folderitem.url or page.sidebaractiveurl == folderitem.url %}
<li class="active"><a href="{{folderitem.url | remove: "/"}}">{{folderitem.title}}</a></li>
{% else %}
<li><a href="{{folderitem.url | remove: "/"}}">{{folderitem.title}}</a></li>
Expand Down
7 changes: 6 additions & 1 deletion docs/_layouts/page.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ <h1 class="post-title-main">{{ page.title }}</h1>

{% if site.github_editme_path %}

<a target="_blank" href="https://github.com/{{site.github_editme_path}}{{page.path}}" class="btn btn-default githubEditButton" role="button"><i class="fa fa-github fa-lg"></i> Edit me</a>
{% assign editmepath = page.path %}
{% if page.editmepath %}
{% assign editmepath = page.editmepath %}
{% endif %}

<a target="_blank" href="https://github.com/{{site.github_editme_path}}{{editmepath}}" class="btn btn-default githubEditButton" role="button"><i class="fa fa-github fa-lg"></i> Edit me</a>

{% endif %}

Expand Down
77 changes: 73 additions & 4 deletions docs/pages/pmd/devdocs/metrics_howto.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: Using code metrics in custom rules
tags: [customizing]
summary: "PMD was recently enhanced with the ability to compute code metrics on Java code (the so-called Metrics
Framework). This framework provides developers with a straightforward interface to use code metrics in their rules, and
to extend the framework with their own custom metrics."
summary: "PMD was recently enhanced with the ability to compute code metrics on Java and Apex source (the so-called
Metrics Framework). This framework provides developers with a straightforward interface to use code metrics in their
rules, and to extend the framework with their own custom metrics."
last_updated: July 20, 2017
sidebar: pmd_sidebar
permalink: pmd_devdocs_metrics_howto.html
Expand All @@ -13,4 +13,73 @@ folder: pmd/devdocs

## Using the metrics framework

## Writing custom metrics
{%include note.html content="Using the metrics framework is for now restricted to Java rules (with plans to support
XPath rules later)." %}

To use the metrics framework in a custom rule, the first thing to do would be to enable metrics by adding the
`metrics="true"` attribute to your rule's XML element.

{%include note.html content="The following explains how to use the Java metrics framework. The Apex framework
differs only by the name of its classes." %}

In PMD's Metrics framework, a metric is an operation that can be carried out on nodes of a certain type and produces
a numeric result. In the Java framework, metrics can be computed on operation declaration nodes (constructor and
method declaration), and type declaration nodes (class, interface, enum, and annotation declarations). A metric
object in the framework can only handle either types or operations, but not both.

The framework provides a library of already implemented metrics. These metrics are referenced by `MetricKey` objects,
which are listed in two public enums: `JavaClassMetricKey` and `JavaOperationMetricKey`. Metric keys wrap a metric, and
know which type of node their metric can be computed on. That way, you cannot compute an operation metric on a class
declaration node. Metrics that can be computed on operation and type declarations (e.g. NCSS) have one metric key in
each enum.

The static façade class `JavaMetrics` is the only entry point to compute metrics in the Java framework.
This class provides the method `get` and its overloads. The following sections describes the interface of this class.

### Basic usage

The simplest overloads of `JavaMetrics.get` take two parameters: **a `MetricKey` and a node of the corresponding type.**
Say you want to write a rule to report methods that have a high cyclomatic complexity. In your rule's visitor, you
can get the value of Cyclo for a method node like so:
```java
public Object visit(ASTMethodDeclaration node, Object data) {
int cyclo = (int) JavaMetrics.get(JavaOperationMetricKey.CYCLO, node);
if (cyclo > 10) {
// add violation
}
return data;
}
```

The same goes for class metrics: you select one among `JavaClassMetricKey`'s constants and pass it along with the node
to `JavaMetrics.get`.

### Capability checking

Metrics are not necessarily computable on any node of the type they handle. For example, Cyclo cannot be computed on
abstract methods. Metric keys provides a `supports(Node)` boolean method to find out if the metric can be computed on
the specified node. **If the metric cannot be computed on the given node, `JavaMetrics.get` will return `Double.NaN` .**
If you're concerned about that, you can condition your call on whether the node is supported or not:
```java
public Object visit(ASTMethodDeclaration node, Object data) {
if (JavaOperationMetricKey.CYCLO.supports(node)) {
int cyclo = (int) JavaMetrics.get(JavaOperationMetricKey.CYCLO, node);
if (cyclo > 10) {
// add violation
}
return data;
}
}
```

### Metric versions

{%include important.html content="Metric versions are about to be revamped into options that can be combined
together." %}


### Result options

## Writing custom metrics

{%include warning.html content="WIP" %}
41 changes: 26 additions & 15 deletions docs/pages/pmd/languages/java_metrics_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,31 @@ class Foo { // +1, total Ncss = 12
Number of acyclic execution paths through a piece of code. This is related to cyclomatic complexity, but the two
metrics don't count the same thing: NPath counts the number of distinct *full* paths from the beginning to the end of
the method, while Cyclo only counts the number of decision points. NPath is not computed as simply as Cyclo. With
NPath, two decision points appearing sequentially have their complexity multiplied. For example:
NPath, two decision points appearing sequentially have their complexity multiplied.

The fact that NPath multiplies the complexity of statements makes it grow exponentially: 10 `if` - `else` statements in
a row would give an NPath of 1024, while Cyclo would evaluate to 20. Methods with an NPath complexity over 200 are
generally considered too complex.

We compute NPath recursively, with the following set of rules:
* An empty block has a complexity of 1.
* The complexity of a block is the product of the NPath complexity of its statements, calculated as follows:
* The complexity of `for`, `do` and `while` statements is 1, plus the complexity of the block, plus the complexity of
the guard condition.
* The complexity of a cascading `if` statement (`if .. else if ..`) is the number of `if` statements in the chain,
plus the complexity of their guard condition, plus the complexity of the unguarded `else` block (or 1 if there is
none).
* The complexity of a `switch` statement is the number of cases, plus the complexity of each `case` block. It's
equivalent to the complexity of the equivalent cascade of `if` statements.
* The complexity of a ternary expression (`?:`) is the complexity of the guard condition, plus the
complexity of both expressions. It's equivalent to the complexity of the equivalent `if .. else` construct.
* The complexity of a `try .. catch` statement is the complexity of the `try` block, plus the complexity of each
catch block.
* The complexity of a `return` statement is the complexity of the expression (or 1 if there is none).
* All other statements have a complexity of 1 and are discarded from the product.

### Code example

```java
void fun(boolean a, boolean b, boolean c) { // NPath = 6

Expand All @@ -176,22 +200,9 @@ void fun(boolean a, boolean b, boolean c) { // NPath = 6
}
```
After block 0, the control flow can either execute block 1 or 2 before jumping to block 3. From block three, the
control flow will again have the choice between blocks 4 and 5 before jumping to block 6. The first `if` offers two
control flow will again have the choice between blocks 4 and 5 before jumping to block 6. The first `if` offers 2
choices, the second offers 3, so the cyclomatic complexity of this method is 2 + 3 = 5. NPath, however, sees 2 * 3 =
6 full paths from the beginning to the end.

The fact that NPath multiplies the complexity of statements makes it grow exponentially: 10 `if` - `else` statements in
a row would give an NPath of 1024, while Cyclo would evaluate to 20.

Methods with an NPath complexity over 200 are generally considered too complex.

We compute NPath recursively, with the following set of rules:
* A block has a base complexity of 1
* The complexity of `for`, `do` and `while` statements is 1 + the complexity of its block + the complexity of its
guard condition
* The complexity of an `if` statement is 1 + the complexity of its guard condition +



## Weighted Method Count (WMC)

Expand Down
Loading

0 comments on commit b24edf9

Please sign in to comment.