-
Notifications
You must be signed in to change notification settings - Fork 0
PMD
O PMD é um analisador estático de código criado no começo dos anos 2000. Ele analisa o código para checar boas práticas e design de código por meio de regras (“rules”). Útil para deixar o código como um todo com o mesmo padrão e evitar erros no código. Ele fornece suporte para muitas linguagens de programação, sendo a Java uma das mais conhecidas.
No momento da escrita está sendo usada a versão 6.55.0 para o Java 17. A versão 7.0.0 está quase sendo lançada, trazendo algumas incompatibilidades, como sintaxe de XPath e versão da ferramenta “Designer”. Essa Wikipage refere-se ao uso específico com a linguagem Java e o gerenciador de pacotes Gradle.
O PMD funciona como uma “task” do Gradlew. As tasks dele são o pmdMain, pmdTest e pmdSourceSet. A primeira analisa os .java de produção, o segundo os de teste e o terceiro é para outros códigos fonte. As tasks são acionadas por meio do comando ./gradlew check ou ./gradlew ch. Importante observar que estes comandos também acionam outros analisadores, como o Spotbugs e o CheckStyle. Configurando o PMD no arquivo build.gradle.
plugins {
// [... outras declarações]
id 'pmd'
}pmd {
sourceSets = [ project.sourceSets.main ] // definem os arquivos dos códigos fonte que serão analisados.
consoleOutput = true // mostra no console o resultado
toolVersion = "6.55.0" // versão do PMD
ruleSets = [] // reseta o conjunto de regras para não conflitar com arquivos de regras, se estes existirem
ruleSetFiles = rootProject.files("config/pmd/ruleset.xml") // path do arquivo de ruleset
pmdMain { // opcional, usada para definir exclusões no build.gradle
excludes = [
'**/Application.*',
]
}
}obs: é possível declarar o ruleSets dessa forma, ruleSets = ["category/java/bestpractices.xml"]
obs 2: Ver documentação do plugin para mais opções de configuração
Regras são checagens (“checks”) do código, possuem nome, versão da linguagem alvo, descrição, mensagem, exemplos e propriedades (nem todos esses são obrigatórios). Cada regra pertence a um dos 8 tipos de categorias, que auxiliam na semântica da regra. Elas podem ser organizadas em um arquivos chamado de conjunto de regras (“ruleset”). Cada regra é explicada na documentação do PMD. Algumas regras podem ter seus nomes mudados ou podem ter sido deprecadas dependendo da versão do PMD.
Uma das formas de se suprimir o aviso levantado por uma rule é acrescentar a anotação @SuppressWarnings(“PMD”) ou SuppressWarnings("PMD.nome-da-rule") na classe ou método onde o erro ocorre. Essa técnica não funciona em casos em que a rule não está em uma classe ou método (ex: está nos “imports”).
Também é possivel suprimir usando expressões regulares ou notação “XPath”.
<rule ref="rulesets/java/unusedcode.xml/UnusedFormalParameter">
<properties>
<property name="violationSuppressXPath" value=".[typeIs('java.lang.String')]"/>
</properties>
</rule>A “property” poderia ser trocada por:
<property name="violationSuppressXPath" value="./ancestor::ClassOrInterfaceDeclaration[contains(@Image, 'Bean')]"/>ou
<property name="violationSuppressXPath" value="./ancestor::ClassOrInterfaceDeclaration[matches(@Image, '^.*Bean$')]"/>obs: A notação XPath foi criada para ser usada com xml e possui outras aplicações na web em geral. No contexto do PMD, é usada a versão 1.0, com alguma adaptabilidade para a 2.0, até a última versão do PMD 6. A partir do PMD 7.0.0, será usada a versão XPath 3.1. As diferentes versões do XPath não são inteiramente compatíveis, podendo trazer problemas.
obs 2: A notação XPath tem funções como “contains()” e “matches()” que ajudam na seleção de nós. O PMD adicionou algumas outras funções como o “typeIs()”. A documentação lista as funções do XPath do PMD.
As categorias que uma regra nativa do PMD são “Best Practices”, “Code Style”, “Design”, “Documentation”, “Error prone”, “Multithreading”, “Performance”, “Security”.
No arquivo de ruleset, as rules podem ser declaradas por categoria,
<rule ref="category/java/bestpractices.xml"/>individualmente,
<rule ref="category/java/bestpractices.xml/AccessorClassGeneration" />com propriedades genéricas (ex: “message” e “priority”),
<rule ref="category/java/errorprone.xml/EmptyCatchBlock"
message="Empty catch blocks should be avoided" >
<priority>5</priority>
</rule>além dessas, também existem as propriedades genéricas que utilizam a tag <property> como “legalCollectionTypes” e “ignoredAnnotations”.
<properties>
<property name="legalCollectionTypes"
value="java.util.ArrayList|java.util.Vector|java.util.HashMap"/>
</properties>Rule usando a property genérica ignoredAnnotations para suspender o aviso na anotação da classe que contém a anotação “SpringBootApplication”.
<rule ref="category/java/design.xml/UseUtilityClass">
<properties>
<property name="ignoredAnnotations"
value="org.springframework.boot.autoconfigure.SpringBootApplication"/>
</properties>
</rule>ou ainda propriedades específicas (ex: “allowPrivate”),
<rule ref="category/java/bestpractices.xml/ArrayIsStoredDirectly">
<properties>
<property name="allowPrivate" value="true" />
</properties>
</rule>Um ruleset é um arquivo xml que é possível definir e configurar as rules desejadas.
<?xml version="1.0"?>
<ruleset name="Regras customizadas"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>
Minhas regras customizadas
</description>
<!-- Suas regras vem aqui -->
</ruleset>Também é possível excluir ou incluir diretórios e/ou arquivos no arquivo do ruleset, adicionando (normalmente antes das rules) as tags:
<exclude-pattern>.*/some/package/.*</exclude-pattern>
<exclude-pattern>.*/some/other/package/FunkyClassNamePrefix.*</exclude-pattern>
<include-pattern>.*/some/package/ButNotThisClass.*</include-pattern>Também é possível fazer exclusões de rules nas rules que englobam categorias inteiras:
<rule ref="category/java/codestyle.xml">
<exclude name="WhileLoopsMustUseBraces"/>
<exclude name="IfElseStmtsMustUseBraces"/>
</rule>obs: cuidado ao declarar as rules, o pmd pode passar codigo com erros por uma linha que foi mal escrita, disparando o erro
> Task :pmdMain
Cannot load ruleset C:\Users\rsp\Documents\Codelab\spring-boot-template\config\pmd\ruleset.xml: 3.1
No rules found. Maybe you misspelled a rule name?
Se desconfiar, faça um teste simples. Adicione a regra <rule ref="category/java/bestpractices.xml/AvoidStringBufferField" /> e instancie a classe StringBuilder em um arquivo e faça ./gradlew check. Ele deve gerar erro no PMD.
obs 2: tem uma seção com exemplos
obs 3: É possível usar rulesets diferentes por contexto (main, teste)
obs 4: É possível fazer arquivo de teste para a nova rule criada
O PMD será usado primeiro deixando todas as rules habilitadas. No arquivo de ruleset desse projeto, foram suprimidas as rules que deram erro e todas que tinham properties específicas, mas depois elas foram acrescentadas denovo. Isso foi feito para não dar aviso de rules declaradas duas vezes. Conforme for escrevendo os códigos dos projetos, o desenvolvedor usa o comando “./gradlew check” para analisar o código. Se levantar erro, temos que nos perguntar.
- A regra faz sentido? É possível a suspender?
- Se a regra faz sentido, é possível suspender o arquivo?
- Se o arquivo como um todo faz sentido, é possível suspender o método ou classe específicos?
- Consertar o código.
Workflow
geogebra (Define muitas regras e usa custom rule)
fdroid (Separa em dois rulesets)
wa-task-management (usa com owasp)
allwpilib (exemplo de custom rule)
rulesets de terceiros da documentação
xpath cheatsheet (resumo de propriedades do XPath)