Skip to content

Commit

Permalink
initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
i582 committed Mar 14, 2022
1 parent 9dccc41 commit c01af26
Show file tree
Hide file tree
Showing 59 changed files with 2,148 additions and 286 deletions.
10 changes: 8 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,23 @@ idea {
}
}

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
freeCompilerArgs += '-Xjvm-default=all'
}
}

intellij {
type = 'IU'
// for release versions: https://www.jetbrains.com/intellij-repository/releases (com.jetbrains.intellij.idea)
// for EAPs: https://www.jetbrains.com/intellij-repository/snapshots
version = '2021.3'
version = '2021.3.1'
plugins = [
'com.jetbrains.php:213.5744.223', // https://plugins.jetbrains.com/plugin/6610-php/versions
]
}
runIde {
ideDir.set(file("/Users/alexandr.kirsanov/Library/Application Support/JetBrains/Toolbox/apps/PhpStorm/ch-0/213.5744.279/PhpStorm.app/Contents"))
// ideDir.set(file("/Users/alexandr.kirsanov/Library/Application Support/JetBrains/Toolbox/apps/PhpStorm/ch-0/213.5744.279/PhpStorm.app/Contents"))
}

patchPluginXml {
Expand Down
17 changes: 17 additions & 0 deletions src/main/kotlin/com/vk/kphpstorm/KphpStormASTFactory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.vk.kphpstorm

import com.intellij.lang.DefaultASTFactoryImpl
import com.intellij.psi.impl.source.tree.LeafElement
import com.intellij.psi.impl.source.tree.PsiCommentImpl
import com.intellij.psi.tree.IElementType
import com.vk.kphpstorm.generics.psi.GenericInstantiationPsiCommentImpl

class KphpStormASTFactory : DefaultASTFactoryImpl() {
override fun createComment(type: IElementType, text: CharSequence): LeafElement {
if (!text.startsWith("/*<") && !text.endsWith(">*/")) {
return PsiCommentImpl(type, text)
}

return GenericInstantiationPsiCommentImpl(type, text)
}
}
36 changes: 19 additions & 17 deletions src/main/kotlin/com/vk/kphpstorm/KphpStormParserDefinition.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,27 @@ class KphpStormParserDefinition() : PhpParserDefinition() {
*/
override fun createElement(node: ASTNode): PsiElement {
return when (node.elementType) {
KphpDocElementTypes.kphpDocTagSimple -> KphpDocTagSimplePsiImpl(node)
KphpDocElementTypes.kphpDocTagTemplateClass -> KphpDocTagTemplateClassPsiImpl(node)
KphpDocTplParameterDeclPsiImpl.elementType -> KphpDocTplParameterDeclPsiImpl(node)
KphpDocElementTypes.kphpDocTagWarnPerformance -> KphpDocTagWarnPerformancePsiImpl(node)
KphpDocWarnPerformanceItemPsiImpl.elementType -> KphpDocWarnPerformanceItemPsiImpl(node)
KphpDocElementTypes.kphpDocTagSimple -> KphpDocTagSimplePsiImpl(node)
KphpDocElementTypes.kphpDocTagTemplateClass -> KphpDocTagTemplateClassPsiImpl(node)
KphpDocElementTypes.kphpDocTagGeneric -> KphpDocTagGenericPsiImpl(node)
KphpDocGenericParameterDeclPsiImpl.elementType -> KphpDocGenericParameterDeclPsiImpl(node)
KphpDocElementTypes.kphpDocTagWarnPerformance -> KphpDocTagWarnPerformancePsiImpl(node)
KphpDocWarnPerformanceItemPsiImpl.elementType -> KphpDocWarnPerformanceItemPsiImpl(node)

ExPhpTypePrimitivePsiImpl.elementType -> ExPhpTypePrimitivePsiImpl(node)
ExPhpTypeInstancePsiImpl.elementType -> ExPhpTypeInstancePsiImpl(node)
ExPhpTypePipePsiImpl.elementType -> ExPhpTypePipePsiImpl(node)
ExPhpTypeAnyPsiImpl.elementType -> ExPhpTypeAnyPsiImpl(node)
ExPhpTypeArrayPsiImpl.elementType -> ExPhpTypeArrayPsiImpl(node)
ExPhpTypeTuplePsiImpl.elementType -> ExPhpTypeTuplePsiImpl(node)
ExPhpTypeShapePsiImpl.elementType -> ExPhpTypeShapePsiImpl(node)
ExPhpTypeNullablePsiImpl.elementType -> ExPhpTypeNullablePsiImpl(node)
ExPhpTypeTplInstantiationPsiImpl.elementType -> ExPhpTypeTplInstantiationPsiImpl(node)
ExPhpTypeCallablePsiImpl.elementType -> ExPhpTypeCallablePsiImpl(node)
ExPhpTypeForcingPsiImpl.elementType -> ExPhpTypeForcingPsiImpl(node)
ExPhpTypePrimitivePsiImpl.elementType -> ExPhpTypePrimitivePsiImpl(node)
ExPhpTypeInstancePsiImpl.elementType -> ExPhpTypeInstancePsiImpl(node)
ExPhpTypePipePsiImpl.elementType -> ExPhpTypePipePsiImpl(node)
ExPhpTypeAnyPsiImpl.elementType -> ExPhpTypeAnyPsiImpl(node)
ExPhpTypeArrayPsiImpl.elementType -> ExPhpTypeArrayPsiImpl(node)
ExPhpTypeTuplePsiImpl.elementType -> ExPhpTypeTuplePsiImpl(node)
ExPhpTypeShapePsiImpl.elementType -> ExPhpTypeShapePsiImpl(node)
ExPhpTypeNullablePsiImpl.elementType -> ExPhpTypeNullablePsiImpl(node)
ExPhpTypeTplInstantiationPsiImpl.elementType -> ExPhpTypeTplInstantiationPsiImpl(node)
ExPhpTypeCallablePsiImpl.elementType -> ExPhpTypeCallablePsiImpl(node)
ExPhpTypeClassStringPsiImpl.elementType -> ExPhpTypeClassStringPsiImpl(node)
ExPhpTypeForcingPsiImpl.elementType -> ExPhpTypeForcingPsiImpl(node)

else -> PhpPsiElementCreator.create(node)
else -> PhpPsiElementCreator.create(node)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.vk.kphpstorm.completion

import com.intellij.openapi.util.TextRange
import com.intellij.patterns.PlatformPatterns
import com.intellij.psi.*
import com.intellij.util.ProcessingContext
import com.jetbrains.php.lang.psi.PhpPsiElementFactory
import com.jetbrains.php.lang.psi.elements.FunctionReference
import com.jetbrains.php.lang.psi.elements.PhpNamedElement
import com.vk.kphpstorm.generics.psi.GenericInstantiationPsiCommentImpl

/**
* Контрибьютор который создает ссылки на классы внутри комментария
* с описанием шаблонных типов при вызове функции.
*
* Подробнее в комментарии для [GenericInstantiationPsiCommentImpl].
*/
class KphpGenericsReferenceContributor : PsiReferenceContributor() {
override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
registrar.registerReferenceProvider(
PlatformPatterns.psiComment(),
PhpPsiReferenceProvider()
)
}

class PhpPsiReferenceProvider : PsiReferenceProvider() {
override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<PsiReference> {
if (element !is GenericInstantiationPsiCommentImpl) {
return emptyArray()
}

val instances = element.extractInstances()

return instances.map { (_, instance) ->
instance.classes(element.project).map { klass ->
PhpElementReference(element, klass, instance.pos.shiftLeft(element.startOffset))
}
}.flatten().toTypedArray()
}

class PhpElementReference(element: PsiElement, result: PsiElement, private val myRange: TextRange? = null) :
PsiReference {
private val myElement: PsiElement
private val myResult: PsiElement

init {
myElement = element
myResult = result
}

override fun getElement() = myElement

override fun getRangeInElement(): TextRange {
if (myRange != null) {
return myRange
}
val startOffset = 1
return TextRange(startOffset, myElement.textLength - 1)
}

override fun resolve() = myResult

override fun getCanonicalText(): String =
if (myResult is PhpNamedElement) myResult.fqn
else myElement.parent.text

override fun handleElementRename(newElementName: String): PsiElement {
val text = element.text
val start = text.slice(0 until rangeInElement.startOffset)
val end = text.slice(rangeInElement.endOffset until text.length)

val specText = start + newElementName + end

val psi = PhpPsiElementFactory.createPhpPsiFromText(
element.project, FunctionReference::class.java, "f$specText();"
)

val comment = psi.firstChild.nextSibling

return myElement.replace(comment)
}

override fun bindToElement(element: PsiElement): PsiElement {
throw UnsupportedOperationException()
}

override fun isReferenceTo(element: PsiElement): Boolean {
return myResult === element
}

override fun isSoft() = true
}
}
}

2 changes: 1 addition & 1 deletion src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ interface ExPhpType {
fun toPhpType(): PhpType
fun toHumanReadable(expr: PhpPsiElement): String
fun getSubkeyByIndex(indexKey: String): ExPhpType?
fun instantiateTemplate(nameMap: Map<String, ExPhpType>): ExPhpType
fun instantiateGeneric(nameMap: Map<String, ExPhpType>): ExPhpType
fun isAssignableFrom(rhs: ExPhpType, project: Project): Boolean

companion object {
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeAny.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ExPhpTypeAny : ExPhpType {
return this
}

override fun instantiateTemplate(nameMap: Map<String, ExPhpType>): ExPhpType {
override fun instantiateGeneric(nameMap: Map<String, ExPhpType>): ExPhpType {
return this
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeArray.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ class ExPhpTypeArray(val inner: ExPhpType) : ExPhpType {
return inner
}

override fun instantiateTemplate(nameMap: Map<String, ExPhpType>): ExPhpType {
return ExPhpTypeArray(inner.instantiateTemplate(nameMap))
override fun instantiateGeneric(nameMap: Map<String, ExPhpType>): ExPhpType {
return ExPhpTypeArray(inner.instantiateGeneric(nameMap))
}

override fun isAssignableFrom(rhs: ExPhpType, project: Project): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ class ExPhpTypeCallable(val argTypes: List<ExPhpType>, val returnType: ExPhpType
return null
}

override fun instantiateTemplate(nameMap: Map<String, ExPhpType>): ExPhpType {
return ExPhpTypeCallable(argTypes.map { it.instantiateTemplate(nameMap) }, returnType?.instantiateTemplate(nameMap))
override fun instantiateGeneric(nameMap: Map<String, ExPhpType>): ExPhpType {
return ExPhpTypeCallable(argTypes.map { it.instantiateGeneric(nameMap) }, returnType?.instantiateGeneric(nameMap))
}

override fun isAssignableFrom(rhs: ExPhpType, project: Project): Boolean = when (rhs) {
Expand Down
69 changes: 69 additions & 0 deletions src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeClassString.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.vk.kphpstorm.exphptype

import com.intellij.openapi.project.Project
import com.jetbrains.php.lang.psi.elements.PhpPsiElement
import com.jetbrains.php.lang.psi.resolve.types.PhpType

/**
* Type of special class constant (`Foo::class`).
*/
class ExPhpTypeClassString(val inner: ExPhpType) : ExPhpType {
override fun toString() = "class-string($inner)"

override fun toHumanReadable(expr: PhpPsiElement) = "class-string($inner)"

override fun equals(other: Any?) = other is ExPhpTypeClassString && inner == other.inner

override fun hashCode() = 35

override fun toPhpType(): PhpType {
return PhpType().add("class-string($inner)")
}

override fun getSubkeyByIndex(indexKey: String): ExPhpType? {
return this
}

override fun instantiateGeneric(nameMap: Map<String, ExPhpType>): ExPhpType {
// TODO: подумать тут
val fqn = when (inner) {
is ExPhpTypeInstance -> inner.fqn
is ExPhpTypeGenericsT -> inner.nameT
else -> ""
}

return nameMap[fqn]?.let { ExPhpTypeClassString(ExPhpTypeInstance(it.toString())) } ?: this
}

override fun isAssignableFrom(rhs: ExPhpType, project: Project): Boolean = when (rhs) {
// нативный вывод типов дает тип string|class-string<T> для T::class, поэтому
// необходимо обработать этот случай отдельно
is ExPhpTypePipe -> {
val containsString = rhs.items.any { it == ExPhpType.STRING }
if (rhs.items.size == 2 && containsString) {
val otherType = rhs.items.find { it != ExPhpType.STRING }
if (otherType == null) false
else inner.isAssignableFrom(otherType, project)
} else false
}
// class-string<T> совместим только с class-string<E> при условии
// что класс E является допустимым для класса T.
is ExPhpTypeClassString -> inner.isAssignableFrom(rhs.inner, project)
else -> false
}

companion object {
// нативный вывод типов дает тип string|class-string<T> для T::class,
// из-за этого в некоторых местах нужна дополнительная логика.
fun isNativePipeWithString(pipe: ExPhpTypePipe): Boolean {
if (pipe.items.size != 2) return false
val otherType = pipe.items.find { it != ExPhpType.STRING }

return otherType is ExPhpTypeClassString
}

fun getClassFromNativePipeWithString(pipe: ExPhpTypePipe): ExPhpType {
return pipe.items.find { it != ExPhpType.STRING }!!
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ class ExPhpTypeForcing(val inner: ExPhpType) : ExPhpType {
return ExPhpTypeForcing(inner.getSubkeyByIndex(indexKey) ?: return null)
}

override fun instantiateTemplate(nameMap: Map<String, ExPhpType>): ExPhpType {
return ExPhpTypeForcing(inner.instantiateTemplate(nameMap))
override fun instantiateGeneric(nameMap: Map<String, ExPhpType>): ExPhpType {
return ExPhpTypeForcing(inner.instantiateGeneric(nameMap))
}

override fun isAssignableFrom(rhs: ExPhpType, project: Project): Boolean {
Expand Down
33 changes: 33 additions & 0 deletions src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeGenericT.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.vk.kphpstorm.exphptype

import com.intellij.openapi.project.Project
import com.jetbrains.php.lang.psi.elements.PhpPsiElement
import com.jetbrains.php.lang.psi.resolve.types.PhpType

/**
* 'T' — is genericsT when it's defined in @kphp-generic, then it's genericsT on resolve, not class T
* @see com.vk.kphpstorm.exphptype.psi.ExPhpTypeInstancePsiImpl.getType
*/
class ExPhpTypeGenericsT(val nameT: String) : ExPhpType {
override fun toString() = "%$nameT"

override fun toHumanReadable(expr: PhpPsiElement) = "%$nameT"

override fun toPhpType(): PhpType {
return PhpType().add("%$nameT")
}

override fun getSubkeyByIndex(indexKey: String): ExPhpType? {
return null
}

override fun instantiateGeneric(nameMap: Map<String, ExPhpType>): ExPhpType {
return nameMap[nameT] ?: this
}

override fun isAssignableFrom(rhs: ExPhpType, project: Project): Boolean = when (rhs) {
is ExPhpTypeAny -> true
// todo shall we add any strict rules here?
else -> true
}
}
16 changes: 15 additions & 1 deletion src/main/kotlin/com/vk/kphpstorm/exphptype/ExPhpTypeInstance.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class ExPhpTypeInstance(val fqn: String) : ExPhpType {
return null
}

override fun instantiateTemplate(nameMap: Map<String, ExPhpType>): ExPhpType {
override fun instantiateGeneric(nameMap: Map<String, ExPhpType>): ExPhpType {
return nameMap[fqn] ?: this
}

Expand All @@ -50,6 +50,20 @@ class ExPhpTypeInstance(val fqn: String) : ExPhpType {
rhsIsChild
}

is ExPhpTypeTplInstantiation -> rhs.classFqn == fqn || run {
val phpIndex = PhpIndex.getInstance(project)
val lhsClass = phpIndex.getAnyByFQN(fqn).firstOrNull() ?: return false
var rhsIsChild = false
phpIndex.getAnyByFQN(rhs.classFqn).forEach { rhsClass ->
PhpClassHierarchyUtils.processSuperWithoutMixins(rhsClass, true, true) { clazz ->
if (PhpClassHierarchyUtils.classesEqual(lhsClass, clazz))
rhsIsChild = true
!rhsIsChild
}
}
rhsIsChild
}

else -> false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class ExPhpTypeNullable(val inner: ExPhpType) : ExPhpType {
return inner.getSubkeyByIndex(indexKey)
}

override fun instantiateTemplate(nameMap: Map<String, ExPhpType>): ExPhpType {
return ExPhpTypeNullable(inner.instantiateTemplate(nameMap))
override fun instantiateGeneric(nameMap: Map<String, ExPhpType>): ExPhpType {
return ExPhpTypeNullable(inner.instantiateGeneric(nameMap))
}

override fun isAssignableFrom(rhs: ExPhpType, project: Project): Boolean = when (rhs) {
Expand Down
Loading

0 comments on commit c01af26

Please sign in to comment.