diff --git a/java/java-frontback-tests/BUILD.bazel b/java/java-frontback-tests/BUILD.bazel
index a5e3db1f1062..88d1550922f8 100644
--- a/java/java-frontback-tests/BUILD.bazel
+++ b/java/java-frontback-tests/BUILD.bazel
@@ -37,6 +37,7 @@ jvm_library(
"//java/testFramework/shared",
"//platform/syntax/syntax-psi:psi",
"//java/java-syntax:syntax",
+ "//platform/syntax/syntax-tree:tree",
],
exports = [
"@lib//:easymock",
diff --git a/java/java-frontback-tests/intellij.java.frontback.tests.iml b/java/java-frontback-tests/intellij.java.frontback.tests.iml
index ad5a6265bd8a..259415074fa1 100644
--- a/java/java-frontback-tests/intellij.java.frontback.tests.iml
+++ b/java/java-frontback-tests/intellij.java.frontback.tests.iml
@@ -28,5 +28,6 @@
+
\ No newline at end of file
diff --git a/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavaParsingTestCase.java b/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavaParsingTestCase.java
index d1c192f3b3fc..d12aa5805f93 100644
--- a/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavaParsingTestCase.java
+++ b/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavaParsingTestCase.java
@@ -8,8 +8,11 @@ import com.intellij.lang.LanguageExtension;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.lang.java.JavaParserDefinition;
import com.intellij.lang.java.parser.BasicJavaParserUtil;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.util.io.FileUtil;
import com.intellij.platform.backend.workspace.WorkspaceModelTopics;
import com.intellij.platform.syntax.psi.LanguageSyntaxDefinitions;
+import com.intellij.platform.syntax.tree.SyntaxNode;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.DebugUtil;
@@ -17,11 +20,14 @@ import com.intellij.testFramework.ParsingTestCase;
import com.intellij.testFramework.TestDataFile;
import org.jetbrains.annotations.NotNull;
+import java.io.File;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
public abstract class AbstractBasicJavaParsingTestCase extends ParsingTestCase {
private final AbstractBasicJavaParsingTestConfigurator myConfigurator;
+ private SyntaxNode myKmpTree;
@SuppressWarnings("JUnitTestCaseWithNonTrivialConstructors")
public AbstractBasicJavaParsingTestCase(String dataPath, AbstractBasicJavaParsingTestConfigurator configurator) {
@@ -50,6 +56,14 @@ public abstract class AbstractBasicJavaParsingTestCase extends ParsingTestCase {
addExplicitExtension(collector, language, object);
}
+ public final void addExplicit(@NotNull ExtensionPointName extensionPointName, @NotNull T extension) {
+ registerExtension(extensionPointName, extension);
+ }
+
+ public final void addExtensionPoint(@NotNull ExtensionPointName extensionPointName, @NotNull Class extensionClass) {
+ super.registerExtensionPoint(extensionPointName, extensionClass);
+ }
+
@Override
protected PsiFile createFile(@NotNull String name, @NotNull String text) {
PsiFile file = super.createFile(name, text);
@@ -81,13 +95,42 @@ public abstract class AbstractBasicJavaParsingTestCase extends ParsingTestCase {
}
String treeDump = DebugUtil.nodeTreeAsElementTypeToString(file.getNode(), !skipSpaces()).trim();
- doCheckResult(myFullDataPath, targetDataName + "_node.txt", treeDump);
+ assertSameLinesWithFile(getExpectedFileName(targetDataName), treeDump);
+
+ checkKmpTree(targetDataName);
+ }
+
+ private void checkKmpTree(@NotNull String targetDataName) {
+ String kmpTreeDump = JavaTestSyntaxParsingUtil.dumpTree(myKmpTree);
+
+ String expectedFileName = getExpectedFileName(targetDataName);
+ String expectedText;
+ try {
+ expectedText = FileUtil.loadFile(new File(expectedFileName), StandardCharsets.UTF_8);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ String normalizedExpected = JavaTestSyntaxParsingUtil.removeEmptyListLines(expectedText);
+ assertSameLines(normalizedExpected, kmpTreeDump);
+ }
+
+ private @NotNull String getExpectedFileName(@NotNull String targetDataName) {
+ return myFullDataPath + File.separatorChar + targetDataName + "_node.txt";
+ }
+
+ @Override
+ protected @NotNull PsiFile parseFile(@NotNull String name, @NotNull String text) {
+ myKmpTree = myConfigurator.createFileSyntaxNode(text, null);
+ return super.parseFile(name, text);
}
protected void doParserTest(@NotNull String text,
BasicJavaParserUtil.@NotNull ParserWrapper parser) {
String name = getTestName(false);
myFile = myConfigurator.createPsiFile(this, name, text, parser);
+ myKmpTree = myConfigurator.createFileSyntaxNode(text, parser);
try {
checkResult(name, myFile);
}
diff --git a/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavaParsingTestConfigurator.java b/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavaParsingTestConfigurator.java
index 5206eac7b687..5e41d9b75ac6 100644
--- a/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavaParsingTestConfigurator.java
+++ b/java/java-frontback-tests/testSrc/com/intellij/java/parser/AbstractBasicJavaParsingTestConfigurator.java
@@ -2,9 +2,11 @@
package com.intellij.java.parser;
import com.intellij.lang.java.parser.BasicJavaParserUtil;
+import com.intellij.platform.syntax.tree.SyntaxNode;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
public interface AbstractBasicJavaParsingTestConfigurator {
void setUp(@NotNull AbstractBasicJavaParsingTestCase thinJavaParsingTestCase);
@@ -17,6 +19,14 @@ public interface AbstractBasicJavaParsingTestConfigurator {
@NotNull String text,
@NotNull BasicJavaParserUtil.ParserWrapper parser);
+ /**
+ * @param text text to parse
+ * @param parserWrapper parser to use or `null` if we want to parse the whole file
+ * @return SyntaxNode node representing the parsed content
+ */
+ @NotNull
+ SyntaxNode createFileSyntaxNode(@NotNull String text, @Nullable BasicJavaParserUtil.ParserWrapper parserWrapper);
+
boolean checkPsi();
void setLanguageLevel(@NotNull LanguageLevel level);
diff --git a/java/java-frontback-tests/testSrc/com/intellij/java/parser/JavaParsingTestConfiguratorBase.kt b/java/java-frontback-tests/testSrc/com/intellij/java/parser/JavaParsingTestConfiguratorBase.kt
index d30a28e98d60..e7e35cb44133 100644
--- a/java/java-frontback-tests/testSrc/com/intellij/java/parser/JavaParsingTestConfiguratorBase.kt
+++ b/java/java-frontback-tests/testSrc/com/intellij/java/parser/JavaParsingTestConfiguratorBase.kt
@@ -2,15 +2,19 @@
package com.intellij.java.parser
import com.intellij.ide.highlighter.JavaFileType
+import com.intellij.java.syntax.JavaSyntaxDefinition
+import com.intellij.java.syntax.element.JavaLanguageLevelProvider
import com.intellij.lang.ASTNode
import com.intellij.lang.LanguageASTFactory
import com.intellij.lang.java.JavaLanguage
import com.intellij.lang.java.parser.BasicJavaParserUtil
import com.intellij.lang.java.parser.JavaParserUtil
import com.intellij.lang.java.syntax.JavaElementTypeConverterExtension
+import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.platform.syntax.SyntaxElementType
import com.intellij.platform.syntax.parser.SyntaxTreeBuilder
import com.intellij.platform.syntax.psi.*
+import com.intellij.platform.syntax.tree.SyntaxNode
import com.intellij.pom.java.LanguageLevel
import com.intellij.psi.*
import com.intellij.psi.impl.source.PsiJavaFileImpl
@@ -36,8 +40,12 @@ abstract class JavaParsingTestConfiguratorBase(
testCase.addExplicit(languageASTFactory, java, JavaASTFactory())
testCase.clearCachesOfLanguageExtension(java, languageASTFactory)
}
- }
+ ExtensionPointName("com.intellij.java.syntax.languageLevelProvider").let { ep ->
+ testCase.addExtensionPoint(ep, JavaLanguageLevelProvider::class.java)
+ testCase.addExplicit(ep, JavaLanguageLevelProvider { this.languageLevel })
+ }
+ }
override fun setLanguageLevel(level: LanguageLevel) {
this.level = level
@@ -52,6 +60,18 @@ abstract class JavaParsingTestConfiguratorBase(
ourLanguageLevel = languageLevel
}
+ override fun createFileSyntaxNode(text: String, parserWrapper: BasicJavaParserUtil.ParserWrapper?): SyntaxNode {
+ ourLanguageLevel = languageLevel
+ return parseForSyntaxTree(text) { builder ->
+ if (parserWrapper != null) {
+ parseWithWrapper(builder, parserWrapper)
+ }
+ else {
+ JavaSyntaxDefinition.parse(ourLanguageLevel, builder)
+ }
+ }
+ }
+
override fun createPsiFile(
thinJavaParsingTestCase: AbstractBasicJavaParsingTestCase,
name: String,
diff --git a/java/java-frontback-tests/testSrc/com/intellij/java/parser/JavaTestSyntaxParsingUtil.kt b/java/java-frontback-tests/testSrc/com/intellij/java/parser/JavaTestSyntaxParsingUtil.kt
new file mode 100644
index 000000000000..8de814df8814
--- /dev/null
+++ b/java/java-frontback-tests/testSrc/com/intellij/java/parser/JavaTestSyntaxParsingUtil.kt
@@ -0,0 +1,67 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+
+@file:JvmName("JavaTestSyntaxParsingUtil")
+
+package com.intellij.java.parser
+
+import com.intellij.java.syntax.JavaSyntaxDefinition
+import com.intellij.platform.syntax.parser.SyntaxTreeBuilder
+import com.intellij.platform.syntax.tree.KmpSyntaxNode
+
+import com.intellij.platform.syntax.tree.SyntaxNode
+import com.intellij.platform.syntax.tree.children
+import com.intellij.platform.syntax.tree.parse
+import com.intellij.platform.syntax.util.language.SyntaxElementLanguageProvider
+
+internal fun parseForSyntaxTree(
+ text: String,
+ parser: ((SyntaxTreeBuilder) -> Unit),
+): KmpSyntaxNode {
+ val mapper = SyntaxElementLanguageProvider {
+ sequenceOf(JavaSyntaxDefinition.language)
+ }
+
+ val tree = parse(
+ text = text,
+ lexerFactory = JavaSyntaxDefinition::createLexer,
+ parser = parser,
+ whitespaces = JavaSyntaxDefinition.whitespaces,
+ comments = JavaSyntaxDefinition.comments,
+ languageMapper = mapper,
+ cancellationProvider = null,
+ logger = null,
+ whitespaceOrCommentBindingPolicy = JavaSyntaxDefinition.whitespaceOrCommentBindingPolicy,
+ )
+
+ return tree
+}
+
+internal fun dumpTree(node: SyntaxNode): String {
+ val all = mutableListOf()
+
+ fun append(node: SyntaxNode) {
+ all.add(node)
+ for (child in node.children()) {
+ append(child)
+ }
+ }
+ append(node)
+
+ val parsing = all.joinToString("\n") { node ->
+ val type = node.type
+ val prefix = " ".repeat((node.level()) * 2)
+ "$prefix$type"
+ }
+ return parsing
+}
+
+private fun SyntaxNode.level(): Int = generateSequence(this) { it.parent() }.count() - 1
+
+/**
+ * remove `` lines from the expected tree
+ * kmp tree cannot have them because "composite/non-composite" node information is missing in the KMP tree
+ */
+internal fun String.removeEmptyListLines(): String =
+ lines()
+ .filterNot { it.trim() == "" }
+ .joinToString("\n")