diff --git a/.all-contributorsrc b/.all-contributorsrc
index dcac68bf60..6efd00a779 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -6639,6 +6639,42 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "lukelukes",
+ "name": "lukelukes",
+ "avatar_url": "https://avatars.githubusercontent.com/u/45536418?v=4",
+ "profile": "https://github.com/lukelukes",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "vibhory2j",
+ "name": "Vibhor Goyal",
+ "avatar_url": "https://avatars.githubusercontent.com/u/15845016?v=4",
+ "profile": "https://github.com/vibhory2j",
+ "contributions": [
+ "bug"
+ ]
+ },
+ {
+ "login": "Ramel0921",
+ "name": "Ramel0921",
+ "avatar_url": "https://avatars.githubusercontent.com/u/104978096?v=4",
+ "profile": "https://github.com/Ramel0921",
+ "contributions": [
+ "bug"
+ ]
+ },
+ {
+ "login": "flyhard",
+ "name": "Per Abich",
+ "avatar_url": "https://avatars.githubusercontent.com/u/409466?v=4",
+ "profile": "https://github.com/flyhard",
+ "contributions": [
+ "code"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/docs/pages/pmd/languages/html.md b/docs/pages/pmd/languages/html.md
index acb96fc416..9c905571e1 100644
--- a/docs/pages/pmd/languages/html.md
+++ b/docs/pages/pmd/languages/html.md
@@ -14,5 +14,11 @@ last_updated: April 2022 (6.45.0)
The HTML language module uses [jsoup](https://jsoup.org/) for parsing.
-XPath rules are supported, but the DOM is not a typical XML/XPath DOM. E.g.
-text nodes are normal nodes. This might change in the future.
+XPath 2.0 rules are supported, but the DOM is not always a typical XML/XPath DOM.
+In the Designer, text nodes appear as nodes with name "#text", but they can
+be selected as usual using `text()`.
+
+XML Namespaces are not supported. The local name of attributes include the prefix,
+so that you have to select attributes by e.g. `//*[@*[local-name() = 'if:true']]`.
+
+Only XPath 1.0 rules are not supported.
diff --git a/docs/pages/pmd/projectdocs/credits.md b/docs/pages/pmd/projectdocs/credits.md
index 392dce14e8..cf6d0805c6 100644
--- a/docs/pages/pmd/projectdocs/credits.md
+++ b/docs/pages/pmd/projectdocs/credits.md
@@ -540,410 +540,416 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Pedro Nuno Santos ๐
Pedro Rijo ๐
Pelisse Romain ๐ป ๐ ๐
+ Per Abich ๐ป
Pete Davids ๐
Peter Bruin ๐
- Peter Chittum ๐ป ๐
+ Peter Chittum ๐ป ๐
Peter Cudmore ๐
Peter Kasson ๐
Peter Kofler ๐
Pham Hai Trung ๐
Philip Graf ๐ป ๐
Philip Hachey ๐
- Philippe Ozil ๐
+ Philippe Ozil ๐
Phinehas Artemix ๐
Phokham Nonava ๐
Piotr Szymaลski ๐
Piotrek ลปygieลo ๐ป ๐
Pranay Jaiswal ๐
Prasad Kamath ๐
- Prasanna ๐
+ Prasanna ๐
Presh-AR ๐
Puneet1726 ๐
Rafael Cortรชs ๐
RaheemShaik999 ๐
RajeshR ๐ป ๐
Ramachandra Mohan ๐
- Raquel Pau ๐
+ Ramel0921 ๐
+ Raquel Pau ๐
Ravikiran Janardhana ๐
Reda Benhemmouche ๐
Renato Oliveira ๐ป ๐
Rich DiCroce ๐
Riot R1cket ๐
- Rishabh Jain ๐
- RishabhDeep Singh ๐
+ Rishabh Jain ๐
+ RishabhDeep Singh ๐
Robbie Martinus ๐ป ๐
Robert Henry ๐
Robert Painsi ๐
Robert Russell ๐
Robert Sรถsemann ๐ป ๐ ๐ข ๐
- Robert Whitebit ๐
- Robin Richtsfeld ๐
+ Robert Whitebit ๐
+ Robin Richtsfeld ๐
Robin Stocker ๐ป ๐
Robin Wils ๐
RochusOest ๐
Rodolfo Noviski ๐
Rodrigo Casara ๐
- Rodrigo Fernandes ๐
- Roman Salvador ๐ป ๐
+ Rodrigo Fernandes ๐
+ Roman Salvador ๐ป ๐
Ronald Blaschke ๐
Rรณbert Papp ๐
Saikat Sengupta ๐
Saksham Handu ๐
Saladoc ๐
- Salesforce Bob Lightning ๐
- Sam Carlberg ๐
+ Salesforce Bob Lightning ๐
+ Sam Carlberg ๐
Satoshi Kubo ๐
Scott Kennedy ๐
Scott Wells ๐ ๐ป
Scrsloota ๐ป
Sebastian Bรถgl ๐
- Sebastian Schuberth ๐
- Sebastian Schwarz ๐
+ Sebastian Schuberth ๐
+ Sebastian Schwarz ๐
Sergey Gorbaty ๐
Sergey Kozlov ๐
Sergey Yanzin ๐ป ๐
Seth Wilcox ๐ป
Shubham ๐ป ๐
- Simon Xiao ๐
- Srinivasan Venkatachalam ๐
+ Simon Xiao ๐
+ Srinivasan Venkatachalam ๐
Stanislav Gromov ๐
Stanislav Myachenkov ๐ป
Stefan Birkner ๐
Stefan Bohn ๐
Stefan Endrullis ๐
- Stefan Klรถss-Schuster ๐
- Stefan Wolf ๐
+ Stefan Klรถss-Schuster ๐
+ Stefan Wolf ๐
Stephan H. Wissel ๐
Stephen ๐
Stephen Friedrich ๐
Steve Babula ๐ป
Stexxe ๐
- Stian Lรฅgstad ๐
- StuartClayton5 ๐
+ Stian Lรฅgstad ๐
+ StuartClayton5 ๐
Supun Arunoda ๐
Suren Abrahamyan ๐
SwatiBGupta1110 ๐
SyedThoufich ๐
Szymon Sasin ๐
- T-chuangxin ๐
- TERAI Atsuhiro ๐
+ T-chuangxin ๐
+ TERAI Atsuhiro ๐
TIOBE Software ๐ป ๐
Taylor Smock ๐
Techeira Damiรกn ๐ป ๐
Ted Husted ๐
TehBakker ๐
- The Gitter Badger ๐
- Theodoor ๐
+ The Gitter Badger ๐
+ Theodoor ๐
Thiago Henrique Hรผpner ๐
Thibault Meyer ๐
Thomas Gรผttler ๐
Thomas Jones-Low ๐
Thomas Smith ๐ป ๐
- ThrawnCA ๐
- Thunderforge ๐ป ๐
+ ThrawnCA ๐
+ Thunderforge ๐ป ๐
Tim van der Lippe ๐
Tobias Weimer ๐ป ๐
Tom Daly ๐
Tomer Figenblat ๐
Tomi De Lucca ๐ป ๐
- Torsten Kleiber ๐
- TrackerSB ๐
+ Torsten Kleiber ๐
+ TrackerSB ๐
Ullrich Hafner ๐
Utku Cuhadaroglu ๐ป ๐
Valentin Brandl ๐
Valeria ๐
Vasily Anisimov ๐
- Vickenty Fesunov ๐
- Victor Noรซl ๐
+ Vibhor Goyal ๐
+ Vickenty Fesunov ๐
+ Victor Noรซl ๐
Vincent Galloy ๐ป
Vincent HUYNH ๐
Vincent Maurin ๐
Vincent Privat ๐
+
+
Vishhwas ๐
Vitaly ๐
Vitaly Polonetsky ๐
-
-
Vojtech Polivka ๐
Vsevolod Zholobov ๐
Vyom Yadav ๐ป
Wang Shidong ๐
+
+
Waqas Ahmed ๐
Wayne J. Earl ๐
Wchenghui ๐
-
-
Will Winder ๐
William Brockhus ๐ป ๐
Wilson Kurniawan ๐
Wim Deblauwe ๐
+
+
Woongsik Choi ๐
XenoAmess ๐ป ๐
Yang ๐ป
-
-
YaroslavTER ๐
Young Chan ๐ป ๐
YuJin Kim ๐
Yuri Dolzhenko ๐
+
+
Yurii Dubinka ๐
Zoltan Farkas ๐
Zustin ๐
-
-
aaronhurst-google ๐
alexmodis ๐
andreoss ๐
andrey81inmd ๐ป ๐
+
+
anicoara ๐
arunprasathav ๐
asiercamara ๐
-
-
astillich-igniti ๐ป
avesolovksyy ๐
avishvat ๐
avivmu ๐
+
+
axelbarfod1 ๐
b-3-n ๐
balbhadra9 ๐
-
-
base23de ๐
bergander ๐
berkam ๐ป ๐
breizh31 ๐
+
+
caesarkim ๐
carolyujing ๐
cesares-basilico ๐
-
-
chrite ๐
cobratbq ๐
coladict ๐
cosmoJFH ๐
+
+
cristalp ๐
crunsk ๐
cwholmes ๐
-
-
cyberjj999 ๐
cyw3 ๐
d1ss0nanz ๐
danbrycefairsailcom ๐
+
+
dariansanity ๐
darrenmiliband ๐
davidburstrom ๐
-
-
dbirkman-paloalto ๐
deepak-patra ๐
dependabot[bot] ๐ป ๐
dinesh150 ๐
+
+
diziaq ๐
dreaminpast123 ๐
duanyanan ๐
-
-
dutt-sanjay ๐
dylanleung ๐
dzeigler ๐
ekkirala ๐
+
+
emersonmoura ๐
fairy ๐
filiprafalowicz ๐ป
-
-
foxmason ๐
frankegabor ๐
frankl ๐
freafrea ๐
+
+
fsapatin ๐
gracia19 ๐
guo fei ๐
-
-
gurmsc5 ๐
gwilymatgearset ๐ป ๐
haigsn ๐
hemanshu070 ๐
+
+
henrik242 ๐
hongpuwu ๐
hvbtup ๐ป ๐
-
-
igniti GmbH ๐
ilovezfs ๐
itaigilo ๐
jakivey32 ๐
+
+
jbennett2091 ๐
jcamerin ๐
jkeener1 ๐
-
-
jmetertea ๐
johnra2 ๐ป
josemanuelrolon ๐ป ๐
kabroxiko ๐ป ๐
+
+
karwer ๐
kaulonline ๐
kdaemonv ๐
-
-
kenji21 ๐ป ๐
kfranic ๐
khalidkh ๐
krzyk ๐
+
+
lasselindqvist ๐
lihuaib ๐
lonelyma1021 ๐
-
-
lpeddy ๐
lujiefsi ๐ป
+ lukelukes ๐ป
lyriccoder ๐
+
+
marcelmore ๐
matchbox ๐
matthiaskraaz ๐
meandonlyme ๐
-
-
mikesive ๐
milossesic ๐
mriddell95 ๐
+
+
mrlzh ๐
msloan ๐
mucharlaravalika ๐
mvenneman ๐
-
-
nareshl119 ๐
nicolas-harraudeau-sonarsource ๐
noerremark ๐
+
+
novsirion ๐
oggboy ๐
oinume ๐
orimarko ๐ป ๐
-
-
pallavi agarwal ๐
parksungrin ๐
patpatpat123 ๐
+
+
patriksevallius ๐
pbrajesh1 ๐
phoenix384 ๐
piotrszymanski-sc ๐ป
-
-
plan3d ๐
poojasix ๐
prabhushrikant ๐
+
+
pujitha8783 ๐
r-r-a-j ๐
raghujayjunk ๐
rajeshveera ๐
-
-
rajeswarreddy88 ๐
recdevs ๐
reudismam ๐ป ๐
+
+
rijkt ๐
rillig-tk ๐
rmohan20 ๐ป ๐
rxmicro ๐
-
-
ryan-gustafson ๐ป ๐
sabi0 ๐
scais ๐
+
+
sebbASF ๐
sergeygorbaty ๐ป
shilko2013 ๐
simeonKondr ๐
-
-
snajberk ๐
sniperrifle2004 ๐
snuyanzin ๐ ๐ป
+
+
sratz ๐
stonio ๐
sturton ๐ป ๐
sudharmohan ๐
-
-
suruchidawar ๐
svenfinitiv ๐
tashiscool ๐
+
+
test-git-hook ๐
testation21 ๐ป ๐
thanosa ๐
tiandiyixian ๐
-
-
tobwoerk ๐
tprouvot ๐
trentchilders ๐
+
+
triandicAnt ๐
trishul14 ๐
tsui ๐
winhkey ๐
-
-
witherspore ๐
wjljack ๐
wuchiuwong ๐
+
+
xingsong ๐
xioayuge ๐
xnYi9wRezm ๐ป ๐
xuanuy ๐
-
-
xyf0921 ๐
yalechen-cyw3 ๐
yasuharu-sato ๐
+
+
zenglian ๐
zgrzyt93 ๐ป ๐
zh3ng ๐
zt_soft ๐
-
-
ztt79 ๐
zzzzfeng ๐
รrpรกd Magosรกnyi ๐
+
+
ไปป่ดตๆฐ ๐
diff --git a/docs/pages/pmd/userdocs/cli_reference.md b/docs/pages/pmd/userdocs/cli_reference.md
index ce4214b9fa..8c1f59df60 100644
--- a/docs/pages/pmd/userdocs/cli_reference.md
+++ b/docs/pages/pmd/userdocs/cli_reference.md
@@ -76,14 +76,14 @@ The tool comes with a rather extensive help text, simply running with `--help`!
%}
{% include custom/cli_option_row.html options="--file-list"
option_arg="filepath"
- description="Path to file containing a comma delimited list of files to analyze.
+ description="Path to file containing a list of files to analyze, one path per line.
If this is given, then you don't need to provide `--dir`."
%}
{% include custom/cli_option_row.html options="--force-language"
option_arg="lang"
description="Force a language to be used for all input files, irrespective of
- filenames. When using this option, the automatic language selection
- by extension is disabled and all files are tried to be parsed with
+ file names. When using this option, the automatic language selection
+ by extension is disabled and PMD tries to parse all files with
the given language `<lang>`. Parsing errors are ignored and unparsable files
are skipped.
@@ -92,9 +92,9 @@ The tool comes with a rather extensive help text, simply running with `--help`!
%}
{% include custom/cli_option_row.html options="--ignore-list"
option_arg="filepath"
- description="Path to file containing a comma delimited list of files to ignore.
+ description="Path to file containing a list of files to ignore, one path per line.
This option can be combined with `--dir` and `--file-list`.
- This ignore list takes precedence over any files in the filelist."
+ This ignore list takes precedence over any files in the file-list."
%}
{% include custom/cli_option_row.html options="--help,-h,-H"
description="Display help on usage."
diff --git a/docs/pages/pmd/userdocs/cpd/cpd.md b/docs/pages/pmd/userdocs/cpd/cpd.md
index dc4577e41d..d909eb4f2f 100644
--- a/docs/pages/pmd/userdocs/cpd/cpd.md
+++ b/docs/pages/pmd/userdocs/cpd/cpd.md
@@ -112,9 +112,9 @@ Novice as much as advanced readers may want to [read on on Refactoring Guru](htt
languages="Java"
%}
{% include custom/cli_option_row.html options="--ignore-annotations"
- description="Ignore language annotations when comparing text"
+ description="Ignore language annotations (Java) or attributes (C#) when comparing text"
default="false"
- languages="Java"
+ languages="C#, Java"
%}
{% include custom/cli_option_row.html options="--ignore-literal-sequences"
description="Ignore sequences of literals (common e.g. in list initializers)"
diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md
index 083d855a4b..b6720bbb0b 100644
--- a/docs/pages/release_notes.md
+++ b/docs/pages/release_notes.md
@@ -19,15 +19,63 @@ This is a {{ site.pmd.release_type }} release.
### New and noteworthy
+#### CLI improvements
+
+The PMD CLI now allows repeating the `--dir` (`-d`) and `--rulesets` (`-R`) options,
+ as well as providing several space-separated arguments to either of them. For instance:
+```shell
+pmd -d src/main/java src/test/java -R rset1.xml -R rset2.xml
+```
+This also allows globs to be used on the CLI if your shell supports shell expansion.
+For instance, the above can be written
+```shell
+pmd -d src/*/java -R rset*.xml
+```
+Please use theses new forms instead of using comma-separated lists as argument to these options.
+
+#### C# Improvements
+
+When executing CPD on C# sources, the option `--ignore-annotations` is now supported as well.
+It ignores C# attributes when detecting duplicated code. This option can also be enabled via
+the CPD GUI. See [#3974](https://github.com/pmd/pmd/pull/3974) for details.
+
### Fixed Issues
+
* core
* [#3942](https://github.com/pmd/pmd/issues/3942): \[core] common-io path traversal vulnerability (CVE-2021-29425)
+* cli
+ * [#1445](https://github.com/pmd/pmd/issues/1445): \[core] Allow CLI to take globs as parameters
+* cs (c#)
+ * [#3974](https://github.com/pmd/pmd/pull/3974): \[cs] Add option to ignore C# attributes (annotations)
+* go
+ * [#2752](https://github.com/pmd/pmd/issues/2752): \[go] Error parsing unicode values
+* html
+ * [#3955](https://github.com/pmd/pmd/pull/3955): \[html] Improvements for handling text and comment nodes
+* java
+ * [#3423](https://github.com/pmd/pmd/issues/3423): \[java] Error processing identifiers with Unicode
+* java-bestpractices
+ * [#3954](https://github.com/pmd/pmd/issues/3954): \[java] NPE in UseCollectionIsEmptyRule when .size() is called in a record
+* java-design
+ * [#3874](https://github.com/pmd/pmd/issues/3874): \[java] ImmutableField reports fields annotated with @Autowired (Spring) and @Mock (Mockito)
* javascript
- * [#3948](https://github.com/pmd/pmd/issues/3948): \[js] Invalid operator error for method property in object literal
+ * [#2605](https://github.com/pmd/pmd/issues/2605): \[js] Support unicode characters
+ * [#3948](https://github.com/pmd/pmd/issues/3948): \[js] Invalid operator error for method property in object literal
+* python
+ * [#2604](https://github.com/pmd/pmd/issues/2604): \[python] Support unicode identifiers
### API Changes
+#### Deprecated API
+
+- {% jdoc core::PMDConfiguration#getInputPaths() %} and
+{% jdoc core::PMDConfiguration#setInputPaths(java.lang.String) %} are now deprecated.
+A new set of methods have been added, which use lists and do not rely on comma splitting.
+
### External Contributions
+* [#3961](https://github.com/pmd/pmd/pull/3961): \[java] Fix #3954 - NPE in UseCollectionIsEmptyRule with record - [@flyhard](https://github.com/flyhard)
+* [#3964](https://github.com/pmd/pmd/pull/3964): \[java] Fix #3874 - ImmutableField: fix mockito/spring false positives - [@lukelukes](https://github.com/lukelukes)
+* [#3974](https://github.com/pmd/pmd/pull/3974): \[cs] Add option to ignore C# attributes (annotations) - [@maikelsteneker](https://github.com/maikelsteneker)
+
{% endtocmaker %}
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java
index 0bf7b1b1e9..13e1a87606 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java
@@ -8,10 +8,12 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
+import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -104,7 +106,7 @@ public class PMDConfiguration extends AbstractConfiguration {
// Rule and source file options
private List ruleSets = new ArrayList<>();
private RulePriority minimumPriority = RulePriority.LOW;
- private String inputPaths;
+ private List inputPaths = new ArrayList<>();
private String inputUri;
private String inputFilePath;
private String ignoreFilePath;
@@ -435,19 +437,54 @@ public class PMDConfiguration extends AbstractConfiguration {
* Get the comma separated list of input paths to process for source files.
*
* @return A comma separated list.
+ *
+ * @deprecated Use {@link #getAllInputPaths()}
*/
+ @Deprecated
public String getInputPaths() {
- return inputPaths;
+ return inputPaths.isEmpty() ? null : StringUtils.join(inputPaths, ",");
+ }
+
+ /**
+ * Returns an unmodifiable list.
+ *
+ * @throws NullPointerException If the parameter is null
+ */
+ public List getAllInputPaths() {
+ return Collections.unmodifiableList(inputPaths);
}
/**
* Set the comma separated list of input paths to process for source files.
*
- * @param inputPaths
- * The comma separated list.
+ * @param inputPaths The comma separated list.
+ *
+ * @throws NullPointerException If the parameter is null
+ * @deprecated Use {@link #setInputPaths(List)} or {@link #addInputPath(String)}
*/
+ @Deprecated
public void setInputPaths(String inputPaths) {
- this.inputPaths = inputPaths;
+ List paths = new ArrayList<>();
+ Collections.addAll(paths, inputPaths.split(","));
+ this.inputPaths = paths;
+ }
+
+ /**
+ * Set the input paths to the given list of paths.
+ * @throws NullPointerException If the parameter is null
+ */
+ public void setInputPaths(List inputPaths) {
+ this.inputPaths = new ArrayList<>(inputPaths);
+ }
+
+ /**
+ * Add an input path. It is not split on commas.
+ *
+ * @throws NullPointerException If the parameter is null
+ */
+ public void addInputPath(String inputPath) {
+ Objects.requireNonNull(inputPath);
+ this.inputPaths.add(inputPath);
}
public String getInputFilePath() {
@@ -541,7 +578,7 @@ public class PMDConfiguration extends AbstractConfiguration {
Renderer renderer = RendererFactory.createRenderer(reportFormat, reportProperties);
renderer.setShowSuppressedViolations(showSuppressedViolations);
if (reportShortNames && inputPaths != null) {
- renderer.setUseShortNames(Arrays.asList(inputPaths.split(",")));
+ renderer.setUseShortNames(Collections.unmodifiableList(new ArrayList<>(inputPaths)));
}
if (withReportWriter) {
renderer.setReportFile(reportFile);
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java
index df10f008f7..5faa8c523c 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java
@@ -9,6 +9,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Properties;
+import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.Nullable;
import net.sourceforge.pmd.PMD;
@@ -32,20 +33,44 @@ import com.beust.jcommander.validators.PositiveInteger;
@InternalApi
public class PMDParameters {
- @Parameter(names = { "--rulesets", "-rulesets", "-R" }, description = "Comma separated list of ruleset names to use.",
- required = true)
- private String rulesets;
+ @Parameter(names = { "--rulesets", "-rulesets", "-R" },
+ description = "Path to a ruleset xml file. "
+ + "The path may reference a resource on the classpath of the application, be a local file system path, or a URL. "
+ + "The option can be repeated, and multiple arguments can be provided to a single occurrence of the option.",
+ required = true,
+ variableArity = true)
+ private List rulesets;
- @Parameter(names = { "--uri", "-uri", "-u" }, description = "Database URI for sources.")
+ @Parameter(names = { "--uri", "-uri", "-u" },
+ description = "Database URI for sources. "
+ + "One of --dir, --file-list or --uri must be provided. "
+ )
private String uri;
- @Parameter(names = { "--dir", "-dir", "-d" }, description = "Root directory for sources.")
- private String sourceDir;
+ @Parameter(names = { "--dir", "-dir", "-d" },
+ description = "Path to a source file, or directory containing source files to analyze. "
+ // About the following line:
+ // In PMD 6, this is only the case for files found in directories. If you
+ // specify a file directly, and it is unknown, then the Java parser is used.
+ + "Note that a file is only effectively added if it matches a language known by PMD. "
+ + "Zip and Jar files are also supported, if they are specified directly "
+ + "(archive files found while exploring a directory are not recursively expanded). "
+ + "This option can be repeated, and multiple arguments can be provided to a single occurrence of the option. "
+ + "One of --dir, --file-list or --uri must be provided. ",
+ variableArity = true)
+ private List inputPaths = new ArrayList<>();
- @Parameter(names = { "--file-list", "-filelist" }, description = "Path to a file containing a list of files to analyze.")
+ @Parameter(names = { "--file-list", "-filelist" },
+ description =
+ "Path to a file containing a list of files to analyze, one path per line. "
+ + "One of --dir, --file-list or --uri must be provided. "
+ )
private String fileListPath;
- @Parameter(names = { "--ignore-list", "-ignorelist" }, description = "Path to a file containing a list of files to ignore.")
+ @Parameter(names = { "--ignore-list", "-ignorelist" },
+ description = "Path to a file containing a list of files to exclude from the analysis, one path per line. "
+ + "This option can be combined with --dir and --file-list. "
+ )
private String ignoreListPath;
@Parameter(names = { "--format", "-format", "-f" }, description = "Report format type.")
@@ -107,12 +132,17 @@ public class PMDParameters {
@Parameter(names = { "-language", "-l" }, description = "Specify a language PMD should use.")
private String language = null;
- @Parameter(names = { "--force-language", "-force-language" }, description = "Force a language to be used for all input files, irrespective of filenames.")
+ @Parameter(names = { "--force-language", "-force-language" },
+ description = "Force a language to be used for all input files, irrespective of file names. "
+ + "When using this option, the automatic language selection by extension is disabled, and PMD "
+ + "tries to parse all input files with the given language's parser. "
+ + "Parsing errors are ignored."
+ )
private String forceLanguage = null;
@Parameter(names = { "--aux-classpath", "-auxclasspath" },
description = "Specifies the classpath for libraries used by the source code. "
- + "This is used by the type resolution. The platform specific path delimiter "
+ + "This is used to resolve types in Java source files. The platform specific path delimiter "
+ "(\":\" on Linux, \";\" on Windows) is used to separate the entries. "
+ "Alternatively, a single 'file:' URL to a text file containing path elements on consecutive lines "
+ "can be specified.")
@@ -199,10 +229,6 @@ public class PMDParameters {
* @throws IllegalArgumentException if the parameters are inconsistent or incomplete
*/
public PMDConfiguration toConfiguration() {
- if (null == this.getSourceDir() && null == this.getUri() && null == this.getFileListPath()) {
- throw new IllegalArgumentException(
- "Please provide a parameter for source root directory (-dir or -d), database URI (-uri or -u), or file list path (-filelist).");
- }
PMDConfiguration configuration = new PMDConfiguration();
configuration.setInputPaths(this.getSourceDir());
configuration.setInputFilePath(this.getFileListPath());
@@ -348,12 +374,22 @@ public class PMDParameters {
return auxclasspath;
}
+ @Deprecated
public String getRulesets() {
+ return StringUtils.join(rulesets, ",");
+ }
+
+ public List getRulesetRefs() {
return rulesets;
}
+ public List getInputPaths() {
+ return inputPaths;
+ }
+
+ @Deprecated
public String getSourceDir() {
- return sourceDir;
+ return StringUtils.join(inputPaths, ",");
}
public String getFileListPath() {
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PmdParametersParseResult.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PmdParametersParseResult.java
index 0ffc8465d0..2f863ba4d9 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PmdParametersParseResult.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PmdParametersParseResult.java
@@ -105,13 +105,29 @@ public final class PmdParametersParseResult {
jcommander.setProgramName("pmd");
try {
- jcommander.parse(args);
+ parseAndValidate(jcommander, result, args);
return new PmdParametersParseResult(result, filterDeprecatedOptions(args));
} catch (ParameterException e) {
return new PmdParametersParseResult(e, filterDeprecatedOptions(args));
}
}
+ private static void parseAndValidate(JCommander jcommander, PMDParameters result, String[] args) {
+ jcommander.parse(args);
+ if (result.isHelp() || result.isVersion()) {
+ return;
+ }
+ // jcommander has no special support for global parameter validation like this
+ // For consistency we report this with a ParameterException
+ if (result.getInputPaths().isEmpty()
+ && null == result.getUri()
+ && null == result.getFileListPath()) {
+ throw new ParameterException(
+ "Please provide a parameter for source root directory (--dir or -d), database URI (--uri or -u), or file list path (--file-list).");
+ }
+
+ }
+
private static Map filterDeprecatedOptions(String... args) {
Map argSet = new LinkedHashMap<>(SUGGESTED_REPLACEMENT);
argSet.keySet().retainAll(new HashSet<>(Arrays.asList(args)));
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java
index 91f8979744..9524fef9c2 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java
@@ -147,7 +147,16 @@ public class GUI implements CPDListener {
@Override
public boolean canIgnoreAnnotations() {
- return "java".equals(terseName);
+ if (terseName == null) {
+ return false;
+ }
+ switch (terseName) {
+ case "cs":
+ case "java":
+ return true;
+ default:
+ return false;
+ }
}
@Override
diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/AstElementNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/AstElementNode.java
index 1a83310247..847b96175a 100644
--- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/AstElementNode.java
+++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/AstElementNode.java
@@ -52,7 +52,7 @@ public final class AstElementNode extends BaseNodeInfo implements SiblingCountin
BaseNodeInfo parent,
Node wrappedNode,
Configuration configuration) {
- super(Type.ELEMENT, configuration.getNamePool(), wrappedNode.getXPathNodeName(), parent);
+ super(determineType(wrappedNode), configuration.getNamePool(), wrappedNode.getXPathNodeName(), parent);
this.treeInfo = document;
this.wrappedNode = wrappedNode;
@@ -65,6 +65,19 @@ public final class AstElementNode extends BaseNodeInfo implements SiblingCountin
}
}
+ private static int determineType(Node node) {
+ // As of PMD 6.48.0, only the experimental HTML module uses this naming
+ // convention to identify non-element nodes.
+ // TODO PMD 7: maybe generalize this to other languages
+ String name = node.getXPathNodeName();
+ if ("#text".equals(name)) {
+ return Type.TEXT;
+ } else if ("#comment".equals(name)) {
+ return Type.COMMENT;
+ }
+ return Type.ELEMENT;
+ }
+
public Map makeAttributes(Node wrappedNode) {
Map atts = new HashMap<>();
Iterator it = wrappedNode.getXPathAttributesIterator();
@@ -179,6 +192,10 @@ public final class AstElementNode extends BaseNodeInfo implements SiblingCountin
@Override
public CharSequence getStringValueCS() {
+ if (getNodeKind() == Type.TEXT || getNodeKind() == Type.COMMENT) {
+ return getUnderlyingNode().getImage();
+ }
+
// https://www.w3.org/TR/xpath-datamodel-31/#ElementNode
// The string-value property of an Element Node must be the
// concatenation of the string-values of all its Text Node
@@ -187,6 +204,7 @@ public final class AstElementNode extends BaseNodeInfo implements SiblingCountin
// Since we represent all our Nodes as elements, there are no
// text nodes
+ // TODO: for some languages like html we have text nodes
return "";
}
diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDParametersTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDParametersTest.java
index 22ca614a06..40ff67955e 100644
--- a/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDParametersTest.java
+++ b/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDParametersTest.java
@@ -4,10 +4,17 @@
package net.sourceforge.pmd.cli;
+import static net.sourceforge.pmd.util.CollectionUtil.listOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import org.apache.commons.lang3.reflect.FieldUtils;
import org.junit.Assert;
import org.junit.Test;
+import net.sourceforge.pmd.PMDConfiguration;
+
public class PMDParametersTest {
@Test
@@ -20,4 +27,60 @@ public class PMDParametersTest {
FieldUtils.writeDeclaredField(parameters, "language", "dummy2", true);
Assert.assertEquals("1.0", parameters.getVersion());
}
+
+ @Test
+ public void testMultipleDirsAndRuleSets() {
+ PmdParametersParseResult result = PmdParametersParseResult.extractParameters(
+ "-d", "a", "b", "-R", "x.xml", "y.xml"
+ );
+ assertMultipleDirsAndRulesets(result);
+ }
+
+ @Test
+ public void testMultipleDirsAndRuleSetsWithCommas() {
+ PmdParametersParseResult result = PmdParametersParseResult.extractParameters(
+ "-d", "a,b", "-R", "x.xml,y.xml"
+ );
+ assertMultipleDirsAndRulesets(result);
+ }
+
+ @Test
+ public void testMultipleDirsAndRuleSetsWithRepeatedOption() {
+ PmdParametersParseResult result = PmdParametersParseResult.extractParameters(
+ "-d", "a", "-d", "b", "-R", "x.xml", "-R", "y.xml"
+ );
+ assertMultipleDirsAndRulesets(result);
+ }
+
+ @Test
+ public void testNoPositionalParametersAllowed() {
+ assertError(
+ // vvvv
+ "-R", "x.xml", "-d", "a", "--", "-d", "b"
+ );
+ }
+
+
+ private void assertMultipleDirsAndRulesets(PmdParametersParseResult result) {
+ assertFalse(result.isError());
+ PMDConfiguration config = result.toConfiguration();
+ assertEquals(config.getAllInputPaths(), listOf("a", "b"));
+ assertEquals(config.getRuleSetPaths(), listOf("x.xml", "y.xml"));
+ }
+
+ @Test
+ public void testEmptyDirOption() {
+ assertError("-d", "-R", "y.xml");
+ }
+
+ @Test
+ public void testEmptyRulesetOption() {
+ assertError("-R", "-d", "something");
+ }
+
+ private void assertError(String... params) {
+ PmdParametersParseResult result = PmdParametersParseResult.extractParameters(params);
+ assertTrue(result.isError());
+ }
+
}
diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/internal/ElementNodeTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/internal/ElementNodeTest.java
index 30283b683b..44d12b98d8 100644
--- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/internal/ElementNodeTest.java
+++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/internal/ElementNodeTest.java
@@ -57,4 +57,54 @@ public class ElementNodeTest {
Assert.assertEquals(0, elementFoo1.compareOrder(elementFoo1));
}
+
+ @Test
+ public void verifyTextNodeType() {
+ DummyRootNode root = new DummyRootNode();
+
+ DummyNode c0 = new DummyNode(false, "foo");
+ c0.setCoords(1, 1, 2, 2);
+ root.addChild(c0, 0);
+
+ DummyNode c1 = new DummyNode(false, "#text");
+ c1.setCoords(2, 1, 2, 2);
+ root.addChild(c1, 1);
+
+ Configuration configuration = Configuration.newConfiguration();
+ AstTreeInfo treeInfo = new AstTreeInfo(root, configuration);
+
+ AstElementNode rootElt = treeInfo.getRootNode().getRootElement();
+ Assert.assertSame(root, rootElt.getUnderlyingNode());
+ Assert.assertEquals(Type.ELEMENT, rootElt.getNodeKind());
+ Assert.assertSame(rootElt, treeInfo.findWrapperFor(root));
+
+ AstElementNode elementFoo0 = rootElt.getChildren().get(0);
+ Assert.assertEquals(Type.ELEMENT, elementFoo0.getNodeKind());
+ Assert.assertSame(c0, elementFoo0.getUnderlyingNode());
+ Assert.assertSame(elementFoo0, treeInfo.findWrapperFor(c0));
+
+ AstElementNode elementText1 = rootElt.getChildren().get(1);
+ Assert.assertEquals(Type.TEXT, elementText1.getNodeKind());
+ Assert.assertSame(c1, elementText1.getUnderlyingNode());
+ Assert.assertSame(elementText1, treeInfo.findWrapperFor(c1));
+ }
+
+ @Test
+ public void verifyCommentNodeType() {
+ DummyRootNode root = new DummyRootNode();
+
+ DummyNode c1 = new DummyNode(false, "#comment");
+ c1.setCoords(2, 1, 2, 2);
+ root.addChild(c1, 0);
+
+ Configuration configuration = Configuration.newConfiguration();
+ AstTreeInfo treeInfo = new AstTreeInfo(root, configuration);
+ AstElementNode rootElt = treeInfo.getRootNode().getRootElement();
+
+ AstElementNode elementComment = rootElt.getChildren().get(0);
+ Assert.assertEquals(Type.COMMENT, elementComment.getNodeKind());
+ Assert.assertSame(c1, elementComment.getUnderlyingNode());
+ Assert.assertSame(elementComment, treeInfo.findWrapperFor(c1));
+ }
+
}
diff --git a/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsTokenizer.java b/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsTokenizer.java
index 931ff75123..cfb78a34a2 100644
--- a/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsTokenizer.java
+++ b/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsTokenizer.java
@@ -21,6 +21,7 @@ public class CsTokenizer extends AntlrTokenizer {
private boolean ignoreUsings = false;
private boolean ignoreLiteralSequences = false;
+ private boolean ignoreAttributes = false;
/**
* Sets the possible options for the C# tokenizer.
@@ -28,19 +29,16 @@ public class CsTokenizer extends AntlrTokenizer {
* @param properties the properties
* @see #IGNORE_USINGS
* @see #OPTION_IGNORE_LITERAL_SEQUENCES
+ * @see #IGNORE_ANNOTATIONS
*/
public void setProperties(Properties properties) {
- ignoreUsings = Boolean.parseBoolean(properties.getProperty(IGNORE_USINGS, Boolean.FALSE.toString()));
- ignoreLiteralSequences = Boolean.parseBoolean(properties.getProperty(OPTION_IGNORE_LITERAL_SEQUENCES,
- Boolean.FALSE.toString()));
+ ignoreUsings = getBooleanProperty(properties, IGNORE_USINGS);
+ ignoreLiteralSequences = getBooleanProperty(properties, OPTION_IGNORE_LITERAL_SEQUENCES);
+ ignoreAttributes = getBooleanProperty(properties, IGNORE_ANNOTATIONS);
}
- public void setIgnoreUsings(boolean ignoreUsings) {
- this.ignoreUsings = ignoreUsings;
- }
-
- public void setIgnoreLiteralSequences(boolean ignoreLiteralSequences) {
- this.ignoreLiteralSequences = ignoreLiteralSequences;
+ private boolean getBooleanProperty(final Properties properties, final String property) {
+ return Boolean.parseBoolean(properties.getProperty(property, Boolean.FALSE.toString()));
}
@Override
@@ -51,7 +49,7 @@ public class CsTokenizer extends AntlrTokenizer {
@Override
protected AntlrTokenFilter getTokenFilter(final AntlrTokenManager tokenManager) {
- return new CsTokenFilter(tokenManager, ignoreUsings, ignoreLiteralSequences);
+ return new CsTokenFilter(tokenManager, ignoreUsings, ignoreLiteralSequences, ignoreAttributes);
}
/**
@@ -70,15 +68,18 @@ public class CsTokenizer extends AntlrTokenizer {
private final boolean ignoreUsings;
private final boolean ignoreLiteralSequences;
+ private final boolean ignoreAttributes;
private boolean discardingUsings = false;
private boolean discardingNL = false;
+ private boolean isDiscardingAttribute = false;
private AntlrToken discardingLiteralsUntil = null;
private boolean discardCurrent = false;
- CsTokenFilter(final AntlrTokenManager tokenManager, boolean ignoreUsings, boolean ignoreLiteralSequences) {
+ CsTokenFilter(final AntlrTokenManager tokenManager, boolean ignoreUsings, boolean ignoreLiteralSequences, boolean ignoreAttributes) {
super(tokenManager);
this.ignoreUsings = ignoreUsings;
this.ignoreLiteralSequences = ignoreLiteralSequences;
+ this.ignoreAttributes = ignoreAttributes;
}
@Override
@@ -91,6 +92,7 @@ public class CsTokenizer extends AntlrTokenizer {
discardCurrent = false;
skipUsingDirectives(currentToken, remainingTokens);
skipLiteralSequences(currentToken, remainingTokens);
+ skipAttributes(currentToken);
}
private void skipUsingDirectives(final AntlrToken currentToken, final Iterable remainingTokens) {
@@ -167,6 +169,25 @@ public class CsTokenizer extends AntlrTokenizer {
discardingNL = currentToken.getKind() == CSharpLexer.NL;
}
+ private void skipAttributes(final AntlrToken currentToken) {
+ if (ignoreAttributes) {
+ switch (currentToken.getKind()) {
+ case CSharpLexer.OPEN_BRACKET:
+ // Start of an attribute.
+ isDiscardingAttribute = true;
+ break;
+ case CSharpLexer.CLOSE_BRACKET:
+ // End of an attribute.
+ isDiscardingAttribute = false;
+ discardCurrent = true;
+ break;
+ default:
+ // Skip any other token.
+ break;
+ }
+ }
+ }
+
private void skipLiteralSequences(final AntlrToken currentToken, final Iterable remainingTokens) {
if (ignoreLiteralSequences) {
final int type = currentToken.getKind();
@@ -222,7 +243,7 @@ public class CsTokenizer extends AntlrTokenizer {
@Override
protected boolean isLanguageSpecificDiscarding() {
- return discardingUsings || discardingNL || isDiscardingLiterals() || discardCurrent;
+ return discardingUsings || discardingNL || isDiscardingAttribute || isDiscardingLiterals() || discardCurrent;
}
}
}
diff --git a/pmd-cs/src/test/java/net/sourceforge/pmd/cpd/CsTokenizerTest.java b/pmd-cs/src/test/java/net/sourceforge/pmd/cpd/CsTokenizerTest.java
index 63c9cd5aee..d05797d726 100644
--- a/pmd-cs/src/test/java/net/sourceforge/pmd/cpd/CsTokenizerTest.java
+++ b/pmd-cs/src/test/java/net/sourceforge/pmd/cpd/CsTokenizerTest.java
@@ -105,18 +105,33 @@ public class CsTokenizerTest extends CpdTextComparisonTest {
doTest("csharp7And8Additions");
}
+ @Test
+ public void testAttributesAreNotIgnored() {
+ doTest("attributes");
+ }
+
+ @Test
+ public void testAttributesAreIgnored() {
+ doTest("attributes", "_ignored", skipAttributes());
+ }
+
private Properties ignoreUsings() {
- return properties(true, false);
+ return properties(true, false, false);
}
private Properties skipLiteralSequences() {
- return properties(false, true);
+ return properties(false, true, false);
}
- private Properties properties(boolean ignoreUsings, boolean ignoreLiteralSequences) {
+ private Properties skipAttributes() {
+ return properties(false, false, true);
+ }
+
+ private Properties properties(boolean ignoreUsings, boolean ignoreLiteralSequences, boolean ignoreAttributes) {
Properties properties = new Properties();
properties.setProperty(Tokenizer.IGNORE_USINGS, Boolean.toString(ignoreUsings));
properties.setProperty(Tokenizer.OPTION_IGNORE_LITERAL_SEQUENCES, Boolean.toString(ignoreLiteralSequences));
+ properties.setProperty(Tokenizer.IGNORE_ANNOTATIONS, Boolean.toString(ignoreAttributes));
return properties;
}
}
diff --git a/pmd-cs/src/test/resources/net/sourceforge/pmd/lang/cs/cpd/testdata/attributes.cs b/pmd-cs/src/test/resources/net/sourceforge/pmd/lang/cs/cpd/testdata/attributes.cs
new file mode 100644
index 0000000000..811e151362
--- /dev/null
+++ b/pmd-cs/src/test/resources/net/sourceforge/pmd/lang/cs/cpd/testdata/attributes.cs
@@ -0,0 +1,42 @@
+[Serializable]
+public class SampleClass
+{
+ // Objects of this type can be serialized.
+}
+
+[System.Runtime.InteropServices.DllImport("user32.dll")]
+extern static void SampleMethod();
+
+void MethodA([In][Out] ref double x) { }
+void MethodB([Out][In] ref double x) { }
+void MethodC([In, Out] ref double x) { }
+
+[Conditional("DEBUG"), Conditional("TEST1")]
+void TraceMethod()
+{
+ // ...
+}
+
+[DllImport("user32.dll")]
+[DllImport("user32.dll", SetLastError=false, ExactSpelling=false)]
+[DllImport("user32.dll", ExactSpelling=false, SetLastError=false)]
+
+using System;
+using System.Reflection;
+[assembly: AssemblyTitleAttribute("Production assembly 4")]
+[module: CLSCompliant(true)]
+
+// default: applies to method
+[ValidatedContract]
+int Method1() { return 0; }
+
+// applies to method
+[method: ValidatedContract]
+int Method2() { return 0; }
+
+// applies to parameter
+int Method3([ValidatedContract] string contract) { return 0; }
+
+// applies to return value
+[return: ValidatedContract]
+int Method4() { return 0; }
\ No newline at end of file
diff --git a/pmd-cs/src/test/resources/net/sourceforge/pmd/lang/cs/cpd/testdata/attributes.txt b/pmd-cs/src/test/resources/net/sourceforge/pmd/lang/cs/cpd/testdata/attributes.txt
new file mode 100644
index 0000000000..ec85e49500
--- /dev/null
+++ b/pmd-cs/src/test/resources/net/sourceforge/pmd/lang/cs/cpd/testdata/attributes.txt
@@ -0,0 +1,229 @@
+ [Image] or [Truncated image[ Bcol Ecol
+L1
+ [\[] 1 2
+ [Serializable] 2 14
+ [\]] 14 15
+L2
+ [public] 1 7
+ [class] 8 13
+ [SampleClass] 14 25
+L3
+ [{] 1 2
+L5
+ [}] 1 2
+L7
+ [\[] 1 2
+ [System] 2 8
+ [.] 8 9
+ [Runtime] 9 16
+ [.] 16 17
+ [InteropServices] 17 32
+ [.] 32 33
+ [DllImport] 33 42
+ [(] 42 43
+ ["user32.dll"] 43 55
+ [)] 55 56
+ [\]] 56 57
+L8
+ [extern] 1 7
+ [static] 8 14
+ [void] 15 19
+ [SampleMethod] 20 32
+ [(] 32 33
+ [)] 33 34
+ [;] 34 35
+L10
+ [void] 1 5
+ [MethodA] 6 13
+ [(] 13 14
+ [\[] 14 15
+ [In] 15 17
+ [\]] 17 18
+ [\[] 18 19
+ [Out] 19 22
+ [\]] 22 23
+ [ref] 24 27
+ [double] 28 34
+ [x] 35 36
+ [)] 36 37
+ [{] 38 39
+ [}] 40 41
+L11
+ [void] 1 5
+ [MethodB] 6 13
+ [(] 13 14
+ [\[] 14 15
+ [Out] 15 18
+ [\]] 18 19
+ [\[] 19 20
+ [In] 20 22
+ [\]] 22 23
+ [ref] 24 27
+ [double] 28 34
+ [x] 35 36
+ [)] 36 37
+ [{] 38 39
+ [}] 40 41
+L12
+ [void] 1 5
+ [MethodC] 6 13
+ [(] 13 14
+ [\[] 14 15
+ [In] 15 17
+ [,] 17 18
+ [Out] 19 22
+ [\]] 22 23
+ [ref] 24 27
+ [double] 28 34
+ [x] 35 36
+ [)] 36 37
+ [{] 38 39
+ [}] 40 41
+L14
+ [\[] 1 2
+ [Conditional] 2 13
+ [(] 13 14
+ ["DEBUG"] 14 21
+ [)] 21 22
+ [,] 22 23
+ [Conditional] 24 35
+ [(] 35 36
+ ["TEST1"] 36 43
+ [)] 43 44
+ [\]] 44 45
+L15
+ [void] 1 5
+ [TraceMethod] 6 17
+ [(] 17 18
+ [)] 18 19
+L16
+ [{] 1 2
+L18
+ [}] 1 2
+L20
+ [\[] 1 2
+ [DllImport] 2 11
+ [(] 11 12
+ ["user32.dll"] 12 24
+ [)] 24 25
+ [\]] 25 26
+L21
+ [\[] 1 2
+ [DllImport] 2 11
+ [(] 11 12
+ ["user32.dll"] 12 24
+ [,] 24 25
+ [SetLastError] 26 38
+ [=] 38 39
+ [false] 39 44
+ [,] 44 45
+ [ExactSpelling] 46 59
+ [=] 59 60
+ [false] 60 65
+ [)] 65 66
+ [\]] 66 67
+L22
+ [\[] 1 2
+ [DllImport] 2 11
+ [(] 11 12
+ ["user32.dll"] 12 24
+ [,] 24 25
+ [ExactSpelling] 26 39
+ [=] 39 40
+ [false] 40 45
+ [,] 45 46
+ [SetLastError] 47 59
+ [=] 59 60
+ [false] 60 65
+ [)] 65 66
+ [\]] 66 67
+L24
+ [using] 1 6
+ [System] 7 13
+ [;] 13 14
+L25
+ [using] 1 6
+ [System] 7 13
+ [.] 13 14
+ [Reflection] 14 24
+ [;] 24 25
+L26
+ [\[] 1 2
+ [assembly] 2 10
+ [:] 10 11
+ [AssemblyTitleAttribute] 12 34
+ [(] 34 35
+ ["Production assembly 4"] 35 58
+ [)] 58 59
+ [\]] 59 60
+L27
+ [\[] 1 2
+ [module] 2 8
+ [:] 8 9
+ [CLSCompliant] 10 22
+ [(] 22 23
+ [true] 23 27
+ [)] 27 28
+ [\]] 28 29
+L30
+ [\[] 1 2
+ [ValidatedContract] 2 19
+ [\]] 19 20
+L31
+ [int] 1 4
+ [Method1] 5 12
+ [(] 12 13
+ [)] 13 14
+ [{] 15 16
+ [return] 17 23
+ [0] 24 25
+ [;] 25 26
+ [}] 27 28
+L34
+ [\[] 1 2
+ [method] 2 8
+ [:] 8 9
+ [ValidatedContract] 10 27
+ [\]] 27 28
+L35
+ [int] 1 4
+ [Method2] 5 12
+ [(] 12 13
+ [)] 13 14
+ [{] 15 16
+ [return] 17 23
+ [0] 24 25
+ [;] 25 26
+ [}] 27 28
+L38
+ [int] 1 4
+ [Method3] 5 12
+ [(] 12 13
+ [\[] 13 14
+ [ValidatedContract] 14 31
+ [\]] 31 32
+ [string] 33 39
+ [contract] 40 48
+ [)] 48 49
+ [{] 50 51
+ [return] 52 58
+ [0] 59 60
+ [;] 60 61
+ [}] 62 63
+L41
+ [\[] 1 2
+ [return] 2 8
+ [:] 8 9
+ [ValidatedContract] 10 27
+ [\]] 27 28
+L42
+ [int] 1 4
+ [Method4] 5 12
+ [(] 12 13
+ [)] 13 14
+ [{] 15 16
+ [return] 17 23
+ [0] 24 25
+ [;] 25 26
+ [}] 27 28
+EOF
diff --git a/pmd-cs/src/test/resources/net/sourceforge/pmd/lang/cs/cpd/testdata/attributes_ignored.txt b/pmd-cs/src/test/resources/net/sourceforge/pmd/lang/cs/cpd/testdata/attributes_ignored.txt
new file mode 100644
index 0000000000..c2de96f13f
--- /dev/null
+++ b/pmd-cs/src/test/resources/net/sourceforge/pmd/lang/cs/cpd/testdata/attributes_ignored.txt
@@ -0,0 +1,109 @@
+ [Image] or [Truncated image[ Bcol Ecol
+L2
+ [public] 1 7
+ [class] 8 13
+ [SampleClass] 14 25
+L3
+ [{] 1 2
+L5
+ [}] 1 2
+L8
+ [extern] 1 7
+ [static] 8 14
+ [void] 15 19
+ [SampleMethod] 20 32
+ [(] 32 33
+ [)] 33 34
+ [;] 34 35
+L10
+ [void] 1 5
+ [MethodA] 6 13
+ [(] 13 14
+ [ref] 24 27
+ [double] 28 34
+ [x] 35 36
+ [)] 36 37
+ [{] 38 39
+ [}] 40 41
+L11
+ [void] 1 5
+ [MethodB] 6 13
+ [(] 13 14
+ [ref] 24 27
+ [double] 28 34
+ [x] 35 36
+ [)] 36 37
+ [{] 38 39
+ [}] 40 41
+L12
+ [void] 1 5
+ [MethodC] 6 13
+ [(] 13 14
+ [ref] 24 27
+ [double] 28 34
+ [x] 35 36
+ [)] 36 37
+ [{] 38 39
+ [}] 40 41
+L15
+ [void] 1 5
+ [TraceMethod] 6 17
+ [(] 17 18
+ [)] 18 19
+L16
+ [{] 1 2
+L18
+ [}] 1 2
+L24
+ [using] 1 6
+ [System] 7 13
+ [;] 13 14
+L25
+ [using] 1 6
+ [System] 7 13
+ [.] 13 14
+ [Reflection] 14 24
+ [;] 24 25
+L31
+ [int] 1 4
+ [Method1] 5 12
+ [(] 12 13
+ [)] 13 14
+ [{] 15 16
+ [return] 17 23
+ [0] 24 25
+ [;] 25 26
+ [}] 27 28
+L35
+ [int] 1 4
+ [Method2] 5 12
+ [(] 12 13
+ [)] 13 14
+ [{] 15 16
+ [return] 17 23
+ [0] 24 25
+ [;] 25 26
+ [}] 27 28
+L38
+ [int] 1 4
+ [Method3] 5 12
+ [(] 12 13
+ [string] 33 39
+ [contract] 40 48
+ [)] 48 49
+ [{] 50 51
+ [return] 52 58
+ [0] 59 60
+ [;] 60 61
+ [}] 62 63
+L42
+ [int] 1 4
+ [Method4] 5 12
+ [(] 12 13
+ [)] 13 14
+ [{] 15 16
+ [return] 17 23
+ [0] 24 25
+ [;] 25 26
+ [}] 27 28
+EOF
diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java
index 1de270c3f1..7c6d742cc1 100644
--- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java
+++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java
@@ -30,6 +30,8 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest {
SUPPORTED_LANGUAGES_PMD = "apex, ecmascript, html, java, jsp, modelica, plsql, pom, scala, swift, vf, vm, wsdl, xml, xsl";
}
+ private final String srcDir = new File(".", "src/test/resources/sample-source/java/").getAbsolutePath();
+
@Test
public void testFileExistence() {
assertTrue(getBinaryDistribution().exists());
@@ -69,27 +71,34 @@ public class BinaryDistributionIT extends AbstractBinaryDistributionTest {
}
@Test
- public void runPMD() throws Exception {
- String srcDir = new File(".", "src/test/resources/sample-source/java/").getAbsolutePath();
+ public void testPmdJavaQuickstart() throws Exception {
+ ExecutionResult result = PMDExecutor.runPMDRules(folder.newFile().toPath(), tempDir, srcDir, "rulesets/java/quickstart.xml");
+ result.assertExecutionResult(4, "");
+ }
- ExecutionResult result;
-
- result = PMDExecutor.runPMD(tempDir); // without any argument, display usage help and error
- result.assertExecutionResultErrOutput(1, CliMessages.runWithHelpFlagMessage());
-
- result = PMDExecutor.runPMD(tempDir, "-h");
- result.assertExecutionResult(0, SUPPORTED_LANGUAGES_PMD);
-
- result = PMDExecutor.runPMDRules(folder.newFile().toPath(), tempDir, srcDir, "src/test/resources/rulesets/sample-ruleset.xml");
- result.assertExecutionResult(4, "", "JumbledIncrementer.java:8:");
-
- // also test XML format
- result = PMDExecutor.runPMDRules(folder.newFile().toPath(), tempDir, srcDir, "src/test/resources/rulesets/sample-ruleset.xml", "xml");
+ @Test
+ public void testPmdXmlFormat() throws Exception {
+ ExecutionResult result = PMDExecutor.runPMDRules(folder.newFile().toPath(), tempDir, srcDir, "src/test/resources/rulesets/sample-ruleset.xml", "xml");
result.assertExecutionResult(4, "", "JumbledIncrementer.java\">");
result.assertExecutionResult(4, "", "\n"
+ " Hello, { greeting}!
\n"
- + " "
+ + " \n"
+ " \n"
- + "
\n"
+ + "
\n"
+ "
\n"
+ "
Uppercased Full Name: {uppercasedFullName}
\n"
+ "
\n"
+ + " \n"
+ + " Test
\n"
+ + " \n"
+ "";
@Test
public void selectTextNode() {
// from https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.js_props_getter
// "Donโt add spaces around the property, for example, { data } is not valid HTML."
- String xpath = "//*[local-name() = '#text'][contains(@Text, '{ ')]";
+ String xpath = "//text()[contains(., '{ ')]";
List violations = runXPath(LIGHTNING_WEB_COMPONENT, xpath);
Assert.assertEquals(1, violations.size());
Assert.assertEquals(3, violations.get(0).getBeginLine());
}
+ @Test
+ public void selectTextNodeByNodeNameShouldNotWork() {
+ String xpath = "//*[local-name() = '#text']";
+ List violations = runXPath(LIGHTNING_WEB_COMPONENT, xpath);
+ Assert.assertEquals(0, violations.size());
+ }
+
+ @Test
+ public void verifyTextNodeName() {
+ ASTHtmlDocument document = HtmlParsingHelper.DEFAULT.parse("foobar
");
+ ASTHtmlTextNode textNode = document.getFirstDescendantOfType(ASTHtmlTextNode.class);
+ Assert.assertEquals("#text", textNode.getXPathNodeName());
+ }
+
+ @Test
+ public void verifyCommentNodeName() {
+ ASTHtmlDocument document = HtmlParsingHelper.DEFAULT.parse("
");
+ ASTHtmlComment comment = document.getFirstDescendantOfType(ASTHtmlComment.class);
+ Assert.assertEquals("#comment", comment.getXPathNodeName());
+ }
+
@Test
public void selectAttributes() {
// from https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.js_props_getter
@@ -56,6 +84,27 @@ public class HtmlXPathRuleTest {
Assert.assertEquals(4, violations.get(0).getBeginLine());
}
+ @Test
+ public void selectAttributesMultiple() {
+ // from https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.js_props_getter
+ // "Donโt add spaces around the property, for example, { data } is not valid HTML."
+ String xpath = "//*[@*[local-name() = ('value', 'onchange')] = '{']";
+
+ List violations = runXPath(LIGHTNING_WEB_COMPONENT, xpath);
+ Assert.assertEquals(2, violations.size());
+ Assert.assertEquals(4, violations.get(0).getBeginLine());
+ Assert.assertEquals(6, violations.get(1).getBeginLine());
+ }
+
+ @Test
+ public void selectAttributeByName() {
+ String xpath = "//*[@*[local-name() = 'if:true']]";
+
+ List violations = runXPath(LIGHTNING_WEB_COMPONENT, xpath);
+ Assert.assertEquals(1, violations.size());
+ Assert.assertEquals(10, violations.get(0).getBeginLine());
+ }
+
private List runXPath(String html, String xpath) {
LanguageVersion htmlLanguage = LanguageRegistry.findLanguageByTerseName(HtmlLanguageModule.TERSE_NAME).getDefaultVersion();
Parser parser = htmlLanguage.getLanguageVersionHandler().getParser();
diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt
index d175247c17..a75413cd0e 100644
--- a/pmd-java/etc/grammar/Java.jjt
+++ b/pmd-java/etc/grammar/Java.jjt
@@ -796,7 +796,7 @@ TOKEN :
"\u1310","\u1312"-"\u1315","\u1318"-"\u131e","\u1320"-"\u1346","\u1348"-"\u135a",
"\u13a0"-"\u13f4","\u1401"-"\u166c","\u166f"-"\u1676","\u1681"-"\u169a","\u16a0"-"\u16ea",
"\u1780"-"\u17b3","\u17db","\u1820"-"\u1877","\u1880"-"\u18a8","\u1e00"-"\u1e9b",
- "\u1ea0"-"\u1ef9","\u1f00"-"\u1f15","\u1f18"-"\u1f1d","\u1f20"-"\u1f45","\u1f48"-"\u1f4d",
+ "\u1ea0"-"\u1ef9","\u1d00"-"\u1eef","\u1f00"-"\u1f15","\u1f18"-"\u1f1d","\u1f20"-"\u1f45","\u1f48"-"\u1f4d",
"\u1f50"-"\u1f57","\u1f59","\u1f5b","\u1f5d","\u1f5f"-"\u1f7d","\u1f80"-"\u1fb4",
"\u1fb6"-"\u1fbc","\u1fbe","\u1fc2"-"\u1fc4","\u1fc6"-"\u1fcc","\u1fd0"-"\u1fd3",
"\u1fd6"-"\u1fdb","\u1fe0"-"\u1fec","\u1ff2"-"\u1ff4","\u1ff6"-"\u1ffc","\u203f"-"\u2040",
@@ -804,7 +804,7 @@ TOKEN :
"\u2124","\u2126","\u2128","\u212a"-"\u212d","\u212f"-"\u2131","\u2133"-"\u2139",
"\u2160"-"\u2183","\u3005"-"\u3007","\u3021"-"\u3029","\u3031"-"\u3035","\u3038"-"\u303a",
"\u3041"-"\u3094","\u309d"-"\u309e","\u30a1"-"\u30fe","\u3105"-"\u312c","\u3131"-"\u318e",
- "\u31a0"-"\u31b7","\u3400"-"\u4db5","\u4e00"-"\u9fa5","\ua000"-"\ua48c","\uac00"-"\ud7a3",
+ "\u31a0"-"\u31b7","\u3400"-"\u4db5","\u4e00"-"\u9fa5","\ua000"-"\ua48c","\ua490"-"\uabff","\uac00"-"\ud7a3",
"\uf900"-"\ufa2d","\ufb00"-"\ufb06","\ufb13"-"\ufb17","\ufb1d","\ufb1f"-"\ufb28",
"\ufb2a"-"\ufb36","\ufb38"-"\ufb3c","\ufb3e","\ufb40"-"\ufb41","\ufb43"-"\ufb44",
"\ufb46"-"\ufbb1","\ufbd3"-"\ufd3d","\ufd50"-"\ufd8f","\ufd92"-"\ufdc7","\ufdf0"-"\ufdfb",
@@ -865,7 +865,7 @@ TOKEN :
"\u12f0"-"\u130e","\u1310","\u1312"-"\u1315","\u1318"-"\u131e","\u1320"-"\u1346",
"\u1348"-"\u135a","\u1369"-"\u1371","\u13a0"-"\u13f4","\u1401"-"\u166c","\u166f"-"\u1676",
"\u1681"-"\u169a","\u16a0"-"\u16ea","\u1780"-"\u17d3","\u17db","\u17e0"-"\u17e9",
- "\u180b"-"\u180e","\u1810"-"\u1819","\u1820"-"\u1877","\u1880"-"\u18a9","\u1e00"-"\u1e9b",
+ "\u180b"-"\u180e","\u1810"-"\u1819","\u1820"-"\u1877","\u1880"-"\u18a9","\u1d00"-"\u1d7f","\u1e00"-"\u1e9b",
"\u1ea0"-"\u1ef9","\u1f00"-"\u1f15","\u1f18"-"\u1f1d","\u1f20"-"\u1f45","\u1f48"-"\u1f4d",
"\u1f50"-"\u1f57","\u1f59","\u1f5b","\u1f5d","\u1f5f"-"\u1f7d","\u1f80"-"\u1fb4",
"\u1fb6"-"\u1fbc","\u1fbe","\u1fc2"-"\u1fc4","\u1fc6"-"\u1fcc","\u1fd0"-"\u1fd3",
@@ -876,6 +876,7 @@ TOKEN :
"\u2160"-"\u2183","\u3005"-"\u3007","\u3021"-"\u302f","\u3031"-"\u3035","\u3038"-"\u303a",
"\u3041"-"\u3094","\u3099"-"\u309a","\u309d"-"\u309e","\u30a1"-"\u30fe","\u3105"-"\u312c",
"\u3131"-"\u318e","\u31a0"-"\u31b7","\u3400"-"\u4db5","\u4e00"-"\u9fa5","\ua000"-"\ua48c",
+ "\ua490"-"\uabff",
"\uac00"-"\ud7a3","\uf900"-"\ufa2d","\ufb00"-"\ufb06","\ufb13"-"\ufb17","\ufb1d"-"\ufb28",
"\ufb2a"-"\ufb36","\ufb38"-"\ufb3c","\ufb3e","\ufb40"-"\ufb41","\ufb43"-"\ufb44",
"\ufb46"-"\ufbb1","\ufbd3"-"\ufd3d","\ufd50"-"\ufd8f","\ufd92"-"\ufdc7","\ufdf0"-"\ufdfb",
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ImmutableFieldRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ImmutableFieldRule.java
index cdeab9b9b7..d0a47c0b7b 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ImmutableFieldRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ImmutableFieldRule.java
@@ -44,7 +44,13 @@ public class ImmutableFieldRule extends AbstractJavaRulechainRule {
);
private static final Set INVALIDATING_FIELD_ANNOTS =
- setOf("lombok.Setter");
+ setOf(
+ "lombok.Setter",
+ "org.mockito.Mock",
+ "org.mockito.InjectMocks",
+ "org.springframework.beans.factory.annotation.Autowired",
+ "org.springframework.boot.test.mock.mockito.MockBean"
+ );
public ImmutableFieldRule() {
super(ASTFieldDeclaration.class);
@@ -54,7 +60,6 @@ public class ImmutableFieldRule extends AbstractJavaRulechainRule {
@Override
public Object visit(ASTFieldDeclaration field, Object data) {
-
ASTAnyTypeDeclaration enclosingType = field.getEnclosingType();
if (field.getEffectiveVisibility().isAtMost(Visibility.V_PRIVATE)
&& !field.getModifiers().hasAny(JModifier.VOLATILE, JModifier.STATIC, JModifier.FINAL)
diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java
index 422a620615..684662f3fd 100644
--- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java
+++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java
@@ -145,6 +145,12 @@ public class ParserCornersTest extends BaseJavaTreeDumpTest {
java7.parse(code);
}
+ @Test
+ public void testUnicodeIndent() {
+ // https://github.com/pmd/pmd/issues/3423
+ java7.parseResource("UnicodeIdentifier.java");
+ }
+
@Test
public void testParsersCases15() {
doTest("ParserCornerCases", java5);
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/UnicodeIdentifier.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/UnicodeIdentifier.java
new file mode 100644
index 0000000000..3eae15d2a9
--- /dev/null
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/UnicodeIdentifier.java
@@ -0,0 +1,37 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// https://github.com/pmd/pmd/issues/3423
+
+package com.example.pmdtest;
+
+public class PmdTest {
+
+ private static final int lแตค = 1;
+ private static final int ฮผแตค = 2;
+
+ public static void main(String[] args) {
+ System.out.println(lแตค + ฮผแตค);
+ }
+
+}
+
+enum CodeSet {
+
+ START_CODE_A('ร'),
+ START_CODE_B('ร'),
+ START_CODE_C('ร'),
+ A_TILDE('\u00c3'),
+ STOP_CODE('ร');
+
+ private final char encoding;
+
+ CodeSet(final char encoding) {
+ this.encoding = encoding;
+ }
+
+ public char getEncoding() {
+ return encoding;
+ }
+}
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UseCollectionIsEmpty.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UseCollectionIsEmpty.xml
index 9bb180e1a3..fd77707ff7 100644
--- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UseCollectionIsEmpty.xml
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UseCollectionIsEmpty.xml
@@ -516,4 +516,31 @@ public class Foo {
}
]]>
+
+ With records
+ 0
+ stringSet) {
+
+ public boolean hasMoreThanOneItem() {
+ return this.stringSet.size() > 1;
+ }
+} ]]>
+
+
+ With records and size check
+ 1
+ 6
+ stringSet) {
+
+ public boolean hasMoreThanOneItem() {
+ return this.stringSet.size() == 0;
+ }
+} ]]>
+
diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ImmutableField.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ImmutableField.xml
index dae43dfb98..2949c72273 100644
--- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ImmutableField.xml
+++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ImmutableField.xml
@@ -657,4 +657,35 @@ public class ExampleImmutableField {
}
]]>
+
+
+ #3874 [java] ImmutableField reports fields annotated with @Autowired (Spring) and @Mock (Mockito)
+ 0
+
+
diff --git a/pmd-javascript/etc/grammar/Ecmascript5.jj b/pmd-javascript/etc/grammar/Ecmascript5.jj
index e180c2f760..30d8800119 100644
--- a/pmd-javascript/etc/grammar/Ecmascript5.jj
+++ b/pmd-javascript/etc/grammar/Ecmascript5.jj
@@ -488,6 +488,7 @@ TOKEN :
"\u1E00"-"\u1E9B",
"\u1EA0"-"\u1EE0",
"\u1EE1"-"\u1EF9",
+ "\u1EE1"-"\u1EEF",
"\u1F00"-"\u1F15",
"\u1F18"-"\u1F1D",
"\u1F20"-"\u1F39",
@@ -534,11 +535,10 @@ TOKEN :
"\u31A0"-"\u31B7",
"\u3400",
"\u4DB5",
- "\u4E00",
- "\u9FA5",
+ "\u4E00"-"\u9EA5",
"\uA000"-"\uA48C",
- "\uAC00",
- "\uD7A3",
+ "\uA490"-"\uABFF",
+ "\uAC00"-"\ud7AF",
"\uF900"-"\uFA2D",
"\uFB00"-"\uFB06",
"\uFB13"-"\uFB17",
diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptParserTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptParserTest.java
index 95261052c4..2827f1461f 100644
--- a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptParserTest.java
+++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptParserTest.java
@@ -180,6 +180,16 @@ public class EcmascriptParserTest extends EcmascriptParserTestBase {
assertEquals("^=", infix.getImage());
}
+ @Test
+ public void testUnicodeCjk() {
+ // the first is u+4F60
+ js.parse("import { Test } from 'test2'\n"
+ + "define('element', class extends Test {\n"
+ + " ไฝ ๅฅฝ \n"
+ + " }\n"
+ + "})");
+ }
+
/**
* [javascript] Failing with OutOfMemoryError parsing a Javascript file #2081
*/
diff --git a/pmd-python/etc/grammar/Python.jj b/pmd-python/etc/grammar/Python.jj
index b9c2313b62..d1dd62cb0c 100644
--- a/pmd-python/etc/grammar/Python.jj
+++ b/pmd-python/etc/grammar/Python.jj
@@ -133,7 +133,57 @@ TOKEN : /* KEYWORDS */
TOKEN : /* Python identifiers */
{
< NAME: ( | )* >
-| < #LETTER: ["_","a"-"z","A"-"Z"] >
+| < #LETTER : ["$","A"-"Z","_","a"-"z","\u00a2"-"\u00a5","\u00aa","\u00b5","\u00ba",
+ "\u00c0"-"\u00d6","\u00d8"-"\u00f6","\u00f8"-"\u021f","\u0222"-"\u0233","\u0250"-"\u02ad",
+ "\u02b0"-"\u02b8","\u02bb"-"\u02c1","\u02d0"-"\u02d1","\u02e0"-"\u02e4","\u02ee","\u037a",
+ "\u0386","\u0388"-"\u038a","\u038c","\u038e"-"\u03a1","\u03a3"-"\u03ce","\u03d0"-"\u03d7",
+ "\u03da"-"\u03f3","\u0400"-"\u0481","\u048c"-"\u04c4","\u04c7"-"\u04c8","\u04cb"-"\u04cc",
+ "\u04d0"-"\u04f5","\u04f8"-"\u04f9","\u0531"-"\u0556","\u0559","\u0561"-"\u0587",
+ "\u05d0"-"\u05ea","\u05f0"-"\u05f2","\u0621"-"\u063a","\u0640"-"\u064a","\u0671"-"\u06d3",
+ "\u06d5","\u06e5"-"\u06e6","\u06fa"-"\u06fc","\u0710","\u0712"-"\u072c","\u0780"-"\u07a5",
+ "\u0905"-"\u0939","\u093d","\u0950","\u0958"-"\u0961","\u0985"-"\u098c","\u098f"-"\u0990",
+ "\u0993"-"\u09a8","\u09aa"-"\u09b0","\u09b2","\u09b6"-"\u09b9","\u09dc"-"\u09dd",
+ "\u09df"-"\u09e1","\u09f0"-"\u09f3","\u0a05"-"\u0a0a","\u0a0f"-"\u0a10","\u0a13"-"\u0a28",
+ "\u0a2a"-"\u0a30","\u0a32"-"\u0a33","\u0a35"-"\u0a36","\u0a38"-"\u0a39","\u0a59"-"\u0a5c",
+ "\u0a5e","\u0a72"-"\u0a74","\u0a85"-"\u0a8b","\u0a8d","\u0a8f"-"\u0a91","\u0a93"-"\u0aa8",
+ "\u0aaa"-"\u0ab0","\u0ab2"-"\u0ab3","\u0ab5"-"\u0ab9","\u0abd","\u0ad0","\u0ae0",
+ "\u0b05"-"\u0b0c","\u0b0f"-"\u0b10","\u0b13"-"\u0b28","\u0b2a"-"\u0b30","\u0b32"-"\u0b33",
+ "\u0b36"-"\u0b39","\u0b3d","\u0b5c"-"\u0b5d","\u0b5f"-"\u0b61","\u0b85"-"\u0b8a",
+ "\u0b8e"-"\u0b90","\u0b92"-"\u0b95","\u0b99"-"\u0b9a","\u0b9c","\u0b9e"-"\u0b9f",
+ "\u0ba3"-"\u0ba4","\u0ba8"-"\u0baa","\u0bae"-"\u0bb5","\u0bb7"-"\u0bb9","\u0c05"-"\u0c0c",
+ "\u0c0e"-"\u0c10","\u0c12"-"\u0c28","\u0c2a"-"\u0c33","\u0c35"-"\u0c39","\u0c60"-"\u0c61",
+ "\u0c85"-"\u0c8c","\u0c8e"-"\u0c90","\u0c92"-"\u0ca8","\u0caa"-"\u0cb3","\u0cb5"-"\u0cb9",
+ "\u0cde","\u0ce0"-"\u0ce1","\u0d05"-"\u0d0c","\u0d0e"-"\u0d10","\u0d12"-"\u0d28",
+ "\u0d2a"-"\u0d39","\u0d60"-"\u0d61","\u0d85"-"\u0d96","\u0d9a"-"\u0db1","\u0db3"-"\u0dbb",
+ "\u0dbd","\u0dc0"-"\u0dc6","\u0e01"-"\u0e30","\u0e32"-"\u0e33","\u0e3f"-"\u0e46",
+ "\u0e81"-"\u0e82","\u0e84","\u0e87"-"\u0e88","\u0e8a","\u0e8d","\u0e94"-"\u0e97",
+ "\u0e99"-"\u0e9f","\u0ea1"-"\u0ea3","\u0ea5","\u0ea7","\u0eaa"-"\u0eab","\u0ead"-"\u0eb0",
+ "\u0eb2"-"\u0eb3","\u0ebd","\u0ec0"-"\u0ec4","\u0ec6","\u0edc"-"\u0edd","\u0f00",
+ "\u0f40"-"\u0f47","\u0f49"-"\u0f6a","\u0f88"-"\u0f8b","\u1000"-"\u1021","\u1023"-"\u1027",
+ "\u1029"-"\u102a","\u1050"-"\u1055","\u10a0"-"\u10c5","\u10d0"-"\u10f6","\u1100"-"\u1159",
+ "\u115f"-"\u11a2","\u11a8"-"\u11f9","\u1200"-"\u1206","\u1208"-"\u1246","\u1248",
+ "\u124a"-"\u124d","\u1250"-"\u1256","\u1258","\u125a"-"\u125d","\u1260"-"\u1286","\u1288",
+ "\u128a"-"\u128d","\u1290"-"\u12ae","\u12b0","\u12b2"-"\u12b5","\u12b8"-"\u12be","\u12c0",
+ "\u12c2"-"\u12c5","\u12c8"-"\u12ce","\u12d0"-"\u12d6","\u12d8"-"\u12ee","\u12f0"-"\u130e",
+ "\u1310","\u1312"-"\u1315","\u1318"-"\u131e","\u1320"-"\u1346","\u1348"-"\u135a",
+ "\u13a0"-"\u13f4","\u1401"-"\u166c","\u166f"-"\u1676","\u1681"-"\u169a","\u16a0"-"\u16ea",
+ "\u1780"-"\u17b3","\u17db","\u1820"-"\u1877","\u1880"-"\u18a8","\u1e00"-"\u1e9b",
+ "\u1ea0"-"\u1ef9","\u1d00"-"\u1eef","\u1f00"-"\u1f15","\u1f18"-"\u1f1d","\u1f20"-"\u1f45","\u1f48"-"\u1f4d",
+ "\u1f50"-"\u1f57","\u1f59","\u1f5b","\u1f5d","\u1f5f"-"\u1f7d","\u1f80"-"\u1fb4",
+ "\u1fb6"-"\u1fbc","\u1fbe","\u1fc2"-"\u1fc4","\u1fc6"-"\u1fcc","\u1fd0"-"\u1fd3",
+ "\u1fd6"-"\u1fdb","\u1fe0"-"\u1fec","\u1ff2"-"\u1ff4","\u1ff6"-"\u1ffc","\u203f"-"\u2040",
+ "\u207f","\u20a0"-"\u20af","\u2102","\u2107","\u210a"-"\u2113","\u2115","\u2119"-"\u211d",
+ "\u2124","\u2126","\u2128","\u212a"-"\u212d","\u212f"-"\u2131","\u2133"-"\u2139",
+ "\u2160"-"\u2183","\u3005"-"\u3007","\u3021"-"\u3029","\u3031"-"\u3035","\u3038"-"\u303a",
+ "\u3041"-"\u3094","\u309d"-"\u309e","\u30a1"-"\u30fe","\u3105"-"\u312c","\u3131"-"\u318e",
+ "\u31a0"-"\u31b7","\u3400"-"\u4db5","\u4e00"-"\u9fa5","\ua000"-"\ua48c","\ua490"-"\uabff","\uac00"-"\ud7a3",
+ "\uf900"-"\ufa2d","\ufb00"-"\ufb06","\ufb13"-"\ufb17","\ufb1d","\ufb1f"-"\ufb28",
+ "\ufb2a"-"\ufb36","\ufb38"-"\ufb3c","\ufb3e","\ufb40"-"\ufb41","\ufb43"-"\ufb44",
+ "\ufb46"-"\ufbb1","\ufbd3"-"\ufd3d","\ufd50"-"\ufd8f","\ufd92"-"\ufdc7","\ufdf0"-"\ufdfb",
+ "\ufe33"-"\ufe34","\ufe4d"-"\ufe4f","\ufe69","\ufe70"-"\ufe72","\ufe74","\ufe76"-"\ufefc",
+ "\uff04","\uff21"-"\uff3a","\uff3f","\uff41"-"\uff5a","\uff65"-"\uffbe","\uffc2"-"\uffc7",
+ "\uffca"-"\uffcf","\uffd2"-"\uffd7","\uffda"-"\uffdc","\uffe0"-"\uffe1","\uffe5"-"\uffe6"]>
+
}
diff --git a/pmd-python/src/test/java/net/sourceforge/pmd/cpd/PythonTokenizerTest.java b/pmd-python/src/test/java/net/sourceforge/pmd/cpd/PythonTokenizerTest.java
index 254b8cdbe8..e72eb3e489 100644
--- a/pmd-python/src/test/java/net/sourceforge/pmd/cpd/PythonTokenizerTest.java
+++ b/pmd-python/src/test/java/net/sourceforge/pmd/cpd/PythonTokenizerTest.java
@@ -42,8 +42,19 @@ public class PythonTokenizerTest extends CpdTextComparisonTest {
doTest("backticks");
}
+ @Test
+ public void testUnicode() {
+ doTest("sample_unicode");
+ }
+
@Test
public void testTabWidth() {
doTest("tabWidth");
}
+
+ @Test
+ public void testVarWithDollar() {
+ doTest("var_with_dollar");
+ }
+
}
diff --git a/pmd-python/src/test/resources/net/sourceforge/pmd/lang/python/cpd/testdata/sample_unicode.py b/pmd-python/src/test/resources/net/sourceforge/pmd/lang/python/cpd/testdata/sample_unicode.py
new file mode 100644
index 0000000000..b0143e7e1a
--- /dev/null
+++ b/pmd-python/src/test/resources/net/sourceforge/pmd/lang/python/cpd/testdata/sample_unicode.py
@@ -0,0 +1,4 @@
+# note: add more examples here when bugs arise
+
+def check():
+ total_cost_ฮผs = [] # https://github.com/pmd/pmd/issues/2604
diff --git a/pmd-python/src/test/resources/net/sourceforge/pmd/lang/python/cpd/testdata/sample_unicode.txt b/pmd-python/src/test/resources/net/sourceforge/pmd/lang/python/cpd/testdata/sample_unicode.txt
new file mode 100644
index 0000000000..ec7d598bfc
--- /dev/null
+++ b/pmd-python/src/test/resources/net/sourceforge/pmd/lang/python/cpd/testdata/sample_unicode.txt
@@ -0,0 +1,13 @@
+ [Image] or [Truncated image[ Bcol Ecol
+L3
+ [def] 1 4
+ [check] 5 10
+ [(] 10 11
+ [)] 11 12
+ [:] 12 13
+L4
+ [total_cost_ฮผs] 5 18
+ [=] 19 20
+ [\[] 21 22
+ [\]] 22 23
+EOF
diff --git a/pmd-python/src/test/resources/net/sourceforge/pmd/lang/python/cpd/testdata/var_with_dollar.py b/pmd-python/src/test/resources/net/sourceforge/pmd/lang/python/cpd/testdata/var_with_dollar.py
new file mode 100644
index 0000000000..5c42520282
--- /dev/null
+++ b/pmd-python/src/test/resources/net/sourceforge/pmd/lang/python/cpd/testdata/var_with_dollar.py
@@ -0,0 +1,2 @@
+def check():
+ a$a = []
diff --git a/pmd-python/src/test/resources/net/sourceforge/pmd/lang/python/cpd/testdata/var_with_dollar.txt b/pmd-python/src/test/resources/net/sourceforge/pmd/lang/python/cpd/testdata/var_with_dollar.txt
new file mode 100644
index 0000000000..07eeb8ba7d
--- /dev/null
+++ b/pmd-python/src/test/resources/net/sourceforge/pmd/lang/python/cpd/testdata/var_with_dollar.txt
@@ -0,0 +1,13 @@
+ [Image] or [Truncated image[ Bcol Ecol
+L1
+ [def] 1 4
+ [check] 5 10
+ [(] 10 11
+ [)] 11 12
+ [:] 12 13
+L2
+ [a$a] 5 8
+ [=] 9 10
+ [\[] 11 12
+ [\]] 12 13
+EOF