Merge branch 'pr-789'
This commit is contained in:
@ -5,12 +5,11 @@ tags: [customizing]
|
||||
summary: "PMD's Java module has an extensive framework for the calculation of metrics, which allows rule developers
|
||||
to implement and use new code metrics very simply. Most of the functionality of this framework is abstracted in such
|
||||
a way that any PMD supported language can implement such a framework without too much trouble. Here's how."
|
||||
last_updated: August 2017
|
||||
last_updated: December 2017
|
||||
permalink: pmd_devdocs_adding_metrics_support_to_language.html
|
||||
author: Clément Fournier <clement.fournier76@gmail.com>
|
||||
---
|
||||
|
||||
{% include warning.html content="WIP, unstable API" %}
|
||||
|
||||
## Internal architecture of the metrics framework
|
||||
|
||||
@ -33,43 +32,24 @@ Metrics (`Metric<N>`) plug in to this static system and only provide behaviour t
|
||||
Internally, metric keys (`MetricKey<N>`) are parameterized with their version (`MetricVersion`) to index memoisation
|
||||
maps (see `ParameterizedMetricKey<N>`). This allows us to memoise several versions of the same metric without conflict.
|
||||
|
||||
{% include important.html content="The following will be moved when multifile analysis and metrics are separated" %}
|
||||
<!-- We should probably create a dedicated page about the architecture of multifile analysis/ signature matching and how
|
||||
to implement that -->
|
||||
|
||||
At the very least, a metrics framework has those two components and is just a convenient way to compute and memoize
|
||||
metrics on a single file. Yet, one of the goals of the metrics framework is to allow for **multi-file analysis**, which
|
||||
make it possible, for instance, to compute the coupling between two classes. This feature uses two major
|
||||
components:
|
||||
* A **project mirror**. This data structure that stores info about all classes and operations (and other relevant
|
||||
entities, such as fields, packages, etc.) of the analysed project. This is implemented by `PackageStats` in the Java
|
||||
framework. The role of this structure is to make info about other files available to rules. It's filled by a visitor before rules apply.
|
||||
metrics on a single file. The expressive power of metrics can be improved by implementing *signature matching* capabilities,
|
||||
which allows a metric to count signatures matching a specific pattern (a mask) over a whole class. This was originally
|
||||
designed to work across files, given a working usage resolution. However, making that work with incremental analysis is
|
||||
harder than it looks, and has been rescheduled to another project.
|
||||
|
||||
The information stored in this data structure that's accessible to metrics is mainly comprised of method and field
|
||||
signatures (e.g. `JavaOperationSignature`), which describes concisely the characteristics of the method or field
|
||||
(roughly, its modifiers).
|
||||
|
||||
* Some kind of method and field **usage resolution**, i.e. some way to find the fully qualified name of a method from a
|
||||
method call expression node. This is the trickiest part to implement. In Java it depends on type resolution.
|
||||
|
||||
### Abstraction layer
|
||||
|
||||
As you may have seen, most of the functionality of the first two components are abstracted into `pmd-core`. This
|
||||
allows us to implement new metrics frameworks quite quickly. These abstract components are parameterized by the
|
||||
node types of the class and operation AST nodes. Moreover, it makes the external behaviour of the framework is very
|
||||
node types of the class and operation AST nodes. Moreover, it makes the external behaviour of the framework very
|
||||
stable across languages, yet each component can easily be customized by adding methods or overriding existing ones.
|
||||
|
||||
The signature matching aspect is framed by generic interfaces, but it can't really be abstracted more
|
||||
than that. For instance, the project mirror is very language specific. Java's implementation uses the natural structure
|
||||
provided by the language's package system to structure the project's content. Apex, on the other, has no package
|
||||
system and thus can't use the same mechanism. That explains why the interfaces framing the project mirror are very
|
||||
loose. Their main goal is to provide type safety through generics.
|
||||
|
||||
Moreover, usage resolution depends on the availability of type resolution for the given language, which is only implemented in
|
||||
Java. For these reasons, signature matching is considered an optional feature of the metrics framework. But despite
|
||||
this limitation, signature matching still provides a elegant way to find information about the class we're in. This
|
||||
feature requires no usage resolution and can be used to implement sophisticated metrics, that already give access to
|
||||
detection strategies.
|
||||
than that. The info given in the signatures is usually very language specific, as it includes info about e.g.
|
||||
visibility modifiers. So more work is required to implement that, but it can already be used to implement
|
||||
sophisticated metrics, that already give access to detection strategies.
|
||||
|
||||
## Implementation of a new framework
|
||||
|
||||
@ -78,48 +58,34 @@ detection strategies.
|
||||
* Create a class implementing `QualifiedName`. This implementation must be tailored to the target language so
|
||||
that it can indentify unambiguously any class and operation in the analysed project. You
|
||||
must implement `equals`, `hashCode` and `toString`.
|
||||
[Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java)
|
||||
[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java)
|
||||
* Determine the AST nodes that correspond to class and method declaration in your language. These types are
|
||||
referred hereafter as `T` and `O`, respectively. Both these types must implement the interface `QualifiableNode`,
|
||||
which means they must expose a `getQualifiedName` method to give access to their qualified name.
|
||||
|
||||
### 2. Implement and wire the project memoizer
|
||||
* Create a class extending `BasicProjectMemoizer<T, O>`. There's no abstract functionality to implement.
|
||||
[Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMemoizer.java)
|
||||
* Create an AST visitor that fills the project memoizer with memoizers. For that, you use `BasicProjectMemoizer`'s
|
||||
`addClassMemoizer` and `addOperationMemoizer` methods with a qualified name.
|
||||
[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitor.java)
|
||||
* Create a façade class for your visitor. This class extends a `*ParserVisitorAdapter` class and only overrides the
|
||||
`initializeWith(Node)` method. It's supposed to make your real visitor accept the node in parameter.
|
||||
[Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitorFacade.java)
|
||||
* Override the `getMetricsVisitorFacade()` method in your language's handler (e.g. `ApexHandler`). This method gives
|
||||
back a `VisitorStarter` which initializes your façade with a `Node`.
|
||||
[Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/AbstractJavaHandler.java#L100-L108)
|
||||
* Your project memoizer should now get filled when the `metrics` attribute is set to `true` in the rule XML.
|
||||
|
||||
### 3. Implement the façade
|
||||
### 2. Implement the façade
|
||||
* Create a class extending `AbstractMetricsComputer<T, O>`. This object will be responsible for calculating metrics
|
||||
given a memoizer, a node and info about the metric. Typically, this object is stateless so you might as well make it
|
||||
a singleton.
|
||||
[Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsComputer.java)
|
||||
* Create a class extending `BasicProjectMemoizer<T, O>`. There's no abstract functionality to implement.
|
||||
[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMemoizer.java)
|
||||
[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsComputer.java)
|
||||
* Create a class extending `AbstractMetricsFacade<T, O>`. This class needs a reference to your `ProjectMemoizer` and
|
||||
your `MetricsComputer`. It backs the real end user façade, and handles user provided parameters before delegating to
|
||||
your `MetricsComputer`.
|
||||
[Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsFacade.java)
|
||||
[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsFacade.java)
|
||||
* Create the static façade of your framework. This one has an instance of your `MetricsFaçade` object and delegates
|
||||
static methods to that instance.
|
||||
[Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetrics.java)
|
||||
[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetrics.java)
|
||||
* Create classes `AbstractOperationMetric` and `AbstractClassMetric`. These must implement `Metric<T>` and
|
||||
`Metric<O>`, respectively. They typically provide defaults for the `supports` method of each metric.
|
||||
[Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaOperationMetric.java)
|
||||
[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaOperationMetric.java)
|
||||
* Create enums `ClassMetricKey` and `OperationMetricKey`. These must implement `MetricKey<T>` and `MetricKey<O>`. The
|
||||
enums list all available metric keys for your language.
|
||||
[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java)
|
||||
* Create metrics by extending your base classes, reference them in your enums, and you can start using them with your
|
||||
façade!
|
||||
|
||||
{% include important.html content="The following section will be moved when multifile analysis and metrics are separated" %}
|
||||
|
||||
### Optional: Signature matching
|
||||
|
||||
You can match the signature of anything: method, field, class, package... It depends on what's useful for you.
|
||||
@ -133,8 +99,6 @@ build a `Signature<N>` from a `N` are a good idea.
|
||||
* Create signature masks. A mask is an object that matches some signatures based on their features. For example, with
|
||||
the Java framework, you can build a `JavaOperationSigMask` that matches all method signatures with visibility
|
||||
`public`. A sigmask implements `SigMask<S>`, where `S` is the type of signature your mask handles.
|
||||
* Typically, the project mirror stores the signatures, so you have to implement it in a way that makes it possible to
|
||||
associate a signature with the qualified name of its node.
|
||||
* If you want to implement signature matching, create an `AbstractMetric` class, which gives access to a
|
||||
`SignatureMatcher` to your metrics. Typically, your implementation of `ProjectMirror` implements a
|
||||
custom `SignatureMatcher` interface, and your façade can give back its instance of the project mirror.
|
||||
* Create utility methods in your abstract class metric class to count signatures matching a specific mask.
|
||||
[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaClassMetric.java#L50)
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Using code metrics in custom rules
|
||||
tags: [customizing]
|
||||
summary: "PMD was recently enhanced with the ability to compute code metrics on Java and Apex source (the so-called
|
||||
summary: "Since 6.0.0, PMD is 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
|
||||
last_updated: December 18, 2017
|
||||
permalink: pmd_devdocs_metrics_howto.html
|
||||
author: Clément Fournier <clement.fournier76@gmail.com>
|
||||
---
|
||||
@ -12,9 +12,6 @@ author: Clément Fournier <clement.fournier76@gmail.com>
|
||||
|
||||
## Using the metrics framework
|
||||
|
||||
{%include note.html content="Using the metrics framework is for now restricted to Java rules (with plans to support
|
||||
XPath rules later)." %}
|
||||
|
||||
{%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." %}
|
||||
|
||||
@ -54,8 +51,6 @@ or `ConstructorDeclaration`.
|
||||
|
||||
## For Java Rules
|
||||
|
||||
First, similarly to XPath rules, you should add the `metrics="true"` attribute to your rule's XML element.
|
||||
|
||||
The static façade class `JavaMetrics` is the single 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.
|
||||
@ -104,7 +99,7 @@ public Object visit(ASTMethodDeclaration method, Object data) {
|
||||
### Metric options
|
||||
|
||||
Some metrics define options that can be used to slightly modify the computation. You'll typically see these options
|
||||
gathered inside an enum in the implementation class of the metric, for example `CycloMetric.CycloOptions`. They're
|
||||
gathered inside an enum in the implementation class of the metric, for example `CycloMetric.CycloOption`. They're
|
||||
also documented on the [index of metrics](pmd_java_metrics_index.html).
|
||||
|
||||
To use options with a metric, you must first bundle them into a `MetricOptions` object. `MetricOptions` provides the
|
||||
@ -123,7 +118,7 @@ public Object visit(ASTMethodDeclaration method, Object data) {
|
||||
|
||||
The version of `MetricOptions.ofOptions` using a collection is useful when you're building a `MetricOptions` from eg
|
||||
the value of an `EnumeratedMultiProperty`, which gives users control of the options they use. See
|
||||
[CyclomaticComplexityRule]( https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/CyclomaticComplexityRule.java)
|
||||
[CyclomaticComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CyclomaticComplexityRule.java#L35)
|
||||
for an example usage.
|
||||
|
||||
### Result options
|
||||
@ -150,17 +145,18 @@ option too.
|
||||
The following is a sample code for a rule reporting methods with a cyclomatic
|
||||
complexity over 10 and classes with a total cyclo over 50. A metric option can be
|
||||
user-configured with a rule property. More complete examples can be found in
|
||||
[CyclomaticComplexityRule]( https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/CyclomaticComplexityRule.java),
|
||||
[NcssCountRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/NcssCountRule.java),
|
||||
or [GodClassRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/GodClassRule.java).
|
||||
[CyclomaticComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CyclomaticComplexityRule.java#L35),
|
||||
[NcssCountRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssCountRule.java#L30),
|
||||
or [GodClassRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/GodClassRule.java#L24).
|
||||
|
||||
|
||||
```java
|
||||
public class CycloRule extends AbstractJavaMetricsRule {
|
||||
|
||||
public static final BooleanProperty COUNT_BOOLEAN_PATHS
|
||||
= new BooleanProperty("countBooleanPaths", "Count boolean paths",
|
||||
true, 0f);
|
||||
= BooleanProperty.named("countBooleanPaths")
|
||||
.desc("Count boolean paths")
|
||||
.defaultValue(true).build();
|
||||
|
||||
private static final MetricOptions options;
|
||||
|
||||
@ -272,9 +268,10 @@ concrete node types you can target with class and operation metrics, by language
|
||||
|
||||
Language | Java | Apex |
|
||||
-----------|------|------|
|
||||
Operation declaration|`ASTMethodOrConstructorDeclaration`<br/>>: `ASTMethodDeclaration`, `ASTConstructorDeclaration`| `ASTMethod`
|
||||
Operation declaration|`ASTMethodOrConstructorDeclaration`<br/>>: `ASTMethodDeclaration`, `ASTConstructorDeclaration`| `ASTMethod`*
|
||||
Type declaration|`ASTAnyTypeDeclaration` >: `ASTEnumDeclaration`, <br> `ASTAnnotationDeclaration`, `ASTClassOrInterfaceDeclaration`| `ASTUserClassOrInterface` >: `ASTUserClass`, `ASTUserInterface`
|
||||
|
||||
*Apex method metrics are also applied to triggers by default (see [#771](https://github.com/pmd/pmd/pull/771)). Finer capability checking is not available out of the box for now.
|
||||
|
||||
What if you don't want such a generalisation? The `supports` method lets you
|
||||
define a predicate to check that the node is supported by your metric. For example,
|
||||
@ -297,5 +294,5 @@ classes. Here's the default behaviour by language and type of metric:
|
||||
|
||||
Language | Java | Apex |
|
||||
-----------|------|------|
|
||||
Operation metrics| supports constructors and non abstract methods| supports any non abstract method except `<init>`, `<clinit>`, and `clone`
|
||||
Operation metrics| supports constructors and non abstract methods| supports any non abstract method (including triggers), except `<init>`, `<clinit>`, and `clone`
|
||||
Type declaration|supports classes and enums|supports classes
|
||||
|
Reference in New Issue
Block a user