Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

traits require #50

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .run/Run Plugin.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration>
</component>
</component>
48 changes: 48 additions & 0 deletions src/main/kotlin/com/vk/modulite/index/TraitsIndex.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.vk.modulite.index

import com.intellij.openapi.components.Service
import com.intellij.util.indexing.*
import com.intellij.util.io.EnumeratorStringDescriptor
import com.intellij.util.io.KeyDescriptor
import com.jetbrains.php.lang.PhpFileType
import com.jetbrains.php.lang.psi.PhpFile
import com.jetbrains.php.lang.psi.elements.PhpClass
import com.intellij.psi.util.PsiTreeUtil

@Service(Service.Level.PROJECT)
class TraitsIndex : FileBasedIndexExtension<String, String>() {
companion object {
val KEY = ID.create<String, String>("traits.index")
}

override fun getIndexer(): DataIndexer<String, String, FileContent> {
return DataIndexer { inputData ->
val map = mutableMapOf<String, String>()
val psiFile = inputData.psiFile

if (psiFile is PhpFile) {
val classes = PsiTreeUtil.findChildrenOfType(psiFile, PhpClass::class.java)
for (klass in classes) {
if (klass.isTrait) {
map[klass.fqn] = psiFile.virtualFile.path
}
}
}
map
}
}

override fun getName(): ID<String, String> = KEY

override fun getKeyDescriptor(): KeyDescriptor<String> = EnumeratorStringDescriptor.INSTANCE

override fun getValueExternalizer(): EnumeratorStringDescriptor = EnumeratorStringDescriptor.INSTANCE

override fun getInputFilter(): FileBasedIndex.InputFilter {
return DefaultFileTypeSpecificInputFilter(PhpFileType.INSTANCE)
}

override fun dependsOnFileContent() = true

override fun getVersion(): Int = 1
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.jetbrains.php.lang.PhpLangUtil
import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocType
import com.jetbrains.php.lang.psi.elements.*
import com.jetbrains.php.lang.psi.elements.impl.FunctionImpl
import com.jetbrains.php.lang.psi.elements.impl.MethodImpl
import com.jetbrains.php.lang.psi.elements.impl.PhpUseImpl
import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor
import com.vk.modulite.SymbolName
import com.vk.modulite.composer.ComposerPackage
import com.vk.modulite.modulite.Modulite
import com.vk.modulite.modulite.ModuliteRequires
import com.vk.modulite.modulite.ModuliteRestrictionChecker
import com.vk.modulite.psi.extensions.files.containingComposerPackage
import com.vk.modulite.psi.extensions.files.containingModulite
Expand All @@ -19,6 +24,7 @@ import com.vk.modulite.psi.extensions.php.symbolName
import com.vk.modulite.utils.fromStubs
import com.vk.modulite.utils.fromTests
import com.vk.modulite.utils.registerModuliteProblem
import java.util.*

class InternalSymbolUsageInspection : LocalInspectionTool() {
companion object {
Expand All @@ -27,12 +33,13 @@ class InternalSymbolUsageInspection : LocalInspectionTool() {

class AddSymbolToRequiresQuickFix(
private val contextModulite: Modulite,
private val symbol: SymbolName,
private val symbols: List<SymbolName>
) : LocalQuickFix {

override fun getFamilyName() = "Add symbol to requires"

override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
contextModulite.addDependencies(symbol)
contextModulite.addDependencies(symbols)
}
}

Expand All @@ -48,7 +55,17 @@ class InternalSymbolUsageInspection : LocalInspectionTool() {
return
}
}
private fun referenceValidator(reference: PhpReference?): MutableCollection<out PhpNamedElement>? {
if (reference == null) return null

val references = reference.resolveGlobal(false)
if (references.isEmpty()) {
// LOG.warn("Unknown reference for symbol '${reference.safeFqn()}'")
return null
}

return references
}
class AddComposerPackageToRequiresQuickFix(
private val contextModulite: Modulite,
private val referencePackage: ComposerPackage,
Expand Down Expand Up @@ -123,6 +140,15 @@ class InternalSymbolUsageInspection : LocalInspectionTool() {
checkReferenceUsage(type)
}


override fun visitPhpUse(expression: PhpUse?) {
val instance = expression as PhpUseImpl

if (instance.isTraitImport) {
instance.targetReference?.let { checkReferenceUsage(it) }
}
}

private fun checkReferenceUsage(reference: PhpReference, problemElement: PsiElement? = reference) {
val references = reference.resolveElement()
if (references.isEmpty()) {
Expand Down Expand Up @@ -150,10 +176,38 @@ class InternalSymbolUsageInspection : LocalInspectionTool() {
)
}
}

}
}
}

private fun processElement(element: PhpNamedElement, reference: PhpReference? = null): SymbolName? {

if (element is MethodImpl) {
// У магических методов символ - его класс
if (PhpLangUtil.isMagicMethod(element.name)) {
val containingClass = element.containingClass
if (containingClass != null) {
return containingClass.symbolName(reference)
}
}

// Не статические методы добавлять не надо
if (!element.isStatic) {
return null
}
}

// Если у функции нет имени, то это лямбда. Значит мы её пропускаем
if (element is FunctionImpl) {
if (element.name.isEmpty()) {
return null
}
}

return element.symbolName(reference, forNotRequired = true)
}

