add tests for syntax tree with java

GitOrigin-RevId: 1f347798e068d7dc8db136613eeb685badbc7fab
This commit is contained in:
Max Medvedev
2025-08-24 12:44:16 +00:00
committed by intellij-monorepo-bot
parent 0755892c18
commit 467abf483d
6 changed files with 144 additions and 2 deletions
+1
View File
@@ -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",
@@ -28,5 +28,6 @@
<orderEntry type="module" module-name="intellij.java.testFramework.shared" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.syntax.psi" scope="TEST" />
<orderEntry type="module" module-name="intellij.java.syntax" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.syntax.tree" scope="TEST" />
</component>
</module>
@@ -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 <T> void addExplicit(@NotNull ExtensionPointName<T> extensionPointName, @NotNull T extension) {
registerExtension(extensionPointName, extension);
}
public final <T> void addExtensionPoint(@NotNull ExtensionPointName<T> extensionPointName, @NotNull Class<T> 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);
}
@@ -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);
@@ -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<JavaLanguageLevelProvider>("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,
@@ -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<SyntaxNode>()
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 `<empty list>` 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() == "<empty list>" }
.joinToString("\n")