Merge remote-tracking branch 'origin/master' into pmd/7.0.x
This commit is contained in:
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -2,7 +2,7 @@
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
labels: 'a:bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -2,7 +2,7 @@
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
labels: 'an:enhancement'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
2
.github/ISSUE_TEMPLATE/new_rule.md
vendored
2
.github/ISSUE_TEMPLATE/new_rule.md
vendored
@ -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: ''
|
||||
|
||||
---
|
||||
|
2
.github/ISSUE_TEMPLATE/question.md
vendored
2
.github/ISSUE_TEMPLATE/question.md
vendored
@ -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: ''
|
||||
|
||||
---
|
||||
|
2
.github/ISSUE_TEMPLATE/rule_violation.md
vendored
2
.github/ISSUE_TEMPLATE/rule_violation.md
vendored
@ -2,7 +2,7 @@
|
||||
name: Rule violation
|
||||
about: Let us know about a false positive/false negative
|
||||
title: ''
|
||||
labels: bug
|
||||
labels: 'a:bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
@ -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
|
||||
|
||||
|
85
docs/pages/pmd/projectdocs/committers/main_landing_page.md
Normal file
85
docs/pages/pmd/projectdocs/committers/main_landing_page.md
Normal 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.
|
@ -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>
|
||||
---
|
||||
|
@ -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).
|
||||
|
@ -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 %}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
@ -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);
|
||||
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
14
pmd-core/src/main/resources/rulesets/releases/6230.xml
Normal file
14
pmd-core/src/main/resources/rulesets/releases/6230.xml
Normal 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>
|
@ -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() ";"
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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
Reference in New Issue
Block a user