private fun ProblemsHolder.addProblem(
reason: ModuliteRestrictionChecker.ViolationTypes,
symbolElement: PhpNamedElement,
Expand Down Expand Up @@ -192,8 +246,16 @@ class InternalSymbolUsageInspection : LocalInspectionTool() {
"""
restricted to $readableName, $refModulite is not required by ${context.modulite}
""".trimIndent()
} else {
quickFixes.add(AddSymbolToRequiresQuickFix(context.modulite!!, symbol))
} else if(symbolElement is PhpClass && symbolElement.isTrait){
val (classes,methods) = collectTraitReferenceUsage(reference,context.modulite?.requires )
quickFixes.add(AddSymbolToRequiresQuickFix(context.modulite!!, classes+methods))

"""
restricted to $readableName, it's not required by ${context.modulite}
""".trimIndent()
}
else {
quickFixes.add(AddSymbolToRequiresQuickFix(context.modulite!!, listOf(symbol)))

"""
restricted to $readableName, it's not required by ${context.modulite}
Expand All @@ -210,12 +272,161 @@ class InternalSymbolUsageInspection : LocalInspectionTool() {
""".trimIndent()
}
}

context.modulite?.requires
registerModuliteProblem(
problemElement,
text,
ProblemHighlightType.GENERIC_ERROR,
*quickFixes.toTypedArray()
)
}

private fun collectTraitReferenceUsage(reference: PhpReference, moduliteRequires: ModuliteRequires? = null)
: Pair<List<SymbolName>, List<SymbolName>> {
data class TraitData(
val traitsClasses: MutableSet<PhpClass>,
val requireMethods: MutableSet<MethodImpl>,
)

fun collectTraitElements(element: PsiElement): TraitData {
fun processArguments(
arguments: Array<PsiElement>,
classesToRequire: MutableSet<PhpClass>,
methodsToRequire: MutableSet<MethodImpl>,
) {
arguments.forEach { argument ->
when (argument) {
is NewExpression -> {
argument.classReference?.resolve()?.let { resolvedClass ->
if (resolvedClass is PhpClass) {
classesToRequire.add(resolvedClass)
}
}
processArguments(
argument.parameters,
classesToRequire,
methodsToRequire,
)
}

else -> {
val (nestedClasses, nestedMethods) = collectTraitElements(
argument
)
classesToRequire.addAll(nestedClasses)
methodsToRequire.addAll(nestedMethods)
}
}
}
}

val classesToRequire: MutableSet<PhpClass> = mutableSetOf()
val methodsToRequire: MutableSet<MethodImpl> = mutableSetOf()

var child = element.firstChild

while (child != null) {
when (child) {
is Method -> methodsToRequire.add(child as MethodImpl)
is PhpClass -> classesToRequire.add(child)

is MethodReference -> {
val resolvedMethod = child.resolve()
if (resolvedMethod is Method) {
methodsToRequire.add(resolvedMethod as MethodImpl)
}
}

is NewExpression -> {
val classReference = child.classReference
val resolvedClass = classReference?.resolve()
if (resolvedClass is PhpClass) {
classesToRequire.add(resolvedClass)
}
processArguments(
child.parameters,
classesToRequire,
methodsToRequire,
)
}

is ParameterList -> {
processArguments(
child.parameters,
classesToRequire,
methodsToRequire,
)
}

else -> {
val (nestedClasses, nestedMethods) = collectTraitElements(
child
)
classesToRequire.addAll(nestedClasses)
methodsToRequire.addAll(nestedMethods)
}
}

child = child.nextSibling
}

return TraitData(classesToRequire, methodsToRequire)
}

val traitsClasses: MutableList<PhpClass> = arrayListOf()
val methodsNames: MutableCollection<Method> = arrayListOf()

val references = referenceValidator(reference) ?: return Pair(listOf<SymbolName>(), listOf<SymbolName>())

val filteredReferences = references.filter {
val file = it.containingFile.virtualFile
!file.fromTests() && !file.fromStubs() && it !is PhpNamespace
}

val stack = LinkedList<PhpClass>() // Создаем стек для хранения вложенных instance

filteredReferences.forEach { elem ->
val instance = elem as PhpClass
stack.push(instance) // Добавляем текущий instance в стек
while (stack.isNotEmpty()) {
val currentInstance = stack.pop() // Получаем текущий instance из стека
traitsClasses += currentInstance
if (currentInstance.hasTraitUses()) {
val traitsUses = currentInstance.traits
traitsClasses += traitsUses

traitsUses.forEach { it ->
val instanceNesting: Array<PhpClass>? = it.traits

instanceNesting?.forEach { nestedInstance ->
stack.push(nestedInstance) // Добавляем вложенный instance в стек
}

if (instanceNesting != null) {
traitsClasses += instanceNesting
}
}
}
}
methodsNames += instance.methods
}

val requireMethods: MutableList<MethodImpl> = mutableListOf()
methodsNames.forEach { it ->
val (classes, methods) = collectTraitElements(it)
traitsClasses.addAll(classes)
requireMethods.addAll(methods)
}

val traitsClassName = traitsClasses.distinct().mapNotNull { processElement(it, reference) }
val traitsMethodsName = requireMethods.distinct().mapNotNull { processElement(it, reference) }

moduliteRequires?.symbols?.let { currentModuleSymbols ->
val filteredMethods = traitsMethodsName.filterNot { it in currentModuleSymbols }
val filteredClasses = traitsClassName.filterNot { it in currentModuleSymbols }
return Pair(filteredClasses, filteredMethods)
}

return Pair(traitsClassName, traitsMethodsName)
}
}
Loading
Loading