Merge branch 'master' into cli-exit-codes-processing-errors

This commit is contained in:
Andreas Dangel 2024-05-17 14:01:57 +02:00
commit 2d9385f462
No known key found for this signature in database
GPG Key ID: 93450DF2DF9A3FA3
127 changed files with 1794 additions and 1586 deletions

View File

@ -5,28 +5,26 @@
[![Join the chat](https://img.shields.io/gitter/room/pmd/pmd)](https://app.gitter.im/#/room/#pmd_pmd:gitter.im?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build Status](https://github.com/pmd/pmd/workflows/build/badge.svg?branch=master)](https://github.com/pmd/pmd/actions)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.sourceforge.pmd/pmd/badge.svg)](https://maven-badges.herokuapp.com/maven-central/net.sourceforge.pmd/pmd)
[![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-green?labelColor=blue)](https://github.com/jvm-repo-rebuild/reproducible-central#net.sourceforge.pmd:pmd)
[![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-green?labelColor=blue)](https://github.com/jvm-repo-rebuild/reproducible-central/tree/master/content/net/sourceforge/pmd#readme)
[![Coverage Status](https://coveralls.io/repos/github/pmd/pmd/badge.svg)](https://coveralls.io/github/pmd/pmd)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/ea550046a02344ec850553476c4aa2ca)](https://www.codacy.com/gh/pmd/pmd/dashboard?utm_source=github.com&utm_medium=referral&utm_content=pmd/pmd&utm_campaign=Badge_Grade)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/ea550046a02344ec850553476c4aa2ca)](https://app.codacy.com/organizations/gh/pmd/dashboard)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](code_of_conduct.md)
[![Documentation (latest)](https://img.shields.io/badge/docs-latest-green)](https://docs.pmd-code.org/latest/)
**PMD** is a source code analyzer. It finds common programming flaws like unused variables, empty catch blocks,
unnecessary object creation, and so forth. It supports many languages. It can be extended with custom rules.
It uses JavaCC and Antlr to parse source files into abstract syntax trees (AST) and runs rules against them to find violations.
Rules can be written in Java or using a XPath query.
**PMD** is an extensible multilanguage static code analyzer. It finds common programming flaws like unused variables,
empty catch blocks, unnecessary object creation, and so forth. It's mainly concerned with **Java and
Apex**, but **supports 16 other languages**. It comes with **400+ built-in rules**. It can be
extended with custom rules. It uses JavaCC and Antlr to parse source files into abstract syntax trees
(AST) and runs rules against them to find violations. Rules can be written in Java or using a XPath query.
It supports Java, JavaScript, Salesforce.com Apex and Visualforce,
Modelica, PLSQL, Apache Velocity, HTML, XML and XSL.
Currently, PMD supports Java, JavaScript, Salesforce.com Apex and Visualforce,
Kotlin, Swift, Modelica, PLSQL, Apache Velocity, JSP, WSDL, Maven POM, HTML, XML and XSL.
Scala is supported, but there are currently no Scala rules available.
Additionally, it includes **CPD**, the copy-paste-detector. CPD finds duplicated code in
C/C++, C#, Dart, Fortran, Gherkin, Go, Groovy, HTML, Java, JavaScript, JSP, Kotlin, Lua, Matlab, Modelica,
Objective-C, Perl, PHP, PLSQL, Python, Ruby, Salesforce.com Apex and Visualforce, Scala, Swift, T-SQL,
Apache Velocity, and XML.
In the future we hope to add support for data/control flow analysis and automatic (quick) fixes where
it makes sense.
Coco, C/C++, C#, Dart, Fortran, Gherkin, Go, Groovy, HTML, Java, JavaScript, JSP, Julia, Kotlin,
Lua, Matlab, Modelica, Objective-C, Perl, PHP, PLSQL, Python, Ruby, Salesforce.com Apex and
Visualforce, Scala, Swift, T-SQL, Typescript, Apache Velocity, WSDL, XML and XSL.
## 🚀 Installation and Usage

View File

@ -24,10 +24,20 @@ additional_js:
<!-- You can link to an individual panel, the id is determined from the title of the panel -->
<!-- See custom/shuffle_panel.html for the details -->
**PMD** is a static source code analyzer. It finds common programming flaws like
unused variables, empty catch blocks, unnecessary object creation, and
so forth. It's mainly concerned with **Java and Apex**, but **supports 16 other
languages**.
**PMD** is an extensible multilanguage static code analyzer. It finds common programming flaws like unused variables,
empty catch blocks, unnecessary object creation, and so forth. It's mainly concerned with **Java and
Apex**, but **supports 16 other languages**. It comes with **400+ built-in rules**. It can be
extended with custom rules. It uses JavaCC and Antlr to parse source files into abstract syntax trees
(AST) and runs rules against them to find violations. Rules can be written in Java or using a XPath query.
Currently, PMD supports Java, JavaScript, Salesforce.com Apex and Visualforce,
Kotlin, Swift, Modelica, PLSQL, Apache Velocity, JSP, WSDL, Maven POM, HTML, XML and XSL.
Scala is supported, but there are currently no Scala rules available.
Additionally, it includes **CPD**, the copy-paste-detector. CPD finds duplicated code in
Coco, C/C++, C#, Dart, Fortran, Gherkin, Go, Groovy, HTML, Java, JavaScript, JSP, Julia, Kotlin,
Lua, Matlab, Modelica, Objective-C, Perl, PHP, PLSQL, Python, Ruby, Salesforce.com Apex and
Visualforce, Scala, Swift, T-SQL, Typescript, Apache Velocity, WSDL, XML and XSL.
PMD features many **built-in checks** (in PMD lingo, *rules*), which are documented
for each language in our [Rule references](#shuffle-panel-rule-references). We
@ -43,7 +53,7 @@ things, PMD can be run:
* As a [bld operation](pmd_userdocs_tools_bld.html)
* From [command-line](pmd_userdocs_installation.html#running-pmd-via-command-line)
**CPD**, the **copy-paste detector**, is also distributed with PMD. You can also use it
**CPD**, the **copy-paste detector**, is also distributed with PMD. You can use it
in a variety of ways, which are [documented here](pmd_userdocs_cpd.html).
## 💾 Download

View File

@ -49,23 +49,21 @@ To represent attributes, we must map Java values to [XPath Data Model (XDM)](htt
values. In the following table we refer to the type conversion function as `conv`, a function from Java types
to XDM types.
| Java type `T` | XSD type `conv(T)` |
|---------------|---------------------------------------|
| `int` | `xs:integer` |
| `long` | `xs:integer` |
| `double` | `xs:decimal` |
| `float` | `xs:decimal` |
| `boolean` | `xs:boolean` |
| `String` | `xs:string` |
| `Character` | `xs:string` |
| `Enum<E>` | `xs:string` (uses `Object::toString`) |
| `List<E>` | `conv(E)*` (a sequence type) |
| Java type `T` | XSD type `conv(T)` |
|-------------------|---------------------------------------|
| `int` | `xs:integer` |
| `long` | `xs:integer` |
| `double` | `xs:decimal` |
| `float` | `xs:decimal` |
| `boolean` | `xs:boolean` |
| `String` | `xs:string` |
| `Character` | `xs:string` |
| `Enum<E>` | `xs:string` (uses `Object::toString`) |
| `Collection<E>` | `conv(E)*` (a sequence type) |
The same `conv` function is used to translate rule property values to XDM values.
{% include warning.html content="Lists are only supported for rule properties, not attributes." %}
Additionaly, PMD's own `net.sourceforge.pmd.lang.document.Chars` is also translated to a `xs:string`
## Rule properties

View File

@ -14,17 +14,29 @@ This is a {{ site.pmd.release_type }} release.
### 🚀 New and noteworthy
### 🐛 Fixed Issues
#### Collections exposed as XPath attributes
Up to now, all AST node getters would be exposed to XPath, as long as the return type was a primitive (boxed or unboxed), String or Enum. That meant that collections, even of these basic types, were not exposed, so for instance accessing Apex's `ASTUserClass.getInterfaceNames()` to list the interfaces implemented by a class was impossible from XPath, and would require writing a Java rule to check it.
Since this release, PMD will also expose any getter returning a collection of any supported type as a sequence through an XPath attribute. They would require to use apropriate XQuery functions to manipulate the sequence. So for instance, to detect any given `ASTUserClass` in Apex that implements `Queueable`, it is now possible to write:
```xml
/UserClass[@InterfaceNames = 'Queueable']
```
### 🐛 Fixed Issues
* cli
* [#2827](https://github.com/pmd/pmd/issues/2827): \[cli] Consider processing errors in exit status
* core
* [#4467](https://github.com/pmd/pmd/issues/4467): \[core] Expose collections from getters as XPath sequence attributes
* [#4978](https://github.com/pmd/pmd/issues/4978): \[core] Referenced Rulesets do not emit details on validation errors
* [#4983](https://github.com/pmd/pmd/pull/4983): \[cpd] Fix CPD crashes about unicode escapes
* java
* [#4912](https://github.com/pmd/pmd/issues/4912): \[java] Unable to parse some Java9+ resource references
* [#4973](https://github.com/pmd/pmd/pull/4973): \[java] Stop parsing Java for CPD
* [#4980](https://github.com/pmd/pmd/issues/4980): \[java] Bad intersection, unrelated class types java.lang.Object\[] and java.lang.Number
* [#4988](https://github.com/pmd/pmd/pull/4988): \[java] Fix impl of ASTVariableId::isResourceDeclaration / VariableId/@<!-- -->ResourceDeclaration
* [#5006](https://github.com/pmd/pmd/issues/5006): \[java] Bad intersection, unrelated class types Child and Parent<? extends Child>
* java-bestpractices
* [#4278](https://github.com/pmd/pmd/issues/4278): \[java] UnusedPrivateMethod FP with Junit 5 @MethodSource and default factory method name
* [#4852](https://github.com/pmd/pmd/issues/4852): \[java] ReplaceVectorWithList false-positive (neither Vector nor List usage)
@ -32,6 +44,10 @@ This is a {{ site.pmd.release_type }} release.
* [#4985](https://github.com/pmd/pmd/issues/4985): \[java] UnusedPrivateMethod false-positive / method reference in combination with custom object
* java-codestyle
* [#4930](https://github.com/pmd/pmd/issues/4930): \[java] EmptyControlStatement should not allow empty try with concise resources
* java-errorprone
* [#4042](https://github.com/pmd/pmd/issues/4042): \[java] A false negative about the rule StringBufferInstantiationWithChar
* java-multithreading
* [#2368](https://github.com/pmd/pmd/issues/2368): \[java] False positive UnsynchronizedStaticFormatter in static initializer
### 🚨 API Changes

View File

@ -7,6 +7,8 @@ package net.sourceforge.pmd.lang.apex.ast;
import java.util.List;
import java.util.stream.Collectors;
import net.sourceforge.pmd.lang.rule.xpath.NoAttribute;
import com.google.summit.ast.Identifier;
public final class ASTReferenceExpression extends AbstractApexNode.Many<Identifier> {
@ -38,6 +40,7 @@ public final class ASTReferenceExpression extends AbstractApexNode.Many<Identifi
return "";
}
@NoAttribute
public List<String> getNames() {
return nodes.stream().map(Identifier::getString).collect(Collectors.toList());
}

View File

@ -1,7 +1,7 @@
+- ApexFile[@DefiningType = "InnerClassLocations", @RealLoc = true]
+- UserClass[@DefiningType = "InnerClassLocations", @Image = "InnerClassLocations", @RealLoc = true, @SimpleName = "InnerClassLocations", @SuperClassName = ""]
+- UserClass[@DefiningType = "InnerClassLocations", @Image = "InnerClassLocations", @InterfaceNames = (), @RealLoc = true, @SimpleName = "InnerClassLocations", @SuperClassName = ""]
+- ModifierNode[@Abstract = false, @DefiningType = "InnerClassLocations", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 1, @Override = false, @Private = false, @Protected = false, @Public = true, @RealLoc = true, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false]
+- UserClass[@DefiningType = "InnerClassLocations.bar1", @Image = "bar1", @RealLoc = true, @SimpleName = "bar1", @SuperClassName = ""]
+- UserClass[@DefiningType = "InnerClassLocations.bar1", @Image = "bar1", @InterfaceNames = (), @RealLoc = true, @SimpleName = "bar1", @SuperClassName = ""]
| +- ModifierNode[@Abstract = false, @DefiningType = "InnerClassLocations.bar1", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 1, @Override = false, @Private = false, @Protected = false, @Public = true, @RealLoc = true, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false]
| +- Method[@Arity = 0, @CanonicalName = "m", @Constructor = false, @DefiningType = "InnerClassLocations.bar1", @Image = "m", @RealLoc = true, @ReturnType = "void", @StaticInitializer = false]
| +- ModifierNode[@Abstract = false, @DefiningType = "InnerClassLocations.bar1", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 1, @Override = false, @Private = false, @Protected = false, @Public = true, @RealLoc = true, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false]
@ -14,7 +14,7 @@
| +- MethodCallExpression[@DefiningType = "InnerClassLocations.bar1", @FullMethodName = "System.out.println", @InputParametersSize = 1, @MethodName = "println", @RealLoc = true]
| +- ReferenceExpression[@DefiningType = "InnerClassLocations.bar1", @Image = "System", @RealLoc = true, @ReferenceType = ReferenceType.METHOD, @SObjectType = false, @SafeNav = false]
| +- LiteralExpression[@Boolean = false, @Decimal = false, @DefiningType = "InnerClassLocations.bar1", @Double = false, @Image = "foo", @Integer = false, @LiteralType = LiteralType.STRING, @Long = false, @Name = null, @Null = false, @RealLoc = true, @String = true]
+- UserClass[@DefiningType = "InnerClassLocations.bar2", @Image = "bar2", @RealLoc = true, @SimpleName = "bar2", @SuperClassName = ""]
+- UserClass[@DefiningType = "InnerClassLocations.bar2", @Image = "bar2", @InterfaceNames = (), @RealLoc = true, @SimpleName = "bar2", @SuperClassName = ""]
+- ModifierNode[@Abstract = false, @DefiningType = "InnerClassLocations.bar2", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 1, @Override = false, @Private = false, @Protected = false, @Public = true, @RealLoc = true, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false]
+- Method[@Arity = 0, @CanonicalName = "m", @Constructor = false, @DefiningType = "InnerClassLocations.bar2", @Image = "m", @RealLoc = true, @ReturnType = "void", @StaticInitializer = false]
+- ModifierNode[@Abstract = false, @DefiningType = "InnerClassLocations.bar2", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 1, @Override = false, @Private = false, @Protected = false, @Public = true, @RealLoc = true, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false]

View File

@ -1,5 +1,5 @@
+- ApexFile[@DefiningType = "NullCoalescingOperator", @RealLoc = true]
+- UserClass[@DefiningType = "NullCoalescingOperator", @Image = "NullCoalescingOperator", @RealLoc = true, @SimpleName = "NullCoalescingOperator", @SuperClassName = ""]
+- UserClass[@DefiningType = "NullCoalescingOperator", @Image = "NullCoalescingOperator", @InterfaceNames = (), @RealLoc = true, @SimpleName = "NullCoalescingOperator", @SuperClassName = ""]
+- ModifierNode[@Abstract = false, @DefiningType = "NullCoalescingOperator", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 1, @Override = false, @Private = false, @Protected = false, @Public = true, @RealLoc = true, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false]
+- Method[@Arity = 2, @CanonicalName = "leftOrRight", @Constructor = false, @DefiningType = "NullCoalescingOperator", @Image = "leftOrRight", @RealLoc = true, @ReturnType = "String", @StaticInitializer = false]
+- ModifierNode[@Abstract = false, @DefiningType = "NullCoalescingOperator", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 1, @Override = false, @Private = false, @Protected = false, @Public = true, @RealLoc = true, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false]

View File

@ -1,11 +1,11 @@
+- ApexFile[@DefiningType = "Foo", @RealLoc = true]
+- UserClass[@DefiningType = "Foo", @Image = "Foo", @RealLoc = true, @SimpleName = "Foo", @SuperClassName = ""]
+- UserClass[@DefiningType = "Foo", @Image = "Foo", @InterfaceNames = (), @RealLoc = true, @SimpleName = "Foo", @SuperClassName = ""]
+- ModifierNode[@Abstract = false, @DefiningType = "Foo", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 1, @Override = false, @Private = false, @Protected = false, @Public = true, @RealLoc = true, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false]
+- Field[@DefiningType = "Foo", @Image = "x", @Name = "x", @RealLoc = true, @Type = "Integer", @Value = null]
| +- ModifierNode[@Abstract = false, @DefiningType = "Foo", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 0, @Override = false, @Private = false, @Protected = false, @Public = false, @RealLoc = false, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false]
+- Field[@DefiningType = "Foo", @Image = "profileUrl", @Name = "profileUrl", @RealLoc = true, @Type = "String", @Value = null]
| +- ModifierNode[@Abstract = false, @DefiningType = "Foo", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 0, @Override = false, @Private = false, @Protected = false, @Public = false, @RealLoc = false, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false]
+- FieldDeclarationStatements[@DefiningType = "Foo", @RealLoc = true, @TypeName = "Integer"]
+- FieldDeclarationStatements[@DefiningType = "Foo", @RealLoc = true, @TypeArguments = (), @TypeName = "Integer"]
| +- ModifierNode[@Abstract = false, @DefiningType = "Foo", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 0, @Override = false, @Private = false, @Protected = false, @Public = false, @RealLoc = false, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false]
| +- FieldDeclaration[@DefiningType = "Foo", @Image = "x", @Name = "x", @RealLoc = true]
| +- VariableExpression[@DefiningType = "Foo", @Image = "anIntegerField", @RealLoc = true]
@ -14,7 +14,7 @@
| | +- EmptyReferenceExpression[@DefiningType = null, @RealLoc = false]
| +- VariableExpression[@DefiningType = "Foo", @Image = "x", @RealLoc = true]
| +- EmptyReferenceExpression[@DefiningType = null, @RealLoc = false]
+- FieldDeclarationStatements[@DefiningType = "Foo", @RealLoc = true, @TypeName = "String"]
+- FieldDeclarationStatements[@DefiningType = "Foo", @RealLoc = true, @TypeArguments = (), @TypeName = "String"]
| +- ModifierNode[@Abstract = false, @DefiningType = "Foo", @DeprecatedTestMethod = false, @Final = false, @Global = false, @InheritedSharing = false, @Modifiers = 0, @Override = false, @Private = false, @Protected = false, @Public = false, @RealLoc = false, @Static = false, @Test = false, @TestOrTestSetup = false, @Transient = false, @Virtual = false, @WebService = false, @WithSharing = false, @WithoutSharing = false]
| +- FieldDeclaration[@DefiningType = "Foo", @Image = "profileUrl", @Name = "profileUrl", @RealLoc = true]
| +- MethodCallExpression[@DefiningType = "Foo", @FullMethodName = "toExternalForm", @InputParametersSize = 0, @MethodName = "toExternalForm", @RealLoc = true]

View File

@ -31,9 +31,9 @@ class TreeExportCliTest extends BaseCliTest {
final CliExecutionResult output = runCliSuccessfully("-i", "-f", "xml", "-PlineSeparator=LF");
output.checkStdOut(equalTo("<?xml version='1.0' encoding='UTF-8' ?>\n"
+ "<dummyRootNode Image=''>\n"
+ " <dummyNode Image='a'>\n"
+ " <dummyNode Image='b' />\n"
+ "<dummyRootNode Image='' Lines='[, , ]'>\n"
+ " <dummyNode Image='a' Lines='[a, a, a]'>\n"
+ " <dummyNode Image='b' Lines='[b, b, b]' />\n"
+ " </dummyNode>\n"
+ "</dummyRootNode>\n"));
});
@ -44,9 +44,9 @@ class TreeExportCliTest extends BaseCliTest {
File file = newFileWithContents("(a(b))");
final CliExecutionResult result = runCliSuccessfully("--file", file.getAbsolutePath(), "-f", "xml", "-PlineSeparator=LF");
result.checkStdOut(equalTo("<?xml version='1.0' encoding='UTF-8' ?>\n"
+ "<dummyRootNode Image=''>\n"
+ " <dummyNode Image='a'>\n"
+ " <dummyNode Image='b' />\n"
+ "<dummyRootNode Image='' Lines='[, , ]'>\n"
+ " <dummyNode Image='a' Lines='[a, a, a]'>\n"
+ " <dummyNode Image='b' Lines='[b, b, b]' />\n"
+ " </dummyNode>\n"
+ "</dummyRootNode>\n"));
}

View File

@ -7,7 +7,6 @@ package net.sourceforge.pmd.lang.rule.xpath;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.NonNull;
@ -106,16 +105,11 @@ public final class Attribute {
return null;
} else {
DeprecatedAttribute annot = method.getAnnotation(DeprecatedAttribute.class);
String result = annot != null
return annot != null
? annot.replaceWith()
: method.isAnnotationPresent(Deprecated.class)
? DeprecatedAttribute.NO_REPLACEMENT
: null;
if (result == null && List.class.isAssignableFrom(method.getReturnType())) {
// Lists are generally deprecated, see #2451
result = DeprecatedAttribute.NO_REPLACEMENT;
}
return result;
}
}

View File

@ -13,7 +13,11 @@ import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@ -119,7 +123,27 @@ public class AttributeAxisIterator implements Iterator<Attribute> {
private boolean isConsideredReturnType(Method method) {
Class<?> klass = method.getReturnType();
return CONSIDERED_RETURN_TYPES.contains(klass) || klass.isEnum();
if (CONSIDERED_RETURN_TYPES.contains(klass) || klass.isEnum()) {
return true;
}
if (Collection.class.isAssignableFrom(klass)) {
Type t = method.getGenericReturnType();
if (t instanceof ParameterizedType) {
try {
// ignore type variables, such as List<N> we could check all bounds, but probably it's overkill
Type actualTypeArgument = ((ParameterizedType) t).getActualTypeArguments()[0];
if (!TypeVariable.class.isAssignableFrom(actualTypeArgument.getClass())) {
Class<?> elementKlass = Class.forName(actualTypeArgument.getTypeName());
return CONSIDERED_RETURN_TYPES.contains(elementKlass) || elementKlass.isEnum();
}
} catch (ClassNotFoundException e) {
throw AssertionUtil.shouldNotReachHere("Method '" + method + "' should return a known type, but: " + e, e);
}
}
}
return false;
}
private boolean isIgnored(Class<?> nodeClass, Method method) {

View File

@ -68,6 +68,7 @@ class AstAttributeNode extends BaseNodeInfo implements SiblingCountingNode {
@Override
public AtomicSequence atomize() {
getTreeInfo().getLogger().recordUsageOf(attribute);
if (value == null) {
value = DomainConversion.convert(attribute.getValue());
}

View File

@ -258,13 +258,14 @@ public class SaxonXPathRuleQuery {
return NAME_POOL;
}
final class StaticContextWithProperties extends IndependentContext {
private final Map<StructuredQName, PropertyDescriptor<?>> propertiesByName = new HashMap<>();
StaticContextWithProperties(Configuration config) {
super(config);
// This statement is necessary for Saxon to support sequence-valued attributes
getPackageData().setSchemaAware(true);
}
public void declareProperty(PropertyDescriptor<?> prop) {

View File

@ -5,6 +5,7 @@
package net.sourceforge.pmd.lang.ast;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -125,6 +126,11 @@ public class DummyNode extends AbstractNode<DummyNode, DummyNode> {
return attributes.iterator();
}
// phony attribute that repeats the image 3 times
public List<String> getLines() {
return Arrays.asList(getImage(), getImage(), getImage());
}
public static class DummyRootNode extends DummyNode implements RootNode, GenericNode<DummyNode> {
// FIXME remove this

View File

@ -41,7 +41,9 @@ class AttributeAxisIteratorTest {
AttributeAxisIterator it = new AttributeAxisIterator(dummyNode);
assertEquals(DEFAULT_ATTRS, toMap(it).keySet());
Set<String> expected = CollectionUtil.setUnion(DEFAULT_ATTRS, "Lines");
assertEquals(expected, toMap(it).keySet());
}
@Test
@ -50,7 +52,7 @@ class AttributeAxisIteratorTest {
AttributeAxisIterator it = new AttributeAxisIterator(dummyNode);
Set<String> expected = CollectionUtil.setUnion(DEFAULT_ATTRS, "Enum");
Set<String> expected = CollectionUtil.setUnion(DEFAULT_ATTRS, "Enum", "Lines");
assertEquals(expected, toMap(it).keySet());
}
@ -62,7 +64,9 @@ class AttributeAxisIteratorTest {
AttributeAxisIterator it = new AttributeAxisIterator(dummyNode);
assertEquals(DEFAULT_ATTRS, toMap(it).keySet());
Set<String> expected = CollectionUtil.setUnion(DEFAULT_ATTRS, "List", "Lines");
assertEquals(expected, toMap(it).keySet());
}
/**

View File

@ -134,6 +134,15 @@ class SaxonXPathRuleQueryTest {
assertQuery(0, "(/)[self::document-node(element(DummyNodeX))]", dummy);
}
@Test
void testListAttributes() {
DummyRootNode dummy = helper.parse("(a(b))");
List<Node> result = assertQuery(1,
"//dummyNode[count(distinct-values(@Lines)) > 0 and not(empty(index-of(@Lines, 'a')))]", dummy);
assertEquals(dummy.getChild(0), result.get(0));
}
@Test
void ruleChainVisits() {
SaxonXPathRuleQuery query = createQuery("//dummyNode[@Image='baz']/foo | //bar[@Public = 'true'] | //dummyNode[@Public = false()] | //dummyNode");
@ -143,11 +152,11 @@ class SaxonXPathRuleQueryTest {
assertTrue(ruleChainVisits.contains("bar"));
assertEquals(3, query.nodeNameToXPaths.size());
assertExpression("(self::node()[(string(data(@Image))) eq baz])/child::element(foo)", query.getExpressionsForLocalNameOrDefault("dummyNode").get(0));
assertExpression("self::node()[(boolean(data(@Public))) eq false]", query.getExpressionsForLocalNameOrDefault("dummyNode").get(1));
assertExpression("(self::node()[(data(attribute::attribute(Image))) = baz])/child::element(foo)", query.getExpressionsForLocalNameOrDefault("dummyNode").get(0));
assertExpression("self::node()[(data(attribute::attribute(Public))) = false]", query.getExpressionsForLocalNameOrDefault("dummyNode").get(1));
assertExpression("self::node()", query.getExpressionsForLocalNameOrDefault("dummyNode").get(2));
assertExpression("self::node()[(string(data(@Public))) eq true]", query.getExpressionsForLocalNameOrDefault("bar").get(0));
assertExpression("(((docOrder((((/)/descendant::element(dummyNode))[(string(data(@Image))) eq baz])/child::element(foo))) | (((/)/descendant::element(bar))[(string(data(@Public))) eq true])) | (((/)/descendant::element(dummyNode))[(boolean(data(@Public))) eq false])) | ((/)/descendant::element(dummyNode))", query.getFallbackExpr());
assertExpression("self::node()[(data(attribute::attribute(Public))) = true]", query.getExpressionsForLocalNameOrDefault("bar").get(0));
assertExpression("(((docOrder((((/)/descendant::element(dummyNode))[(data(attribute::attribute(Image))) = baz])/child::element(foo))) | (((/)/descendant::element(bar))[(data(attribute::attribute(Public))) = true])) | (((/)/descendant::element(dummyNode))[(data(attribute::attribute(Public))) = false])) | ((/)/descendant::element(dummyNode))", query.getFallbackExpr());
}
@Test
@ -157,8 +166,8 @@ class SaxonXPathRuleQueryTest {
assertEquals(1, ruleChainVisits.size());
assertTrue(ruleChainVisits.contains("dummyNode"));
assertEquals(2, query.nodeNameToXPaths.size());
assertExpression("(self::node()[(boolean(data(@Test1))) eq false])[(boolean(data(@Test2))) eq true]", query.getExpressionsForLocalNameOrDefault("dummyNode").get(0));
assertExpression("(((/)/descendant::element(dummyNode))[(boolean(data(@Test1))) eq false])[(boolean(data(@Test2))) eq true]", query.getFallbackExpr());
assertExpression("(self::node()[(data(attribute::attribute(Test1))) = false])[(data(attribute::attribute(Test2))) = true]", query.getExpressionsForLocalNameOrDefault("dummyNode").get(0));
assertExpression("(((/)/descendant::element(dummyNode))[(data(attribute::attribute(Test1))) = false])[(data(attribute::attribute(Test2))) = true]", query.getFallbackExpr());
}
@Test
@ -168,8 +177,8 @@ class SaxonXPathRuleQueryTest {
assertEquals(1, ruleChainVisits.size());
assertTrue(ruleChainVisits.contains("dummyNode"));
assertEquals(2, query.nodeNameToXPaths.size());
assertExpression("self::node()[Q{http://pmd.sourceforge.net/pmd-dummy}imageIs(exactly-one(convertTo_xs:string(data(@Image))))]", query.getExpressionsForLocalNameOrDefault("dummyNode").get(0));
assertExpression("((/)/descendant::element(Q{}dummyNode))[Q{http://pmd.sourceforge.net/pmd-dummy}imageIs(exactly-one(convertTo_xs:string(data(@Image))))]", query.getFallbackExpr());
assertExpression("self::node()[Q{http://pmd.sourceforge.net/pmd-dummy}imageIs(exactly-one(convertTo_xs:string(data(attribute::attribute(Image)))))]", query.getExpressionsForLocalNameOrDefault("dummyNode").get(0));
assertExpression("((/)/descendant::element(Q{}dummyNode))[Q{http://pmd.sourceforge.net/pmd-dummy}imageIs(exactly-one(convertTo_xs:string(data(attribute::attribute(Image)))))]", query.getFallbackExpr());
}
/**
@ -206,8 +215,8 @@ class SaxonXPathRuleQueryTest {
assertEquals(1, ruleChainVisits.size());
assertTrue(ruleChainVisits.contains("dummyNode"));
assertEquals(2, query.nodeNameToXPaths.size());
assertExpression("(((self::node()/child::element(foo))/child::element())/child::element(bar))[(string(data(@Test))) eq false]", query.getExpressionsForLocalNameOrDefault("dummyNode").get(0));
assertExpression("docOrder(((docOrder((((/)/descendant::element(dummyNode))/child::element(foo))/child::element()))/child::element(bar))[(string(data(@Test))) eq false])", query.getFallbackExpr());
assertExpression("(((self::node()/child::element(foo))/child::element())/child::element(bar))[(data(attribute::attribute(Test))) = false]", query.getExpressionsForLocalNameOrDefault("dummyNode").get(0));
assertExpression("docOrder(((docOrder((((/)/descendant::element(dummyNode))/child::element(foo))/child::element()))/child::element(bar))[(data(attribute::attribute(Test))) = false])", query.getFallbackExpr());
}
@Test
@ -217,8 +226,8 @@ class SaxonXPathRuleQueryTest {
assertEquals(1, ruleChainVisits.size());
assertTrue(ruleChainVisits.contains("dummyNode"));
assertEquals(2, query.nodeNameToXPaths.size());
assertExpression("((((self::node()/child::element(foo))[(string(data(@Baz))) eq a])/child::element())/child::element(bar))[(string(data(@Test))) eq false]", query.getExpressionsForLocalNameOrDefault("dummyNode").get(0));
assertExpression("docOrder(((docOrder(((((/)/descendant::element(dummyNode))/child::element(foo))[(string(data(@Baz))) eq a])/child::element()))/child::element(bar))[(string(data(@Test))) eq false])", query.getFallbackExpr());
assertExpression("((((self::node()/child::element(foo))[(data(attribute::attribute(Baz))) = a])/child::element())/child::element(bar))[(data(attribute::attribute(Test))) = false]", query.getExpressionsForLocalNameOrDefault("dummyNode").get(0));
assertExpression("docOrder(((docOrder(((((/)/descendant::element(dummyNode))/child::element(foo))[(data(attribute::attribute(Baz))) = a])/child::element()))/child::element(bar))[(data(attribute::attribute(Test))) = false])", query.getFallbackExpr());
}
@Test
@ -240,7 +249,7 @@ class SaxonXPathRuleQueryTest {
assertEquals(followPath(tree, "10"), results.get(0));
});
assertExpression("docOrder((((/)/descendant::(element(dummyNode) | element(dummyNodeB)))/child::element(dummyNode))[(string(data(@Image))) eq 10])", query.getExpressionsForLocalNameOrDefault("dummyNode").get(0));
assertExpression("docOrder((((/)/descendant::(element(dummyNode) | element(dummyNodeB)))/child::element(dummyNode))[(data(attribute::attribute(Image))) = 10])", query.getExpressionsForLocalNameOrDefault("dummyNode").get(0));
}
@Test
@ -257,7 +266,7 @@ class SaxonXPathRuleQueryTest {
));
assertEquals(0, query.getRuleChainVisits().size());
assertExpression("docOrder((((((/)/descendant::element(dummyNode))[(string(data(@Image))) eq 0]) | (((/)/descendant::element(dummyNodeB))[(string(data(@Image))) eq 1]))/child::element(dummyNode))[(string(data(@Image))) eq 10])", query.getFallbackExpr());
assertExpression("docOrder((((((/)/descendant::element(dummyNode))[(data(attribute::attribute(Image))) = 0]) | (((/)/descendant::element(dummyNodeB))[(data(attribute::attribute(Image))) = 1]))/child::element(dummyNode))[(data(attribute::attribute(Image))) = 10])", query.getFallbackExpr());
tree.descendantsOrSelf().forEach(n -> {
List<Node> results = query.evaluate(n);
@ -299,8 +308,8 @@ class SaxonXPathRuleQueryTest {
assertEquals(1, ruleChainVisits.size());
assertTrue(ruleChainVisits.contains("dummyNode"));
assertEquals(2, query.nodeNameToXPaths.size());
assertExpression("self::node()[matches(convertTo_xs:string(data(@SimpleName)), a, )]", query.getExpressionsForLocalNameOrDefault("dummyNode").get(0));
assertExpression("((/)/descendant::element(Q{}dummyNode))[matches(convertTo_xs:string(data(@SimpleName)), a, )]", query.getFallbackExpr());
assertExpression("self::node()[matches(zero-or-one(convertTo_xs:string(data(attribute::attribute(SimpleName)))), a, )]", query.getExpressionsForLocalNameOrDefault("dummyNode").get(0));
assertExpression("((/)/descendant::element(Q{}dummyNode))[matches(zero-or-one(convertTo_xs:string(data(attribute::attribute(SimpleName)))), a, )]", query.getFallbackExpr());
}
@Test
@ -311,8 +320,8 @@ class SaxonXPathRuleQueryTest {
assertEquals(1, ruleChainVisits.size());
assertTrue(ruleChainVisits.contains("dummyNode"));
assertEquals(2, query.nodeNameToXPaths.size());
assertExpression("(self::node()[matches(convertTo_xs:string(data(@SimpleName)), a, )])/child::element(Q{}foo)", query.getExpressionsForLocalNameOrDefault("dummyNode").get(0));
assertExpression("docOrder((((/)/descendant::element(Q{}dummyNode))[matches(convertTo_xs:string(data(@SimpleName)), a, )])/child::element(Q{}foo))", query.getFallbackExpr());
assertExpression("(self::node()[matches(zero-or-one(convertTo_xs:string(data(attribute::attribute(SimpleName)))), a, )])/child::element(Q{}foo)", query.getExpressionsForLocalNameOrDefault("dummyNode").get(0));
assertExpression("docOrder((((/)/descendant::element(Q{}dummyNode))[matches(zero-or-one(convertTo_xs:string(data(attribute::attribute(SimpleName)))), a, )])/child::element(Q{}foo))", query.getFallbackExpr());
}
@Test
@ -322,7 +331,7 @@ class SaxonXPathRuleQueryTest {
assertEquals(1, ruleChainVisits.size());
assertTrue(ruleChainVisits.contains("dummyNode"));
assertEquals(2, query.nodeNameToXPaths.size());
assertExpression("let $v0 := imageIs(bar) return ((self::node()[ends-with(convertTo_xs:string(data(@Image)), foo)])[$v0])", query.nodeNameToXPaths.get("dummyNode").get(0));
assertExpression("let $v0 := imageIs(bar) return ((self::node()[ends-with(zero-or-one(convertTo_xs:string(data(attribute::attribute(Image)))), foo)])[$v0])", query.nodeNameToXPaths.get("dummyNode").get(0));
}
@Test
@ -364,7 +373,7 @@ class SaxonXPathRuleQueryTest {
assertTrue(ruleChainVisits.contains("WhileStatement"));
assertTrue(ruleChainVisits.contains("DoStatement"));
final String expectedSubexpression = "(self::node()/descendant::element(dummyNode))[imageIs(exactly-one(convertTo_xs:string(data(@Image))))]";
final String expectedSubexpression = "(self::node()/descendant::element(dummyNode))[imageIs(exactly-one(convertTo_xs:string(data(attribute::attribute(Image)))))]";
assertExpression(expectedSubexpression, query.nodeNameToXPaths.get("ForStatement").get(0));
assertExpression(expectedSubexpression, query.nodeNameToXPaths.get("WhileStatement").get(0));
assertExpression(expectedSubexpression, query.nodeNameToXPaths.get("DoStatement").get(0));

View File

@ -21,7 +21,7 @@ import net.sourceforge.pmd.lang.java.types.JClassType;
*
* </pre>
*/
public final class ASTAnnotation extends AbstractJavaTypeNode implements TypeNode, ASTMemberValue, Iterable<ASTMemberValuePair> {
public final class ASTAnnotation extends AbstractJavaTypeNode implements ASTMemberValue, Iterable<ASTMemberValuePair> {
ASTAnnotation(int id) {
super(id);

View File

@ -17,7 +17,7 @@ package net.sourceforge.pmd.lang.java.ast;
*
* </pre>
*/
public final class ASTArrayDimExpr extends ASTArrayTypeDim implements Annotatable {
public final class ASTArrayDimExpr extends ASTArrayTypeDim {
ASTArrayDimExpr(int id) {
super(id);

View File

@ -22,7 +22,7 @@ import java.util.Iterator;
* </pre>
*
*/
public final class ASTArrayInitializer extends AbstractJavaExpr implements ASTExpression, Iterable<ASTExpression> {
public final class ASTArrayInitializer extends AbstractJavaExpr implements Iterable<ASTExpression> {
ASTArrayInitializer(int id) {
super(id);

View File

@ -6,7 +6,6 @@ package net.sourceforge.pmd.lang.java.ast;
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
import net.sourceforge.pmd.lang.java.ast.ASTList.ASTMaybeEmptyListOf;
import net.sourceforge.pmd.lang.java.ast.InternalInterfaces.AllChildrenAreOfType;
/**
* A block of code. This is a {@linkplain ASTStatement statement} that
@ -19,7 +18,7 @@ import net.sourceforge.pmd.lang.java.ast.InternalInterfaces.AllChildrenAreOfType
* </pre>
*/
public final class ASTBlock extends ASTMaybeEmptyListOf<ASTStatement>
implements ASTSwitchArrowRHS, ASTStatement, AllChildrenAreOfType<ASTStatement> {
implements ASTSwitchArrowRHS, ASTStatement {
ASTBlock(int id) {
super(id, ASTStatement.class);

View File

@ -9,7 +9,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
/**
* The boolean literal, either "true" or "false".
*/
public final class ASTBooleanLiteral extends AbstractLiteral implements ASTLiteral {
public final class ASTBooleanLiteral extends AbstractLiteral {
private boolean isTrue;

View File

@ -14,7 +14,7 @@ package net.sourceforge.pmd.lang.java.ast;
*
* </pre>
*/
public final class ASTCastExpression extends AbstractJavaExpr implements ASTExpression {
public final class ASTCastExpression extends AbstractJavaExpr {
ASTCastExpression(int id) {
super(id);

View File

@ -15,7 +15,7 @@ import net.sourceforge.pmd.lang.document.Chars;
* retrieve the actual runtime value. Use {@link #getLiteralText()} to
* retrieve the text.
*/
public final class ASTCharLiteral extends AbstractLiteral implements ASTLiteral {
public final class ASTCharLiteral extends AbstractLiteral {
ASTCharLiteral(int id) {

View File

@ -13,7 +13,6 @@ import net.sourceforge.pmd.annotation.Experimental;
import net.sourceforge.pmd.lang.ast.AstInfo;
import net.sourceforge.pmd.lang.ast.NodeStream;
import net.sourceforge.pmd.lang.ast.RootNode;
import net.sourceforge.pmd.lang.ast.impl.GenericNode;
import net.sourceforge.pmd.lang.java.symbols.table.JSymbolTable;
import net.sourceforge.pmd.lang.java.types.TypeSystem;
import net.sourceforge.pmd.lang.java.types.ast.internal.LazyTypeResolver;
@ -49,7 +48,7 @@ import net.sourceforge.pmd.lang.rule.xpath.NoAttribute;
* @see <a href="https://openjdk.org/jeps/445">JEP 445: Unnamed Classes and Instance Main Methods (Preview)</a> (Java 21)
* @see #isUnnamedClass()
*/
public final class ASTCompilationUnit extends AbstractJavaNode implements JavaNode, GenericNode<JavaNode>, RootNode {
public final class ASTCompilationUnit extends AbstractJavaNode implements RootNode {
private LazyTypeResolver lazyTypeResolver;
private List<JavaComment> comments;

View File

@ -22,11 +22,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*
* </pre>
*/
public final class ASTConstructorCall extends AbstractInvocationExpr
implements ASTPrimaryExpression,
QualifiableExpression,
LeftRecursiveNode,
InvocationNode {
public final class ASTConstructorCall extends AbstractInvocationExpr implements QualifiableExpression, LeftRecursiveNode {
ASTConstructorCall(int id) {
super(id);

View File

@ -19,9 +19,8 @@ import net.sourceforge.pmd.lang.java.types.OverloadSelectionResult;
* </pre>
*/
public final class ASTEnumConstant extends AbstractJavaTypeNode
implements Annotatable,
InvocationNode,
ModifierOwner,
implements InvocationNode,
ModifierOwner,
ASTBodyDeclaration,
InternalInterfaces.VariableIdOwner,
JavadocCommentOwner {

View File

@ -7,7 +7,6 @@ package net.sourceforge.pmd.lang.java.ast;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import net.sourceforge.pmd.lang.ast.impl.GenericNode;
import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol;
import net.sourceforge.pmd.lang.java.types.JClassType;
import net.sourceforge.pmd.lang.java.types.JMethodSig;
@ -32,7 +31,6 @@ public interface ASTExecutableDeclaration
extends ModifierOwner,
ASTBodyDeclaration,
TypeParamOwnerNode,
GenericNode<JavaNode>,
JavadocCommentOwner {

View File

@ -39,11 +39,7 @@ import net.sourceforge.pmd.lang.java.types.ast.ExprContext;
*
* </pre>
*/
public interface ASTExpression
extends JavaNode,
TypeNode,
ASTMemberValue,
ASTSwitchArrowRHS {
public interface ASTExpression extends TypeNode, ASTMemberValue, ASTSwitchArrowRHS {
/**
* Always returns true. This is to allow XPath queries

View File

@ -22,9 +22,7 @@ import net.sourceforge.pmd.lang.rule.xpath.DeprecatedAttribute;
* </pre>
*/
public final class ASTFieldDeclaration extends AbstractJavaNode
implements Iterable<ASTVariableId>,
LeftRecursiveNode,
ModifierOwner,
implements LeftRecursiveNode,
ASTBodyDeclaration,
InternalInterfaces.MultiVariableIdOwner,
JavadocCommentOwner {

View File

@ -25,10 +25,7 @@ import net.sourceforge.pmd.lang.java.types.TypingContext;
* </pre>
*/
public final class ASTFormalParameter extends AbstractJavaNode
implements ModifierOwner,
TypeNode,
Annotatable,
VariableIdOwner {
implements ModifierOwner, TypeNode, VariableIdOwner {
ASTFormalParameter(int id) {
super(id);

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