diff --git a/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md b/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md index 2e14e78412..28534d4a18 100644 --- a/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md +++ b/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md @@ -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 --- -{% include warning.html content="WIP, unstable API" %} ## Internal architecture of the metrics framework @@ -33,43 +32,24 @@ Metrics (`Metric`) plug in to this static system and only provide behaviour t Internally, metric keys (`MetricKey`) are parameterized with their version (`MetricVersion`) to index memoisation maps (see `ParameterizedMetricKey`). 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" %} - - 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`. 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`. 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`. 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`. 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` and `Metric`, 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` and `MetricKey`. 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` 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`, 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) + diff --git a/docs/pages/pmd/devdocs/metrics_howto.md b/docs/pages/pmd/devdocs/metrics_howto.md index e0e7812fab..9c75bc0015 100644 --- a/docs/pages/pmd/devdocs/metrics_howto.md +++ b/docs/pages/pmd/devdocs/metrics_howto.md @@ -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 --- @@ -12,9 +12,6 @@ author: Clément Fournier ## 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`
>: `ASTMethodDeclaration`, `ASTConstructorDeclaration`| `ASTMethod` +Operation declaration|`ASTMethodOrConstructorDeclaration`
>: `ASTMethodDeclaration`, `ASTConstructorDeclaration`| `ASTMethod`* Type declaration|`ASTAnyTypeDeclaration` >: `ASTEnumDeclaration`,
`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 ``, ``, and `clone` +Operation metrics| supports constructors and non abstract methods| supports any non abstract method (including triggers), except ``, ``, and `clone` Type declaration|supports classes and enums|supports classes