The test helper TestParser hand-rolls the "parse → unwrap the ParseResult → handle failure" flow that JavaParserAdapter already implements. This issue proposes reimplementing TestParser on top of JavaParserAdapter, keeping its public API unchanged, so there is a single implementation of that flow in the codebase.
Background
TestParser (javaparser-core-testing/src/test/java/com/github/javaparser/utils/TestParser.java) is a static test helper used by ~16 test classes. It:
- caches one JavaParser per LanguageLevel in a static map;
- exposes terse, level-parameterized static methods (parseExpression, parseStatement, parseCompilationUnit, parseBodyDeclaration, parseVariableDeclarationExpr, each with a BLEEDING_EDGE default and a (LanguageLevel, String) overload);
- unwraps the ParseResult via a private unpack(...) that calls fail(result.getProblems().toString()) on error and returns the node otherwise.
Separately, JavaParserAdapter (javaparser-core) already wraps a JavaParser and offers parseX(String) methods that return the node directly and throw ParseProblemException on failure. Since the migration that introduced StaticJavaParser.newParserAdapter(...), isolated per-configuration parsing is available through JavaParserAdapter as well — so there are now two idioms for the same thing, and TestParser.unpack duplicates JavaParserAdapter.handleResult.
Problem
Duplicated logic: the parse-and-unwrap mechanics exist in two places. This is a maintenance smell (two failure-handling paths, two unwrap paths) with no functional justification.
Proposed approach — wrap, don't rewrite
Keep TestParser's public API identical (so the ~16 callers are untouched) and change only its internals to delegate to JavaParserAdapter:
-
Change the cache value type to JavaParserAdapter:
Map<LanguageLevel, JavaParserAdapter> parserCache;
JavaParserAdapter parser(LanguageLevel ll) =
parserCache.computeIfAbsent(ll, l ->
StaticJavaParser.newParserAdapter(new ParserConfiguration().setLanguageLevel(l)));
-
Delegate each method and delete the private unpack(...):
parseExpression(ll, expr) -> parser(ll).parseExpression(expr)
parseStatement(ll, s) -> parser(ll).parseStatement(s)
parseCompilationUnit(ll, s) -> parser(ll).parse(s) // name mapping
// ...same for the remaining methods
Key decision: failure semantics
The one behavioral difference to resolve:
- TestParser today: fail(problems) → a parse problem is reported as a JUnit failure (AssertionFailedError).
- JavaParserAdapter: throws ParseProblemException → the test is reported as an error.
Two options:
- (a) Adopt the adapter's behavior — parse problems become errors instead of failures. Defensible (a parse problem in a test is an error) but changes the reporting category for all callers.
- (b) Preserve current behavior — wrap the delegated call to convert the exception back into fail():
try { return parser(ll).parseExpression(expr); }
catch (ParseProblemException e) { return fail(e.getProblems().toString()); }
Slightly less elegant, but strictly non-regressive.
Recommendation: (b) — the reconciliation should be invisible to callers.
Minor considerations
- Generics: parseBodyDeclaration returns <T extends BodyDeclaration> T while the adapter returns BodyDeclaration; keep the same unchecked (T) cast as today. parseExpression already matches ().
- Edge case: unpack does getResult().get() (throws if empty) whereas handleResult returns orElse(null) on a successful-but-empty result — a near-theoretical difference, worth a glance.
- Out of scope: the parser cache's thread-safety caveat (shared, non-thread-safe JavaParser instances) is orthogonal and already documented in TestParser. It should be addressed separately, before enabling parallel test execution.
Acceptance criteria
- TestParser's public method signatures are unchanged; no caller is modified.
non terminé
- unpack(...) and the manual ParseResult handling are removed; parsing goes through JavaParserAdapter.
non terminé
- Failure semantics preserved (option b) or an explicit, reviewed decision to adopt option a.
non terminé
- Full javaparser-core-testing suite passes with no other changes.
Notes
Low-risk, self-contained cleanup — independent of the StaticJavaParser test-isolation work; not a prerequisite for anything, just one less piece of duplicated logic.
The test helper TestParser hand-rolls the "parse → unwrap the ParseResult → handle failure" flow that JavaParserAdapter already implements. This issue proposes reimplementing TestParser on top of JavaParserAdapter, keeping its public API unchanged, so there is a single implementation of that flow in the codebase.
Background
TestParser (javaparser-core-testing/src/test/java/com/github/javaparser/utils/TestParser.java) is a static test helper used by ~16 test classes. It:
Separately, JavaParserAdapter (javaparser-core) already wraps a JavaParser and offers parseX(String) methods that return the node directly and throw ParseProblemException on failure. Since the migration that introduced StaticJavaParser.newParserAdapter(...), isolated per-configuration parsing is available through JavaParserAdapter as well — so there are now two idioms for the same thing, and TestParser.unpack duplicates JavaParserAdapter.handleResult.
Problem
Duplicated logic: the parse-and-unwrap mechanics exist in two places. This is a maintenance smell (two failure-handling paths, two unwrap paths) with no functional justification.
Proposed approach — wrap, don't rewrite
Keep TestParser's public API identical (so the ~16 callers are untouched) and change only its internals to delegate to JavaParserAdapter:
Change the cache value type to JavaParserAdapter:
Map<LanguageLevel, JavaParserAdapter> parserCache;
JavaParserAdapter parser(LanguageLevel ll) =
parserCache.computeIfAbsent(ll, l ->
StaticJavaParser.newParserAdapter(new ParserConfiguration().setLanguageLevel(l)));
Delegate each method and delete the private unpack(...):
parseExpression(ll, expr) -> parser(ll).parseExpression(expr)
parseStatement(ll, s) -> parser(ll).parseStatement(s)
parseCompilationUnit(ll, s) -> parser(ll).parse(s) // name mapping
// ...same for the remaining methods
Key decision: failure semantics
The one behavioral difference to resolve:
Two options:
try { return parser(ll).parseExpression(expr); }
catch (ParseProblemException e) { return fail(e.getProblems().toString()); }
Slightly less elegant, but strictly non-regressive.
Recommendation: (b) — the reconciliation should be invisible to callers.
Minor considerations
Acceptance criteria
non terminé
non terminé
non terminé
Notes
Low-risk, self-contained cleanup — independent of the StaticJavaParser test-isolation work; not a prerequisite for anything, just one less piece of duplicated logic.