|
| 1 | +--- |
| 2 | +title: Adding PMD support for a new dialect for an already existing language |
| 3 | +short_title: Adding a new dialect |
| 4 | +tags: [devdocs, extending, experimental] |
| 5 | +summary: "How to add a new dialect." |
| 6 | +last_updated: April 2025 (7.13.0) |
| 7 | +sidebar: pmd_sidebar |
| 8 | +permalink: pmd_devdocs_major_adding_dialect.html |
| 9 | +folder: pmd/devdocs |
| 10 | +--- |
| 11 | + |
| 12 | +{% include callout.html type="info" content=" |
| 13 | + |
| 14 | +**What is a dialect?**<br><br> |
| 15 | + |
| 16 | +A dialect is a particular form of another supported language. For example, an XSLT is a particular form of an XML. |
| 17 | +Even though the dialect has its own semantics and uses, the contents are still readable by any tool capable of understanding the base language.<br><br> |
| 18 | + |
| 19 | +In PMD, a dialect allows to set up completely custom rules, XPath functions, properties and metrics for these files; |
| 20 | +while retaining the full support of the underlying language. That means:<br><br> |
| 21 | + |
| 22 | +- All rules applicable to the base language are automatically applicable to all files processed as a dialect.<br> |
| 23 | +- All XPath functions existing in the base language are available when creating new rules.<br> |
| 24 | +- All metrics supported by the base language are available when creating new rules.<br> |
| 25 | +- All properties (ie: support to suppress literals in CPD) supported by the base language are supported by the dialect.<br> |
| 26 | + |
| 27 | +" %} |
| 28 | + |
| 29 | +## Steps |
| 30 | + |
| 31 | +### 1. Create a dialect module |
| 32 | +* Dialects usually reside in the same module of the base language they leverage; but can technically live standalone in a separate module if needed. |
| 33 | +* Create your subclass of `net.sourceforge.pmd.lang.impl.SimpleDialectLanguageModuleBase`, see XSL as an example: [`XslDialectModule`](https://github.com/pmd/pmd/blob/main/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/xsl/XslDialectModule.java). |
| 34 | +* For a minimal implementation, it just needs a constructor calling super() with the required metadata. |
| 35 | + Dialect metadata is created through the builder obtained `LanguageMetadata.withId` |
| 36 | + * Define the human readable name of the language by calling `name` |
| 37 | + * Define all extensions PMD should consider when applying this dialect by calling `extensions` |
| 38 | + * Add for each version of your language a call to `addVersion` in your language module’s constructor. |
| 39 | + Use `addDefaultVersion` for defining the default version. |
| 40 | + * Finalize the metadata construction by calling `asDialectOf` to reference the base language by id. |
| 41 | +* Create the service registration via the text file `src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language`. |
| 42 | + Add your fully qualified class name as a single line into it. |
| 43 | + |
| 44 | +### 2. Create a language handler (Optional) |
| 45 | +* This step is only required if you either want the dialect to: |
| 46 | + * expose additional XPath functions |
| 47 | + * compute additional metrics |
| 48 | + * customize violation suppress logic |
| 49 | + * define {% jdoc core::reporting.ViolationDecorator %}s, to add additional dialect specific information to the |
| 50 | + created violations. The [Java language module](pmd_languages_java.html#violation-decorators) uses this to |
| 51 | + provide the method name or class name, where the violation occurred. |
| 52 | +* To do this, create a new class extending from [`BasePmdDialectLanguageVersionHandler`](https://github.com/pmd/pmd/blob/main/pmd-core/src/main/java/net/sourceforge/pmd/lang/impl/BasePmdDialectLanguageVersionHandler.java), and override the getter corresponding to what you want to extend. |
| 53 | + You don't need to worry about including anything from the base language, only include your extensions. PMD will take care of merging everything together. |
| 54 | +* Ensure to pass a new instance of your dialect handler as a second parameter in your dialect module (see Step 1) when calling `super`. |
| 55 | + |
| 56 | +### 3. Create rules |
| 57 | +* Creating rules is already pretty well documented in PMD - and it’s no different for a new dialect. |
| 58 | +* PMD supports 2 types of rules, through visitors or XPath. |
| 59 | +* To add a visitor rule: |
| 60 | + * You need to extend the abstract rule provided by the base language, for instance in XML dialects, you would extend [`AbstractXmlRule`](https://github.com/pmd/pmd/blob/main/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/AbstractXmlRule.java). |
| 61 | + Note, that all rule classes should be suffixed with `Rule` and should be placed |
| 62 | + in a package the corresponds to their dialect and category. |
| 63 | +* To add an XPath rule you can follow our guide [Writing XPath Rules](pmd_userdocs_extending_writing_xpath_rules.html). |
| 64 | +* When creating the category ruleset XML file, the XML can reference build properties that are replaced |
| 65 | + during the build. This is used for the `externalInfoUrl` attribute of a rule. E.g. we use `${pmd.website.baseurl}` |
| 66 | + to point to the correct webpage (depending on the PMD version). |
| 67 | + |
| 68 | +### 4. Test the rules |
| 69 | +* Testing rules is described in depth in [Testing your rules](pmd_userdocs_extending_testing.html). |
| 70 | + * Each rule has its own test class: Create a test class for your rule extending `PmdRuleTst` |
| 71 | + *(see |
| 72 | + [`UnavailableFunctionTest`](https://github.com/pmd/pmd/blob/main/pmd-swift/src/test/java/net/sourceforge/pmd/lang/swift/rule/bestpractices/UnavailableFunctionTest.java) |
| 73 | + for example)* |
| 74 | + * Create a category rule set for your dialect *(see |
| 75 | + [`category/swift/bestpractices.xml`](https://github.com/pmd/pmd/blob/main/pmd-swift/src/main/resources/category/swift/bestpractices.xml) |
| 76 | + for example)* |
| 77 | + * Place the test XML file with the test cases in the correct location |
| 78 | + * When executing the test class |
| 79 | + * this triggers the unit test to read the corresponding XML file with the rule test data |
| 80 | + *(see |
| 81 | + [`UnavailableFunction.xml`](https://github.com/pmd/pmd/blob/main/pmd-swift/src/test/resources/net/sourceforge/pmd/lang/swift/rule/bestpractices/xml/UnavailableFunction.xml) |
| 82 | + for example)* |
| 83 | + * This test XML file contains sample pieces of code which should trigger a specified number of |
| 84 | + violations of this rule. The unit test will execute the rule on this piece of code, and verify |
| 85 | + that the number of violations matches. |
| 86 | +* To verify the validity of all the created rulesets, create a subclass of `AbstractRuleSetFactoryTest` |
| 87 | + (*see `RuleSetFactoryTest` in pmd-swift for example)*. |
| 88 | + This will load all rulesets and verify, that all required attributes are provided. |
| 89 | + |
| 90 | + *Note:* You'll need to add your ruleset to `categories.properties`, so that it can be found. |
| 91 | + |
| 92 | +### 5. Create documentation page |
| 93 | +Finishing up your new dialect by adding a page in the documentation. Create a new markdown file |
| 94 | +`<langId>.md` in `docs/pages/pmd/languages/`. This file should have the following frontmatter: |
| 95 | + |
| 96 | +``` |
| 97 | +--- |
| 98 | +title: <Language Name> |
| 99 | +permalink: pmd_languages_<langId>.html |
| 100 | +last_updated: <Month> <Year> (<PMD Version>) |
| 101 | +tags: [languages, PmdCapableLanguage, CpdCapableLanguage] |
| 102 | +--- |
| 103 | +``` |
| 104 | + |
| 105 | +On this page, language specifics can be documented, e.g. when the language was first supported by PMD. |
| 106 | +There is also the following Jekyll Include, that creates summary box for the language: |
| 107 | + |
| 108 | +``` |
| 109 | +{% raw %} |
| 110 | +{% include language_info.html name='<Language Name>' id='<langId>' implementation='<langId>::lang.<langId>.<langId>LanguageModule' supports_cpd=true supports_pmd=true since='<PMD Version>' %} |
| 111 | +{% endraw %} |
| 112 | +``` |
| 113 | + |
0 commit comments