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 @@ +