diff --git a/.run/Run Plugin.run.xml b/.run/Run Plugin.run.xml
index 20a70af..d15ff68 100644
--- a/.run/Run Plugin.run.xml
+++ b/.run/Run Plugin.run.xml
@@ -21,4 +21,4 @@
false
-
+
\ No newline at end of file
diff --git a/src/main/kotlin/com/vk/modulite/index/TraitsIndex.kt b/src/main/kotlin/com/vk/modulite/index/TraitsIndex.kt
new file mode 100644
index 0000000..61406e4
--- /dev/null
+++ b/src/main/kotlin/com/vk/modulite/index/TraitsIndex.kt
@@ -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() {
+ companion object {
+ val KEY = ID.create("traits.index")
+ }
+
+ override fun getIndexer(): DataIndexer {
+ return DataIndexer { inputData ->
+ val map = mutableMapOf()
+ 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 = KEY
+
+ override fun getKeyDescriptor(): KeyDescriptor = 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
+}
diff --git a/src/main/kotlin/com/vk/modulite/inspections/InternalSymbolUsageInspection.kt b/src/main/kotlin/com/vk/modulite/inspections/InternalSymbolUsageInspection.kt
index 0f29166..cbf521d 100644
--- a/src/main/kotlin/com/vk/modulite/inspections/InternalSymbolUsageInspection.kt
+++ b/src/main/kotlin/com/vk/modulite/inspections/InternalSymbolUsageInspection.kt
@@ -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
@@ -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 {
@@ -27,12 +33,13 @@ class InternalSymbolUsageInspection : LocalInspectionTool() {
class AddSymbolToRequiresQuickFix(
private val contextModulite: Modulite,
- private val symbol: SymbolName,
+ private val symbols: List
) : LocalQuickFix {
+
override fun getFamilyName() = "Add symbol to requires"
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
- contextModulite.addDependencies(symbol)
+ contextModulite.addDependencies(symbols)
}
}
@@ -48,7 +55,17 @@ class InternalSymbolUsageInspection : LocalInspectionTool() {
return
}
}
+ private fun referenceValidator(reference: PhpReference?): MutableCollection? {
+ 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,
@@ -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()) {
@@ -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,
@@ -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}
@@ -210,7 +272,7 @@ class InternalSymbolUsageInspection : LocalInspectionTool() {
""".trimIndent()
}
}
-
+context.modulite?.requires
registerModuliteProblem(
problemElement,
text,
@@ -218,4 +280,153 @@ class InternalSymbolUsageInspection : LocalInspectionTool() {
*quickFixes.toTypedArray()
)
}
+
+ private fun collectTraitReferenceUsage(reference: PhpReference, moduliteRequires: ModuliteRequires? = null)
+ : Pair, List> {
+ data class TraitData(
+ val traitsClasses: MutableSet,
+ val requireMethods: MutableSet,
+ )
+
+ fun collectTraitElements(element: PsiElement): TraitData {
+ fun processArguments(
+ arguments: Array,
+ classesToRequire: MutableSet,
+ methodsToRequire: MutableSet,
+ ) {
+ 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 = mutableSetOf()
+ val methodsToRequire: MutableSet = 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 = arrayListOf()
+ val methodsNames: MutableCollection = arrayListOf()
+
+ val references = referenceValidator(reference) ?: return Pair(listOf(), listOf())
+
+ val filteredReferences = references.filter {
+ val file = it.containingFile.virtualFile
+ !file.fromTests() && !file.fromStubs() && it !is PhpNamespace
+ }
+
+ val stack = LinkedList() // Создаем стек для хранения вложенных 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? = it.traits
+
+ instanceNesting?.forEach { nestedInstance ->
+ stack.push(nestedInstance) // Добавляем вложенный instance в стек
+ }
+
+ if (instanceNesting != null) {
+ traitsClasses += instanceNesting
+ }
+ }
+ }
+ }
+ methodsNames += instance.methods
+ }
+
+ val requireMethods: MutableList = 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)
+ }
}
diff --git a/src/main/kotlin/com/vk/modulite/services/ModuliteDependenciesCollector.kt b/src/main/kotlin/com/vk/modulite/services/ModuliteDependenciesCollector.kt
index 9d43e86..5796387 100644
--- a/src/main/kotlin/com/vk/modulite/services/ModuliteDependenciesCollector.kt
+++ b/src/main/kotlin/com/vk/modulite/services/ModuliteDependenciesCollector.kt
@@ -8,26 +8,13 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import com.intellij.util.containers.TreeNodeProcessingResult
+import com.jetbrains.php.PhpIndex
import com.jetbrains.php.lang.PhpLangUtil
import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocType
import com.jetbrains.php.lang.psi.PhpFile
-import com.jetbrains.php.lang.psi.elements.ClassConstantReference
-import com.jetbrains.php.lang.psi.elements.ClassReference
-import com.jetbrains.php.lang.psi.elements.ConstantReference
-import com.jetbrains.php.lang.psi.elements.FieldReference
-import com.jetbrains.php.lang.psi.elements.FunctionReference
-import com.jetbrains.php.lang.psi.elements.Global
-import com.jetbrains.php.lang.psi.elements.MethodReference
-import com.jetbrains.php.lang.psi.elements.NewExpression
-import com.jetbrains.php.lang.psi.elements.PhpNamedElement
-import com.jetbrains.php.lang.psi.elements.PhpNamespace
-import com.jetbrains.php.lang.psi.elements.PhpNamespaceReference
-import com.jetbrains.php.lang.psi.elements.PhpReference
-import com.jetbrains.php.lang.psi.elements.PhpUse
-import com.jetbrains.php.lang.psi.elements.Variable
-import com.jetbrains.php.lang.psi.elements.impl.FieldImpl
-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.*
+import com.jetbrains.php.lang.psi.elements.impl.*
+import com.jetbrains.php.lang.psi.resolve.types.PhpType
import com.vk.modulite.Namespace
import com.vk.modulite.SymbolName
import com.vk.modulite.actions.dialogs.DepsRegenerationResultDialog
@@ -41,6 +28,7 @@ import com.vk.modulite.psi.extensions.php.symbolName
import com.vk.modulite.utils.fromKphpPolyfills
import com.vk.modulite.utils.fromStubs
import com.vk.modulite.utils.fromTests
+import java.util.*
class ModuliteDepsDiff(
private val project: Project,
@@ -201,6 +189,7 @@ class ModuliteDependenciesCollector(val project: Project) {
)
}
+ // тут запускается наш сборщик
fun collect(
dir: VirtualFile,
ownSymbols: List? = null,
@@ -216,7 +205,7 @@ class ModuliteDependenciesCollector(val project: Project) {
val moduliteConfig = dir.findChild(".modulite.yaml")
dir.forEachFilesEx(project) { file ->
- // Проверяем не отменил ои пользователь операцию.
+ // Проверяем не отменил ли пользователь операцию.
ProgressManager.checkCanceled()
if (isInnerModulite(file, moduliteConfig)) {
@@ -228,12 +217,25 @@ class ModuliteDependenciesCollector(val project: Project) {
psiFile.accept(object : PhpRecursiveElementVisitor() {
override fun visitPhpClassReference(reference: ClassReference) {
when (reference.context) {
- is PhpUse, is MethodReference, is ClassConstantReference -> {
+ is PhpUse -> {
+ /* if (reference is ClassReferenceImpl) {
+ handleReference(reference)
+ }*/
+
+ val useInstance = reference.context as PhpUseImpl
+ if (useInstance.isTraitImport) {
+ handleTraitReference(reference)
+ } else {
+ return
+ }
+ }
+
+ is MethodReference, is ClassConstantReference -> {
return
}
}
- handleReference(reference)
+ handleReference(reference)
}
override fun visitPhpFunctionCall(reference: FunctionReference) {
@@ -293,15 +295,268 @@ class ModuliteDependenciesCollector(val project: Project) {
}
}
- private fun handleReference(reference: PhpReference?, traverseFurther: Boolean = true) {
- if (reference == null) return
+ private fun referenceValidator(reference: PhpReference?): MutableCollection? {
+ if (reference == null)
+ return null
val references = reference.resolveElement()
if (references.isEmpty()) {
- LOG.warn("Unknown reference '${reference.safeFqn()}'")
- return
+ LOG.warn("Неизвестная ссылка '${reference.safeFqn()}'")
+ return null
+ }
+
+ return references
+ }
+
+ private fun handleTraitReference(reference: PhpReference?, traverseFurther: Boolean = true) {
+ data class TraitData(
+ val traitsClasses: MutableSet,
+ val requireMethods: MutableSet,
+ val requireConstants: MutableSet,
+ val functionsToRequire: MutableSet,
+ // val classesToRequire: MutableSet,
+ )
+
+ fun collectTraitElements(element: PsiElement): TraitData {
+ fun processParameters(parameters: Array, classesToRequire: MutableSet) {
+ parameters.forEach { parameter ->
+ if (!PhpType.isScalar(parameter.type, project)) {
+ var type = parameter.type.toString().substringAfterLast('\\')
+ if (type.endsWith("[]")) {
+ type = type.dropLast(2)
+ }
+ PhpIndex.getInstance(project).getClassByName(type)?.let { klass ->
+ classesToRequire.add(klass)
+ }
+ }
+ }
+ }
+
+ fun processArguments(
+ arguments: Array,
+ classesToRequire: MutableSet,
+ methodsToRequire: MutableSet,
+ constantsToRequire: MutableSet,
+ functionsToRequire: MutableSet
+ ) {
+ arguments.forEach { argument ->
+ when (argument) {
+ is FunctionReference -> {
+ val resolvedFunction = argument.resolve()
+ if (resolvedFunction is FunctionImpl) {
+ functionsToRequire.add(resolvedFunction)
+ }
+ }
+
+ is NewExpression -> {
+ argument.classReference?.resolve()?.let { resolvedClass ->
+ if (resolvedClass is PhpClass) {
+ classesToRequire.add(resolvedClass)
+ }
+ }
+ processArguments(
+ argument.parameters,
+ classesToRequire,
+ methodsToRequire,
+ constantsToRequire,
+ functionsToRequire
+ )
+ }
+
+ else -> {
+ val (nestedClasses, nestedMethods, nestedConstants, nestedFunctions) = collectTraitElements(
+ argument
+ )
+ classesToRequire.addAll(nestedClasses)
+ methodsToRequire.addAll(nestedMethods)
+ constantsToRequire.addAll(nestedConstants)
+ functionsToRequire.addAll(nestedFunctions)
+ }
+ }
+ }
+ }
+
+ val classesToRequire: MutableSet = mutableSetOf()
+ val methodsToRequire: MutableSet = mutableSetOf()
+ val constantsToRequire: MutableSet = mutableSetOf()
+ val functionsToRequire: MutableSet = 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)
+ processParameters(resolvedMethod.parameters, classesToRequire)
+ }
+ }
+
+ is ConstantReference -> {
+ val resolvedConstant = child.resolve()
+ if (resolvedConstant is PhpDefineImpl) {
+ constantsToRequire.add(resolvedConstant)
+ }
+ }
+
+ is NewExpression -> {
+ val classReference = child.classReference
+ val resolvedClass = classReference?.resolve()
+ if (resolvedClass is PhpClass) {
+ classesToRequire.add(resolvedClass)
+ }
+ processArguments(
+ child.parameters,
+ classesToRequire,
+ methodsToRequire,
+ constantsToRequire,
+ functionsToRequire
+ )
+ }
+
+ is FunctionReference -> {
+ val resolvedFunction = child.resolve()
+ if (resolvedFunction is FunctionImpl) {
+ functionsToRequire.add(resolvedFunction)
+ processParameters(resolvedFunction.parameters, classesToRequire)
+ }
+ }
+
+ is ParameterList -> {
+ processArguments(
+ child.parameters,
+ classesToRequire,
+ methodsToRequire,
+ constantsToRequire,
+ functionsToRequire
+ )
+ }
+
+ else -> {
+ val (nestedClasses, nestedMethods, nestedConstants, nestedFunctions) = collectTraitElements(
+ child
+ )
+ classesToRequire.addAll(nestedClasses)
+ methodsToRequire.addAll(nestedMethods)
+ constantsToRequire.addAll(nestedConstants)
+ functionsToRequire.addAll(nestedFunctions)
+ }
+ }
+
+ child = child.nextSibling
+ }
+
+ return TraitData(classesToRequire, methodsToRequire, constantsToRequire, functionsToRequire)
+ }
+
+ val references = referenceValidator(reference) ?: return
+ val methodsNames: MutableCollection = arrayListOf()
+
+
+ val traitsClasses = mutableListOf()
+ val requireMethods: MutableList = mutableListOf()
+ val requireConstants: MutableList = mutableListOf()
+ val requireFunctions: MutableList = mutableListOf()
+ val classesToRequire: MutableList = mutableListOf()
+
+ references.forEach { elem ->
+ val instance = elem as? PhpClass ?: return@forEach
+ val stack = LinkedList()
+ stack.push(instance) // Добавляем текущий instance в стек
+
+ val traitService = TraitIndexService.getInstance(project)
+ val seenMethods = mutableSetOf() // Для отслеживания методов, которые уже были обработаны
+
+ while (stack.isNotEmpty()) {
+ val currentInstance = stack.pop() // Получаем текущий instance из стека
+ if (!traitsClasses.contains(currentInstance)) {
+ traitsClasses.add(currentInstance)
+
+ // Получаем файл, содержащий этот класс
+ val virtualFile = currentInstance.containingFile?.virtualFile ?: continue
+
+ // Получаем трейты, связанные с этим файлом в порядке их использования
+ val traitsInOrder = traitService.getTraitsInFileOrder(virtualFile)
+
+ traitsInOrder.forEach { trait ->
+ if (!traitsClasses.contains(trait)) {
+ traitsClasses.add(trait)
+
+ // Рекурсивно обрабатываем вложенные трейты
+ val nestingTraits = traitService.getTraitsInFileOrder(trait.containingFile?.virtualFile ?: return@forEach)
+ nestingTraits.forEach { nestedTrait ->
+ if (!traitsClasses.contains(nestedTrait)) {
+ stack.push(nestedTrait)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Обрабатываем методы в порядке их использования, учитывая приоритет
+ val prioritizedMethods = mutableListOf()
+ traitsClasses.forEach { trait ->
+ //TODO: something wrong maybe here with priority (standard method on [0])
+ trait.methods.forEach { method ->
+ if (method.name !in seenMethods) {
+ seenMethods.add(method.name)
+ prioritizedMethods.add(method as MethodImpl)
+ }
+ }
+ }
+
+ // Используем только приоритетные методы
+ //TODO: recheck this one
+ prioritizedMethods.forEach { method ->
+ val (classes, methods, constants, functions) = collectTraitElements(method)
+
+ // Добавляем только те трейты и методы, которые используются
+ traitsClasses.addAll(classes)
+ requireMethods.addAll(methods)
+ requireConstants.addAll(constants)
+ requireFunctions.addAll(functions)
+ }
}
+ methodsNames.forEach { trait ->
+ val (classes, methods, constants, functions) = collectTraitElements(trait)
+
+ // Добавляем методы с учетом приоритета: если метод уже добавлен, новый игнорируется
+ methods.forEach { method ->
+ if (requireMethods.none { it.name == method.name }) {
+ requireMethods.add(method)
+ }
+ }
+
+ requireConstants.addAll(constants)
+ requireFunctions.addAll(functions)
+ classesToRequire.addAll(classes)
+ }
+
+ val traitsClassName = traitsClasses.distinct().mapNotNull { processElement(it, reference) }
+ val traitsMethodsName = requireMethods.distinct().mapNotNull { processElement(it, reference) }
+ val traitsConstantsName = requireConstants.distinct().mapNotNull { processElement(it, reference) }
+ val traitsFunctionsName = requireFunctions.distinct().mapNotNull { processElement(it, reference) }
+
+ addSymbols(traitsClassName)
+ addSymbols(traitsMethodsName)
+ addSymbols(traitsConstantsName)
+ addSymbols(traitsFunctionsName)
+
+ if (traverseFurther) {
+ super.visitPhpElement(reference)
+ }
+ }
+
+
+ private fun handleReference(reference: PhpReference?, traverseFurther: Boolean = true) {
+ val references = referenceValidator(reference)?:return
+
val names = references.mapNotNull { processElement(it, reference) }
addSymbols(names)
diff --git a/src/main/kotlin/com/vk/modulite/services/TraitIndex.kt b/src/main/kotlin/com/vk/modulite/services/TraitIndex.kt
new file mode 100644
index 0000000..527394b
--- /dev/null
+++ b/src/main/kotlin/com/vk/modulite/services/TraitIndex.kt
@@ -0,0 +1,86 @@
+package com.vk.modulite.services
+
+import com.intellij.openapi.components.Service
+import com.intellij.openapi.components.service
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.psi.PsiManager
+import com.intellij.psi.search.GlobalSearchScope
+import com.intellij.psi.util.PsiTreeUtil
+import com.intellij.util.indexing.FileBasedIndex
+import com.vk.modulite.index.TraitsIndex
+import com.jetbrains.php.lang.psi.PhpFile
+import com.jetbrains.php.lang.psi.elements.PhpClass
+import com.jetbrains.php.lang.psi.elements.PhpNamespace
+import com.jetbrains.php.lang.psi.elements.PhpReference
+import com.jetbrains.php.lang.psi.elements.PhpUse
+import com.jetbrains.php.lang.psi.elements.impl.PhpUseImpl
+
+@Service(Service.Level.PROJECT)
+class TraitIndexService(private val project: Project) {
+
+ companion object {
+ fun getInstance(project: Project): TraitIndexService {
+ return project.service()
+ }
+ }
+
+ fun findTraitsByName(name: String): List {
+ val allScope = GlobalSearchScope.allScope(project)
+ val traitFiles = FileBasedIndex.getInstance().getContainingFiles(TraitsIndex.KEY, name, allScope)
+ val traits = mutableListOf()
+
+ traitFiles.forEach { file ->
+ val psiFile = PsiManager.getInstance(project).findFile(file) as? PhpFile
+ psiFile?.let {
+ val classes = PsiTreeUtil.findChildrenOfType(it, PhpClass::class.java)
+ classes.filter { klass -> klass.isTrait && klass.fqn == name }
+ .forEach { trait -> traits.add(trait) }
+ }
+ }
+
+ return traits
+ }
+
+ fun getAllTraits(): List {
+ val allScope = GlobalSearchScope.allScope(project)
+ val allKeys = FileBasedIndex.getInstance().getAllKeys(TraitsIndex.KEY, project)
+ val traits = mutableListOf()
+
+ allKeys.forEach { key ->
+ traits.addAll(findTraitsByName(key))
+ }
+
+ return traits
+ }
+
+ fun findTraitByFile(file: VirtualFile): PhpClass? {
+ val psiFile = PsiManager.getInstance(project).findFile(file) as? PhpFile
+ psiFile?.let {
+ val classes = PsiTreeUtil.findChildrenOfType(it, PhpClass::class.java)
+ return classes.firstOrNull { klass -> klass.isTrait }
+ }
+ return null
+ }
+
+ fun getTraitsInFileOrder(file: VirtualFile): List {
+ val psiFile = PsiManager.getInstance(project).findFile(file) as? PhpFile ?: return emptyList()
+ val traits = mutableListOf()
+
+ // Находим все use-операторы в файле
+ val useStatements = PsiTreeUtil.findChildrenOfType(psiFile, PhpUse::class.java)
+
+ useStatements.forEach { useStatement ->
+ useStatement.children.forEach { child ->
+ if (child is PhpReference) {
+ val resolvedTrait = child.resolve() as? PhpClass
+ if (resolvedTrait != null && resolvedTrait.isTrait) {
+ traits.add(resolvedTrait)
+ }
+ }
+ }
+ }
+
+ return traits
+ }
+}
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 67e73dd..1765fef 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -149,6 +149,7 @@
+
diff --git a/src/test/fixtures/golden/TraitsNesting/Engines/FriendsEngineTrait.php b/src/test/fixtures/golden/TraitsNesting/Engines/FriendsEngineTrait.php
new file mode 100644
index 0000000..726826c
--- /dev/null
+++ b/src/test/fixtures/golden/TraitsNesting/Engines/FriendsEngineTrait.php
@@ -0,0 +1,14 @@
+