Merge branch 'main' into issue-5322

This commit is contained in:
Juan Martín Sotuyo Dodero 2024-11-14 14:38:45 -06:00 committed by GitHub
commit 4ef8fb21a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 519 additions and 123 deletions

View File

@ -7865,6 +7865,24 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "chenguangqi",
"name": "天热吃西瓜",
"avatar_url": "https://avatars.githubusercontent.com/u/6231010?v=4",
"profile": "http://chenguangqi.github.io/",
"contributions": [
"bug"
]
},
{
"login": "wahajenius",
"name": "Willem A. Hajenius",
"avatar_url": "https://avatars.githubusercontent.com/u/7836322?v=4",
"profile": "https://github.com/wahajenius",
"contributions": [
"code"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

File diff suppressed because it is too large Load Diff

View File

@ -17,11 +17,16 @@ This is a {{ site.pmd.release_type }} release.
### 🐛 Fixed Issues ### 🐛 Fixed Issues
* ant * ant
* [#1860](https://github.com/pmd/pmd/issues/1860): \[ant] Reflective access warnings on java > 9 and java < 17 * [#1860](https://github.com/pmd/pmd/issues/1860): \[ant] Reflective access warnings on java > 9 and java < 17
* apex
* [#5333](https://github.com/pmd/pmd/issues/5333): \[apex] Token recognition errors for string containing unicode escape sequence
* html
* [5322](https://github.com/pmd/pmd/issues/5322): \[html] CPD throws exception on when HTML file is missing closing tag
* java * java
* [#5293](https://github.com/pmd/pmd/issues/5293): \[java] Deadlock when executing PMD in multiple threads * [#5293](https://github.com/pmd/pmd/issues/5293): \[java] Deadlock when executing PMD in multiple threads
* [#5324](https://github.com/pmd/pmd/issues/5324): \[java] Issue with type inference of nested lambdas * [#5324](https://github.com/pmd/pmd/issues/5324): \[java] Issue with type inference of nested lambdas
* html * [#5329](https://github.com/pmd/pmd/issues/5329): \[java] Type inference issue with unknown method ref in call chain
* [5322](https://github.com/pmd/pmd/issues/5322): \[html] CPD throws exception on when HTML file is missing closing tag * java-performance
* [#5314](https://github.com/pmd/pmd/issues/5314): \[java] InsufficientStringBufferDeclarationRule: Lack of handling for char type parameters
### 🚨 API Changes ### 🚨 API Changes
@ -31,6 +36,7 @@ This is a {{ site.pmd.release_type }} release.
instead (note different package `ast` instead of `antlr4`). instead (note different package `ast` instead of `antlr4`).
### ✨ External Contributions ### ✨ External Contributions
* [#5284](https://github.com/pmd/pmd/pull/5284): \[apex] Use case-insensitive input stream to avoid choking on Unicode escape sequences - [Willem A. Hajenius](https://github.com/wahajenius) (@wahajenius)
{% endtocmaker %} {% endtocmaker %}

View File

@ -14,14 +14,19 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.RandomAccess; import java.util.RandomAccess;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.Token;
import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.annotation.InternalApi;
import net.sourceforge.pmd.lang.ast.LexException;
import net.sourceforge.pmd.lang.document.TextDocument; import net.sourceforge.pmd.lang.document.TextDocument;
import net.sourceforge.pmd.lang.document.TextRegion; import net.sourceforge.pmd.lang.document.TextRegion;
import io.github.apexdevtools.apexparser.ApexLexer; import io.github.apexdevtools.apexparser.ApexLexer;
import io.github.apexdevtools.apexparser.CaseInsensitiveInputStream;
@InternalApi @InternalApi
final class ApexCommentBuilder { final class ApexCommentBuilder {
@ -103,7 +108,15 @@ final class ApexCommentBuilder {
} }
private static CommentInformation extractInformationFromComments(TextDocument sourceCode, String suppressMarker) { private static CommentInformation extractInformationFromComments(TextDocument sourceCode, String suppressMarker) {
ApexLexer lexer = new ApexLexer(CharStreams.fromString(sourceCode.getText().toString())); String source = sourceCode.getText().toString();
ApexLexer lexer = new ApexLexer(new CaseInsensitiveInputStream(CharStreams.fromString(source)));
lexer.removeErrorListeners();
lexer.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) {
throw new LexException(line, charPositionInLine, sourceCode.getFileId(), msg, e);
}
});
List<Token> allCommentTokens = new ArrayList<>(); List<Token> allCommentTokens = new ArrayList<>();
Map<Integer, String> suppressMap = new HashMap<>(); Map<Integer, String> suppressMap = new HashMap<>();

View File

@ -66,4 +66,12 @@ class ApexCommentTest extends ApexParserTestBase {
ASTFormalComment comment = file.descendants(ASTUserClass.class).children(ASTFormalComment.class).first(); ASTFormalComment comment = file.descendants(ASTUserClass.class).children(ASTFormalComment.class).first();
assertEquals(FORMAL_COMMENT_CONTENT, comment.getImage()); assertEquals(FORMAL_COMMENT_CONTENT, comment.getImage());
} }
@Test
void fileWithUnicodeEscapes() {
ASTApexFile file = apex.parse(FORMAL_COMMENT_CONTENT + "\n"
+ "class MyClass { String s = 'Fran\\u00E7ois'; }");
ASTFormalComment comment = file.descendants(ASTUserClass.class).children(ASTFormalComment.class).first();
assertEquals(FORMAL_COMMENT_CONTENT, comment.getImage());
}
} }

View File

@ -8,14 +8,18 @@ package net.sourceforge.pmd.lang.apex.ast;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CharStream; import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.Token;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import io.github.apexdevtools.apexparser.ApexLexer; import io.github.apexdevtools.apexparser.ApexLexer;
import io.github.apexdevtools.apexparser.ApexParser; import io.github.apexdevtools.apexparser.ApexParser;
import io.github.apexdevtools.apexparser.CaseInsensitiveInputStream;
/** /**
* This is an exploration test for {@link ApexLexer}. * This is an exploration test for {@link ApexLexer}.
@ -49,4 +53,36 @@ class ApexLexerTest {
ApexParser.CompilationUnitContext compilationUnit = parser.compilationUnit(); ApexParser.CompilationUnitContext compilationUnit = parser.compilationUnit();
assertNotNull(compilationUnit); assertNotNull(compilationUnit);
} }
@Test
void testLexerUnicodeEscapes() {
String s = "'Fran\\u00E7ois'";
// note: with apex-parser 4.3.1, no errors are reported anymore
assertEquals(2, getLexingErrors(CharStreams.fromString(s)));
assertEquals(0, getLexingErrors(new CaseInsensitiveInputStream(CharStreams.fromString(s))));
}
private int getLexingErrors(CharStream stream) {
ApexLexer lexer = new ApexLexer(stream);
ErrorListener errorListener = new ErrorListener();
lexer.removeErrorListeners(); // Avoid distracting "token recognition error" stderr output
lexer.addErrorListener(errorListener);
CommonTokenStream tokens = new CommonTokenStream(lexer);
tokens.fill();
return errorListener.getErrorCount();
}
private static class ErrorListener extends BaseErrorListener {
private int errorCount = 0;
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line,
int charPositionInLine, String msg, RecognitionException e) {
++errorCount;
}
public int getErrorCount() {
return errorCount;
}
}
} }

View File

@ -240,6 +240,12 @@ public class InsufficientStringBufferDeclarationRule extends AbstractJavaRulecha
private int calculateExpression(ASTExpression expression) { private int calculateExpression(ASTExpression expression) {
Object value = expression.getConstValue(); Object value = expression.getConstValue();
return value == null ? State.UNKNOWN_CAPACITY : (Integer) value; if (value == null) {
return State.UNKNOWN_CAPACITY;
}
if (value instanceof Character) {
return (Character) value;
}
return (Integer) value;
} }
} }

View File

@ -335,6 +335,13 @@ final class ExprCheckHelper {
checker.checkExprConstraint(infCtx, capture(r2), r); checker.checkExprConstraint(infCtx, capture(r2), r);
} }
completeMethodRefInference(mref, nonWildcard, fun, exactMethod, true); completeMethodRefInference(mref, nonWildcard, fun, exactMethod, true);
} else if (TypeOps.isUnresolved(mref.getTypeToSearch())) {
// Then this is neither an exact nor inexact method ref,
// we just don't know what it is.
// The return values of the mref are assimilated to an (*unknown*) type.
checker.checkExprConstraint(infCtx, ts.UNKNOWN, fun.getReturnType());
completeMethodRefInference(mref, nonWildcard, fun, ts.UNRESOLVED_METHOD, false);
} else { } else {
// Otherwise, the method reference is inexact, and: // Otherwise, the method reference is inexact, and:

View File

@ -227,7 +227,6 @@ final class ExprOps {
} }
} else { } else {
JClassType enclosing = mref.getEnclosingType(); JClassType enclosing = mref.getEnclosingType();
accessible = mref.getTypeToSearch() accessible = mref.getTypeToSearch()
.streamMethods(TypeOps.accessibleMethodFilter(mref.getMethodName(), enclosing.getSymbol())) .streamMethods(TypeOps.accessibleMethodFilter(mref.getMethodName(), enclosing.getSymbol()))
.collect(OverloadSet.collectMostSpecific(enclosing)); .collect(OverloadSet.collectMostSpecific(enclosing));

View File

@ -525,6 +525,7 @@ final class InferenceContext {
* we try again to make progress. * we try again to make progress.
*/ */
private boolean solve(VarWalkStrategy walker) { private boolean solve(VarWalkStrategy walker) {
graphWasChanged = false;
incorporate(); incorporate();
while (walker.hasNext()) { while (walker.hasNext()) {

View File

@ -22,6 +22,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.ast.JavaNode;
import net.sourceforge.pmd.lang.java.internal.JavaLanguageProperties;
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol; import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
import net.sourceforge.pmd.lang.java.types.JMethodSig; import net.sourceforge.pmd.lang.java.types.JMethodSig;
import net.sourceforge.pmd.lang.java.types.JTypeMirror; import net.sourceforge.pmd.lang.java.types.JTypeMirror;
@ -34,6 +35,12 @@ import net.sourceforge.pmd.util.StringUtil;
/** /**
* A strategy to log the execution traces of {@link Infer}. * A strategy to log the execution traces of {@link Infer}.
* The default does nothing, so the logger calls can be optimized out
* at runtime, while not having to check that logging is enabled at the
* call sites.
*
* <p>To enable logging for the CLI, use the language property ({@link JavaLanguageProperties})
* {@code xTypeInferenceLogging}. From tests, see {@code JavaParsingHelper#logTypeInferenceVerbose()}.
*/ */
@SuppressWarnings("PMD.UncommentedEmptyMethodBody") @SuppressWarnings("PMD.UncommentedEmptyMethodBody")
public interface TypeInferenceLogger { public interface TypeInferenceLogger {
@ -64,8 +71,7 @@ public interface TypeInferenceLogger {
default void applicabilityTest(InferenceContext ctx) { } default void applicabilityTest(InferenceContext ctx) { }
default void finishApplicabilityTest() { default void finishApplicabilityTest() { }
}
default void startArgsChecks() { } default void startArgsChecks() { }

View File

@ -55,6 +55,11 @@ class TypesTreeDumpTest extends BaseTreeDumpTest {
doTest("NestedLambdasAndMethodCalls"); doTest("NestedLambdasAndMethodCalls");
} }
@Test
void testUnresolvedThings() {
doTest("UnresolvedThings");
}
@Override @Override
protected @NonNull String normalize(@NonNull String str) { protected @NonNull String normalize(@NonNull String str) {
return super.normalize(str) return super.normalize(str)

View File

@ -8,7 +8,10 @@ import static net.sourceforge.pmd.lang.java.types.TestUtilitiesForTypesKt.captur
import static net.sourceforge.pmd.lang.java.types.internal.infer.BaseTypeInferenceUnitTest.Bound.eqBound; import static net.sourceforge.pmd.lang.java.types.internal.infer.BaseTypeInferenceUnitTest.Bound.eqBound;
import static net.sourceforge.pmd.lang.java.types.internal.infer.BaseTypeInferenceUnitTest.Bound.lower; import static net.sourceforge.pmd.lang.java.types.internal.infer.BaseTypeInferenceUnitTest.Bound.lower;
import static net.sourceforge.pmd.lang.java.types.internal.infer.BaseTypeInferenceUnitTest.Bound.upper; import static net.sourceforge.pmd.lang.java.types.internal.infer.BaseTypeInferenceUnitTest.Bound.upper;
import static net.sourceforge.pmd.util.CollectionUtil.setOf;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -19,11 +22,17 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import java.util.List;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import net.sourceforge.pmd.lang.java.types.JTypeMirror; import net.sourceforge.pmd.lang.java.types.JTypeMirror;
import net.sourceforge.pmd.lang.java.types.TypeOps; import net.sourceforge.pmd.lang.java.types.TypeOps;
import net.sourceforge.pmd.lang.java.types.internal.infer.InferenceVar.BoundKind; import net.sourceforge.pmd.lang.java.types.internal.infer.InferenceVar.BoundKind;
import net.sourceforge.pmd.lang.java.types.internal.infer.VarWalkStrategy.GraphWalk;
import net.sourceforge.pmd.util.IteratorUtil;
/** /**
* *
@ -331,4 +340,88 @@ class InferenceCtxUnitTests extends BaseTypeInferenceUnitTest {
assertThat(a, hasBoundsExactly(upper(ts.BOOLEAN.box()))); assertThat(a, hasBoundsExactly(upper(ts.BOOLEAN.box())));
} }
private static @NotNull List<Set<InferenceVar>> createBatchSetsFromGraph(InferenceContext ctx) {
GraphWalk graphWalk = new GraphWalk(ctx, false);
List<Set<InferenceVar>> batches = IteratorUtil.toList(graphWalk);
return batches;
}
@Test
void testGraphBuilding() {
InferenceContext ctx = emptyCtx();
InferenceVar a = newIvar(ctx);
InferenceVar b = newIvar(ctx);
List<Set<InferenceVar>> batches = createBatchSetsFromGraph(ctx);
// no dependency: unordered
assertThat(batches, containsInAnyOrder(setOf(a), setOf(b)));
}
@Test
void testGraphBuildingWithDependency() {
InferenceContext ctx = emptyCtx();
InferenceVar a = newIvar(ctx);
InferenceVar b = newIvar(ctx);
// a -> b
addSubtypeConstraint(ctx, a, ts.arrayType(b));
List<Set<InferenceVar>> batches = createBatchSetsFromGraph(ctx);
assertThat(batches, contains(setOf(b), setOf(a)));
}
@Test
void testGraphBuildingWithDependency2() {
InferenceContext ctx = emptyCtx();
InferenceVar a = newIvar(ctx);
InferenceVar b = newIvar(ctx);
// a -> b
// b -> a (because of propagation)
addSubtypeConstraint(ctx, a, b);
List<Set<InferenceVar>> batches = createBatchSetsFromGraph(ctx);
assertThat(batches, contains(setOf(b, a)));
}
@Test
void testGraphBuildingWithExtraDependency() {
InferenceContext ctx = emptyCtx();
InferenceVar a = newIvar(ctx);
InferenceVar b = newIvar(ctx);
// b -> a
ctx.addInstantiationDependencies(setOf(b), setOf(a));
List<Set<InferenceVar>> batches = createBatchSetsFromGraph(ctx);
assertThat(batches, contains(setOf(a), setOf(b)));
}
@Test
void testGraphBuildingWithDependencyCycle() {
InferenceContext ctx = emptyCtx();
InferenceVar a = newIvar(ctx);
InferenceVar b = newIvar(ctx);
InferenceVar c = newIvar(ctx);
// a -> b, b -> a,
// a -> c, b -> c
a.addBound(BoundKind.UPPER, b);
a.addBound(BoundKind.EQ, listType(c));
b.addBound(BoundKind.LOWER, a);
b.addBound(BoundKind.LOWER, listType(c));
List<Set<InferenceVar>> batches = createBatchSetsFromGraph(ctx);
assertThat(batches, contains(setOf(c), setOf(b, a)));
}
} }

View File

@ -21,6 +21,7 @@ fun JavaNode.declaredMethodSignatures(): List<JMethodSig> = methodDeclarations()
fun JavaNode.methodCalls(): DescendantNodeStream<ASTMethodCall> = descendants(ASTMethodCall::class.java) fun JavaNode.methodCalls(): DescendantNodeStream<ASTMethodCall> = descendants(ASTMethodCall::class.java)
fun JavaNode.firstMethodCall() = methodCalls().crossFindBoundaries().firstOrThrow() fun JavaNode.firstMethodCall() = methodCalls().crossFindBoundaries().firstOrThrow()
fun JavaNode.firstMethodCall(name: String) = methodCalls().crossFindBoundaries().filter { it.methodName == name }.firstOrThrow()
fun JavaNode.ctorCalls(): DescendantNodeStream<ASTConstructorCall> = descendants(ASTConstructorCall::class.java) fun JavaNode.ctorCalls(): DescendantNodeStream<ASTConstructorCall> = descendants(ASTConstructorCall::class.java)
fun JavaNode.firstCtorCall() = ctorCalls().crossFindBoundaries().firstOrThrow() fun JavaNode.firstCtorCall() = ctorCalls().crossFindBoundaries().firstOrThrow()

View File

@ -664,4 +664,42 @@ class C {
fooToInt.referencedMethod.symbol shouldBe toIntFun fooToInt.referencedMethod.symbol shouldBe toIntFun
} }
} }
parserTest("Type inference should not resolve UNKNOWN bounded types to Object #5329") {
val (acu, _) = parser.parseWithTypeInferenceSpy(
"""
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import java.util.stream.Collectors;
class Foo {
public Item methodA(List<Item> loads) {
List<SummaryDto.ItemDto> items = new ArrayList<>();
loads.stream()
// Here this collect call should have type
// Map<(*unknown*), List<*Item>>
// ie, key is unknown, not Object.
.collect(Collectors.groupingBy(Item::getValue))
.forEach((a, b) -> items.add(buildItem(a, b)));
}
private SummaryDto.ItemDto buildItem(BigDecimal a, List<Item> b) {
return SummaryDto.ItemDto.builder().build();
}
}
"""
)
val collect = acu.firstMethodCall("collect")
val buildItem = acu.firstMethodCall("buildItem")
val (_, buildItemDecl) = acu.methodDeclarations().toList { it.symbol }
val (itemT) = acu.descendants(ASTClassType::class.java).toList { it.typeMirror }
acu.withTypeDsl {
collect shouldHaveType java.util.Map::class[ts.UNKNOWN, java.util.List::class[itemT]]
buildItem.methodType.symbol shouldBe buildItemDecl
}
}
}) })

View File

@ -2177,4 +2177,45 @@ public class ObtainViaTest {
record Library(Collection<Book> books) {} record Library(Collection<Book> books) {}
]]></code> ]]></code>
</test-code> </test-code>
<test-code>
<description>#5324 UnusedPrivateMethod with unresolved types</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
class Foo {
public User methodA() {
val user = userOpt.orElseGet(() -> {
try {
return registerUser(email, firstName, lastName);
} catch (Exception e) {
throw new IllegalStateException("Failed to register user for " + email, e);
}
});
// ...
return user;
}
private User registerUser(String email, String firstName, String lastName) throws Exception {
// register user logic here...
}
}
]]></code>
</test-code>
<test-code>
<description>#5329 UnusedPrivateMethod with unresolved types</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
class Foo {
public User methodA() {
List<SummaryDto.ItemDto> items = new ArrayList<>();
loads.stream()
.collect(Collectors.groupingBy(Item::getValue))
.forEach((a, b) -> items.add(buildItem(a, b)));
}
private SummaryDto.ItemDto buildItem(BigDecimal a, List<Item> b) {
return SummaryDto.ItemDto.builder().build();
}
}
]]></code>
</test-code>
</test-data> </test-data>

View File

@ -1419,4 +1419,23 @@ public class LiteralExpression {
} }
]]></code> ]]></code>
</test-code> </test-code>
<test-code>
<description>#5314 [java] InsufficientStringBufferDeclarationRule: Lack of handling for char type parameters</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
public class StringBufferInstantiationWithCharTest {
public void example01() {
// misleading instantiation, these buffers
// are actually sized to 99 characters long
StringBuffer sb1 = new StringBuffer('c');
StringBuilder sb2 = new StringBuilder('c');
// in these forms, just single characters are allocated
StringBuffer sb3 = new StringBuffer("c");
StringBuilder sb4 = new StringBuilder("c");
}
}
]]></code>
</test-code>
</test-data> </test-data>

View File

@ -0,0 +1,16 @@
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import java.util.stream.Collectors;
class Foo {
public User methodA(List<Item> loads) {
List<SummaryDto.ItemDto> items = new ArrayList<>();
loads.stream()
.collect(Collectors.groupingBy(Item::getValue))
.forEach((a, b) -> items.add(buildItem(a, b)));
}
private SummaryDto.ItemDto buildItem(BigDecimal a, List<Item> b) {
return SummaryDto.ItemDto.builder().build();
}
}

View File

@ -0,0 +1,80 @@
+- CompilationUnit[]
+- ImportDeclaration[]
+- ImportDeclaration[]
+- ImportDeclaration[]
+- ImportDeclaration[]
+- ClassDeclaration[@TypeMirror = "Foo"]
+- ModifierList[]
+- ClassBody[]
+- MethodDeclaration[@Name = "methodA"]
| +- ModifierList[]
| +- ClassType[@TypeMirror = "*User"]
| +- FormalParameters[]
| | +- FormalParameter[@TypeMirror = "java.util.List<*Item>"]
| | +- ModifierList[]
| | +- ClassType[@TypeMirror = "java.util.List<*Item>"]
| | | +- TypeArguments[]
| | | +- ClassType[@TypeMirror = "*Item"]
| | +- VariableId[@Name = "loads", @TypeMirror = "java.util.List<*Item>"]
| +- Block[]
| +- LocalVariableDeclaration[]
| | +- ModifierList[]
| | +- ClassType[@TypeMirror = "java.util.List<*SummaryDto.ItemDto>"]
| | | +- TypeArguments[]
| | | +- ClassType[@TypeMirror = "*SummaryDto.ItemDto"]
| | +- VariableDeclarator[]
| | +- VariableId[@Name = "items", @TypeMirror = "java.util.List<*SummaryDto.ItemDto>"]
| | +- ConstructorCall[@Failed = false, @Function = "java.util.ArrayList<*SummaryDto.ItemDto>.new() -> java.util.ArrayList<*SummaryDto.ItemDto>", @MethodName = "new", @TypeMirror = "java.util.ArrayList<*SummaryDto.ItemDto>", @Unchecked = false, @VarargsCall = false]
| | +- ClassType[@TypeMirror = "java.util.ArrayList"]
| | | +- TypeArguments[]
| | +- ArgumentList[]
| +- ExpressionStatement[]
| +- MethodCall[@Failed = false, @Function = "java.util.Map<(*unknown*), java.util.List<*Item>>.forEach(java.util.function.BiConsumer<? super (*unknown*), ? super java.util.List<*Item>>) -> void", @MethodName = "forEach", @TypeMirror = "void", @Unchecked = false, @VarargsCall = false]
| +- MethodCall[@Failed = false, @Function = "java.util.stream.Stream<*Item>.<R, A> collect(java.util.stream.Collector<? super *Item, java.lang.Object, java.util.Map<(*unknown*), java.util.List<*Item>>>) -> java.util.Map<(*unknown*), java.util.List<*Item>>", @MethodName = "collect", @TypeMirror = "java.util.Map<(*unknown*), java.util.List<*Item>>", @Unchecked = false, @VarargsCall = false]
| | +- MethodCall[@Failed = false, @Function = "java.util.Collection<*Item>.stream() -> java.util.stream.Stream<*Item>", @MethodName = "stream", @TypeMirror = "java.util.stream.Stream<*Item>", @Unchecked = false, @VarargsCall = false]
| | | +- VariableAccess[@Name = "loads", @TypeMirror = "java.util.List<*Item>"]
| | | +- ArgumentList[]
| | +- ArgumentList[]
| | +- MethodCall[@Failed = false, @Function = "java.util.stream.Collectors.<T, K> groupingBy(java.util.function.Function<? super *Item, ? extends (*unknown*)>) -> java.util.stream.Collector<*Item, java.lang.Object, java.util.Map<(*unknown*), java.util.List<*Item>>>", @MethodName = "groupingBy", @TypeMirror = "java.util.stream.Collector<*Item, java.lang.Object, java.util.Map<(*unknown*), java.util.List<*Item>>>", @Unchecked = false, @VarargsCall = false]
| | +- TypeExpression[@TypeMirror = "java.util.stream.Collectors"]
| | | +- ClassType[@TypeMirror = "java.util.stream.Collectors"]
| | +- ArgumentList[]
| | +- MethodReference[@TypeMirror = "java.util.function.Function<*Item, (*unknown*)>"]
| | +- AmbiguousName[@TypeMirror = "(*unknown*)"]
| +- ArgumentList[]
| +- LambdaExpression[@TypeMirror = "java.util.function.BiConsumer<(*unknown*), java.util.List<*Item>>"]
| +- LambdaParameterList[]
| | +- LambdaParameter[@TypeMirror = "(*unknown*)"]
| | | +- ModifierList[]
| | | +- VariableId[@Name = "a", @TypeMirror = "(*unknown*)"]
| | +- LambdaParameter[@TypeMirror = "java.util.List<*Item>"]
| | +- ModifierList[]
| | +- VariableId[@Name = "b", @TypeMirror = "java.util.List<*Item>"]
| +- MethodCall[@Failed = false, @Function = "java.util.List<*SummaryDto.ItemDto>.add(*SummaryDto.ItemDto) -> boolean", @MethodName = "add", @TypeMirror = "boolean", @Unchecked = false, @VarargsCall = false]
| +- VariableAccess[@Name = "items", @TypeMirror = "java.util.List<*SummaryDto.ItemDto>"]
| +- ArgumentList[]
| +- MethodCall[@Failed = false, @Function = "Foo.buildItem(*BigDecimal, java.util.List<*Item>) -> *SummaryDto.ItemDto", @MethodName = "buildItem", @TypeMirror = "*SummaryDto.ItemDto", @Unchecked = false, @VarargsCall = false]
| +- ArgumentList[]
| +- VariableAccess[@Name = "a", @TypeMirror = "(*unknown*)"]
| +- VariableAccess[@Name = "b", @TypeMirror = "java.util.List<*Item>"]
+- MethodDeclaration[@Name = "buildItem"]
+- ModifierList[]
+- ClassType[@TypeMirror = "*SummaryDto.ItemDto"]
+- FormalParameters[]
| +- FormalParameter[@TypeMirror = "*BigDecimal"]
| | +- ModifierList[]
| | +- ClassType[@TypeMirror = "*BigDecimal"]
| | +- VariableId[@Name = "a", @TypeMirror = "*BigDecimal"]
| +- FormalParameter[@TypeMirror = "java.util.List<*Item>"]
| +- ModifierList[]
| +- ClassType[@TypeMirror = "java.util.List<*Item>"]
| | +- TypeArguments[]
| | +- ClassType[@TypeMirror = "*Item"]
| +- VariableId[@Name = "b", @TypeMirror = "java.util.List<*Item>"]
+- Block[]
+- ReturnStatement[]
+- MethodCall[@Failed = true, @Function = "(*unknown*).(*unknown method*)() -> (*unknown*)", @MethodName = "build", @TypeMirror = "(*unknown*)", @Unchecked = false, @VarargsCall = false]
+- MethodCall[@Failed = true, @Function = "(*unknown*).(*unknown method*)() -> (*unknown*)", @MethodName = "builder", @TypeMirror = "(*unknown*)", @Unchecked = false, @VarargsCall = false]
| +- AmbiguousName[@TypeMirror = "(*unknown*)"]
| +- ArgumentList[]
+- ArgumentList[]