From 35d2be25b2e938ace84299948fa5d78b5b120543 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Mon, 18 Dec 2023 07:59:08 +0100 Subject: [PATCH] [psi] extract KtCommonFile which doesn't depend on java psi should not be used directly ^KT-64409 fixed --- .../kotlin/parsing/KotlinParserDefinition.kt | 16 +- .../org/jetbrains/kotlin/psi/KtCommonFile.kt | 296 ++++++++++++++++++ .../src/org/jetbrains/kotlin/psi/KtFile.kt | 287 +---------------- .../org/jetbrains/kotlin/psi/KtVisitor.java | 10 + 4 files changed, 327 insertions(+), 282 deletions(-) create mode 100644 compiler/psi/src/org/jetbrains/kotlin/psi/KtCommonFile.kt diff --git a/compiler/psi/src/org/jetbrains/kotlin/parsing/KotlinParserDefinition.kt b/compiler/psi/src/org/jetbrains/kotlin/parsing/KotlinParserDefinition.kt index 17fe4425b1b..8d349759f70 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/parsing/KotlinParserDefinition.kt +++ b/compiler/psi/src/org/jetbrains/kotlin/parsing/KotlinParserDefinition.kt @@ -44,8 +44,11 @@ import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType import org.jetbrains.kotlin.psi.stubs.elements.KtFileElementType import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementType -class KotlinParserDefinition : ParserDefinition { - +/** + * Creates [org.jetbrains.kotlin.psi.KtCommonFile] when java psi is not available e.g. on JB Client. + * Otherwise, normal [KotlinParserDefinition] should be used. + */ +sealed class KotlinCommonParserDefinition : ParserDefinition { override fun createLexer(project: Project): Lexer = KotlinLexer() override fun createParser(project: Project): PsiParser = KotlinParser(project) @@ -72,7 +75,8 @@ class KotlinParserDefinition : ParserDefinition { } } - override fun createFile(fileViewProvider: FileViewProvider): PsiFile = KtFile(fileViewProvider, false) + @Suppress("DEPRECATION") + override fun createFile(fileViewProvider: FileViewProvider): PsiFile = org.jetbrains.kotlin.psi.KtCommonFile(fileViewProvider, false) @Deprecated("Deprecated in Java") override fun spaceExistanceTypeBetweenTokens(left: ASTNode, right: ASTNode): ParserDefinition.SpaceRequirements { @@ -99,6 +103,12 @@ class KotlinParserDefinition : ParserDefinition { // Default return MAY } +} + +class KotlinParserDefinition : KotlinCommonParserDefinition() { + override fun createFile(fileViewProvider: FileViewProvider): PsiFile { + return KtFile(fileViewProvider, false) + } companion object { diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/KtCommonFile.kt b/compiler/psi/src/org/jetbrains/kotlin/psi/KtCommonFile.kt new file mode 100644 index 00000000000..b557695157b --- /dev/null +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/KtCommonFile.kt @@ -0,0 +1,296 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.psi + +import com.intellij.extapi.psi.PsiFileBase +import com.intellij.openapi.fileTypes.FileType +import com.intellij.openapi.vfs.VirtualFileWithId +import com.intellij.psi.* +import com.intellij.psi.stubs.StubElement +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.util.ArrayFactory +import com.intellij.util.FileContentUtilCore +import com.intellij.util.IncorrectOperationException +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.parsing.KotlinParserDefinition +import org.jetbrains.kotlin.psi.psiUtil.getChildOfType +import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier +import org.jetbrains.kotlin.psi.stubs.KotlinFileStub +import org.jetbrains.kotlin.psi.stubs.elements.KtPlaceHolderStubElementType +import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes + +/** + * This class represents kotlin psi file, independently of java psi (no [PsiClassOwner] super). + * It can be created by [org.jetbrains.kotlin.parsing.KotlinCommonParserDefinition], if java psi is not available e.g., on JB Client. + * + * It's not supposed to be used directly, use [PsiFile] or if you need to check instanceof, check its' file type or language instead. + */ +@Deprecated("Don't use directly, use file.getFileType() instead") +open class KtCommonFile(viewProvider: FileViewProvider, val isCompiled: Boolean) : + PsiFileBase(viewProvider, KotlinLanguage.INSTANCE), + KtDeclarationContainer, + KtAnnotated, + KtElement, + PsiNamedElement { + + @Volatile + private var isScript: Boolean? = null + + @Volatile + private var hasTopLevelCallables: Boolean? = null + + @Volatile + private var pathCached: String? = null + + val importList: KtImportList? + get() = importLists.firstOrNull() + + @Volatile + private var hasImportAlias: Boolean? = null + + fun hasImportAlias(): Boolean { + val hasImportAlias = hasImportAlias + if (hasImportAlias != null) return hasImportAlias + + val newValue = importLists.any(KtImportList::computeHasImportAlias) + this.hasImportAlias = newValue + return newValue + } + + protected open val importLists: List + get() = findChildrenByTypeOrClass(KtStubElementTypes.IMPORT_LIST, KtImportList::class.java).asList() + + val fileAnnotationList: KtFileAnnotationList? + get() = findChildByTypeOrClass(KtStubElementTypes.FILE_ANNOTATION_LIST, KtFileAnnotationList::class.java) + + open val importDirectives: List + get() = importLists.flatMap { it.imports } + + // scripts have no package directive, all other files must have package directives + val packageDirective: KtPackageDirective? + get() { + val stub = stub + if (stub != null) { + val packageDirectiveStub = stub.findChildStubByType(KtStubElementTypes.PACKAGE_DIRECTIVE) + return packageDirectiveStub?.psi + } + return packageDirectiveByTree + } + + private val packageDirectiveByTree: KtPackageDirective? + get() { + val ast = node.findChildByType(KtNodeTypes.PACKAGE_DIRECTIVE) + return if (ast != null) ast.psi as KtPackageDirective else null + } + + var packageFqName: FqName + get() = stub?.getPackageFqName() ?: packageFqNameByTree + set(value) { + val packageDirective = packageDirective + if (packageDirective != null) { + packageDirective.fqName = value + } else { + val newPackageDirective = KtPsiFactory(project).createPackageDirectiveIfNeeded(value) ?: return + addAfter(newPackageDirective, null) + } + } + + val packageFqNameByTree: FqName + get() = packageDirectiveByTree?.fqName ?: FqName.ROOT + + val script: KtScript? + get() { + isScript?.let { if (!it) return null } + stub?.let { if (!it.isScript()) return null } + + val result = getChildOfType() + if (isScript == null) { + isScript = result != null + } + + return result + } + + val virtualFilePath + get(): String { + pathCached?.let { return it } + + return virtualFile.path.also { + pathCached = it + } + } + + val isScriptByTree: Boolean + get() = script != null + + /** + * @return modifier lists that do not belong to any declaration due to incomplete code or syntax errors + */ + val danglingModifierLists: Array + get() { + val stub = stub + return stub?.getChildrenByType( + KtStubElementTypes.MODIFIER_LIST, + KtStubElementTypes.MODIFIER_LIST.arrayFactory + ) ?: findChildrenByClass(KtModifierList::class.java) + } + + /** + * @return annotations that do not belong to any declaration due to incomplete code or syntax errors + */ + val danglingAnnotations: List + get() = danglingModifierLists.flatMap { obj: KtModifierList -> obj.annotationEntries } + + override fun getFileType(): FileType = KotlinFileType.INSTANCE + + override fun toString(): String = "KtFile: $name" + + override fun getDeclarations(): List { + val stub = stub + return stub?.getChildrenByType(KtFile.FILE_DECLARATION_TYPES, KtDeclaration.ARRAY_FACTORY)?.toList() + ?: PsiTreeUtil.getChildrenOfTypeAsList(this, KtDeclaration::class.java) + } + + fun >> findChildByTypeOrClass( + elementType: KtPlaceHolderStubElementType, + elementClass: Class + ): T? { + val stub = stub + if (stub != null) { + val importListStub = stub.findChildStubByType(elementType) + return importListStub?.psi + } + return findChildByClass(elementClass) + } + + fun >> findChildrenByTypeOrClass( + elementType: KtPlaceHolderStubElementType, + elementClass: Class + ): Array { + val stub = stub + if (stub != null) { + val arrayFactory: ArrayFactory = elementType.arrayFactory + return stub.getChildrenByType(elementType, arrayFactory) + } + return findChildrenByClass(elementClass) + } + + + fun findImportByAlias(name: String): KtImportDirective? { + if (!hasImportAlias()) return null + + return importDirectives.firstOrNull { name == it.aliasName } + } + + fun findAliasByFqName(fqName: FqName): KtImportAlias? { + if (!hasImportAlias()) return null + + return importDirectives.firstOrNull { + it.alias != null && fqName == it.importedFqName + }?.alias + } + + fun getNameForGivenImportAlias(name: Name): Name? { + if (!hasImportAlias()) return null + + return importDirectives.find { it.importedName == name }?.importedFqName?.pathSegments()?.last() + } + + override fun getStub(): KotlinFileStub? { + if (virtualFile !is VirtualFileWithId) return null + val stub = super.getStub() + if (stub is KotlinFileStub?) { + return stub + } + + error("Illegal stub for KtFile: type=${this.javaClass}, stub=${stub?.javaClass} name=$name") + } + + override fun clearCaches() { + @Suppress("RemoveExplicitSuperQualifier") + super.clearCaches() + isScript = null + hasTopLevelCallables = null + pathCached = null + hasImportAlias = null + } + + fun isScript(): Boolean = isScript ?: stub?.isScript() ?: isScriptByTree + + fun hasTopLevelCallables(): Boolean { + hasTopLevelCallables?.let { return it } + + val result = declarations.any { + (it is KtProperty || + it is KtNamedFunction || + it is KtScript || + it is KtTypeAlias) && !it.hasExpectModifier() + } + + hasTopLevelCallables = result + return result + } + + override fun accept(visitor: PsiElementVisitor) { + if (visitor is KtVisitor<*, *>) { + @Suppress("UNCHECKED_CAST") + accept(visitor as KtVisitor, null) + } else { + visitor.visitFile(this) + } + } + + override fun getContainingKtFile(): KtFile = this as KtFile + + override fun acceptChildren(visitor: KtVisitor, data: D) { + KtPsiUtil.visitChildren(this, visitor, data) + } + + override fun accept(visitor: KtVisitor, data: D): R { + return visitor.visitKtCommonFile(this) + } + + override fun getAnnotations(): List = + fileAnnotationList?.annotations ?: emptyList() + + override fun getAnnotationEntries(): List = + fileAnnotationList?.annotationEntries ?: emptyList() + + @Throws(IncorrectOperationException::class) + override fun setName(name: String): PsiElement { + val result = super.setName(name) + val willBeScript = name.endsWith(KotlinParserDefinition.STD_SCRIPT_EXT) + if (isScript() != willBeScript) { + FileContentUtilCore.reparseFiles(listOfNotNull(virtualFile)) + } + return result + } + + override fun getPsiOrParent(): KtElement = this + + @Suppress("unused") //keep for compatibility with potential plugins + fun shouldChangeModificationCount(@Suppress("UNUSED_PARAMETER") place: PsiElement): Boolean { + // Modification count for Kotlin files is tracked entirely by KotlinCodeBlockModificationListener + return false + } +} + +private fun KtImportList.computeHasImportAlias(): Boolean { + var child: PsiElement? = firstChild + while (child != null) { + if (child is KtImportDirective && child.alias != null) { + return true + } + + child = child.nextSibling + } + + return false +} \ No newline at end of file diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/KtFile.kt b/compiler/psi/src/org/jetbrains/kotlin/psi/KtFile.kt index cf614486282..3543e53f43b 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/psi/KtFile.kt +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/KtFile.kt @@ -16,215 +16,15 @@ package org.jetbrains.kotlin.psi -import com.intellij.extapi.psi.PsiFileBase -import com.intellij.openapi.fileTypes.FileType -import com.intellij.openapi.vfs.VirtualFileWithId -import com.intellij.psi.* -import com.intellij.psi.stubs.StubElement +import com.intellij.psi.FileViewProvider +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiClassOwner import com.intellij.psi.tree.TokenSet -import com.intellij.psi.util.PsiTreeUtil -import com.intellij.util.ArrayFactory -import com.intellij.util.FileContentUtilCore -import com.intellij.util.IncorrectOperationException -import org.jetbrains.kotlin.KtNodeTypes -import org.jetbrains.kotlin.idea.KotlinFileType -import org.jetbrains.kotlin.idea.KotlinLanguage -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.parsing.KotlinParserDefinition -import org.jetbrains.kotlin.psi.psiUtil.getChildOfType -import org.jetbrains.kotlin.psi.psiUtil.hasExpectModifier -import org.jetbrains.kotlin.psi.stubs.KotlinFileStub -import org.jetbrains.kotlin.psi.stubs.elements.KtPlaceHolderStubElementType import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes import org.jetbrains.kotlin.psi.stubs.elements.KtTokenSets -open class KtFile(viewProvider: FileViewProvider, val isCompiled: Boolean) : - PsiFileBase(viewProvider, KotlinLanguage.INSTANCE), - KtDeclarationContainer, - KtAnnotated, - KtElement, - PsiClassOwner, - PsiNamedElement { - - @Volatile - private var isScript: Boolean? = null - - @Volatile - private var hasTopLevelCallables: Boolean? = null - - @Volatile - private var pathCached: String? = null - - val importList: KtImportList? - get() = importLists.firstOrNull() - - @Volatile - private var hasImportAlias: Boolean? = null - - fun hasImportAlias(): Boolean { - val hasImportAlias = hasImportAlias - if (hasImportAlias != null) return hasImportAlias - - val newValue = importLists.any(KtImportList::computeHasImportAlias) - this.hasImportAlias = newValue - return newValue - } - - protected open val importLists: List - get() = findChildrenByTypeOrClass(KtStubElementTypes.IMPORT_LIST, KtImportList::class.java).asList() - - val fileAnnotationList: KtFileAnnotationList? - get() = findChildByTypeOrClass(KtStubElementTypes.FILE_ANNOTATION_LIST, KtFileAnnotationList::class.java) - - open val importDirectives: List - get() = importLists.flatMap { it.imports } - - // scripts have no package directive, all other files must have package directives - val packageDirective: KtPackageDirective? - get() { - val stub = stub - if (stub != null) { - val packageDirectiveStub = stub.findChildStubByType(KtStubElementTypes.PACKAGE_DIRECTIVE) - return packageDirectiveStub?.psi - } - return packageDirectiveByTree - } - - private val packageDirectiveByTree: KtPackageDirective? - get() { - val ast = node.findChildByType(KtNodeTypes.PACKAGE_DIRECTIVE) - return if (ast != null) ast.psi as KtPackageDirective else null - } - - var packageFqName: FqName - get() = stub?.getPackageFqName() ?: packageFqNameByTree - set(value) { - val packageDirective = packageDirective - if (packageDirective != null) { - packageDirective.fqName = value - } else { - val newPackageDirective = KtPsiFactory(project).createPackageDirectiveIfNeeded(value) ?: return - addAfter(newPackageDirective, null) - } - } - - val packageFqNameByTree: FqName - get() = packageDirectiveByTree?.fqName ?: FqName.ROOT - - val script: KtScript? - get() { - isScript?.let { if (!it) return null } - stub?.let { if (!it.isScript()) return null } - - val result = getChildOfType() - if (isScript == null) { - isScript = result != null - } - - return result - } - - val virtualFilePath - get(): String { - pathCached?.let { return it } - - return virtualFile.path.also { - pathCached = it - } - } - - val isScriptByTree: Boolean - get() = script != null - - /** - * @return modifier lists that do not belong to any declaration due to incomplete code or syntax errors - */ - val danglingModifierLists: Array - get() { - val stub = stub - return stub?.getChildrenByType( - KtStubElementTypes.MODIFIER_LIST, - KtStubElementTypes.MODIFIER_LIST.arrayFactory - ) ?: findChildrenByClass(KtModifierList::class.java) - } - - /** - * @return annotations that do not belong to any declaration due to incomplete code or syntax errors - */ - val danglingAnnotations: List - get() = danglingModifierLists.flatMap { obj: KtModifierList -> obj.annotationEntries } - - override fun getFileType(): FileType = KotlinFileType.INSTANCE - - override fun toString(): String = "KtFile: $name" - - override fun getDeclarations(): List { - val stub = stub - return stub?.getChildrenByType(FILE_DECLARATION_TYPES, KtDeclaration.ARRAY_FACTORY)?.toList() - ?: PsiTreeUtil.getChildrenOfTypeAsList(this, KtDeclaration::class.java) - } - - fun >> findChildByTypeOrClass( - elementType: KtPlaceHolderStubElementType, - elementClass: Class - ): T? { - val stub = stub - if (stub != null) { - val importListStub = stub.findChildStubByType(elementType) - return importListStub?.psi - } - return findChildByClass(elementClass) - } - - fun >> findChildrenByTypeOrClass( - elementType: KtPlaceHolderStubElementType, - elementClass: Class - ): Array { - val stub = stub - if (stub != null) { - val arrayFactory: ArrayFactory = elementType.arrayFactory - return stub.getChildrenByType(elementType, arrayFactory) - } - return findChildrenByClass(elementClass) - } - - - fun findImportByAlias(name: String): KtImportDirective? { - if (!hasImportAlias()) return null - - return importDirectives.firstOrNull { name == it.aliasName } - } - - fun findAliasByFqName(fqName: FqName): KtImportAlias? { - if (!hasImportAlias()) return null - - return importDirectives.firstOrNull { - it.alias != null && fqName == it.importedFqName - }?.alias - } - - fun getNameForGivenImportAlias(name: Name): Name? { - if (!hasImportAlias()) return null - - return importDirectives.find { it.importedName == name }?.importedFqName?.pathSegments()?.last() - } - - @Deprecated("") // getPackageFqName should be used instead - override fun getPackageName(): String { - return packageFqName.asString() - } - - override fun getStub(): KotlinFileStub? { - if (virtualFile !is VirtualFileWithId) return null - val stub = super.getStub() - if (stub is KotlinFileStub?) { - return stub - } - - error("Illegal stub for KtFile: type=${this.javaClass}, stub=${stub?.javaClass} name=$name") - } - +open class KtFile(viewProvider: FileViewProvider, isCompiled: Boolean) : @Suppress("DEPRECATION") KtCommonFile(viewProvider, isCompiled), + PsiClassOwner { override fun getClasses(): Array { val fileClassProvider = project.getService(KtFileClassProvider::class.java) return fileClassProvider?.getFileClasses(this) ?: PsiClass.EMPTY_ARRAY @@ -232,88 +32,17 @@ open class KtFile(viewProvider: FileViewProvider, val isCompiled: Boolean) : override fun setPackageName(packageName: String) {} - override fun clearCaches() { - @Suppress("RemoveExplicitSuperQualifier") - super.clearCaches() - isScript = null - hasTopLevelCallables = null - pathCached = null - hasImportAlias = null - } - - fun isScript(): Boolean = isScript ?: stub?.isScript() ?: isScriptByTree - - fun hasTopLevelCallables(): Boolean { - hasTopLevelCallables?.let { return it } - - val result = declarations.any { - (it is KtProperty || - it is KtNamedFunction || - it is KtScript || - it is KtTypeAlias) && !it.hasExpectModifier() - } - - hasTopLevelCallables = result - return result - } - - override fun accept(visitor: PsiElementVisitor) { - if (visitor is KtVisitor<*, *>) { - @Suppress("UNCHECKED_CAST") - accept(visitor as KtVisitor, null) - } else { - visitor.visitFile(this) - } - } - - override fun getContainingKtFile(): KtFile = this - - override fun acceptChildren(visitor: KtVisitor, data: D) { - KtPsiUtil.visitChildren(this, visitor, data) + @Deprecated("getPackageFqName should be used instead") + override fun getPackageName(): String { + return packageFqName.asString() } override fun accept(visitor: KtVisitor, data: D): R { return visitor.visitKtFile(this, data) } - override fun getAnnotations(): List = - fileAnnotationList?.annotations ?: emptyList() - - override fun getAnnotationEntries(): List = - fileAnnotationList?.annotationEntries ?: emptyList() - - @Throws(IncorrectOperationException::class) - override fun setName(name: String): PsiElement { - val result = super.setName(name) - val willBeScript = name.endsWith(KotlinParserDefinition.STD_SCRIPT_EXT) - if (isScript() != willBeScript) { - FileContentUtilCore.reparseFiles(listOfNotNull(virtualFile)) - } - return result - } - - override fun getPsiOrParent(): KtElement = this - - @Suppress("unused") //keep for compatibility with potential plugins - fun shouldChangeModificationCount(@Suppress("UNUSED_PARAMETER") place: PsiElement): Boolean { - // Modification count for Kotlin files is tracked entirely by KotlinCodeBlockModificationListener - return false - } - companion object { val FILE_DECLARATION_TYPES = TokenSet.orSet(KtTokenSets.DECLARATION_TYPES, TokenSet.create(KtStubElementTypes.SCRIPT)) } } -private fun KtImportList.computeHasImportAlias(): Boolean { - var child: PsiElement? = firstChild - while (child != null) { - if (child is KtImportDirective && child.alias != null) { - return true - } - - child = child.nextSibling - } - - return false -} diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/KtVisitor.java b/compiler/psi/src/org/jetbrains/kotlin/psi/KtVisitor.java index d9eec6071ee..0894e0bce35 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/psi/KtVisitor.java +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/KtVisitor.java @@ -74,6 +74,16 @@ public class KtVisitor extends PsiElementVisitor { return null; } + /** + * Required for {@link KtCommonFile} implementation. + * It is not expected to be used, because {@link KtCommonFile} should not be used directly. + */ + @SuppressWarnings("deprecation") + public final R visitKtCommonFile(@NotNull KtCommonFile file) { + visitFile(file); + return null; + } + public R visitScript(@NotNull KtScript script, D data) { return visitDeclaration(script, data); }