From 1d2db3dfe050ceb8dfe9077d968d827a495bec30 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Fri, 1 Dec 2023 09:41:09 +0100 Subject: [PATCH] [doc] ADR 3 - API principles --- docs/pages/pmd/projectdocs/decisions/adr-3.md | 162 +++++++++++------- 1 file changed, 96 insertions(+), 66 deletions(-) diff --git a/docs/pages/pmd/projectdocs/decisions/adr-3.md b/docs/pages/pmd/projectdocs/decisions/adr-3.md index 1ad5cda493..4ffc7e8959 100644 --- a/docs/pages/pmd/projectdocs/decisions/adr-3.md +++ b/docs/pages/pmd/projectdocs/decisions/adr-3.md @@ -17,109 +17,136 @@ The API of PMD has been growing over the years and needed some cleanup. The goal have a clear separation between a well-defined API and the implementation, which is internal. This should help us in future development. -Until now, all released public members and types were implicitly considered part -of PMD's public API, including inheritance-specific members (protected members, abstract methods). +Until PMD 7.0.0, all released public members and types were implicitly considered part +of public PMD API, including inheritance-specific members (protected members, abstract methods). We have maintained those APIs with the goal to preserve full binary compatibility between minor releases, only breaking those APIs infrequently, for major releases. +PMD is used and integrated in many different tools such as IDE plugins or build plugins. These plugins +use our public API and rely on it being stable, hence we tried to break it only infrequently. + In order to allow PMD to move forward at a faster pace, this implicit contract will -be invalidated with PMD 7.0.0. We now introduce more fine-grained distinctions between +be invalidated with PMD 7.0.0 and onwards. We now introduce more fine-grained distinctions between the type of compatibility support we guarantee for our libraries, and ways to make them explicit to clients of PMD. - -PMD is used and integrated by many different tools such as IDE plugins or build plugins. -Having a public stable API helps to make this -integration possible without much effort. But freezing the API will prevent further development. - -In order to balance the two needs - stable API vs. space for new development - we need to -document principles on API evolution. - The actual API development and marking some part of the API as internal or add new API is an ongoing task, that will need to be done everytime. We won't just define an API and then are done with it. The API will change as new features want to be implemented. -This decision document aims to document - -- what the criteria is for a public API - - clear-cut between public APIs and internal - - which packages to use, e.g. `internal` for internal "API" - - when to use package `impl` -- how to deprecate and remove old APIs - - how long to support a given API - - with respect to semantic version -- when to define a new API as experimental - - when to promote an experimental API as stable -- annotations to use, see also -- guidelines for AST classes (package private ctor, final, etc.) - -Clearly defining the API PMD provides, makes sense and will help, if we at some time in the future want to -modularize PMD using java9. That would prevent API leaking then at compile time already... +This decision document aims to document principles and guidelines that are used for PMD development. # Decision -## Java packages names and structure -* PMD is mainly developed in the Java programming language. It consists of several modules. -The classes belonging to one module should be in the same package. From the package name, it should be clear to -which module this package belongs (there is a 1:1 mapping). This rule helps to find the source code for any -fully qualified (Java) class name. -* The base package for all PMD source code is `net.sourceforge.pmd`. -* Language modules, such as `pmd-java` will use the package `net.sourceforge.pmd.lang.java`, or in general: - `net.sourceforge.pmd.lang.`. -* The core module `pmd-core` will use the base package as an exception. -* All other modules should use a subpackage of the base package, e.g. `pmd-cli` uses the package `net.sourceforge.pmd.cli`, - or in general: `net.sourceforge.pmd.` +## Semantic Versioning -Sub-package `impl` +PMD and all its modules are versioned together. PMD uses [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). +This means, that each PMD version consists of MAJOR.MINOR.PATCH components: -Sub-package `internal` +* MAJOR version is incremented for incompatible API changes +* MINOR version is incremented for added functionality in a backwards compatible way +* PATCH version is incremented for backward compatible bug fixes +Additional labels for release candidates might be used. + +Incompatible API changes shouldn't be introduced lightly. See +[FAQ: If even the tiniest backward incompatible changes to the public API require a major version bump, won’t I end up at version 42.0.0 very rapidly?](https://semver.org/spec/v2.0.0.html#if-even-the-tiniest-backward-incompatible-changes-to-the-public-api-require-a-major-version-bump-wont-i-end-up-at-version-4200-very-rapidly). + +## Project structure and Java base packages names + +PMD is mainly developed in the Java programming language. The build tool is Maven and the PMD build consists +of several maven modules. + +* Classes belonging to one module should be in the same package (or sub-packages). +* Given a package name, it should be easy to figure out to which module this package belongs. There is a 1:1 mapping + between maven module and package. This rule helps to find the source code for any fully qualified (Java) class name. +* Two modules must not define the same packages. That means, it is not allowed that any given package spans more than + one module. Otherwise, the mapping between module and package wouldn't be unambiguous. +* The base package for all PMD source code is `net.sourceforge.pmd`. There are many different sub packages. +* The core module `pmd-core` uses directly the base package as the only module. All other modules must use + specific sub packages. +* Language modules use the base package `net.sourceforge.pmd.lang.`. + E.g. `pmd-java` uses the package `net.sourceforge.pmd.lang.java`. +* All other modules use the base package `net.sourceforge.pmd.`, + E.g. `pmd-cli` uses the package `net.sourceforge.pmd.cli`. ## Criteria for public API +Public API is +* API needed to execute PMD analysis + * Renderers + * RuleSet XML Schema + * Configuration + * Ant Tasks +* API needed to implement custom rules + * AST structure and classes of languages (incl. AST structure for XPath rules) + * XPath functions + * Language Symbol Table / Metrics / Type Resolution (Not the implementation) -Not public API: +**Not** public API is - Inheritance-specific members of AST related classes & interfaces. eg adding a member to an interface shouldn’t be considered api breaking - Setters only used in the parser - Exceptions… (I’ve seen JaxenException in the Node interface…) - XPath, AttributeAxisIterator - RuleSetFactory +* Anything in packages `internal` and `impl` +* Inheritance-specific members of AST related classes and interfaces. E.g. adding a member to an + interface shouldn't be considered API breaking +* Setters in AST classes are private. They are only used in the parser -public API +## Separation between public API, internal and implementation - AST structure - Language Symbol Table / Metrics / Type Resolution …(Not the implementation!) - Implementing custom Rules (AbstractRule, concrete Properties, …) - Renderers - Executing PMD, Ant Task, Configuration - RuleSet XML +All packages are considered to be public API by default, with **two exceptions**: -*Internal API* is meant for use *only* by the main PMD codebase. Internal types and methods -may be modified in any way, or even removed, at any time. +* Any package that contains an `internal` segment is considered internal. E.g. `net.sourceforge.pmd.internal`. + *Internal API* is meant for use *only* by the main PMD codebase. Internal types and methods + may be modified in any way, or even removed, at any time without a MAJOR version change. -Any API in a package that contains an `.internal` segment is considered internal. -The `@InternalApi` annotation will be used for APIs that have to live outside of -these packages, e.g. methods of a public type that shouldn't be used outside of PMD (again, -these can be removed anytime). + The `@InternalApi` annotation will be used for types that have to live outside of + these packages, e.g. methods of a public type that shouldn't be used outside PMD (again, + these can be removed anytime). + +* Any package that contains an `impl` segment is considered internal. E.g. `net.sourceforge.pmd.lang.impl`. + These packages contain base classes that are needed for extending PMD (like adding a new language). + These can change at any time without a MAJOR version change. ## Deprecation and removing of old APIs -Use @Deprecated during the transition (from the point we announce decide which APIs will be non-public until 7.0.0). -Update the 7.0.0 branch as we go along + +* APIs can be deprecated at any time (even in PATCH versions). Deprecated APIs are marked with the + `@Deprecated` annotation. +* Deprecations should be listed in the release notes. +* Deprecated APIs can only be removed with a MAJOR version change. ## Experimental APIs -New Features, maybe @Incubating or @Experimental at first +* New features often introduce new APIs. These new APIs can be marked with the annotation `@Experimental` at + the class or method level. +* APIs marked with the `@Experimental` annotation are subject to change and are considered **not stable**. + They can be modified in any way, or even removed, at any time. You should not use or rely + on them in any production code. They are purely to allow broad testing and feedback. +* Experimental APIs can be introduced or removed with at least a MINOR version change. + These experimental APIs should be listed in the release notes. +* Experimental APIs can be promoted to Public APIs with at least a MINOR version change. ## Guidelines for AST classes -maybe, concrete AST node classes should have a package-private constructor, and be final. -They're not meant to be instantiated by hand, but that could break some tests and rules + +AST classes of the individual language modules are used by custom rule implementations and are considered +Public API in general. Rules only read the AST and do not need to modify it. + +In order to minimize the public API surface of AST classes, the following guidelines apply: + +* Concrete AST classes should be final, to avoid custom subclasses. +* Concrete AST classes should only have a package private constructor to avoid manual instantiation. + Only the parser of the language (which lives in the same package) should be able to create new instances + of AST classes. +* Concrete AST classes should not have public setters. All setters should be package private, so that + only the parser of the language can call the setters during AST construction. + +Non-concrete AST classes (like base classes or common interfaces) should follow similar guidelines: +* Only package private constructor +* Only package private setters ## Summary of the annotations -* `@InternalApi` -Full name: `net.sourceforge.pmd.annotation.InternalApi` +* `@InternalApi` (`net.sourceforge.pmd.annotation.InternalApi`) + * `@ReservedSubclassing` Full name: `net.sourceforge.pmd.annotation.ReservedSubclassing` @@ -153,6 +180,9 @@ Full name: `net.sourceforge.pmd.annotation.DeprecatedUntil700` # Consequences +* Clearly defining the API PMD provides, makes sense and will help, if we at some time in the future want to +modularize PMD using java9. That would prevent API leaking then at compile time already... + What becomes easier or more difficult to do because of this change? # Change History