Merge remote-tracking branch 'upstream/pmd/7.0.x' into text-utils-simple

This commit is contained in:
Clément Fournier 2022-07-10 13:53:30 +02:00
commit e248156d96
No known key found for this signature in database
GPG Key ID: 4D8D42402E4F47E2
30 changed files with 1721 additions and 167 deletions

View File

@ -3267,7 +3267,8 @@
"avatar_url": "https://avatars.githubusercontent.com/u/24591067?v=4",
"profile": "https://github.com/jborgers",
"contributions": [
"bug"
"bug",
"code"
]
},
{
@ -6730,6 +6731,33 @@
"contributions": [
"bug"
]
},
{
"login": "stokpop",
"name": "Peter Paul Bakker",
"avatar_url": "https://avatars.githubusercontent.com/u/8797018?v=4",
"profile": "https://www.stokpop.nl/",
"contributions": [
"code"
]
},
{
"login": "ASBrouwers",
"name": "ASBrouwers",
"avatar_url": "https://avatars.githubusercontent.com/u/23551289?v=4",
"profile": "https://github.com/ASBrouwers",
"contributions": [
"code"
]
},
{
"login": "341816041",
"name": "茅延安",
"avatar_url": "https://avatars.githubusercontent.com/u/100549608?v=4",
"profile": "https://github.com/341816041",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

View File

@ -412,6 +412,9 @@ entries:
- title: HTML
url: /pmd_languages_html.html
output: web, pdf
- title: Gherkin
url: /pmd_languages_gherkin.html
output: web, pdf
- title: Developer Documentation
output: web, pdf
folderitems:

View File

@ -0,0 +1,16 @@
---
title: Gherkin
permalink: pmd_languages_gherkin.html
---
The [Gherkin](https://cucumber.io/docs/gherkin/) language is used to define test cases for the
[Cucumber](https://cucumber.io/) testing tool for behavior-driven development.
The Gherkin syntax is designed to be non-technical, making it human-readable for a wide audience.
## Support in PMD
Starting from version 6.48.0, Gherkin support was added to CPD.
### Limitations
- Support for Gherkin only extends to CPD to detect code duplication in Cucumber test cases.
- While Gherkin keywords have been translated into various
languages, CPD currently supports only the English version of the Gherkin language.

File diff suppressed because it is too large Load Diff

View File

@ -19,11 +19,33 @@ This is a {{ site.pmd.release_type }} release.
### New and noteworthy
#### Gherkin support
Thanks to the contribution from [Anne Brouwers](https://github.com/ASBrouwers) PMD now has CPD support
for the [Gherkin](https://cucumber.io/docs/gherkin/) language. It is used to defined test cases for the
[Cucumber](https://cucumber.io/) testing tool for behavior-driven development.
Being based on a proper Antlr grammar, CPD can:
* ignore comments
* honor [comment-based suppressions](pmd_userdocs_cpd.html#suppression)
### Fixed Issues
* java-bestpractices
* [#3455](https://github.com/pmd/pmd/issues/3455): \[java] WhileLoopWithLiteralBoolean - false negative with complex expressions
* java-design
* [#3729](https://github.com/pmd/pmd/issues/3729): \[java] TooManyMethods ignores "real" methods which are named like getters or setters
* [#3949](https://github.com/pmd/pmd/issues/3949): \[java] FinalFieldCouldBeStatic - false negative with unnecessary parenthesis
* java-performance
* [#3625](https://github.com/pmd/pmd/issues/3625): \[java] AddEmptyString - false negative with empty var
### API Changes
### External Contributions
* [#3984](https://github.com/pmd/pmd/pull/3984): \[java] Fix AddEmptyString false-negative issue - [@LiGaOg](https://github.com/LiGaOg)
* [#3988](https://github.com/pmd/pmd/pull/3988): \[java] Modify WhileLoopWithLiteralBoolean to meet the missing case #3455 - [@VoidxHoshi](https://github.com/VoidxHoshi)
* [#3992](https://github.com/pmd/pmd/pull/3992): \[java] FinalFieldCouldBeStatic - fix false negative with unnecessary parenthesis - [@dalizi007](https://github.com/dalizi007)
* [#3994](https://github.com/pmd/pmd/pull/3994): \[java] TooManyMethods - improve getter/setter detection (#3729) - [@341816041](https://github.com/341816041)
* [#4017](https://github.com/pmd/pmd/pull/4017): Add Gherkin support to CPD - [@ASBrouwers](https://github.com/ASBrouwers)
{% endtocmaker %}

View File

@ -0,0 +1,121 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang;
import net.sourceforge.pmd.annotation.Experimental;
import net.sourceforge.pmd.lang.ast.AstInfo;
import net.sourceforge.pmd.lang.ast.Parser;
import net.sourceforge.pmd.lang.ast.RootNode;
import net.sourceforge.pmd.lang.ast.SourceCodePositioner;
import net.sourceforge.pmd.lang.ast.impl.AbstractNode;
/**
* A dummy language implementation whose parser produces a single node.
* This is provided for cases where a non-null language is required, but
* the parser is not useful. This is useful eg to mock rules when no other
* language is on the classpath. This language is not exposed by {@link LanguageRegistry}
* and can only be used explicitly with {@link #getInstance()}.
*
* @author Clément Fournier
* @since 6.48.0
*/
@Experimental
public final class PlainTextLanguage extends BaseLanguageModule {
private static final Language INSTANCE = new PlainTextLanguage();
static final String TERSE_NAME = "text";
private PlainTextLanguage() {
super("Plain text", "Plain text", TERSE_NAME, "plain-text-file-goo-extension");
addVersion("default", new TextLvh(), true);
}
/**
* Returns the singleton instance of this language.
*/
public static Language getInstance() {
return INSTANCE;
}
private static final class TextLvh implements LanguageVersionHandler {
@Override
public Parser getParser() {
return parserTask -> new PlainTextFile(parserTask);
}
}
/**
* The only node produced by the parser of {@link PlainTextLanguage}.
*/
public static final class PlainTextFile extends AbstractNode implements RootNode {
private final int beginLine;
private final int beginColumn;
private final int endLine;
private final int endColumn;
private final AstInfo<PlainTextFile> astInfo;
PlainTextFile(Parser.ParserTask parserTask) {
SourceCodePositioner positioner = new SourceCodePositioner(parserTask.getSourceText());
this.beginLine = 1;
this.beginColumn = 1;
this.endLine = positioner.getLastLine();
this.endColumn = positioner.getLastLineColumn();
this.astInfo = new AstInfo<>(parserTask, this);
}
@Override
public String getXPathNodeName() {
return "TextFile";
}
@Override
public String getImage() {
return null;
}
@Override
public int getBeginLine() {
return beginLine;
}
@Override
public int getBeginColumn() {
return beginColumn;
}
@Override
public int getEndLine() {
return endLine;
}
@Override
public int getEndColumn() {
return endColumn;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public void removeChildAtIndex(int childIndex) {
throw new IndexOutOfBoundsException();
}
@Override
public String toString() {
return "Plain text file (" + endLine + "lines)";
}
@Override
public AstInfo<? extends RootNode> getAstInfo() {
return astInfo;
}
}
}

View File

@ -154,6 +154,11 @@
<artifactId>pmd-fortran</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-gherkin</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-go</artifactId>

View File

@ -26,7 +26,7 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest {
private static final String SUPPORTED_LANGUAGES_PMD;
static {
SUPPORTED_LANGUAGES_CPD = "Supported languages: [apex, cpp, cs, dart, ecmascript, fortran, go, groovy, html, java, jsp, kotlin, lua, matlab, modelica, objectivec, perl, php, plsql, python, ruby, scala, swift, vf, xml]";
SUPPORTED_LANGUAGES_CPD = "Supported languages: [apex, cpp, cs, dart, ecmascript, fortran, gherkin, go, groovy, html, java, jsp, kotlin, lua, matlab, modelica, objectivec, perl, php, plsql, python, ruby, scala, swift, vf, xml]";
SUPPORTED_LANGUAGES_PMD = "apex, ecmascript, html, java, jsp, kotlin, modelica, plsql, pom, scala, swift, vf, vm, wsdl, xml, xsl";
}

59
pmd-gherkin/pom.xml Normal file
View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>pmd-gherkin</artifactId>
<name>PMD Gherkin</name>
<parent>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd</artifactId>
<version>7.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<build>
<plugins>
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<useDefaultDelimiters>false</useDefaultDelimiters>
<delimiters>
<delimiter>${*}</delimiter>
</delimiters>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-core</artifactId>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-lang-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,118 @@
grammar Gherkin;
// PARSER
main
// start comment needed because each comment should start on a new line except for the start comment
: STARTCOMMENT? feature description* instructionLine* NL* EOF
;
feature
: (NL* tagline)* NL* FEATURE?
;
instructionLine
: NL+ (instruction | datatable)
;
instruction
: rulex description* // the name "rule" is not allowed by ANTLR (used for internal usage), so calling it rulex
| stepInstruction description* (NL+ stepDescription description*)* (NL+ step)*
| tagline
| instructionDescription description*
;
stepInstruction
: background
| scenario
| scenarioOutline
;
background: BACKGROUND ;
rulex: RULEX ;
scenario: SCENARIO ;
scenarioOutline : SCENARIOOUTLINE ;
step : stepItem description*;
stepItem
: and
| anystep
| but
| datatable
| given
| then
| when
| (NL* tagline )* NL* examples
;
tagline
: TAG+
;
and: AND ;
anystep: ANYSTEP ;
but: BUT ;
datatable: DATATABLE+ ;
given: GIVEN ;
then: THEN ;
when: WHEN ;
examples: EXAMPLES ;
// Descriptions
instructionDescription: text | PARAMETER | AND | ANYSTEP | BUT | GIVEN | THEN | WHEN | SCENARIO ; // We have to deal with overlaps with keywords
stepDescription: text | PARAMETER ; // We have to deal with overlaps with keywords
description: text | PARAMETER | TAG | AND | ANYSTEP | BUT | DATATABLE | GIVEN | THEN | WHEN | SCENARIO | SCENARIOOUTLINE | STARTCOMMENT ; // We have to deal with overlaps with keywords
text: TOKEN+ ;
// LEXER
// skipped
BOMUTF8 : '\u00EF\u00BB\u00BF' -> skip ;
BOMUTF16 : '\uFEFF' -> skip ;
WHITESPACE: [ \t]+ -> channel(1) ;
COMMENT: '\r'?'\n' [ \t]* '#' ~[\r\n]* -> channel(2) ;
STARTCOMMENT: '#' ~[\r\n]* ;
DOCSTRING1
: '"""' .*? '"""' ;
DOCSTRING2
: '```' .*? '```' ;
// Instructions
BACKGROUND: 'Background:' ;
EXAMPLES: ('Examples:' | 'Scenarios:') ;
FEATURE: 'Feature:';
RULEX: 'Rule:' ;
SCENARIO: ('Example:' | 'Scenario:') ;
SCENARIOOUTLINE : 'Scenario ' ('Outline:' | 'Template:') ;
// Steps
AND: 'And' ;
ANYSTEP: '*' ;
BUT: 'But' ;
DATATABLE: '|' DATATABLEID? ; // must be an ID because it can contain a space
GIVEN: 'Given' ;
THEN: 'Then' ;
WHEN: 'When' ;
TAG: '@' ELEMENT+ ;
PARAMETER: '<' PARID '>' | '"' '<' PARID '>' '"' | '\'' '<' PARID '>' '\'';
fragment PARID: [A-Za-z0-9] ([!-=?-~ ]* [!-=?-~])?; // start with an alpha numerical and then all printable characters and end with a non-space
fragment ID: (IDELEMENT | ' ')* IDELEMENT (IDELEMENT | ' ')*; // ID should contain at least one non-whitespace character otherwise the trailing | with a trailing space will match
fragment DATATABLEID: (DATATABLEELEMENT | ' ')* DATATABLEELEMENT (DATATABLEELEMENT | ' ')*; // ID should contain at least one non-whitespace character otherwise the trailing | with a trailing space will match
fragment DATATABLEELEMENT: ELEMENT | '<' | '>' | '"' | '\'' | '\\|' ;
fragment IDELEMENT: ELEMENT | '|' ;
fragment ELEMENT: [!-&(-;=?-{}~\u00A0-\uFFFF] ;
NL: '\r'? '\n' ;
TOKEN: [!-{}-~\u00A0-\uFFFF]+ ; // match everything that isn't matched yet

View File

@ -0,0 +1,8 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
/**
* Contains the Antlr grammar for Gherkin.
*/
package net.sourceforge.pmd.lang.gherkin.ast;

View File

@ -0,0 +1,20 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.gherkin.cpd;
import net.sourceforge.pmd.cpd.AbstractLanguage;
/**
* Language implementation for Gherkin.
*/
public class GherkinLanguage extends AbstractLanguage {
/**
* Creates a new Gherkin Language instance.
*/
public GherkinLanguage() {
super("Gherkin", "gherkin", new GherkinTokenizer(), ".feature");
}
}

View File

@ -0,0 +1,24 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.gherkin.cpd;
import org.antlr.v4.runtime.CharStream;
import net.sourceforge.pmd.cpd.SourceCode;
import net.sourceforge.pmd.cpd.internal.AntlrTokenizer;
import net.sourceforge.pmd.lang.ast.impl.antlr4.AntlrTokenManager;
import net.sourceforge.pmd.lang.gherkin.ast.GherkinLexer;
/**
* The Gherkin Tokenizer.
*/
public class GherkinTokenizer extends AntlrTokenizer {
@Override
protected AntlrTokenManager getLexerForSource(SourceCode sourceCode) {
CharStream charStream = AntlrTokenizer.getCharStreamFromSourceCode(sourceCode);
return new AntlrTokenManager(new GherkinLexer(charStream), sourceCode.getFileName());
}
}

View File

@ -0,0 +1,8 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
/**
* Contains Gherkin tokenizer and language classes.
*/
package net.sourceforge.pmd.lang.gherkin.cpd;

View File

@ -0,0 +1 @@
net.sourceforge.pmd.lang.gherkin.cpd.GherkinLanguage

View File

@ -0,0 +1,39 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.cpd;
import java.util.Properties;
import org.junit.Test;
import net.sourceforge.pmd.cpd.test.CpdTextComparisonTest;
import net.sourceforge.pmd.lang.gherkin.cpd.GherkinTokenizer;
public class GherkinTokenizerTest extends CpdTextComparisonTest {
public GherkinTokenizerTest() {
super(".feature");
}
@Override
protected String getResourcePrefix() {
return "../lang/gherkin/cpd/testdata";
}
@Override
public Tokenizer newTokenizer(Properties properties) {
GherkinTokenizer tok = new GherkinTokenizer();
return tok;
}
@Test
public void testAnnotatedSource() {
doTest("annotatedSource");
}
@Test
public void testDocstring() {
doTest("docstring");
}
}

View File

@ -0,0 +1,73 @@
@AnnotatedSource
Feature: Annotated Source
The annotated source displays violations in the source file. It opens in a new window.
Rules:
1 Annotation metrics can be selected with a dropdown menu. Only enabled metrics must be shown.
2 When the annotated source is opened while a specific metric is selected, only that metric should be selected.
3 A user can scroll through the violations using two buttons (illustrated by up and down arrows). The arrows wrap around the document.
4 The table collumn "Type" is only shown when multiple metrics are selected
Scenario: Select a metric type
Given the Annotated Source for file "HIE://11261-37/main/monop/execute.c"
When a user opens the dropdown menu containing "Metric:"
And the user clicks on the dropdown option "Violations/Coding Standard Violations"
Then the selected annotation in the source code should be on line 38
And the selected annotation in the table should be on line 38
Scenario: The user can use the arrows, or "a" and "z" keys, to scroll through the annotations
Given the Annotated Source for file "HIE://11261-37/main/monop/execute.c"
And metric "Coding Standard Violation Annotations" is selected
When the user clicks on the down arrow
And the user presses the "Z" key
And the user clicks on the up arrow
And the user clicks on the up arrow
And the user presses the "A" key
Then the selected annotation in the source code should be on line 254
@Rule2
Scenario Outline: If the user opens the annotated source from e.g. the dashboard for a metric,
only the related annotations should be shown.
Given the Dashboard
And filtering by Project "17607"
And grouping by "File"
And metric "<metric>" is selected
When a user opens the file "clalgorithm_settings.c" using the metric table
Then a new browser window with an "Annotated Source" should be opened
Then only the annotations "<annotations>" should be selected
Examples:
| metric | annotations |
| TQI Coding Standards | Coding Standard Violation Annotations |
| Coding Standard Violations | Coding Standard Violation Annotations |
| TQI Compiler Warnings | Compiler Warning Annotations |
| Fan Out (%) | Fan Out Annotations |
| TQI Dead Code | Dead Code Annotations |
| TQI Code Duplication | Code Duplication Annotations |
Scenario: The user should be able to filter Coding Standard Violations by Level
Given the Annotated Source for file "HIE://11514/trunk/components/java/BuildUtil/src/com/tiobe/util/BuildProperties.java"
And the metric "Coding Standard Violations" is selected
When the user opens the dropdown menu "Level"
And the user clicks on the dropdown option "5"
Then there should be 1 violation
And the selected annotation in the source code should be on line 57
And the annotation should be of level 5
@PR27030
Scenario Outline: The user should be able to filter Coding Standard Violations by Level, Category, Rule, etc
Given the Annotated Source for file "HIE://12939/main/Implementatie/DRGL/src/DirectDoorvoerenAdmin.cpp"
And metric "<metric>" is selected
When the user opens the dropdown menu containing "<filter>" inside the filter bar
And the user clicks on the dropdown option "<option>"
Then there should be <number> violations
Examples:
| metric | filter | option | number |
| Coding Standard Violations | Level | 2 | 7 |
| Coding Standard Violations | Category | Comments | 1 |
| Coding Standard Violations | Rule | CFL#011 | 2 |
| Coding Standard Violations | Suppressions | Yes | 0 |
| TQI Code Coverage | Coverage Type | Decision | 7 |
| TQI Code Coverage | Kind | Issue | 10 |

View File

@ -0,0 +1,6 @@
Given I have a lot to say:
"""
One
Two
Three
"""

View File

@ -0,0 +1,15 @@
[Image] or [Truncated image[ Bcol Ecol
L1
[Given] 1 6
[I] 7 8
[have] 9 13
[a] 14 15
[lot] 16 19
[to] 20 22
[say:] 23 27
[\n] 27 28
L2
["""\n One\n Two\n Three\n """] 1 5
L6
[\n] 5 6
EOF

View File

@ -1824,21 +1824,48 @@ a block `{}` is sufficient.
<properties>
<property name="xpath">
<value><![CDATA[
//DoStatement/BooleanLiteral
| //WhileStatement/BooleanLiteral[@True = false()]
(: while loops with single boolean literal, maybe parenthesized :)
//WhileStatement/BooleanLiteral[@True = false()]
|
(: do-while loops with single boolean literal, maybe parenthesized :)
//DoStatement/BooleanLiteral
|
(: while loops with conditional or'ed or and'ed boolean literals, maybe parenthesized :)
//WhileStatement[(.|InfixExpression)
[count(BooleanLiteral) = 2]
(: at least one false literal :)
[count(BooleanLiteral[@True = false()]) >= 1]]
|
(: while loops with conditional and'ed boolean literals, maybe parenthesized :)
//WhileStatement[(InfixExpression[@Operator=('&', '&&')])
(: at least one false literal :)
[count(BooleanLiteral[@True = false()]) >= 1]]
|
(: do-while loops with conditional or'ed or and'ed boolean literals, maybe parenthesized :)
//DoStatement[(.|InfixExpression)
[count(BooleanLiteral) = 2]]
|
(: do-while loops with conditional and'ed boolean literals, maybe parenthesized :)
//DoStatement[(InfixExpression[@Operator=('&', '&&')])
(: at least one false literal :)
[count(BooleanLiteral[@True = false()]) >= 1]]
]]></value>
</property>
<property name="version" value="2.0" />
</properties>
<example>
<![CDATA[
public class Example {
{
while (true) { } // allowed
while (false) { } // disallowed
do { } while (true); // disallowed
do { } while (false); // disallowed
do { } while (false | false); // disallowed
do { } while (false || false); // disallowed
}
}
]]>
</example>
</rule>

View File

@ -785,7 +785,8 @@ in each object at runtime.
[not(.//Annotation[pmd-java:typeIs('lombok.Builder.Default')])]
/VariableDeclarator[*[pmd-java:nodeIs('Literal')]
or VariableAccess[@Name = //FieldDeclaration[pmd-java:modifiers() = 'static']/VariableDeclarator/VariableDeclaratorId/@Name]
or FieldAccess]
or FieldAccess
or ArrayAllocation/ArrayType/ArrayDimensions/ArrayDimExpr/NumericLiteral[@IntLiteral = true()][@Image = "0"]]
/VariableDeclaratorId
]]>
</value>
@ -1391,11 +1392,9 @@ complexity and find a way to have more fine grained objects.
[
count(MethodDeclaration[
not (
starts-with(@Name,'get')
or
starts-with(@Name,'set')
or
starts-with(@Name,'is')
(starts-with(@Name,'get') or starts-with(@Name,'set') or starts-with(@Name,'is'))
and
count(Block/*) <= 1
)
]) > $maxmethods
]

View File

@ -17,7 +17,7 @@ Rules that flag suboptimal code.
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_performance.html#addemptystring">
<description>
The conversion of literals to strings by concatenating them with empty strings is inefficient.
It is much better to use one of the type-specific toString() methods instead.
It is much better to use one of the type-specific `toString()` methods instead or `String.valueOf()`.
</description>
<priority>3</priority>
<properties>
@ -25,6 +25,12 @@ It is much better to use one of the type-specific toString() methods instead.
<value>
<![CDATA[
//InfixExpression[@Operator='+']/StringLiteral[@Empty=true()][not(ancestor::Annotation)]
|
//InfixExpression[@Operator='+']/VariableAccess
[@Name = (//FieldDeclaration[pmd-java:modifiers() = 'final']|ancestor::MethodDeclaration//LocalVariableDeclaration[pmd-java:modifiers() = 'final'])
/VariableDeclarator[@Initializer = true()]
[StringLiteral[@Empty=true()]]
/VariableDeclaratorId/@Name]
]]>
</value>
</property>

View File

@ -6,13 +6,31 @@
<test-code>
<description>do while true</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>4</expected-linenumbers>
<expected-problems>2</expected-problems>
<expected-linenumbers>4,6</expected-linenumbers>
<code><![CDATA[
class Foo {
{
do {
} while (true);
do {
} while ((true));
}
}
]]></code>
</test-code>
<test-code>
<description>do while true | true</description>
<expected-problems>4</expected-problems>
<expected-linenumbers>3,4,5,6</expected-linenumbers>
<code><![CDATA[
class Foo {
{
do { } while (true | true);
do { } while (true || true);
do { } while ((true | true));
do { } while ((true || true));
}
}
]]></code>
@ -20,13 +38,48 @@ class Foo {
<test-code>
<description>do while false</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>4</expected-linenumbers>
<expected-problems>2</expected-problems>
<expected-linenumbers>4,6</expected-linenumbers>
<code><![CDATA[
class Foo {
{
do {
} while (false);
do {
} while ((false));
}
}
]]></code>
</test-code>
<test-code>
<description>do while false | false #3455</description>
<expected-problems>2</expected-problems>
<expected-linenumbers>3,5</expected-linenumbers>
<code><![CDATA[
class Foo {
{
do {
} while (false | false);
do {
} while ((false | false));
}
}
]]></code>
</test-code>
<test-code>
<description>do while false || false #3455</description>
<expected-problems>2</expected-problems>
<expected-linenumbers>3,6</expected-linenumbers>
<code><![CDATA[
class Foo {
{
do {
} while (false || false);
do {
} while ((false || false));
}
}
]]></code>
@ -46,7 +99,7 @@ class Foo {
</test-code>
<test-code>
<description>while true</description>
<description>while true - allowed</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
class Foo {
@ -60,13 +113,31 @@ class Foo {
<test-code>
<description>while false</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>3</expected-linenumbers>
<expected-problems>2</expected-problems>
<expected-linenumbers>3,5</expected-linenumbers>
<code><![CDATA[
class Foo {
{
while (false) {
}
while ((false)) {
}
}
}
]]></code>
</test-code>
<test-code>
<description>while false | false</description>
<expected-problems>4</expected-problems>
<expected-linenumbers>3,4,5,6</expected-linenumbers>
<code><![CDATA[
class Foo {
{
while (false | false) { }
while ((false | false)) { }
while (false || false) { }
while ((false || false)) { }
}
}
]]></code>
@ -84,4 +155,82 @@ class Foo {
}
]]></code>
</test-code>
<test-code>
<description>conditional or with only one boolean literal</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
class Foo {
void bar(boolean arg) {
while (false | arg) { }
while (false || arg) { }
do { } while (false || arg);
}
}
]]></code>
</test-code>
<test-code>
<description>conditional and with only one boolean literal</description>
<expected-problems>3</expected-problems>
<expected-linenumbers>3,4,5</expected-linenumbers>
<code><![CDATA[
class Foo {
void bar(boolean arg) {
while (false & arg) { }
while (false && arg) { }
do { } while (false && arg);
}
}
]]></code>
</test-code>
<test-code>
<description>conditional-and boolean literal</description>
<expected-problems>28</expected-problems>
<expected-linenumbers>10,11,12,13,14,15,16,17,18,19,20,21,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39</expected-linenumbers>
<code><![CDATA[
class Foo {
{
// these evaluate to true and are allowed
while (true & true) { }
while ((true & true)) { }
while (true && true) { }
while ((true && true)) { }
// these evaluate to false and should be flagged
while (false & false) { } // line 10
while ((false & false)) { }
while (false && false) { }
while ((false && false)) { }
while (false & true) { }
while ((false & true)) { }
while (false && true) { }
while ((false && true)) { }
while (true & false) { }
while ((true & false)) { }
while (true && false) { }
while ((true && false)) { }
// do-while loops should always be flagged
do {} while (false & false);
do {} while ((false & false));
do {} while (false && false);
do {} while ((false && false));
do {} while (true & false);
do {} while ((true & false));
do {} while (true && false);
do {} while ((true && false));
do {} while (false & true);
do {} while ((false & true));
do {} while (false && true);
do {} while ((false && true));
do {} while (true & true);
do {} while ((true & true));
do {} while (true && true);
do {} while ((true && true));
}
}
]]></code>
</test-code>
</test-data>

View File

@ -193,6 +193,45 @@ public class Foo {
//private static final int staticFinal = nonStatic; //noncompliant: Non-static field 'nonStatic' cannot be referenced from a static context
private static int staticNonFinal = 1; //no violation cause non-final
private final int nonStatic2 = staticNonFinal; //violation because it could be static
}
]]></code>
</test-code>
<test-code>
<description>#3949 - FinalFieldCouldBeStatic false negative with unnecessary parenthesis</description>
<expected-problems>3</expected-problems>
<expected-linenumbers>5,8,9</expected-linenumbers>
<code><![CDATA[
import java.util.ArrayList;
import java.util.List;
public class Foo {
public final int BAR = (42);
// these empty arrays could theoretically be shared and therefore be static
private final Object[] argsObjs1 = new Object[0];
private final Object[] argsObjs2 = new Object[(0)];
// not flagging anonymous class instantiation
private final StringBuffer mFilter = new StringBuffer(new CharSequence() {
@Override public char charAt(int index) { return 'A'; }
@Override public int length() { return 1; }
@Override public CharSequence subSequence(int start, int end) { return this; }
@Override public String toString() { return "Foo"; }
});
// not flagging any instantiation in order to avoid false positives
// especially for lists one could still require a separate list for each instance...
public final List<String> mList = new ArrayList<>();
// not flagging instantiation of boxed types - would be a different rule
// this is to keep this rule simple
public final Integer DefaultInit = new Integer(27);
private final String mString = new String("Foo");
// not flagging array creation. Same reasoning as for lists:
// one could still require a separate array for each instance...
private final int[] p = new int[42];
}
]]></code>
</test-code>

View File

@ -113,4 +113,53 @@ public class OuterClass {
}
]]></code>
</test-code>
<code-fragment id="code_22_real_methods"><![CDATA[
public class Foo {
public void setMethod1(){int a = 1;a++;}
public void setMethod2(){int a = 1;a++;}
public void setMethod3(){int a = 1;a++;}
public void setMethod4(){int a = 1;a++;}
public void setMethod5(){int a = 1;a++;}
public void getMethod6(){int a = 1;a++;}
public void getMethod7(){int a = 1;a++;}
public void getMethod8(){int a = 1;a++;}
public void getMethod9(){int a = 1;a++;}
public void getMethod10(){int a = 1;a++;}
public boolean isMethod11(){int a = 1;a++;}
public boolean isMethod12(){int a = 1;a++;}
public boolean isMethod13(){int a = 1;a++;}
public boolean isMethod14(){int a = 1;a++;}
public boolean isMethod15(){int a = 1;a++;}
public boolean isMethod16(){int a = 1;a++;}
public void method17(){}
public void method18(){}
public void method19(){}
public void method20(){}
public void method21(){}
public void method22(){}
private String field;
public String getField() { return field; } // real getter
public void setField(String field) { this.field = field; } // real setter
}
]]>
</code-fragment>
<test-code>
<description>#3729 TooManyMethods ignores "real" methods which are named like getters or setters - default</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>1</expected-linenumbers>
<code-ref id="code_22_real_methods"/>
</test-code>
<test-code>
<description>#3729 TooManyMethods ignores "real" methods which are named like getters or setters - max 22 methods</description>
<rule-property name="maxmethods">22</rule-property>
<expected-problems>0</expected-problems>
<code-ref id="code_22_real_methods"/>
</test-code>
</test-data>

View File

@ -50,6 +50,72 @@ public class Foo {
@Deprecated(since = "" + VERSION)
public void foo() {
}
}
]]></code>
</test-code>
<test-code>
<description>[java] AddEmptyString - false negative with empty var #3625</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>4</expected-linenumbers>
<code><![CDATA[
public class Main {
public void func() {
final String var = "";
String test = var + "123"; // should report a warning here
}
}
]]></code>
</test-code>
<test-code>
<description>Local Variables be empty String</description>
<expected-problems>3</expected-problems>
<expected-linenumbers>8,9,10</expected-linenumbers>
<code><![CDATA[
class Main {
public static void main(String[] args) {
final String string1 = "";
final String string2 = "";
final String string3 = "";
String string4 = "";
String a = string1 + 114;
String b = string2 + 514;
String c = string3 + 1919810;
string4 = "foo";
String d = string4 + 1; // should not be flagged, because string4 is not empty anymore
final String s = "bar";
String e = s + 2; // should not be flagged, because s is not empty
}
}
]]></code>
</test-code>
<test-code>
<description>Local and global variables be empty String</description>
<expected-problems>4</expected-problems>
<expected-linenumbers>9,10,11,12</expected-linenumbers>
<code><![CDATA[
class Main {
final String outerString1 = "";
final String outerString2 = "";
public static void main(String[] args, String otherString) {
final String innerString1 = "";
final String innerString2 = "";
String a = outerString1 + 114;
String b = outerString2 + 514;
String c = innerString1 + 1919;
String d = innerString2 + 810;
String e = otherString + 42; // should not be flagged, otherString is a method parameter. Not to be confused with otherString local var in otherMethod
}
void otherMethod() {
final String otherString = "";
}
}
]]></code>
</test-code>

View File

@ -4,11 +4,15 @@
package net.sourceforge.pmd.lang.kotlin;
import net.sourceforge.pmd.annotation.Experimental;
import net.sourceforge.pmd.lang.BaseLanguageModule;
/**
* Language Module for Kotlin
*
* <p>Note: Kotlin support is considered an experimental feature. The AST structure might change.</p>
*/
@Experimental
public class KotlinLanguageModule extends BaseLanguageModule {
/** The name. */

View File

@ -4,6 +4,7 @@
package net.sourceforge.pmd.test.lang;
import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.lang.AbstractPmdLanguageVersionHandler;
import net.sourceforge.pmd.lang.BaseLanguageModule;
import net.sourceforge.pmd.lang.LanguageRegistry;
@ -20,7 +21,12 @@ import net.sourceforge.pmd.test.lang.ast.DummyNode;
/**
* Dummy language used for testing PMD.
*
* @deprecated Don't use this directly. We can probably remove this in favour of plaintextlanguage
* when https://github.com/pmd/pmd/issues/3918 is merged
*/
@Deprecated
@InternalApi
public class DummyLanguageModule extends BaseLanguageModule {
public static final String NAME = "Dummy";

View File

@ -1156,6 +1156,7 @@
<module>pmd-dist</module>
<module>pmd-doc</module>
<module>pmd-fortran</module>
<module>pmd-gherkin</module>
<module>pmd-go</module>
<module>pmd-groovy</module>
<module>pmd-html</module>