Merge remote-tracking branch 'origin/master' into pmd/7.0.x

This commit is contained in:
Andreas Dangel
2020-04-04 18:36:27 +02:00
44 changed files with 1438 additions and 161 deletions

View File

@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
labels: 'a:bug'
assignees: ''
---

View File

@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
labels: 'an:enhancement'
assignees: ''
---

View File

@ -2,7 +2,7 @@
name: New Rule
about: You have an idea for a new rule? Great!
title: ''
labels: new-rule
labels: 'a:new-rule'
assignees: ''
---

View File

@ -2,7 +2,7 @@
name: Question
about: Feel free to ask any question about PMD and its usage
title: ''
labels: question
labels: 'a:question'
assignees: ''
---

View File

@ -2,7 +2,7 @@
name: Rule violation
about: Let us know about a false positive/false negative
title: ''
labels: bug
labels: 'a:bug'
assignees: ''
---

View File

@ -445,4 +445,7 @@ entries:
- title: Merging pull requests
url: /pmd_projectdocs_committers_merging_pull_requests.html
output: web, pdf
- title: Main Landing page
url: /pmd_projectdocs_committers_main_landing_page.html
output: web, pdf

View File

@ -0,0 +1,85 @@
---
title: Main Landing Page
permalink: pmd_projectdocs_committers_main_landing_page.html
last_updated: March 2020
author: Andreas Dangel <andreas.dangel@pmd-code.org>
---
The main homepage of PMD <https://pmd.github.io> is hosted by Github Pages.
The repository is <https://github.com/pmd/pmd.github.io>.
It uses [Jekyll](https://jekyllrb.com/) to generate the static html pages. Jekyll is
executed by github for every push to the repository. Please note, that it takes some time
until Jekyll has been executed and due to caching, the homepage is not updated immediately.
It usually takes 15 minutes.
## Contents
* Main page - aka "Landing page": <https://pmd.github.io>
* Layout: [_layouts/default.html](https://github.com/pmd/pmd.github.io/blob/master/_layouts/default.html).
It includes all the sub section, which can be found in the includes directory [_includes/](https://github.com/pmd/pmd.github.io/tree/master/_includes)
* The latest PMD version is configured in `_config.yml` and the variables `site.pmd.latestVersion` are used
e.g. in [_includes/home.html](https://github.com/pmd/pmd.github.io/blob/master/_includes/home.html).
* Blog - aka "News": <https://pmd.github.io/news/>
* This is a section on main page. It shows the 5 latest news. See [_includes/news.html](https://github.com/pmd/pmd.github.io/blob/master/_includes/news.html).
* There is also a sub page "news" which lists all news.
* Layout: [_layouts/news.html](https://github.com/pmd/pmd.github.io/blob/master/_layouts/news.html)
* Page (which is pretty empty): [news.html](https://github.com/pmd/pmd.github.io/blob/master/news.html)
* Documentation for the latest release: <https://pmd.github.io/latest/>
* The PMD documentation of the latest release is simply copied as static html into the folder [latest/](https://github.com/pmd/pmd.github.io/tree/master/latest).
This makes the latest release documentation available under the stable URL
<https://pmd.github.io/latest/>. This URL is also used for the [sitemap.xml](https://github.com/pmd/pmd.github.io/blob/master/sitemap.xml).
* Documentation for previous releases are still being kept under the folders `pmd-<version>/`.
## Building the page locally
Since the repository contains the documentation for many old PMD releases, it is quite big. When executing
Jekyll to generate the site, it copies all the files to the folder `_site/` - and this can take a while.
In order to speed things up locally, consider to add `pmd-*` to the exclude patterns in `_config.yml`. See
also the comments in this file.
Then it is a matter of simply executing `bundle exec jekyll serve`. This will generate the site and host
it on localhost, so you can test the page at <http://127.0.0.1:4000>.
## Updates during a release
When creating a new PMD release, some content of the main page need to be updated as well.
This done as part of the [Release process](pmd_projectdocs_committers_releasing.html), but is
summarized here as well:
* The versions (e.g. `pmd.latestVersion`) needs to be updated in `_config.yml`
* This is needed to generate the correct links and texts for the latest version on landing page
* The new PMD documentation needs to be copied to `/pmd-<version>/`
* Then this folder needs to copied to `/latest/`, actually replacing the old version.
* A new blog post with release notes is added: `/_posts/YYYY-mm-dd-PMD-<version>.md`
* The sitemap `sitemap.xml` is regenerated
Some of these steps are automated through `do-release.sh` (like blog post), some are manual steps
(updating the version in _config.yml) and other steps are done on the travis-ci-build (like
copying the new documentation).
## Adding a new blog post
Adding a new blog post is as easy as:
* Creating a new file in the folder "_posts": `/_posts/YYYY-mm-dd-<title>.md`
* The file name needs to fit this pattern. The date of the blog post is taken from the file name. The "<title>"
is used for the url.
* The file is a markdown file starting with a frontmatter for jekyll. Just use this template for the new file:
```
---
layout: post
title: Title
---
Here comes the text
```
Once you commit and push it, Github will run Jekyll and update the page. The Jekyll templates take care that
the new post is recognized and added to the news section and also on the news subpage.

View File

@ -1,5 +1,5 @@
---
title: Releasing
title: Release process
permalink: pmd_projectdocs_committers_releasing.html
author: Romain Pelisse <rpelisse@users.sourceforge.net>, Andreas Dangel <adangel@users.sourceforge.net>
---

View File

@ -24,7 +24,7 @@ untouched, files with violations will be listed with full detail. Therefore, its
Incremental analysis is enabled automatically once a location to store the cache has been defined.
From command-line that is done through the [`-cache`](pmd_userdocs_cli_reference.html#cache) argument, but support for the feature is
available for tools integrating PMD such as [Ant](pmd_userdocs_tools_ant.html),
[Maven](pmd_userdocs_tools_maven.html), and Gradle.
[Maven](pmd_userdocs_tools_maven.html), and [Gradle](pmd_userdocs_tools_gradle.html).
### Disabling incremental analysis
@ -32,3 +32,83 @@ available for tools integrating PMD such as [Ant](pmd_userdocs_tools_ant.html),
By default, PMD will suggest to use an analysis cache by logging a warning.
If you'd like to disable this warning, or ignore the analysis cache for a
few runs, you can use the [`-no-cache`](pmd_userdocs_cli_reference.html#no-cache) switch.
### FAQ
#### When is the cache invalidated?
On the following reasons, the complete cache file is considered invalid:
* The PMD version differs. Since each PMD version might have fixed some false-positives or false-negatives for rules,
a cache file created with a different version is considered invalid. The version comparison is exact.
* The used ruleset has been changed. If the ruleset is changed in any way (e.g. adding/removing rules, changing
rule properties, ...), the cache is considered invalid.
* The [`auxclasspath`](pmd_userdocs_cli_reference.html#auxclasspath) changed. The auxclasspath is used during
type resolution. A changed auxclasspath can result for rules, that use type resolution, in different
violations. Usually, if the auxclasspath is correct and type resolution works, the rules report less false-positives.
To make sure, the correct violations are reported, the cache is considered invalid, if the auxclasspath has changed.
* The execution classpath has been changed. On the execution classpath not only the PMD classes are located, but also
the implementation of e.g. custom rules. If any jar file/class file on the execution classpath is changed, then
the cache is considered invalid as well.
#### What is stored in the cache file?
The cache file consists of a header and a body. The header stores the information which is used to decided
whether the whole cache file is valid or not (see above). The following information is stored:
* PMD Version
* Ruleset checksum
* Auxclasspath checksum
* Execution classpath checksum
The body contains an entry for every file that has been analyzed. For every file, the following information
is stored:
* The full (absolute) pathname of the file
* The checksum of the file itself
* 0 or more rule violations with all the info (line number, etc.)
You can think of the cache as a Map where the filepath is used as the key
and the violations found in previous runs are the value.
The cache is in the end just a file with serialized data (binary). The implementation is
{% jdoc core::cache.FileAnalysisCache %}.
#### How does PMD detect whether a file has been changed?
When analyzing a file, PMD records the checksum of the file content and stores this
together with the violations in the cache file. When running PMD with the cache file,
PMD looks up the file in the cache and compares the checksums.
If the checksums match, then the file is not even parsed, the rules
are not executed and the violations for this file are entirely used from the cache.
If the checksum doesn't match, then the cached violations are discarded (if there are any)
and the file is fully processed: the file is parsed and all the rules are run for it.
After we are done, the cache is updated with the new violations.
#### Can I reuse a cache created on branch A for analyzing my project on branch B?
This is possible. As long as the same PMD version and same ruleset is used on both branches.
Also note, that if the branch uses a different dependencies, the auxclasspath is different on both
classes, which invalidates the cache completely. If you project uses e.g. Maven for dependency
management and your branch uses different dependencies (either different version or completely different
artifacts), then the auxclasspath is changed.
If files have been renamed on the branch, these files will be analyzed again since PMD uses
the file names to assign existing rule violations from the cache. Also, if the full path name
of the file changes, because the other branch is checked out at a different location, then all
the cached files don't match.
Apart from these restrictions, PMD will only analyze files that changed between runs.
If your previous run was on branch A and then you run on branch B using the same cache file,
it will only look at files that are different between the 2 branches.
#### Can I reuse a cache file across different machines?
This is only possible, if the other machine uses the exact same path names. That means that
your project needs to be checked out into the same directory structure.
Additionally, all the other restrictions apply (same PMD version, same ruleset, same auxclasspath,
same execution classpath).
See also issue [#2063 [core] Support sharing incremental analysis cache file across different machines](https://github.com/pmd/pmd/issues/2063).

View File

@ -35,13 +35,30 @@ not change the result of your rules*, if it does, please report a bug at https:/
Note that XPath 1.0 support, the default XPath version, is deprecated since PMD 6.22.0.
**We highly recommend that you upgrade your rules to XPath 2.0**. Please refer to the [migration guide](https://pmd.github.io/latest/pmd_userdocs_extending_writing_xpath_rules.html#migrating-from-10-to-20).
#### New Rules
* The new Apex rule {% rule "apex/codestyle/FieldDeclarationsShouldBeAtStart" %} (`apex-codestyle`)
helps to ensure that field declarations are always at the beginning of a class.
* The new Apex rule {% rule "apex/bestpractices/UnusedLocalVariable" %} (`apex-bestpractices`) detects unused
local variables.
### Fixed Issues
* apex
* [#2210](https://github.com/pmd/pmd/issues/2210): \[apex] ApexCRUDViolation: Support WITH SECURITY_ENFORCED
* apex-design
* [#2358](https://github.com/pmd/pmd/issues/2358): \[apex] Invalid Apex in Cognitive Complexity tests
* apex-security
* [#2210](https://github.com/pmd/pmd/issues/2210): \[apex] ApexCRUDViolation: Support WITH SECURITY_ENFORCED
* [#2399](https://github.com/pmd/pmd/issues/2399): \[apex] ApexCRUDViolation: false positive with security enforced with line break
* core
* [#2355](https://github.com/pmd/pmd/issues/2355): \[doc] Improve documentation about incremental analysis
* [#2356](https://github.com/pmd/pmd/issues/2356): \[doc] Add missing doc about pmd.github.io
* java
* [#2378](https://github.com/pmd/pmd/issues/2378): \[java] AbstractJUnitRule has bad performance on large code bases
* java-codestyle
* [#1723](https://github.com/pmd/pmd/issues/1723): \[java] UseDiamondOperator false-positive inside lambda
* java-design
* [#2390](https://github.com/pmd/pmd/issues/2390): \[java] AbstractClassWithoutAnyMethod: missing violation for nested classes
### API Changes
@ -101,6 +118,12 @@ implementations, and their corresponding Parser if it exists (in the same packag
* {% jdoc matlab::lang.matlab.MatlabTokenManager %}
* {% jdoc objectivec::lang.objectivec.ObjectiveCTokenManager %}
##### For removal
* {% jdoc !!core::lang.Parser#getTokenManager(java.lang.String,java.io.Reader) %}
* {% jdoc !!core::lang.TokenManager#setFileName(java.lang.String) %}
* {% jdoc !!core::lang.ast.AbstractTokenManager#setFileName(java.lang.String) %}
* {% jdoc !!core::lang.ast.AbstractTokenManager#getFileName(java.lang.String) %}
### External Contributions
@ -108,6 +131,9 @@ implementations, and their corresponding Parser if it exists (in the same packag
* [#2314](https://github.com/pmd/pmd/pull/2314): \[doc] maven integration - Add version to plugin - [Pham Hai Trung](https://github.com/gpbp)
* [#2353](https://github.com/pmd/pmd/pull/2353): \[plsql] xmlforest with optional AS - [Piotr Szymanski](https://github.com/szyman23)
* [#2383](https://github.com/pmd/pmd/pull/2383): \[apex] Fix invalid apex in documentation - [Gwilym Kuiper](https://github.com/gwilymatgearset)
* [#2395](https://github.com/pmd/pmd/pull/2395): \[apex] New Rule: Unused local variables - [Gwilym Kuiper](https://github.com/gwilymatgearset)
* [#2396](https://github.com/pmd/pmd/pull/2396): \[apex] New rule: field declarations should be at start - [Gwilym Kuiper](https://github.com/gwilymatgearset)
* [#2397](https://github.com/pmd/pmd/pull/2397): \[apex] fixed WITH SECURITY_ENFORCED regex to recognise line break characters - [Kieran Black](https://github.com/kieranlblack)
{% endtocmaker %}

View File

@ -0,0 +1,38 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.apex.rule.bestpractices;
import java.util.List;
import net.sourceforge.pmd.lang.apex.ast.ASTBlockStatement;
import net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclaration;
import net.sourceforge.pmd.lang.apex.ast.ASTVariableExpression;
import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule;
public class UnusedLocalVariableRule extends AbstractApexRule {
public UnusedLocalVariableRule() {
addRuleChainVisit(ASTVariableDeclaration.class);
}
@Override
public Object visit(ASTVariableDeclaration node, Object data) {
String variableName = node.getImage();
ASTBlockStatement variableContext = node.getFirstParentOfType(ASTBlockStatement.class);
List<ASTVariableExpression> potentialUsages = variableContext.findDescendantsOfType(ASTVariableExpression.class);
for (ASTVariableExpression usage : potentialUsages) {
if (usage.getParent() == node) {
continue;
}
if (usage.getImage().equals(variableName)) {
return data;
}
}
addViolation(data, node, variableName);
return data;
}
}

View File

@ -0,0 +1,74 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.apex.rule.codestyle;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.sourceforge.pmd.lang.apex.ast.ASTBlockStatement;
import net.sourceforge.pmd.lang.apex.ast.ASTField;
import net.sourceforge.pmd.lang.apex.ast.ASTMethod;
import net.sourceforge.pmd.lang.apex.ast.ASTProperty;
import net.sourceforge.pmd.lang.apex.ast.ASTUserClass;
import net.sourceforge.pmd.lang.apex.ast.ApexNode;
import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule;
public class FieldDeclarationsShouldBeAtStartRule extends AbstractApexRule {
private static final Comparator<ApexNode<?>> NODE_BY_SOURCE_LOCATION_COMPARATOR =
Comparator
.<ApexNode<?>>comparingInt(ApexNode::getBeginLine)
.thenComparing(ApexNode::getBeginColumn);
public static final String STATIC_INITIALIZER_METHOD_NAME = "<clinit>";
public FieldDeclarationsShouldBeAtStartRule() {
addRuleChainVisit(ASTUserClass.class);
}
@Override
public Object visit(ASTUserClass node, Object data) {
// Unfortunately the parser re-orders the AST to put field declarations before method declarations
// so we have to rely on line numbers / positions to work out where the first non-field declaration starts
// so we can check if the fields are in acceptable places.
List<ASTField> fields = node.findChildrenOfType(ASTField.class);
List<ApexNode<?>> nonFieldDeclarations = new ArrayList<>();
nonFieldDeclarations.addAll(getMethodNodes(node));
nonFieldDeclarations.addAll(node.findChildrenOfType(ASTUserClass.class));
nonFieldDeclarations.addAll(node.findChildrenOfType(ASTProperty.class));
nonFieldDeclarations.addAll(node.findChildrenOfType(ASTBlockStatement.class));
Optional<ApexNode<?>> firstNonFieldDeclaration = nonFieldDeclarations.stream()
.filter(ApexNode::hasRealLoc)
.min(NODE_BY_SOURCE_LOCATION_COMPARATOR);
if (!firstNonFieldDeclaration.isPresent()) {
// there is nothing except field declaration, so that has to come first
return data;
}
for (ASTField field : fields) {
if (NODE_BY_SOURCE_LOCATION_COMPARATOR.compare(field, firstNonFieldDeclaration.get()) > 0) {
addViolation(data, field, field.getName());
}
}
return data;
}
private List<ApexNode<?>> getMethodNodes(ASTUserClass node) {
// The method <clinit> represents static initializer blocks, of which there can be many. The
// <clinit> method doesn't contain location information, however the containing ASTBlockStatements do,
// so we fetch them for that method only.
return node.findChildrenOfType(ASTMethod.class).stream()
.flatMap(method -> method.getImage().equals(STATIC_INITIALIZER_METHOD_NAME)
? method.findChildrenOfType(ASTBlockStatement.class).stream() : Stream.of(method))
.collect(Collectors.toList());
}
}

View File

@ -85,7 +85,7 @@ public class ApexCRUDViolationRule extends AbstractApexRule {
private static final String[] RESERVED_KEYS_FLS = new String[] { "Schema", S_OBJECT_TYPE, };
private static final Pattern WITH_SECURITY_ENFORCED = Pattern.compile("(?i).*[^']\\s*WITH\\s+SECURITY_ENFORCED\\s*[^']*");
private static final Pattern WITH_SECURITY_ENFORCED = Pattern.compile("(?is).*[^']\\s*WITH\\s+SECURITY_ENFORCED\\s*[^']*");
private final Map<String, String> varToTypeMapping = new HashMap<>();
private final ListMultimap<String, String> typeToDMLOperationMapping = ArrayListMultimap.create();

View File

@ -208,4 +208,25 @@ public class Foo {
</example>
</rule>
<rule name="UnusedLocalVariable"
since="6.23.0"
language="apex"
message="Variable ''{0}'' defined but not used"
class="net.sourceforge.pmd.lang.apex.rule.bestpractices.UnusedLocalVariableRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_bestpractices.html#unusedlocalvariable">
<description>
Detects when a local variable is declared and/or assigned but not used.
</description>
<example>
<![CDATA[
public Boolean bar(String z) {
String x = 'some string'; // not used
String y = 'some other string'; // used in the next line
return z.equals(y);
}
]]>
</example>
</rule>
</ruleset>

View File

@ -102,6 +102,30 @@ if (foo) { // preferred approach
</example>
</rule>
<rule name="FieldDeclarationsShouldBeAtStart"
language="apex"
since="6.23.0"
message="Field declaration for ''{0}'' should be before method declarations in its class"
class="net.sourceforge.pmd.lang.apex.rule.codestyle.FieldDeclarationsShouldBeAtStartRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_codestyle.html#fielddeclarationsshouldbeatstart">
<description>
Field declarations should appear before method declarations within a class.
</description>
<priority>3</priority>
<example>
<![CDATA[
class Foo {
public Integer someField; // good
public void someMethod() {
}
public Integer anotherField; // bad
}
]]>
</example>
</rule>
<rule name="FieldNamingConventions"
since="6.15.0"
message="The {0} name ''{1}'' doesn''t match ''{2}''"
@ -336,5 +360,4 @@ while (true) { // preferred approach
]]>
</example>
</rule>
</ruleset>

View File

@ -0,0 +1,11 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.apex.rule.bestpractices;
import net.sourceforge.pmd.testframework.PmdRuleTst;
public class UnusedLocalVariableTest extends PmdRuleTst {
// no additional unit tests
}

View File

@ -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 FieldDeclarationsShouldBeAtStartTest extends PmdRuleTst {
// no additional unit tests
}

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8" ?>
<test-data
xmlns="http://pmd.sourceforge.net/rule-tests"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd">
<test-code>
<description>Unused variables should result in errors</description>
<expected-problems>2</expected-problems>
<expected-linenumbers>3,7</expected-linenumbers>
<expected-messages>
<message>Variable 'foo' defined but not used</message>
<message>Variable 'foo' defined but not used</message>
</expected-messages>
<code>
<![CDATA[
public class Foo {
public void assignedVariable() {
String foo = 'unused string';
}
public void justADeclaration() {
String foo;
}
}
]]>
</code>
</test-code>
<test-code>
<description>Used variables should not result in errors</description>
<expected-problems>0</expected-problems>
<code>
<![CDATA[
public class Foo {
public String basicUsage() {
String x = 'used variable';
return x;
}
public Account moreComplexUsage() {
String x = 'blah';
return [SELECT Id FROM Account WHERE Name = :x];
}
public String usageInBlocks(Boolean y) {
String x = 'used variable';
if (y) {
return x;
} else {
return 'some other string';
}
}
}
]]>
</code>
</test-code>
<test-code>
<description>Shadowing a field</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>5</expected-linenumbers>
<code><![CDATA[
public class Foo {
private String myfield;
public void unused() {
String myfield = 'unused string';
}
public String usedDifferentMethod() {
String myfield = 'used';
return myfield;
}
public String fieldUsage() {
return myfield;
}
}
]]></code>
</test-code>
</test-data>

View File

@ -0,0 +1,207 @@
<?xml version="1.0" encoding="UTF-8"?>
<test-data
xmlns="http://pmd.sourceforge.net/rule-tests"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd">
<test-code>
<description>Does not warn if there are no methods</description>
<expected-problems>0</expected-problems>
<code>
<![CDATA[
class Foo {
public Integer thisIsOkay;
}
]]>
</code>
</test-code>
<test-code>
<description>Does warn if a field is after a method</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>4</expected-linenumbers>
<expected-messages>
<message>Field declaration for 'thisIsNotOkay' should be before method declarations in its class</message>
</expected-messages>
<code>
<![CDATA[
class Foo {
public void someMethod() {}
public Integer thisIsNotOkay;
}
]]>
</code>
</test-code>
<test-code>
<description>Warns if field is after constructor</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>6</expected-linenumbers>
<expected-messages>
<message>Field declaration for 'someField' should be before method declarations in its class</message>
</expected-messages>
<code>
<![CDATA[
class Foo {
public Foo(Integer someValue) {
someField = someValue;
}
private Integer someField;
}
]]>
</code>
</test-code>
<test-code>
<description>Warns only for fields after the first method declaration</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>8</expected-linenumbers>
<expected-messages>
<message>Field declaration for 'thisFieldIsNotOkay' should be before method declarations in its class</message>
</expected-messages>
<code>
<![CDATA[
class Foo {
private Integer thisFieldIsOkay;
public Foo(Integer someValue) {
someField = someValue;
}
private Integer thisFieldIsNotOkay;
}
]]>
</code>
</test-code>
<test-code>
<description>Warns for fields defined on the same line after a method</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>2</expected-linenumbers>
<expected-messages>
<message>Field declaration for 'thisFieldIsNotOkay' should be before method declarations in its class</message>
</expected-messages>
<code>
<![CDATA[
class Foo {
public Foo(Integer someValue) { someField = someValue; } private Integer thisFieldIsNotOkay;
}
]]>
</code>
</test-code>
<test-code>
<description>Does not warn for fields defined on the same line before a method</description>
<expected-problems>0</expected-problems>
<code>
<![CDATA[
class Foo {
private Integer thisFieldIsOkay; public Foo(Integer someValue) { someField = someValue; }
}
]]>
</code>
</test-code>
<test-code>
<description>Allows nested classes to have fields</description>
<expected-problems>0</expected-problems>
<code>
<![CDATA[
class Foo {
void bar() { }
private class InnerFoo {
public Integer thisIsOkay;
}
}
]]>
</code>
</test-code>
<test-code>
<description>Allows nested classes to have fields</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>9</expected-linenumbers>
<code>
<![CDATA[
class Foo {
void bar() { }
private class InnerFoo {
public Integer thisIsOkay;
public void bar() {}
public Integer thisIsNotOkay;
}
}
]]>
</code>
</test-code>
<test-code>
<description>Fields should go before inner classes too</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>4</expected-linenumbers>
<code>
<![CDATA[
class Foo {
private class InnerFoo {}
public Integer thisIsNotOkay;
}
]]>
</code>
</test-code>
<test-code>
<description>Fields should go before properties too</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>4</expected-linenumbers>
<code>
<![CDATA[
class Foo {
public Integer someProperty { get; }
public Integer thisIsNotOkay;
}
]]>
</code>
</test-code>
<test-code>
<description>Fields should go before block statements</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>6</expected-linenumbers>
<code>
<![CDATA[
class Foo {
{
System.debug('Hello');
}
public Integer thisIsNotOkay;
}
]]>
</code>
</test-code>
<test-code>
<description>Fields should go before static block statements</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>6</expected-linenumbers>
<code>
<![CDATA[
class Foo {
static {
System.debug('Hello');
}
public Integer thisIsNotOkay;
}
]]>
</code>
</test-code>
</test-data>

View File

@ -275,6 +275,19 @@ public class Foo {
} ]]></code>
</test-code>
<test-code>
<description>Accepts Closure SECURITY ENFORCED Line Break </description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Foo {
public Contact foo(String tempID) {
Contact c = [SELECT Name FROM Contact WHERE Id=: tempID
WITH SECURITY_ENFORCED];
return c;
}
} ]]></code>
</test-code>
<test-code>
<description>Accepts Closure SECURITY ENFORCED in a List </description>
<expected-problems>0</expected-problems>
@ -287,6 +300,19 @@ public class Foo {
} ]]></code>
</test-code>
<test-code>
<description>Accepts Closure SECURITY ENFORCED in a List Line Break</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Foo {
public List<Contact> m() {
List<Contact> c = [SELECT Name FROM Contact
WITH SECURITY_ENFORCED LIMIT 1];
return c;
}
} ]]></code>
</test-code>
<test-code>
<description>Accepts Closure SECURITY ENFORCED with Case Insensitivity </description>
<expected-problems>0</expected-problems>
@ -299,6 +325,19 @@ public class Foo {
} ]]></code>
</test-code>
<test-code>
<description>Accepts Closure SECURITY ENFORCED with Case Insensitivity Line Break </description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Foo {
public Contact foo(String tempID) {
Contact c = [SELECT Name FROM Contact WHERE Id=: tempID
WItH SECURITY_ENFORCED];
return c;
}
} ]]></code>
</test-code>
<test-code>
<description>Accepts Closure SECURITY ENFORCED Not Secured </description>
<expected-problems>1</expected-problems>
@ -323,6 +362,19 @@ public class Foo {
} ]]></code>
</test-code>
<test-code>
<description>Accepts Closure SECURITY ENFORCED Secured Line Break </description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class Foo {
public Contact foo() {
Contact c = [SELECT Name FROM Contact WHERE Name = 'WITH SECURITY_ENFORCED'
WITH SECURITY_ENFORCED];
return c;
}
} ]]></code>
</test-code>
<test-code>
<description>Proper accessibility CRUD,FLS </description>
<expected-problems>0</expected-problems>

View File

@ -36,7 +36,9 @@ public interface Parser {
* @param source
* Reader that provides the source code to tokenize.
* @return A TokenManager for reading token.
* @deprecated For removal in 7.0.0
*/
@Deprecated
TokenManager getTokenManager(String fileName, Reader source);

View File

@ -11,5 +11,10 @@ public interface TokenManager {
// TODO : Change the return to GenericToken in 7.0.0 - maybe even use generics TokenManager<T extends GenericToken>
Object getNextToken();
/**
* @deprecated For removal in 7.0.0
*/
@Deprecated
void setFileName(String fileName);
}

View File

@ -19,10 +19,18 @@ public abstract class AbstractTokenManager {
protected Map<Integer, String> suppressMap = new HashMap<>();
protected String suppressMarker = PMD.SUPPRESS_MARKER;
/**
* @deprecated For removal in 7.0.0
*/
@Deprecated
public static void setFileName(String fileName) {
AbstractTokenManager.fileName.set(fileName);
}
/**
* @deprecated For removal in 7.0.0
*/
@Deprecated
public static String getFileName() {
String fileName = AbstractTokenManager.fileName.get();
return fileName == null ? "(no file name provided)" : fileName;

View File

@ -329,7 +329,7 @@ public interface Node {
* @param targetType class which you want to find.
* @return List of all children of type targetType. Returns an empty list if none found.
*/
default <T extends Node> List<T> findDescendantsOfType(Class<T> targetType) {
default <T extends Node> List<T> findDescendantsOfType(Class<? extends T> targetType) {
return descendants(targetType).toList();
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0"?>
<ruleset name="6230"
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
<description>
This ruleset contains links to rules that are new in PMD v6.23.0
</description>
<rule ref="category/apex/bestpractices.xml/UnusedLocalVariable"/>
<rule ref="category/apex/codestyle.xml/FieldDeclarationsShouldBeAtStart"/>
</ruleset>

View File

@ -287,6 +287,53 @@ class JavaParserImpl {
return getToken(1).kind == IDENTIFIER && getToken(1).image.equals(keyword);
}
/**
* True if we're in a switch block, one precondition for parsing a yield
* statement.
*/
private boolean inSwitchExprBlock = false;
private boolean isYieldStart() {
return inSwitchExprBlock && isJava13PreviewOr14()
&& isKeyword("yield")
&& mayStartExprAfterYield(2);
}
private boolean mayStartExprAfterYield(final int offset) {
// based off of https://hg.openjdk.java.net/jdk/jdk/file/bc3da0226ffa/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java#l2580
// please don't sue me
Token token = getToken(offset);
if (token == null) return false; // eof
switch (token.kind) {
case PLUS: case MINUS: case STRING_LITERAL: case CHARACTER_LITERAL:
case INTEGER_LITERAL: case FLOATING_POINT_LITERAL: case HEX_FLOATING_POINT_LITERAL:
case NULL: case IDENTIFIER: case TRUE: case FALSE:
case NEW: case SWITCH: case THIS: case SUPER:
return true;
case INCR: case DECR:
return getToken(offset + 1).kind != SEMICOLON; // eg yield++;
case LPAREN:
int lookahead = offset + 1;
int balance = 1;
Token t;
while ((t = getToken(lookahead)) != null && balance > 0) {
switch (t.kind) {
case LPAREN: balance++; break;
case RPAREN: balance--; break;
case COMMA: if (balance == 1) return false; // a method call, eg yield(1, 2);
}
lookahead++;
}
// lambda: yield () -> {};
// method call: yield ();
return t != null
&& (lookahead != offset + 2 // ie ()
|| t.kind == LAMBDA);
default:
return false;
}
}
private boolean shouldStartStatementInSwitch() {
switch (getToken(1).kind) {
case _DEFAULT:
@ -1373,9 +1420,12 @@ void CastExpression() :
}
void SwitchExpression() :
{}
{boolean prevInSwitchBlock = inSwitchExprBlock;}
{
"switch" "(" Expression() ")" SwitchBlock()
"switch" "(" Expression() ")"
{inSwitchExprBlock = true;}
SwitchBlock()
{inSwitchExprBlock = prevInSwitchBlock;}
}
void PrimaryExpression() :
@ -1540,6 +1590,7 @@ void Statement() :
{}
{
LOOKAHEAD( { isNextTokenAnAssert() } ) AssertStatement()
| LOOKAHEAD( { isYieldStart() } ) YieldStatement()
| LOOKAHEAD(2) LabeledStatement()
| Block()
| EmptyStatement()
@ -1575,7 +1626,7 @@ void BlockStatement():
{}
{
LOOKAHEAD( { isNextTokenAnAssert() } ) AssertStatement()
| LOOKAHEAD({ jdkVersion >= 13 && isKeyword("yield") }) YieldStatement()
| LOOKAHEAD( { isYieldStart() } ) YieldStatement()
|
LOOKAHEAD(( "final" | Annotation() )* Type() <IDENTIFIER>)
LocalVariableDeclaration() ";"

View File

@ -66,6 +66,11 @@ public class ASTCompilationUnit extends AbstractJavaTypeNode implements RootNode
return null;
}
@Override
public ASTCompilationUnit getRoot() {
return this;
}
/**
* Returns the package name of this compilation unit. If this is in
* the default package, returns the empty string.

View File

@ -44,6 +44,14 @@ public abstract class AbstractJavaNode extends AbstractJjtreeNode<JavaNode> impl
}
@Override
public ASTCompilationUnit getRoot() {
if (root == null) {
root = getParent().getRoot();
}
return root;
}
@Override
public Scope getScope() {
if (scope == null) {

View File

@ -46,6 +46,7 @@ public interface JavaNode extends ScopedNode, TextAvailableNode {
@Deprecated
Object childrenAccept(JavaParserVisitor visitor, Object data);
ASTCompilationUnit getRoot();
/**
* Calls back the visitor's visit method corresponding to the runtime type of this Node.

View File

@ -66,7 +66,7 @@ public class DuplicateImportsRule extends AbstractJavaRule {
return true;
}
} else {
Class<?> importClass = node.getClassTypeResolver().loadClass(thisImportOnDemand.getName());
Class<?> importClass = node.getClassTypeResolver().loadClassOrNull(thisImportOnDemand.getName());
if (importClass != null) {
for (Method m : importClass.getMethods()) {
if (Modifier.isStatic(m.getModifiers()) && m.getName().equals(singleTypeName)) {

Some files were not shown because too many files have changed in this diff Show More