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 feature #32

Closed
wants to merge 17 commits into from
Closed
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>
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