diff --git a/docs/pages/pmd/rules/apex.md b/docs/pages/pmd/rules/apex.md index f58e1edb33..9dcfda90a5 100644 --- a/docs/pages/pmd/rules/apex.md +++ b/docs/pages/pmd/rules/apex.md @@ -22,13 +22,17 @@ folder: pmd/rules {% include callout.html content="Rules which enforce a specific coding style." %} -* [ClassNamingConventions](pmd_rules_apex_codestyle.html#classnamingconventions): Class names should always begin with an upper case character. +* [ClassNamingConventions](pmd_rules_apex_codestyle.html#classnamingconventions): Configurable naming conventions for type declarations. This rule reports type declarat... +* [FieldNamingConventions](pmd_rules_apex_codestyle.html#fieldnamingconventions): Configurable naming conventions for field declarations. This rule reports variable declarations ... * [ForLoopsMustUseBraces](pmd_rules_apex_codestyle.html#forloopsmustusebraces): Avoid using 'for' statements without using surrounding braces. If the code formatting orindentati... +* [FormalParameterNamingConventions](pmd_rules_apex_codestyle.html#formalparameternamingconventions): Configurable naming conventions for formal parameters of methods. This rule reports fo... * [IfElseStmtsMustUseBraces](pmd_rules_apex_codestyle.html#ifelsestmtsmustusebraces): Avoid using if..else statements without using surrounding braces. If the code formattingor indent... * [IfStmtsMustUseBraces](pmd_rules_apex_codestyle.html#ifstmtsmustusebraces): Avoid using if statements without using braces to surround the code block. If the codeformatting ... -* [MethodNamingConventions](pmd_rules_apex_codestyle.html#methodnamingconventions): Method names should always begin with a lower case character, and should not contain underscores. +* [LocalVariableNamingConventions](pmd_rules_apex_codestyle.html#localvariablenamingconventions): Configurable naming conventions for local variable declarations. This rule reports var... +* [MethodNamingConventions](pmd_rules_apex_codestyle.html#methodnamingconventions): Configurable naming conventions for method declarations. This rule reports method decl... * [OneDeclarationPerLine](pmd_rules_apex_codestyle.html#onedeclarationperline): Apex allows the use of several variables declaration of the same type on one line. However, itcan... -* [VariableNamingConventions](pmd_rules_apex_codestyle.html#variablenamingconventions): A variable naming conventions rule - customize this to your liking. Currently, itchecks for fina... +* [PropertyNamingConventions](pmd_rules_apex_codestyle.html#propertynamingconventions): Configurable naming conventions for property declarations. This rule reports property ... +* [VariableNamingConventions](pmd_rules_apex_codestyle.html#variablenamingconventions): Deprecated A variable naming conventions rule - customize this to your liking. Currently, itchecks for fina... * [WhileLoopsMustUseBraces](pmd_rules_apex_codestyle.html#whileloopsmustusebraces): Avoid using 'while' statements without using braces to surround the code block. If the codeformat... ## Design diff --git a/docs/pages/pmd/rules/apex/codestyle.md b/docs/pages/pmd/rules/apex/codestyle.md index f70bfb0b78..26712b16a8 100644 --- a/docs/pages/pmd/rules/apex/codestyle.md +++ b/docs/pages/pmd/rules/apex/codestyle.md @@ -5,7 +5,7 @@ permalink: pmd_rules_apex_codestyle.html folder: pmd/rules/apex sidebaractiveurl: /pmd_rules_apex.html editmepath: ../pmd-apex/src/main/resources/category/apex/codestyle.xml -keywords: Code Style, ClassNamingConventions, IfElseStmtsMustUseBraces, IfStmtsMustUseBraces, ForLoopsMustUseBraces, MethodNamingConventions, OneDeclarationPerLine, VariableNamingConventions, WhileLoopsMustUseBraces +keywords: Code Style, ClassNamingConventions, IfElseStmtsMustUseBraces, IfStmtsMustUseBraces, FieldNamingConventions, ForLoopsMustUseBraces, FormalParameterNamingConventions, LocalVariableNamingConventions, MethodNamingConventions, OneDeclarationPerLine, PropertyNamingConventions, VariableNamingConventions, WhileLoopsMustUseBraces language: Apex --- @@ -15,21 +15,103 @@ language: Apex **Priority:** High (1) -Class names should always begin with an upper case character. +Configurable naming conventions for type declarations. This rule reports +type declarations which do not match the regex that applies to their +specific kind (e.g. enum or interface). Each regex can be configured through +properties. + +By default this rule uses the standard Apex naming convention (Pascal case). **This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.codestyle.ClassNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/ClassNamingConventionsRule.java) **Example(s):** ``` java -public class Foo {} +public class FooClass { } // This is in pascal case, so it's ok + +public class fooClass { } // This will be reported unless you change the regex ``` -**Use this rule by referencing it:** +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|testClassPattern|\[A-Z\]\[a-zA-Z0-9\_\]\*|Regex which applies to test class names|no| +|abstractClassPattern|\[A-Z\]\[a-zA-Z0-9\_\]\*|Regex which applies to abstract class names|no| +|classPattern|\[A-Z\]\[a-zA-Z0-9\_\]\*|Regex which applies to class names|no| +|interfacePattern|\[A-Z\]\[a-zA-Z0-9\_\]\*|Regex which applies to interface names|no| +|enumPattern|\[A-Z\]\[a-zA-Z0-9\_\]\*|Regex which applies to enum names|no| + +**Use this rule with the default properties by just referencing it:** ``` xml ``` +**Use this rule and customize it:** +``` xml + + + + + + + + + +``` + +## FieldNamingConventions + +**Since:** PMD 6.15.0 + +**Priority:** High (1) + +Configurable naming conventions for field declarations. This rule reports variable declarations +which do not match the regex that applies to their specific kind ---e.g. constants (static final), +static field, final field. Each regex can be configured through properties. + +By default this rule uses the standard Apex naming convention (Camel case). + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.codestyle.FieldNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FieldNamingConventionsRule.java) + +**Example(s):** + +``` java +public class Foo { + Integer instanceField; // This is in camel case, so it's ok + + Integer INSTANCE_FIELD; // This will be reported unless you change the regex +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|enumConstantPattern|\[A-Z\]\[A-Z0-9\_\]\*|Regex which applies to enum constant field names|no| +|constantPattern|\[A-Z\]\[A-Z0-9\_\]\*|Regex which applies to constant field names|no| +|finalPattern|\[a-z\]\[a-zA-Z0-9\]\*|Regex which applies to final field names|no| +|staticPattern|\[a-z\]\[a-zA-Z0-9\]\*|Regex which applies to static field names|no| +|instancePattern|\[a-z\]\[a-zA-Z0-9\]\*|Regex which applies to instance field names|no| + +**Use this rule with the default properties by just referencing it:** +``` xml + +``` + +**Use this rule and customize it:** +``` xml + + + + + + + + + +``` + ## ForLoopsMustUseBraces **Since:** PMD 5.6.0 @@ -63,6 +145,53 @@ for (int i = 0; i < 42; i++) { // preferred approach ``` +## FormalParameterNamingConventions + +**Since:** PMD 6.15.0 + +**Priority:** High (1) + +Configurable naming conventions for formal parameters of methods. +This rule reports formal parameters which do not match the regex that applies to their +specific kind (e.g. method parameter, or final method parameter). Each regex can be +configured through properties. + +By default this rule uses the standard Apex naming convention (Camel case). + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.codestyle.FormalParameterNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FormalParameterNamingConventionsRule.java) + +**Example(s):** + +``` java +public class Foo { + public bar(Integer methodParameter) { } // This is in camel case, so it's ok + + public baz(Integer METHOD_PARAMETER) { } // This will be reported unless you change the regex +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|finalMethodParameterPattern|\[a-z\]\[a-zA-Z0-9\]\*|Regex which applies to final method parameter names|no| +|methodParameterPattern|\[a-z\]\[a-zA-Z0-9\]\*|Regex which applies to method parameter names|no| + +**Use this rule with the default properties by just referencing it:** +``` xml + +``` + +**Use this rule and customize it:** +``` xml + + + + + + +``` + ## IfElseStmtsMustUseBraces **Since:** PMD 5.6.0 @@ -129,21 +258,29 @@ if (foo) { // preferred approach ``` -## MethodNamingConventions +## LocalVariableNamingConventions -**Since:** PMD 5.5.0 +**Since:** PMD 6.15.0 **Priority:** High (1) -Method names should always begin with a lower case character, and should not contain underscores. +Configurable naming conventions for local variable declarations. +This rule reports variable declarations which do not match the regex that applies to their +specific kind (e.g. local variable, or final local variable). Each regex can be configured through +properties. -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.codestyle.MethodNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/MethodNamingConventionsRule.java) +By default this rule uses the standard Apex naming convention (Camel case). + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.codestyle.LocalVariableNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/LocalVariableNamingConventionsRule.java) **Example(s):** ``` java public class Foo { - public void fooStuff() { + public Foo() { + Integer localVariable; // This is in camel case, so it's ok + + Integer LOCAL_VARIABLE; // This will be reported unless you change the regex } } ``` @@ -152,7 +289,56 @@ public class Foo { |Name|Default Value|Description|Multivalued| |----|-------------|-----------|-----------| -|skipTestMethodUnderscores|false|Skip underscores in test methods|no| +|finalLocalPattern|\[a-z\]\[a-zA-Z0-9\]\*|Regex which applies to final local variable names|no| +|localPattern|\[a-z\]\[a-zA-Z0-9\]\*|Regex which applies to local variable names|no| + +**Use this rule with the default properties by just referencing it:** +``` xml + +``` + +**Use this rule and customize it:** +``` xml + + + + + + +``` + +## MethodNamingConventions + +**Since:** PMD 5.5.0 + +**Priority:** High (1) + +Configurable naming conventions for method declarations. This rule reports +method declarations which do not match the regex that applies to their +specific kind (e.g. static method, or test method). Each regex can be +configured through properties. + +By default this rule uses the standard Apex naming convention (Camel case). + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.codestyle.MethodNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/MethodNamingConventionsRule.java) + +**Example(s):** + +``` java +public class Foo { + public void instanceMethod() { } // This is in camel case, so it's ok + + public void INSTANCE_METHOD() { } // This will be reported unless you change the regex +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|skipTestMethodUnderscores|false|Deprecated Skip underscores in test methods|no| +|testPattern|\[a-z\]\[a-zA-Z0-9\]\*|Regex which applies to test method names|no| +|staticPattern|\[a-z\]\[a-zA-Z0-9\]\*|Regex which applies to static method names|no| +|instancePattern|\[a-z\]\[a-zA-Z0-9\]\*|Regex which applies to instance method names|no| **Use this rule with the default properties by just referencing it:** ``` xml @@ -163,7 +349,9 @@ public class Foo { ``` xml - + + + ``` @@ -220,8 +408,57 @@ Integer b; ``` +## PropertyNamingConventions + +**Since:** PMD 6.15.0 + +**Priority:** High (1) + +Configurable naming conventions for property declarations. This rule reports +property declarations which do not match the regex that applies to their +specific kind (e.g. static property, or instance property). Each regex can be +configured through properties. + +By default this rule uses the standard Apex naming convention (Camel case). + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.codestyle.PropertyNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/PropertyNamingConventionsRule.java) + +**Example(s):** + +``` java +public class Foo { + public Integer instanceProperty { get; set; } // This is in camel case, so it's ok + + public Integer INSTANCE_PROPERTY { get; set; } // This will be reported unless you change the regex +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|staticPattern|\[a-z\]\[a-zA-Z0-9\]\*|Regex which applies to static property names|no| +|instancePattern|\[a-z\]\[a-zA-Z0-9\]\*|Regex which applies to instance property names|no| + +**Use this rule with the default properties by just referencing it:** +``` xml + +``` + +**Use this rule and customize it:** +``` xml + + + + + + +``` + ## VariableNamingConventions +Deprecated + **Since:** PMD 5.5.0 **Priority:** High (1) @@ -230,6 +467,12 @@ A variable naming conventions rule - customize this to your liking. Currently, checks for final variables that should be fully capitalized and non-final variables that should not include underscores. +This rule is deprecated and will be removed with PMD 7.0.0. The rule is replaced +by the more general rules {% rule "apex/codestyle/FieldNamingConventions" %}, +{% rule "apex/codestyle/FormalParameterNamingConventions" %}, +{% rule "apex/codestyle/LocalVariableNamingConventions" %}, and +{% rule "apex/codestyle/PropertyNamingConventions" %}. + **This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.codestyle.VariableNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/VariableNamingConventionsRule.java) **Example(s):** diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index de9a686162..268048a8fc 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -19,8 +19,77 @@ This is a {{ site.pmd.release_type }} release. ### New and noteworthy +#### Enhanced Matlab support + +Thanks to the contributions from [Maikel Steneker](https://github.com/maikelsteneker) CPD for Matlab can +now parse Matlab programs which use the question mark operator to specify access to +class members: + +``` +classdef Class1 +properties (SetAccess = ?Class2) +``` + +CPD also understands now double quoted strings, which are supported since version R2017a of Matlab: + +``` +str = "This is a string" +``` + +#### Enhanced C++ support + +CPD now supports digit separators in C++ (language module "cpp"). This is a C++14 feature. + +Example: `auto integer_literal = 1'000'000;` + +The single quotes can be used to add some structure to large numbers. + +CPD also parses raw string literals now correctly (see [#1784](https://github.com/pmd/pmd/issues/1784)). + +#### New Rules + +* The new Apex rule {% rule "apex/codestyle/FieldNamingConventions" %} (`apex-codestyle`) checks the naming + conventions for field declarations. By default this rule uses the standard Apex naming convention (Camel case), + but it can be configured through properties. + +* The new Apex rule {% rule "apex/codestyle/FormalParameterNamingConventions" %} (`apex-codestyle`) checks the + naming conventions for formal parameters of methods. By default this rule uses the standard Apex naming + convention (Camel case), but it can be configured through properties. + +* The new Apex rule {% rule "apex/codestyle/LocalVariableNamingConventions" %} (`apex-codestyle`) checks the + naming conventions for local variable declarations. By default this rule uses the standard Apex naming + convention (Camel case), but it can be configured through properties. + +* The new Apex rule {% rule "apex/codestyle/PropertyNamingConventions" %} (`apex-codestyle`) checks the naming + conventions for property declarations. By default this rule uses the standard Apex naming convention (Camel case), + but it can be configured through properties. + +#### Modified Rules + +* The Apex rule {% rule "apex/codestyle/ClassNamingConventions" %} (`apex-codestyle`) can now be configured + using various properties for the specific kind of type declarations (e.g. class, interface, enum). + As before, this rule uses by default the standard Apex naming convention (Pascal case). + +* The Apex rule {% rule "apex/codestyle/MethodNamingConventions" %} (`apex-codestyle`) can now be configured + using various properties to differenciate e.g. static methods and test methods. + As before, this rule uses by default the standard Apex naming convention (Camel case). + +#### Deprecated Rules + +* The Apex rule {% rule "apex/codestyle/VariableNamingConventions" %} (`apex-codestyle`) has been deprecated and + will be removed with PMD 7.0.0. The rule is replaced by the more general rules + {% rule "apex/codestyle/FieldNamingConventions" %}, + {% rule "apex/codestyle/FormalParameterNamingConventions" %}, + {% rule "apex/codestyle/LocalVariableNamingConventions" %}, and + {% rule "apex/codestyle/PropertyNamingConventions" %}. + ### Fixed Issues +* apex + * [#1321](https://github.com/pmd/pmd/issues/1321): \[apex] Should VariableNamingConventions require properties to start with a lowercase letter? + * [#1783](https://github.com/pmd/pmd/issues/1783): \[apex] comments on constructor not recognized when the Class has inner class +* cpp + * [#1784](https://github.com/pmd/pmd/issues/1784): \[cpp] Improve support for raw string literals * dart * [#1809](https://github.com/pmd/pmd/issues/1809): \[dart] \[cpd] Parse error with escape sequences * java-bestpractices @@ -29,6 +98,8 @@ This is a {{ site.pmd.release_type }} release. * [#1804](https://github.com/pmd/pmd/issues/1804): \[java] NPE in UnnecessaryLocalBeforeReturnRule * python * [#1810](https://github.com/pmd/pmd/issues/1810): \[python] \[cpd] Parse error when using Python 2 backticks +* matlab + * [#1830](https://github.com/pmd/pmd/issues/1830): \[matlab] \[cpd] Parse error with comments ### API Changes @@ -38,6 +109,13 @@ This is a {{ site.pmd.release_type }} release. * [#1802](https://github.com/pmd/pmd/pull/1802): \[python] \[cpd] Add support for Python 2 backticks - [Maikel Steneker](https://github.com/maikelsteneker) * [#1803](https://github.com/pmd/pmd/pull/1803): \[dart] \[cpd] Dart escape sequences - [Maikel Steneker](https://github.com/maikelsteneker) * [#1807](https://github.com/pmd/pmd/pull/1807): \[ci] Fix missing local branch issues when executing pmd-regression-tester - [BBG](https://github.com/djydewang) +* [#1813](https://github.com/pmd/pmd/pull/1813): \[matlab] \[cpd] Matlab comments - [Maikel Steneker](https://github.com/maikelsteneker) +* [#1816](https://github.com/pmd/pmd/pull/1816): \[apex] Fix ApexDoc handling with inner classes - [Jeff Hube](https://github.com/jeffhube) +* [#1817](https://github.com/pmd/pmd/pull/1817): \[apex] Add configurable naming convention rules - [Jeff Hube](https://github.com/jeffhube) +* [#1819](https://github.com/pmd/pmd/pull/1819): \[cpp] \[cpd] Add support for digit separators - [Maikel Steneker](https://github.com/maikelsteneker) +* [#1820](https://github.com/pmd/pmd/pull/1820): \[cpp] \[cpd] Improve support for raw string literals - [Maikel Steneker](https://github.com/maikelsteneker) +* [#1821](https://github.com/pmd/pmd/pull/1821): \[matlab] \[cpd] Matlab question mark token - [Maikel Steneker](https://github.com/maikelsteneker) +* [#1822](https://github.com/pmd/pmd/pull/1822): \[matlab] \[cpd] Double quoted string - [Maikel Steneker](https://github.com/maikelsteneker) {% endtocmaker %} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClass.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClass.java index 87f94bc3ce..08a7eb2857 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClass.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClass.java @@ -4,11 +4,8 @@ package net.sourceforge.pmd.lang.apex.ast; -import java.lang.reflect.Field; - import net.sourceforge.pmd.Rule; -import apex.jorje.data.Identifier; import apex.jorje.semantic.ast.compilation.UserClass; public class ASTUserClass extends ApexRootNode implements ASTUserClassOrInterface, @@ -29,14 +26,8 @@ public class ASTUserClass extends ApexRootNode implements ASTUserClas @Override public String getImage() { - try { - Field field = node.getClass().getDeclaredField("name"); - field.setAccessible(true); - Identifier name = (Identifier) field.get(node); - return name.getValue(); - } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { - throw new RuntimeException(e); - } + String apexName = node.getDefiningType().getApexName(); + return apexName.substring(apexName.lastIndexOf('.') + 1); } @Override diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserEnum.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserEnum.java index 7249cba5ce..54e5034fdc 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserEnum.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserEnum.java @@ -19,7 +19,8 @@ public class ASTUserEnum extends ApexRootNode { @Override public String getImage() { - return node.getClass().getName(); + String apexName = node.getDefiningType().getApexName(); + return apexName.substring(apexName.lastIndexOf('.') + 1); } public ASTModifierNode getModifiers() { diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserInterface.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserInterface.java index af6ccb8100..645b6e02aa 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserInterface.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserInterface.java @@ -4,11 +4,8 @@ package net.sourceforge.pmd.lang.apex.ast; -import java.lang.reflect.Field; - import net.sourceforge.pmd.Rule; -import apex.jorje.data.Identifier; import apex.jorje.semantic.ast.compilation.UserInterface; public class ASTUserInterface extends ApexRootNode implements ASTUserClassOrInterface, @@ -27,14 +24,8 @@ public class ASTUserInterface extends ApexRootNode implements AST @Override public String getImage() { - try { - Field field = node.getClass().getDeclaredField("name"); - field.setAccessible(true); - Identifier name = (Identifier) field.get(node); - return name.getValue(); - } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { - throw new RuntimeException(e); - } + String apexName = node.getDefiningType().getApexName(); + return apexName.substring(apexName.lastIndexOf('.') + 1); } @Override diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserTrigger.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserTrigger.java index 3360750889..66c9b32004 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserTrigger.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserTrigger.java @@ -4,9 +4,6 @@ package net.sourceforge.pmd.lang.apex.ast; -import java.lang.reflect.Field; - -import apex.jorje.data.Identifier; import apex.jorje.semantic.ast.compilation.UserTrigger; public class ASTUserTrigger extends ApexRootNode { @@ -22,14 +19,7 @@ public class ASTUserTrigger extends ApexRootNode { @Override public String getImage() { - try { - Field field = node.getClass().getDeclaredField("name"); - field.setAccessible(true); - Identifier name = (Identifier) field.get(node); - return name.getValue(); - } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { - throw new RuntimeException(e); - } + return node.getDefiningType().getApexName(); } public ASTModifierNode getModifiers() { diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeBuilder.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeBuilder.java index fc44166cb6..75a5fc0904 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeBuilder.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeBuilder.java @@ -6,10 +6,9 @@ package net.sourceforge.pmd.lang.apex.ast; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; -import java.util.ListIterator; import java.util.Map; import java.util.Stack; @@ -19,6 +18,8 @@ import org.antlr.runtime.Token; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.SourceCodePositioner; +import apex.jorje.data.Location; +import apex.jorje.data.Locations; import apex.jorje.parser.impl.ApexLexer; import apex.jorje.semantic.ast.AstNode; import apex.jorje.semantic.ast.compilation.AnonymousClass; @@ -221,17 +222,17 @@ public final class ApexTreeBuilder extends AstVisitor { // The Apex nodes with children to build. private Stack parents = new Stack<>(); - + private AdditionalPassScope scope = new AdditionalPassScope(Errors.createErrors()); private final SourceCodePositioner sourceCodePositioner; private final String sourceCode; - private ListIterator apexDocTokenLocations; + private List apexDocTokenLocations; public ApexTreeBuilder(String sourceCode) { this.sourceCode = sourceCode; sourceCodePositioner = new SourceCodePositioner(sourceCode); - apexDocTokenLocations = buildApexDocTokenLocations(sourceCode).listIterator(); + apexDocTokenLocations = buildApexDocTokenLocations(sourceCode); } static AbstractApexNode createNodeAdapter(T node) { @@ -273,60 +274,87 @@ public final class ApexTreeBuilder extends AstVisitor { nodes.pop(); parents.pop(); + if (nodes.isEmpty()) { + // add the comments only at the end of the processing as the last step + addFormalComments(); + } + return node; } - private void buildFormalComment(AstNode node) { - if (parents.peek() == node) { - ApexNode parent = (ApexNode) nodes.peek(); - TokenLocation tokenLocation = getApexDocTokenLocation(getApexDocIndex(parent)); - if (tokenLocation != null) { + private void addFormalComments() { + for (ApexDocTokenLocation tokenLocation : apexDocTokenLocations) { + ApexNode parent = tokenLocation.nearestNode; + if (parent != null) { ASTFormalComment comment = new ASTFormalComment(tokenLocation.token); comment.calculateLineNumbers(sourceCodePositioner, tokenLocation.index, tokenLocation.index + tokenLocation.token.length()); + + // move existing nodes so that we can insert the comment as the first node + for (int i = parent.jjtGetNumChildren(); i > 0; i--) { + parent.jjtAddChild(parent.jjtGetChild(i - 1), i); + } + parent.jjtAddChild(comment, 0); comment.jjtSetParent(parent); } } } - private int getApexDocIndex(ApexNode node) { - final int index = node.getNode().getLoc().getStartIndex(); - return sourceCode.lastIndexOf('\n', index); - } - - private TokenLocation getApexDocTokenLocation(int index) { - TokenLocation last = null; - while (apexDocTokenLocations.hasNext()) { - final TokenLocation location = apexDocTokenLocations.next(); - if (location.index >= index) { - // rollback, the next token corresponds to a different node - apexDocTokenLocations.previous(); - - if (last != null) { - return last; - } - return null; - } - last = location; + private void buildFormalComment(AstNode node) { + if (parents.peek() == node) { + ApexNode parent = (ApexNode) nodes.peek(); + assignApexDocTokenToNode(node, parent); } - return last; } - private static List buildApexDocTokenLocations(String source) { + /** + * Only remembers the node, to which the comment could belong. + * Since the visiting order of the nodes does not match the source order, + * the nodes appearing later in the source might be visiting first. + * The correct node will then be visited afterwards, and since the distance + * to the comment is smaller, it overrides the remembered node. + * @param jorjeNode the original node + * @param node the potential parent node, to which the comment could belong + */ + private void assignApexDocTokenToNode(AstNode jorjeNode, ApexNode node) { + Location loc = jorjeNode.getLoc(); + if (!Locations.isReal(loc)) { + // Synthetic nodes such as "" don't have a location in the + // source code, since they are generated by the compiler + return; + } + // find the token, that appears as close as possible before the node + int nodeStart = loc.getStartIndex(); + for (ApexDocTokenLocation tokenLocation : apexDocTokenLocations) { + if (tokenLocation.index > nodeStart) { + // this and all remaining tokens are after the node + // so no need to check the remaining tokens. + break; + } + + int distance = nodeStart - tokenLocation.index; + if (tokenLocation.nearestNode == null || distance < tokenLocation.nearestNodeDistance) { + tokenLocation.nearestNode = node; + tokenLocation.nearestNodeDistance = distance; + } + } + } + + private static List buildApexDocTokenLocations(String source) { ANTLRStringStream stream = new ANTLRStringStream(source); ApexLexer lexer = new ApexLexer(stream); - ArrayList tokenLocations = new ArrayList<>(); + List tokenLocations = new LinkedList<>(); int startIndex = 0; Token token = lexer.nextToken(); int endIndex = lexer.getCharIndex(); + while (token.getType() != Token.EOF) { if (token.getType() == ApexLexer.BLOCK_COMMENT) { - // Filter only block comments starting with "/**" if (token.getText().startsWith("/**")) { - tokenLocations.add(new TokenLocation(startIndex, token.getText())); + tokenLocations.add(new ApexDocTokenLocation(startIndex, token.getText())); } } // TODO : Check other non-doc comments and tokens of type ApexLexer.EOL_COMMENT for "NOPMD" suppressions @@ -338,11 +366,13 @@ public final class ApexTreeBuilder extends AstVisitor { return tokenLocations; } - private static class TokenLocation { + private static class ApexDocTokenLocation { int index; String token; + ApexNode nearestNode; + int nearestNodeDistance; - TokenLocation(int index, String token) { + ApexDocTokenLocation(int index, String token) { this.index = index; this.token = token; } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/AbstractNamingConventionsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/AbstractNamingConventionsRule.java new file mode 100644 index 0000000000..53e6fa6894 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/AbstractNamingConventionsRule.java @@ -0,0 +1,46 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import static net.sourceforge.pmd.properties.PropertyFactory.regexProperty; + +import java.util.Map; +import java.util.regex.Pattern; + +import net.sourceforge.pmd.lang.apex.ast.ApexNode; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; +import net.sourceforge.pmd.properties.PropertyBuilder; +import net.sourceforge.pmd.properties.PropertyDescriptor; + +abstract class AbstractNamingConventionsRule extends AbstractApexRule { + protected static final Pattern CAMEL_CASE = Pattern.compile("[a-z][a-zA-Z0-9]*"); + protected static final Pattern CAMEL_CASE_WITH_UNDERSCORES = Pattern.compile("[a-z][a-zA-Z0-9_]*"); + protected static final Pattern PASCAL_CASE_WITH_UNDERSCORES = Pattern.compile("[A-Z][a-zA-Z0-9_]*"); + protected static final Pattern ALL_CAPS = Pattern.compile("[A-Z][A-Z0-9_]*"); + + abstract String displayName(String name); + + protected void checkMatches(PropertyDescriptor propertyDescriptor, ApexNode node, Object data) { + Pattern regex = getProperty(propertyDescriptor); + String name = node.getImage(); + if (!regex.matcher(name).matches()) { + String displayName = displayName(propertyDescriptor.name()); + addViolation(data, node, new Object[] { displayName, name, regex.toString() }); + } + } + + protected void checkMatches(PropertyDescriptor propertyDescriptor, Pattern overridePattern, ApexNode node, Object data) { + String name = node.getImage(); + if (!overridePattern.matcher(name).matches()) { + String displayName = displayName(propertyDescriptor.name()); + addViolation(data, node, new Object[] { displayName, name, overridePattern.toString() }); + } + } + + protected static PropertyBuilder.RegexPropertyBuilder prop(String name, String displayName, Map descriptorToDisplayNames) { + descriptorToDisplayNames.put(name, displayName); + return regexProperty(name).desc("Regex which applies to " + displayName + " names"); + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/ClassNamingConventionsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/ClassNamingConventionsRule.java index 1d1529793e..a2e235f127 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/ClassNamingConventionsRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/ClassNamingConventionsRule.java @@ -4,25 +4,74 @@ package net.sourceforge.pmd.lang.apex.rule.codestyle; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; -import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface; -import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; -public class ClassNamingConventionsRule extends AbstractApexRule { +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ASTUserEnum; +import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface; +import net.sourceforge.pmd.properties.PropertyDescriptor; + +public class ClassNamingConventionsRule extends AbstractNamingConventionsRule { + private static final Map DESCRIPTOR_TO_DISPLAY_NAME = new HashMap<>(); + + private static final PropertyDescriptor TEST_CLASS_REGEX = prop("testClassPattern", "test class", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(PASCAL_CASE_WITH_UNDERSCORES).build(); + + private static final PropertyDescriptor ABSTRACT_CLASS_REGEX = prop("abstractClassPattern", "abstract class", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(PASCAL_CASE_WITH_UNDERSCORES).build(); + + private static final PropertyDescriptor CLASS_REGEX = prop("classPattern", "class", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(PASCAL_CASE_WITH_UNDERSCORES).build(); + + private static final PropertyDescriptor INTERFACE_REGEX = prop("interfacePattern", "interface", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(PASCAL_CASE_WITH_UNDERSCORES).build(); + + private static final PropertyDescriptor ENUM_REGEX = prop("enumPattern", "enum", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(PASCAL_CASE_WITH_UNDERSCORES).build(); + + public ClassNamingConventionsRule() { + definePropertyDescriptor(TEST_CLASS_REGEX); + definePropertyDescriptor(ABSTRACT_CLASS_REGEX); + definePropertyDescriptor(CLASS_REGEX); + definePropertyDescriptor(INTERFACE_REGEX); + definePropertyDescriptor(ENUM_REGEX); + + addRuleChainVisit(ASTUserClass.class); + addRuleChainVisit(ASTUserInterface.class); + addRuleChainVisit(ASTUserEnum.class); + } @Override public Object visit(ASTUserClass node, Object data) { - if (Character.isLowerCase(node.getImage().charAt(0))) { - addViolation(data, node); + if (node.getModifiers().isTest()) { + checkMatches(TEST_CLASS_REGEX, node, data); + } else if (node.getModifiers().isAbstract()) { + checkMatches(ABSTRACT_CLASS_REGEX, node, data); + } else { + checkMatches(CLASS_REGEX, node, data); } + return data; } @Override public Object visit(ASTUserInterface node, Object data) { - if (Character.isLowerCase(node.getImage().charAt(0))) { - addViolation(data, node); - } + checkMatches(INTERFACE_REGEX, node, data); + return data; } + + @Override + public Object visit(ASTUserEnum node, Object data) { + checkMatches(ENUM_REGEX, node, data); + + return data; + } + + @Override + protected String displayName(String name) { + return DESCRIPTOR_TO_DISPLAY_NAME.get(name); + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FieldNamingConventionsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FieldNamingConventionsRule.java new file mode 100644 index 0000000000..d5c19968dc --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FieldNamingConventionsRule.java @@ -0,0 +1,77 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import net.sourceforge.pmd.lang.apex.ast.ASTField; +import net.sourceforge.pmd.lang.apex.ast.ASTModifierNode; +import net.sourceforge.pmd.lang.apex.ast.ASTProperty; +import net.sourceforge.pmd.lang.apex.ast.ASTUserEnum; +import net.sourceforge.pmd.properties.PropertyDescriptor; + +public class FieldNamingConventionsRule extends AbstractNamingConventionsRule { + private static final Map DESCRIPTOR_TO_DISPLAY_NAME = new HashMap<>(); + + private static final PropertyDescriptor ENUM_CONSTANT_REGEX = prop("enumConstantPattern", "enum constant field", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(ALL_CAPS).build(); + + private static final PropertyDescriptor CONSTANT_REGEX = prop("constantPattern", "constant field", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(ALL_CAPS).build(); + + private static final PropertyDescriptor FINAL_REGEX = prop("finalPattern", "final field", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(CAMEL_CASE).build(); + + private static final PropertyDescriptor STATIC_REGEX = prop("staticPattern", "static field", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(CAMEL_CASE).build(); + + private static final PropertyDescriptor INSTANCE_REGEX = prop("instancePattern", "instance field", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(CAMEL_CASE).build(); + + public FieldNamingConventionsRule() { + definePropertyDescriptor(ENUM_CONSTANT_REGEX); + definePropertyDescriptor(CONSTANT_REGEX); + definePropertyDescriptor(FINAL_REGEX); + definePropertyDescriptor(STATIC_REGEX); + definePropertyDescriptor(INSTANCE_REGEX); + + setProperty(CODECLIMATE_CATEGORIES, "Style"); + // Note: x10 as Apex has not automatic refactoring + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 1); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + + addRuleChainVisit(ASTField.class); + } + + @Override + public Object visit(ASTField node, Object data) { + if (node.getFirstParentOfType(ASTProperty.class) != null) { + return data; + } + + ASTModifierNode modifiers = node.getModifiers(); + + if (node.getFirstParentOfType(ASTUserEnum.class) != null) { + checkMatches(ENUM_CONSTANT_REGEX, node, data); + } else if (modifiers.isFinal() && modifiers.isStatic()) { + checkMatches(CONSTANT_REGEX, node, data); + } else if (modifiers.isFinal()) { + checkMatches(FINAL_REGEX, node, data); + } else if (modifiers.isStatic()) { + checkMatches(STATIC_REGEX, node, data); + } else { + checkMatches(INSTANCE_REGEX, node, data); + } + + return data; + } + + @Override + protected String displayName(String name) { + return DESCRIPTOR_TO_DISPLAY_NAME.get(name); + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FormalParameterNamingConventionsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FormalParameterNamingConventionsRule.java new file mode 100644 index 0000000000..8c611cb2e3 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FormalParameterNamingConventionsRule.java @@ -0,0 +1,55 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import net.sourceforge.pmd.lang.apex.ast.ASTParameter; +import net.sourceforge.pmd.properties.PropertyDescriptor; + +public class FormalParameterNamingConventionsRule extends AbstractNamingConventionsRule { + private static final Map DESCRIPTOR_TO_DISPLAY_NAME = new HashMap<>(); + + private static final PropertyDescriptor FINAL_METHOD_PARAMETER_REGEX = prop("finalMethodParameterPattern", "final method parameter", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(CAMEL_CASE).build(); + + private static final PropertyDescriptor METHOD_PARAMETER_REGEX = prop("methodParameterPattern", "method parameter", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(CAMEL_CASE).build(); + + public FormalParameterNamingConventionsRule() { + definePropertyDescriptor(FINAL_METHOD_PARAMETER_REGEX); + definePropertyDescriptor(METHOD_PARAMETER_REGEX); + + setProperty(CODECLIMATE_CATEGORIES, "Style"); + // Note: x10 as Apex has not automatic refactoring + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 1); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + + addRuleChainVisit(ASTParameter.class); + } + + @Override + public Object visit(ASTParameter node, Object data) { + // classes that extend Exception will contains methods that have parameters with null names + if (node.getImage() == null) { + return data; + } + + if (node.getModifiers().isFinal()) { + checkMatches(FINAL_METHOD_PARAMETER_REGEX, node, data); + } else { + checkMatches(METHOD_PARAMETER_REGEX, node, data); + } + + return data; + } + + @Override + protected String displayName(String name) { + return DESCRIPTOR_TO_DISPLAY_NAME.get(name); + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/LocalVariableNamingConventionsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/LocalVariableNamingConventionsRule.java new file mode 100644 index 0000000000..ee19980719 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/LocalVariableNamingConventionsRule.java @@ -0,0 +1,51 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclaration; +import net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclarationStatements; +import net.sourceforge.pmd.properties.PropertyDescriptor; + +public class LocalVariableNamingConventionsRule extends AbstractNamingConventionsRule { + private static final Map DESCRIPTOR_TO_DISPLAY_NAME = new HashMap<>(); + + private static final PropertyDescriptor FINAL_REGEX = prop("finalLocalPattern", "final local variable", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(CAMEL_CASE).build(); + + private static final PropertyDescriptor LOCAL_REGEX = prop("localPattern", "local variable", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(CAMEL_CASE).build(); + + public LocalVariableNamingConventionsRule() { + definePropertyDescriptor(FINAL_REGEX); + definePropertyDescriptor(LOCAL_REGEX); + + setProperty(CODECLIMATE_CATEGORIES, "Style"); + // Note: x10 as Apex has not automatic refactoring + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 1); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + + addRuleChainVisit(ASTVariableDeclaration.class); + } + + @Override + public Object visit(ASTVariableDeclaration node, Object data) { + if (node.getFirstParentOfType(ASTVariableDeclarationStatements.class).getModifiers().isFinal()) { + checkMatches(FINAL_REGEX, node, data); + } else { + checkMatches(LOCAL_REGEX, node, data); + } + + return data; + } + + @Override + protected String displayName(String name) { + return DESCRIPTOR_TO_DISPLAY_NAME.get(name); + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/MethodNamingConventionsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/MethodNamingConventionsRule.java index 23d2d8ec63..9c551f951d 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/MethodNamingConventionsRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/MethodNamingConventionsRule.java @@ -6,21 +6,39 @@ package net.sourceforge.pmd.lang.apex.rule.codestyle; import static net.sourceforge.pmd.properties.PropertyFactory.booleanProperty; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + import net.sourceforge.pmd.lang.apex.ast.ASTMethod; import net.sourceforge.pmd.lang.apex.ast.ASTProperty; -import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; +import net.sourceforge.pmd.lang.apex.ast.ASTUserEnum; import net.sourceforge.pmd.properties.PropertyDescriptor; -public class MethodNamingConventionsRule extends AbstractApexRule { +public class MethodNamingConventionsRule extends AbstractNamingConventionsRule { + private static final Map DESCRIPTOR_TO_DISPLAY_NAME = new HashMap<>(); + + private static final PropertyDescriptor TEST_REGEX = prop("testPattern", "test method", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(CAMEL_CASE).build(); + + private static final PropertyDescriptor STATIC_REGEX = prop("staticPattern", "static method", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(CAMEL_CASE).build(); + + private static final PropertyDescriptor INSTANCE_REGEX = prop("instancePattern", "instance method", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(CAMEL_CASE).build(); private static final PropertyDescriptor SKIP_TEST_METHOD_UNDERSCORES_DESCRIPTOR = booleanProperty("skipTestMethodUnderscores") - .desc("Skip underscores in test methods") + .desc("deprecated! Skip underscores in test methods") .defaultValue(false) .build(); public MethodNamingConventionsRule() { definePropertyDescriptor(SKIP_TEST_METHOD_UNDERSCORES_DESCRIPTOR); + definePropertyDescriptor(TEST_REGEX); + definePropertyDescriptor(STATIC_REGEX); + definePropertyDescriptor(INSTANCE_REGEX); + addRuleChainVisit(ASTMethod.class); } @@ -29,21 +47,35 @@ public class MethodNamingConventionsRule extends AbstractApexRule { if (isOverriddenMethod(node) || isPropertyAccessor(node) || isConstructor(node)) { return data; } - if (isTestMethod(node) && getProperty(SKIP_TEST_METHOD_UNDERSCORES_DESCRIPTOR)) { + + if ("".equals(node.getImage()) || "clone".equals(node.getImage())) { return data; } - String methodName = node.getImage(); + if (node.getFirstParentOfType(ASTUserEnum.class) != null) { + return data; + } - if (Character.isUpperCase(methodName.charAt(0))) { - addViolationWithMessage(data, node, "Method names should not start with capital letters"); - } - if (methodName.indexOf('_') >= 0) { - addViolationWithMessage(data, node, "Method names should not contain underscores"); + if (node.getModifiers().isTest()) { + if (getProperty(SKIP_TEST_METHOD_UNDERSCORES_DESCRIPTOR)) { + checkMatches(TEST_REGEX, CAMEL_CASE_WITH_UNDERSCORES, node, data); + } else { + checkMatches(TEST_REGEX, node, data); + } + } else if (node.getModifiers().isStatic()) { + checkMatches(STATIC_REGEX, node, data); + } else { + checkMatches(INSTANCE_REGEX, node, data); } + return data; } + @Override + protected String displayName(String name) { + return DESCRIPTOR_TO_DISPLAY_NAME.get(name); + } + private boolean isOverriddenMethod(ASTMethod node) { return node.getModifiers().isOverride(); } @@ -55,8 +87,4 @@ public class MethodNamingConventionsRule extends AbstractApexRule { private boolean isConstructor(ASTMethod node) { return node.isConstructor(); } - - private boolean isTestMethod(ASTMethod node) { - return node.getModifiers().isTest(); - } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/PropertyNamingConventionsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/PropertyNamingConventionsRule.java new file mode 100644 index 0000000000..70ad5594e1 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/PropertyNamingConventionsRule.java @@ -0,0 +1,55 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import net.sourceforge.pmd.lang.apex.ast.ASTField; +import net.sourceforge.pmd.lang.apex.ast.ASTProperty; +import net.sourceforge.pmd.properties.PropertyDescriptor; + +public class PropertyNamingConventionsRule extends AbstractNamingConventionsRule { + private static final Map DESCRIPTOR_TO_DISPLAY_NAME = new HashMap<>(); + + private static final PropertyDescriptor STATIC_REGEX = prop("staticPattern", "static property", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(CAMEL_CASE).build(); + + private static final PropertyDescriptor INSTANCE_REGEX = prop("instancePattern", "instance property", + DESCRIPTOR_TO_DISPLAY_NAME).defaultValue(CAMEL_CASE).build(); + + public PropertyNamingConventionsRule() { + definePropertyDescriptor(STATIC_REGEX); + definePropertyDescriptor(INSTANCE_REGEX); + + setProperty(CODECLIMATE_CATEGORIES, "Style"); + // Note: x10 as Apex has not automatic refactoring + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 1); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + + addRuleChainVisit(ASTField.class); + } + + @Override + public Object visit(ASTField node, Object data) { + if (node.getFirstParentOfType(ASTProperty.class) == null) { + return data; + } + + if (node.getModifiers().isStatic()) { + checkMatches(STATIC_REGEX, node, data); + } else { + checkMatches(INSTANCE_REGEX, node, data); + } + + return data; + } + + @Override + protected String displayName(String name) { + return DESCRIPTOR_TO_DISPLAY_NAME.get(name); + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/VariableNamingConventionsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/VariableNamingConventionsRule.java index 830403adaf..b5ba653d29 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/VariableNamingConventionsRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/VariableNamingConventionsRule.java @@ -21,6 +21,7 @@ import net.sourceforge.pmd.lang.apex.ast.ApexNode; import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; import net.sourceforge.pmd.properties.PropertyDescriptor; +@Deprecated public class VariableNamingConventionsRule extends AbstractApexRule { private boolean checkMembers; diff --git a/pmd-apex/src/main/resources/category/apex/codestyle.xml b/pmd-apex/src/main/resources/category/apex/codestyle.xml index ef53ac90d0..262b6f9757 100644 --- a/pmd-apex/src/main/resources/category/apex/codestyle.xml +++ b/pmd-apex/src/main/resources/category/apex/codestyle.xml @@ -11,16 +11,23 @@ Rules which enforce a specific coding style. -Class names should always begin with an upper case character. + Configurable naming conventions for type declarations. This rule reports + type declarations which do not match the regex that applies to their + specific kind (e.g. enum or interface). Each regex can be configured through + properties. + + By default this rule uses the standard Apex naming convention (Pascal case). 1 - @@ -95,6 +102,30 @@ if (foo) { // preferred approach + + + Configurable naming conventions for field declarations. This rule reports variable declarations + which do not match the regex that applies to their specific kind ---e.g. constants (static final), + static field, final field. Each regex can be configured through properties. + + By default this rule uses the standard Apex naming convention (Camel case). + + 1 + + + + + - + -Method names should always begin with a lower case character, and should not contain underscores. + Configurable naming conventions for formal parameters of methods. + This rule reports formal parameters which do not match the regex that applies to their + specific kind (e.g. method parameter, or final method parameter). Each regex can be + configured through properties. + + By default this rule uses the standard Apex naming convention (Camel case). 1 + + + + + + Configurable naming conventions for local variable declarations. + This rule reports variable declarations which do not match the regex that applies to their + specific kind (e.g. local variable, or final local variable). Each regex can be configured through + properties. + + By default this rule uses the standard Apex naming convention (Camel case). + + 1 + + + + + Configurable naming conventions for method declarations. This rule reports + method declarations which do not match the regex that applies to their + specific kind (e.g. static method, or test method). Each regex can be + configured through properties. + + By default this rule uses the standard Apex naming convention (Camel case). + + 1 + + + + + + + + Configurable naming conventions for property declarations. This rule reports + property declarations which do not match the regex that applies to their + specific kind (e.g. static property, or instance property). Each regex can be + configured through properties. + + By default this rule uses the standard Apex naming convention (Camel case). + + 1 + + + + + @@ -199,6 +313,12 @@ Integer b; A variable naming conventions rule - customize this to your liking. Currently, it checks for final variables that should be fully capitalized and non-final variables that should not include underscores. + +This rule is deprecated and will be removed with PMD 7.0.0. The rule is replaced +by the more general rules {% rule "apex/codestyle/FieldNamingConventions" %}, +{% rule "apex/codestyle/FormalParameterNamingConventions" %}, +{% rule "apex/codestyle/LocalVariableNamingConventions" %}, and +{% rule "apex/codestyle/PropertyNamingConventions" %}. 1 diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClassTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClassTest.java new file mode 100644 index 0000000000..7e2ea41201 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClassTest.java @@ -0,0 +1,31 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.ast; + +import static net.sourceforge.pmd.lang.apex.ast.ApexParserTestHelpers.parse; + +import org.junit.Assert; +import org.junit.Test; + +import apex.jorje.semantic.ast.compilation.Compilation; + +public class ASTUserClassTest { + + @Test + public void testClassName() { + ApexNode node = parse("class Foo { }"); + Assert.assertSame(ASTUserClass.class, node.getClass()); + Assert.assertEquals("Foo", node.getImage()); + } + + @Test + public void testInnerClassName() { + ApexNode node = parse("class Foo { class Bar { } }"); + Assert.assertSame(ASTUserClass.class, node.getClass()); + ASTUserClass innerNode = node.getFirstDescendantOfType(ASTUserClass.class); + Assert.assertNotNull(innerNode); + Assert.assertEquals("Bar", innerNode.getImage()); + } +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ASTUserEnumTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ASTUserEnumTest.java new file mode 100644 index 0000000000..827833e4bd --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ASTUserEnumTest.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.ast; + +import static net.sourceforge.pmd.lang.apex.ast.ApexParserTestHelpers.parse; + +import org.junit.Assert; +import org.junit.Test; + +import apex.jorje.semantic.ast.compilation.Compilation; + +public class ASTUserEnumTest { + + @Test + public void testEnumName() { + ApexNode node = parse("class Foo { enum Bar { } }"); + Assert.assertSame(ASTUserClass.class, node.getClass()); + ASTUserEnum enumNode = node.getFirstDescendantOfType(ASTUserEnum.class); + Assert.assertNotNull(enumNode); + Assert.assertEquals("Bar", enumNode.getImage()); + } +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ASTUserInterfaceTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ASTUserInterfaceTest.java new file mode 100644 index 0000000000..f7944db734 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ASTUserInterfaceTest.java @@ -0,0 +1,31 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.ast; + +import static net.sourceforge.pmd.lang.apex.ast.ApexParserTestHelpers.parse; + +import org.junit.Assert; +import org.junit.Test; + +import apex.jorje.semantic.ast.compilation.Compilation; + +public class ASTUserInterfaceTest { + + @Test + public void testInterfaceName() { + ApexNode node = parse("interface Foo { }"); + Assert.assertSame(ASTUserInterface.class, node.getClass()); + Assert.assertEquals("Foo", node.getImage()); + } + + @Test + public void testInnerInterfaceName() { + ApexNode node = parse("class Foo { interface Bar { } }"); + Assert.assertSame(ASTUserClass.class, node.getClass()); + ASTUserInterface innerNode = node.getFirstDescendantOfType(ASTUserInterface.class); + Assert.assertNotNull(innerNode); + Assert.assertEquals("Bar", innerNode.getImage()); + } +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FieldNamingConventionsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FieldNamingConventionsTest.java new file mode 100644 index 0000000000..823a7f32fc --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FieldNamingConventionsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class FieldNamingConventionsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FormalParameterNamingConventionsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FormalParameterNamingConventionsTest.java new file mode 100644 index 0000000000..45dba2964e --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/FormalParameterNamingConventionsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class FormalParameterNamingConventionsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/LocalVariableNamingConventionsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/LocalVariableNamingConventionsTest.java new file mode 100644 index 0000000000..6ddf91a354 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/LocalVariableNamingConventionsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class LocalVariableNamingConventionsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/PropertyNamingConventionsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/PropertyNamingConventionsTest.java new file mode 100644 index 0000000000..413e80ed09 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/PropertyNamingConventionsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class PropertyNamingConventionsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/ClassNamingConventions.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/ClassNamingConventions.xml index 4f9f93b79f..8817e4f30b 100644 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/ClassNamingConventions.xml +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/ClassNamingConventions.xml @@ -20,5 +20,143 @@ public class foo {}; public class FooBar {}; ]]> + + + test class all is well + 0 + + + + + abstract class all is well + 0 + + + + + class all is well + 0 + + + + + interface all is well + 0 + + + + + enum all is well + 0 + + + + + test class default is title case + 1 + + The test class name 'testClass' doesn't match '[A-Z][a-zA-Z0-9_]*' + + + + + + abstract class default is title case + 1 + + The abstract class name 'abstractClass' doesn't match '[A-Z][a-zA-Z0-9_]*' + + + + + + class default is title case + 1 + + The class name 'fooClass' doesn't match '[A-Z][a-zA-Z0-9_]*' + + + + + + interface default is title case + 1 + + The interface name 'fooInterface' doesn't match '[A-Z][a-zA-Z0-9_]*' + + + + + + enum default is title case + 1 + + The enum name 'fooEnum' doesn't match '[A-Z][a-zA-Z0-9_]*' + + + + + + custom test class pattern + [a-zA-Z0-9_]+ + 0 + + + + + custom abstract class pattern + [a-zA-Z0-9_]+ + 0 + + + + + custom class pattern + [a-zA-Z0-9_]+ + 0 + + + + + custom interface pattern + [a-zA-Z0-9_]+ + 0 + + diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/FieldNamingConventions.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/FieldNamingConventions.xml new file mode 100644 index 0000000000..b5de65a6b6 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/FieldNamingConventions.xml @@ -0,0 +1,157 @@ + + + + + + all is well + 0 + + + + + default is all caps for constants, camel case for others + 4 + + The constant field name 'constantField' doesn't match '[A-Z][A-Z0-9_]*' + The final field name 'FINAL_FIELD' doesn't match '[a-z][a-zA-Z0-9]*' + The static field name 'STATIC_FIELD' doesn't match '[a-z][a-zA-Z0-9]*' + The instance field name 'INSTANCE_FIELD' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + enum default is all caps and underscores + 1 + + The enum constant field name 'default' doesn't match '[A-Z][A-Z0-9_]*' + + + + + + ignores properties + 0 + + + + + custom enum constant pattern + [a-zA-Z0-9]+ + 0 + + + + + custom constant pattern + [a-zA-Z0-9_]+ + 3 + + The final field name 'FINAL_FIELD' doesn't match '[a-z][a-zA-Z0-9]*' + The static field name 'STATIC_FIELD' doesn't match '[a-z][a-zA-Z0-9]*' + The instance field name 'INSTANCE_FIELD' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + custom final pattern + [a-zA-Z0-9_]+ + 3 + + The constant field name 'constantField' doesn't match '[A-Z][A-Z0-9_]*' + The static field name 'STATIC_FIELD' doesn't match '[a-z][a-zA-Z0-9]*' + The instance field name 'INSTANCE_FIELD' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + custom static pattern + [a-zA-Z0-9_]+ + 3 + + The constant field name 'constantField' doesn't match '[A-Z][A-Z0-9_]*' + The final field name 'FINAL_FIELD' doesn't match '[a-z][a-zA-Z0-9]*' + The instance field name 'INSTANCE_FIELD' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + custom instance pattern + [a-zA-Z0-9_]+ + 3 + + The constant field name 'constantField' doesn't match '[A-Z][A-Z0-9_]*' + The final field name 'FINAL_FIELD' doesn't match '[a-z][a-zA-Z0-9]*' + The static field name 'STATIC_FIELD' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/FormalParameterNamingConventions.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/FormalParameterNamingConventions.xml new file mode 100644 index 0000000000..7cc3fc943d --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/FormalParameterNamingConventions.xml @@ -0,0 +1,68 @@ + + + + + + all is well + 0 + + + + + default is camel case + 2 + + The final method parameter name 'FINAL_METHOD_PARAMETER' doesn't match '[a-z][a-zA-Z0-9]*' + The method parameter name 'METHOD_PARAMETER' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + custom final method parameter pattern + [a-zA-Z0-9_]+ + 1 + + The method parameter name 'METHOD_PARAMETER' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + custom method parameter pattern + [a-zA-Z0-9_]+ + 1 + + The final method parameter name 'FINAL_METHOD_PARAMETER' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + ignores null parameter names + 0 + + + + diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/LocalVariableNamingConventions.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/LocalVariableNamingConventions.xml new file mode 100644 index 0000000000..ff5098dfd2 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/LocalVariableNamingConventions.xml @@ -0,0 +1,72 @@ + + + + + + all is well + 0 + + + + + default is camel case + 2 + + The final local variable name 'FINAL_LOCAL_VARIABLE' doesn't match '[a-z][a-zA-Z0-9]*' + The local variable name 'LOCAL_VARIABLE' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + custom final local pattern + [a-zA-Z0-9_]+ + 1 + + The local variable name 'LOCAL_VARIABLE' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + custom local pattern + [a-zA-Z0-9_]+ + 1 + + The final local variable name 'FINAL_LOCAL_VARIABLE' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/MethodNamingConventions.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/MethodNamingConventions.xml index 0a1798e969..ea9db4a8d0 100644 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/MethodNamingConventions.xml +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/MethodNamingConventions.xml @@ -100,4 +100,131 @@ public class Foo { } ]]> + + + all is well + 0 + + + + + default is camel case + 3 + + The test method name 'TEST_METHOD' doesn't match '[a-z][a-zA-Z0-9]*' + The static method name 'STATIC_METHOD' doesn't match '[a-z][a-zA-Z0-9]*' + The instance method name 'INSTANCE_METHOD' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + ignores overrides + 0 + + + + + ignores properties + 0 + + + + + ignores constructors + 0 + + + + + ignores enum methods + Z + 0 + + + + + custom test method pattern + [a-zA-Z0-9_]+ + 2 + + The static method name 'STATIC_METHOD' doesn't match '[a-z][a-zA-Z0-9]*' + The instance method name 'INSTANCE_METHOD' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + custom static method pattern + [a-zA-Z0-9_]+ + 2 + + The test method name 'TEST_METHOD' doesn't match '[a-z][a-zA-Z0-9]*' + The instance method name 'INSTANCE_METHOD' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + custom instance method pattern + [a-zA-Z0-9_]+ + 2 + + The test method name 'TEST_METHOD' doesn't match '[a-z][a-zA-Z0-9]*' + The static method name 'STATIC_METHOD' doesn't match '[a-z][a-zA-Z0-9]*' + + + diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/PropertyNamingConventions.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/PropertyNamingConventions.xml new file mode 100644 index 0000000000..4fdd885597 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/PropertyNamingConventions.xml @@ -0,0 +1,74 @@ + + + + + + all is well + 0 + + + + + default is camel case + 2 + + The static property name 'STATIC_PROPERTY' doesn't match '[a-z][a-zA-Z0-9]*' + The instance property name 'INSTANCE_PROPERTY' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + ignores methods + 0 + + + + + custom static property pattern + [a-zA-Z0-9_]+ + 1 + + The instance property name 'INSTANCE_PROPERTY' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + custom instance property pattern + [a-zA-Z0-9_]+ + 1 + + The static property name 'STATIC_PROPERTY' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/documentation/xml/ApexDoc.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/documentation/xml/ApexDoc.xml index 0e890cd274..28946ce929 100644 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/documentation/xml/ApexDoc.xml +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/documentation/xml/ApexDoc.xml @@ -52,6 +52,10 @@ private class Foo { } class comment should have description 1 + 3 + + Missing ApexDoc @description + + + #1783 correct comments for constructor and inner class + 0 + + + + + #1783 correct comments for constructor and inner class - false negative + 1 + 12 + + diff --git a/pmd-core/src/main/resources/rulesets/releases/6150.xml b/pmd-core/src/main/resources/rulesets/releases/6150.xml new file mode 100644 index 0000000000..25c9b94dad --- /dev/null +++ b/pmd-core/src/main/resources/rulesets/releases/6150.xml @@ -0,0 +1,17 @@ + + + + +This ruleset contains links to rules that are new in PMD v6.15.0 + + + + + + + + + diff --git a/pmd-cpp/etc/grammar/cpp.jj b/pmd-cpp/etc/grammar/cpp.jj index a39effe982..4e9c4586c0 100644 --- a/pmd-cpp/etc/grammar/cpp.jj +++ b/pmd-cpp/etc/grammar/cpp.jj @@ -96,7 +96,7 @@ public final class CppParser { if (t.kind != ID && t.kind != SCOPE) return null; - StringBuffer s = new StringBuffer(); + StringBuilder s = new StringBuilder(); int i; if (t.kind != SCOPE) @@ -284,37 +284,100 @@ TOKEN : TOKEN [IGNORE_CASE] : { - < OCTALINT : "0" (["0"-"7"])* > + < OCTALINT : "0" (["'", "0"-"7"])* > | < OCTALLONG : "l" > | < UNSIGNED_OCTALINT : "u" > | < UNSIGNED_OCTALLONG : ("ul" | "lu") > -| < DECIMALINT : ["1"-"9"] (["0"-"9"])* > +| < #DECIMALDIGIT : ["'", "0"-"9"] > + +| < DECIMALINT : ["1"-"9"] ()* > | < DECIMALLONG : ["u","l"] > | < UNSIGNED_DECIMALINT : "u" > | < UNSIGNED_DECIMALLONG : ("ul" | "lu") > -| < HEXADECIMALINT : "0x" (["0"-"9","a"-"f"])+ > +| < HEXADECIMALINT : "0x" ( | ["a"-"f"])+ > | < HEXADECIMALLONG : (["u","l"])? > | < UNSIGNED_HEXADECIMALINT : "u" > | < UNSIGNED_HEXADECIMALLONG : ("ul" | "lu") > -| < FLOATONE : ((["0"-"9"])+ "." (["0"-"9"])* | (["0"-"9"])* "." (["0"-"9"])+) - ("e" (["-","+"])? (["0"-"9"])+)? (["f","l"])? > +| < FLOATONE : (["0"-"9"]()* "." + | "." ()+ + | ["0"-"9"]()* "." ()+) + ("e" (["-","+"])? ()+)? (["f","l"])? > -| < FLOATTWO : (["0"-"9"])+ "e" (["-","+"])? (["0"-"9"])+ (["f","l"])? > +| < FLOATTWO : ["0"-"9"]()* "e" (["-","+"])? ()+ (["f","l"])? > } TOKEN : { + < #CHRPREF : > +| < CHARACTER : + "'" ( ( ~["'","\\","\r","\n"] ) | ( "\\" ( ~["\n","\r"] ) ) )* "'" > - < CHARACTER : ("L")? "'" ( ( ~["'","\\","\r","\n"] ) | ( "\\" ( ~["\n","\r"] ) ) )* "'" > +| < #STRPREF : ("L" | "u" | "U" | "u8")? > +| < STRING : + "\"" ( ( ~["\"","\\","\r","\n"] ) | ( "\\" ( ~["\n","\r"] | "\n" | "\r\n" ) ) )* "\"" > -| < STRING : ("L")? "\"" ( ( ~["\"","\\","\r","\n"] ) | ( "\\" ( ~["\n","\r"] | "\n" | "\r\n" ) ) )* "\"" > +} -| < RSTRING : "R\"(" ( ~[")"] | ( ")" ~["\""] ) )* ")\"" > +// Raw C++11 string literal support +// https://en.cppreference.com/w/cpp/language/string_literal +TOKEN : +{ + < RSTRING : "R\"" > + { + StringBuilder sb = new StringBuilder(16); + + // delim ------+ + // vvv + // Matching R"...(...)..." + // ^ + for (;;) { + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return; } + if (curChar == '(') break; + sb.append(curChar); + } + final String delim = sb.toString(); + +rstringbody: + // Matching R"...(...)..." + // ^ + for (;;) { + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return; } + if (curChar == ')') { + // delim --------------+ + // vvv + // Matching R"...(...)..." + // ^^^ + for (int i = 0; i < delim.length(); i++) { + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return; } + if (delim.charAt(i) != curChar) { + input_stream.backup(1); + continue rstringbody; + } + } + // Matching R"...(...)..." + // ^ + try { curChar = input_stream.readChar(); } + catch(java.io.IOException e) { return; } + if (curChar != '"') { + input_stream.backup(1); + continue rstringbody; + } + break; + } + } + // Setting final token image + matchedToken.image = input_stream.GetImage(); + matchedToken.endLine = input_stream.getEndLine(); + matchedToken.endColumn = input_stream.getEndColumn(); + } } TOKEN : diff --git a/pmd-cpp/src/test/java/net/sourceforge/pmd/cpd/CPPTokenizerTest.java b/pmd-cpp/src/test/java/net/sourceforge/pmd/cpd/CPPTokenizerTest.java index 4bffb5208d..c8caeb354e 100644 --- a/pmd-cpp/src/test/java/net/sourceforge/pmd/cpd/CPPTokenizerTest.java +++ b/pmd-cpp/src/test/java/net/sourceforge/pmd/cpd/CPPTokenizerTest.java @@ -159,6 +159,86 @@ public class CPPTokenizerTest { tokenizer.tokenize(code, new Tokens()); } + public void testStringPrefix(String code, String expToken, int tokenIndex, int expNoTokens) { + final Tokens tokens = parse(code); + final TokenEntry token = tokens.getTokens().get(tokenIndex); + assertEquals(expNoTokens, tokens.size()); + assertEquals(expToken, token.toString()); + } + + public void testCharacterPrefix(String code, String expToken) { + testStringPrefix(code, expToken, 3, 6); + } + + public void testStringPrefix(String code, String expToken) { + testStringPrefix(code, expToken, 5, 8); + } + + @Test + public void testCharacterPrefixNoPrefix() { + testCharacterPrefix("char a = '\\x30';", "'\\x30'"); + } + + @Test + public void testCharacterPrefixWideCharacter() { + testCharacterPrefix("wchar_t b = L'\\xFFEF';", "L'\\xFFEF'"); + } + + @Test + public void testCharacterPrefixChar16() { + testCharacterPrefix("char16_t c = u'\\u00F6';", "u'\\u00F6'"); + } + + @Test + public void testCharacterPrefixChar32() { + testCharacterPrefix("char32_t d = U'\\U0010FFFF';", "U'\\U0010FFFF'"); + } + + @Test + public void testStringPrefixNoPrefix() { + testStringPrefix("char A[] = \"Hello\\x0A\";", "\"Hello\\x0A\""); + } + + @Test + public void testStringPrefixWideString() { + testStringPrefix("wchar_t B[] = L\"Hell\\xF6\\x0A\";", "L\"Hell\\xF6\\x0A\""); + } + + @Test + public void testStringPrefixChar16() { + testStringPrefix("char16_t C[] = u\"Hell\\u00F6\";", "u\"Hell\\u00F6\""); + } + + @Test + public void testStringPrefixChar32() { + testStringPrefix("char32_t D[] = U\"Hell\\U000000F6\\U0010FFFF\";", "U\"Hell\\U000000F6\\U0010FFFF\""); + } + + @Test + public void testStringPrefixUtf8() { + testStringPrefix("auto E[] = u8\"\\u00F6\\U0010FFFF\";", "u8\"\\u00F6\\U0010FFFF\""); + } + + @Test + public void testRawStringLiterals() throws IOException { + final String code = IOUtils.toString(CPPTokenizerTest.class.getResourceAsStream("cpp/issue-1784.cpp"), StandardCharsets.UTF_8); + Tokens tokens = parse(code); + assertTrue(TokenEntry.getEOF() != tokens.getTokens().get(0)); + assertEquals(16, tokens.size()); + } + + @Test + public void testDigitSeparators() { + final String code = "auto integer_literal = 1'000'000;" + PMD.EOL + + "auto floating_point_literal = 0.000'015'3;" + PMD.EOL + + "auto hex_literal = 0x0F00'abcd'6f3d;" + PMD.EOL + + "auto silly_example = 1'0'0'000'00;"; + Tokens tokens = parse(code); + assertTrue(TokenEntry.getEOF() != tokens.getTokens().get(0)); + assertEquals("1'000'000", tokens.getTokens().get(3).toString()); + assertEquals(21, tokens.size()); + } + private Tokens parse(String snippet) { try { return parse(snippet, false, new Tokens()); diff --git a/pmd-cpp/src/test/resources/net/sourceforge/pmd/cpd/cpp/issue-1559.cpp b/pmd-cpp/src/test/resources/net/sourceforge/pmd/cpd/cpp/issue-1559.cpp index 010ec09fc6..cdf47c53e6 100644 --- a/pmd-cpp/src/test/resources/net/sourceforge/pmd/cpd/cpp/issue-1559.cpp +++ b/pmd-cpp/src/test/resources/net/sourceforge/pmd/cpd/cpp/issue-1559.cpp @@ -4,7 +4,7 @@ namespace ABC { #ifdef USE_QT - const char* perPixelQml = R"QML( + const char* perPixelQml = "QML( // provoking a parser error )QML"; } } diff --git a/pmd-cpp/src/test/resources/net/sourceforge/pmd/cpd/cpp/issue-1784.cpp b/pmd-cpp/src/test/resources/net/sourceforge/pmd/cpd/cpp/issue-1784.cpp new file mode 100644 index 0000000000..29d644f449 --- /dev/null +++ b/pmd-cpp/src/test/resources/net/sourceforge/pmd/cpd/cpp/issue-1784.cpp @@ -0,0 +1,12 @@ +namespace ABC +{ + namespace DEF + { + +#ifdef USE_QT + const char* perPixelQml = R"QML( + )NOTTHEND"; +)QML"; + } +} +#endif // USE_QT diff --git a/pmd-matlab/etc/grammar/matlab.jj b/pmd-matlab/etc/grammar/matlab.jj index 6f61dd0d42..31aca621e7 100644 --- a/pmd-matlab/etc/grammar/matlab.jj +++ b/pmd-matlab/etc/grammar/matlab.jj @@ -41,11 +41,11 @@ PARSER_END(MatlabParser) "\n" : DEFAULT } -MORE: + MORE: { "%{": IN_COMMENT } -SPECIAL_TOKEN: -{ } + SPECIAL_TOKEN: +{ } SPECIAL_TOKEN: { : DEFAULT } @@ -65,6 +65,7 @@ SPECIAL_TOKEN: | < AT: "@" > : DEFAULT | < DOT: "." > : TRANSPOSE | < COMMA: "," > : DEFAULT +| < QUESTIONMARK: "?" > : DEFAULT } TOKEN : /* OPERATORS AND ASSIGNMENTS */ @@ -139,6 +140,7 @@ SPECIAL_TOKEN: TOKEN : { < STRING: "'" ( | "'" "'" | ~["\\","'","\n"] )* "'" > +| < DSTRING: "\"" ( "\\" | "\"" "\"" | ~["\\","\"","\n"] )* "\"" > | < #ESC_SEQ: "\\" ( "b" | "t" | "n" | "f" | "r" | "\"" | "'" | "\\" ) | diff --git a/pmd-matlab/src/test/java/net/sourceforge/pmd/cpd/MatlabTokenizerTest.java b/pmd-matlab/src/test/java/net/sourceforge/pmd/cpd/MatlabTokenizerTest.java index 50fdb1832e..cc45bfb48f 100644 --- a/pmd-matlab/src/test/java/net/sourceforge/pmd/cpd/MatlabTokenizerTest.java +++ b/pmd-matlab/src/test/java/net/sourceforge/pmd/cpd/MatlabTokenizerTest.java @@ -51,7 +51,55 @@ public class MatlabTokenizerTest extends AbstractTokenizerTest { )); Tokens tokens = new Tokens(); tokenizer.tokenize(sourceCode, tokens); - TokenEntry.getEOF(); assertEquals(2, tokens.size()); // 2 tokens: "end" + EOF } + + @Test + public void testComments() throws IOException { + SourceCode sourceCode = new SourceCode(new SourceCode.StringCodeLoader("classdef LC" + PMD.EOL + + " methods" + PMD.EOL + + " function [obj, c,t, s ] = Classification( obj,m,t, cm )%#codegen" + PMD.EOL + + " end" + PMD.EOL + + " end" + PMD.EOL + + "end")); + Tokens tokens = new Tokens(); + tokenizer.tokenize(sourceCode, tokens); // should not result in parse error + assertEquals(28, tokens.size()); + } + + @Test + public void testBlockComments() throws IOException { + SourceCode sourceCode = new SourceCode(new SourceCode.StringCodeLoader("%{" + PMD.EOL + + " Name: helloworld.m\n" + PMD.EOL + + " Purpose: Say \"Hello World!\" in two different ways" + PMD.EOL + + "%}" + PMD.EOL + + PMD.EOL + + "% Do it the good ol' fashioned way...command window" + PMD.EOL + + "disp('Hello World!');\n" + PMD.EOL + + "%" + PMD.EOL + + "% Do it the new hip GUI way...with a message box" + PMD.EOL + + "msgbox('Hello World!','Hello World!');")); + Tokens tokens = new Tokens(); + tokenizer.tokenize(sourceCode, tokens); // should not result in parse error + assertEquals(13, tokens.size()); + } + + @Test + public void testQuestionMark() throws IOException { + SourceCode sourceCode = new SourceCode(new SourceCode.StringCodeLoader("classdef Class1" + PMD.EOL + + "properties (SetAccess = ?Class2)")); + Tokens tokens = new Tokens(); + tokenizer.tokenize(sourceCode, tokens); + assertEquals(10, tokens.size()); + } + + @Test + public void testDoubleQuotedStrings() throws IOException { + SourceCode sourceCode = new SourceCode(new SourceCode.StringCodeLoader( + "error(\"This is a double-quoted string\");")); + Tokens tokens = new Tokens(); + tokenizer.tokenize(sourceCode, tokens); + assertEquals("\"This is a double-quoted string\"", tokens.getTokens().get(2).toString()); + assertEquals(6, tokens.size()); + } }