Skip to content

Commit

Permalink
Better resolve if method belongs to a class factory, so things like `…
Browse files Browse the repository at this point in the history
…Class::factory()->make(['prop'])` would work.
  • Loading branch information
ekvedaras committed Feb 16, 2023
1 parent 92bbdf8 commit 8fe84a2
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 23 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# class-factory-phpstorm Changelog

## [Unreleased]
### Fixed
- Better resolve if method belongs to a class factory, so things like `Class::factory()->make(['prop'])` would work.

## [2.0.3]
### Fixed
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pluginGroup=com.github.ekvedaras.classfactoryphpstorm
pluginName=class-factory-phpstorm
# SemVer format -> https://semver.org
pluginVersion=2.0.3
pluginVersion=2.0.4
# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild=222
pluginUntilBuild=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.github.ekvedaras.classfactoryphpstorm.domain.ClassFactoryMethodRefere
import com.github.ekvedaras.classfactoryphpstorm.support.DomainException
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.getActualClassReference
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.getClass
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.getClassFactoryClass
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.isClassFactoryMakeMethod
import com.intellij.psi.util.childrenOfType
import com.jetbrains.php.lang.psi.elements.ArrayCreationExpression
Expand All @@ -17,8 +18,7 @@ class MakeMethodReference(private val methodReference: MethodReference) : ClassF
init {
if (!methodReference.isClassFactoryMakeMethod()) throw MakeMethodReferenceException.notMakeMethodReference()
classFactory = ClassFactory(
methodReference.getActualClassReference()?.getClass()
?: throw MakeMethodReferenceException.unableToFindMethodClass()
methodReference.getClassFactoryClass() ?: throw MakeMethodReferenceException.unableToFindMethodClass()
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.github.ekvedaras.classfactoryphpstorm.integration.otherMethods.type.A
import com.github.ekvedaras.classfactoryphpstorm.support.DomainException
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.getActualClassReference
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.getClass
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.getClassFactoryClass
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.isClassFactory
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.isClassFactoryStateMethod
import com.intellij.psi.util.childrenOfType
Expand All @@ -27,8 +28,7 @@ class StateMethodReferenceOutsideFactory(private val methodReference: MethodRefe
if (!methodReference.isClassFactoryStateMethod()) throw StateMethodReferenceOutsideFactoryException.notStateMethodReference()

ClassFactory(
methodReference.getActualClassReference()?.getClass()
?: throw StateMethodReferenceOutsideFactoryException.unableToFindMethodClass()
methodReference.getClassFactoryClass() ?: throw StateMethodReferenceOutsideFactoryException.unableToFindMethodClass()
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.github.ekvedaras.classfactoryphpstorm.domain.method.state.StateMethod
import com.github.ekvedaras.classfactoryphpstorm.support.DomainException
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.getActualClassReference
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.getClass
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.getClassFactoryClass
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.includes
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.isArrayHashValueOf
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.isClassFactory
Expand Down Expand Up @@ -69,11 +70,10 @@ class IncorrectPropertyTypeInspection : PhpInspection() {

// TODO There must be a better way
val classFactoryUsed =
factoryValue is MethodReference && factoryValue.getActualClassReference()?.getClass()
?.isClassFactory() == true
factoryValue is MethodReference && factoryValue.getClassFactoryClass() != null

if ((classFactoryUsed && ClassFactory(
((factoryValue as MethodReference).getActualClassReference() ?: return).getClass() ?: return
(factoryValue as MethodReference).getClassFactoryClass() ?: return
).targetClass.type != property.type) || (!classFactoryUsed && property.type.includes(factoryValue.type.unwrapClosureValue(expression.project), expression.project))
) {
holder.registerProblem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,27 @@ import com.github.ekvedaras.classfactoryphpstorm.domain.ClassFactory.Companion.a
import com.github.ekvedaras.classfactoryphpstorm.domain.closureState.AttributeAccess
import com.github.ekvedaras.classfactoryphpstorm.integration.otherMethods.type.AttributesArrayValueTypeProvider
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.classFactoryTargetOrSelf
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.isClassFactoryMakeMethod
import com.github.ekvedaras.classfactoryphpstorm.support.Utilities.Companion.unwrapClosureValue
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.util.childrenOfType
import com.intellij.psi.util.parentOfType
import com.jetbrains.php.PhpIndex
import com.jetbrains.php.lang.psi.elements.ArrayAccessExpression
import com.jetbrains.php.lang.psi.elements.ArrayHashElement
import com.jetbrains.php.lang.psi.elements.ClassReference
import com.jetbrains.php.lang.psi.elements.*
import com.jetbrains.php.lang.psi.elements.Function
import com.jetbrains.php.lang.psi.elements.GroupStatement
import com.jetbrains.php.lang.psi.elements.Method
import com.jetbrains.php.lang.psi.elements.MethodReference
import com.jetbrains.php.lang.psi.elements.PhpClass
import com.jetbrains.php.lang.psi.elements.PhpExpression
import com.jetbrains.php.lang.psi.elements.PhpReturn
import com.jetbrains.php.lang.psi.elements.Variable
import com.jetbrains.php.lang.psi.resolve.types.PhpType

private const val CLASS_FACTORY_FQN = "\\EKvedaras\\ClassFactory\\ClassFactory"
private const val CLOSURE_VALUE_FQN = "\\EKvedaras\\ClassFactory\\ClosureValue"

class Utilities private constructor() {
companion object {
fun String.unquoteAndCleanup() = this.replace("IntellijIdeaRulezzz", "").trim('\'', '"').trim()

fun PhpClass.isClassFactory() =
(this.extendsList.lastChild is ClassReference) && (this.extendsList.lastChild as ClassReference).fqn == "\\EKvedaras\\ClassFactory\\ClassFactory"
(this.extendsList.lastChild is ClassReference) && (this.extendsList.lastChild as ClassReference).fqn == CLASS_FACTORY_FQN

// TODO There must be a better way
fun PhpType.getFirstClass(project: Project) =
Expand All @@ -39,7 +35,7 @@ class Utilities private constructor() {

fun PhpType.classFactoryTargetOrSelf(project: Project) = this.asClassFactory(project)?.targetClass?.type ?: this

fun PhpType.unwrapClosureValue(project: Project): PhpType = if (this.getFirstClass(project)?.fqn == "\\EKvedaras\\ClassFactory\\ClosureValue") {
fun PhpType.unwrapClosureValue(project: Project): PhpType = if (this.getFirstClass(project)?.fqn == CLOSURE_VALUE_FQN) {
PhpType.CLOSURE
} else {
this
Expand Down Expand Up @@ -92,14 +88,34 @@ class Utilities private constructor() {
this.name == "state" && this.getActualClassReference()?.fqn?.endsWith("Factory") == true

fun MethodReference.isClassFactoryMakeMethod() =
this.name == "make" && this.getActualClassReference()?.getClass()?.isClassFactory() ?: false
this.name == "make" && this.isMemberOfAny(CLASS_FACTORY_FQN)

fun MethodReference.getClassFactoryClass(): PhpClass? =
if (DumbService.isDumb(project)) null
else if (classReference !is PhpTypedElement) null
else PhpIndex.getInstance(project)
.completeType(project, (classReference as PhpTypedElement).type, mutableSetOf())
.types
.flatMap { PhpIndex.getInstance(project).getClassesByFQN(it) }
.firstOrNull { it.isChildOfAny(CLASS_FACTORY_FQN, orIsAny = false) }

fun MethodReference.isMemberOfAny(vararg classes: String): Boolean =
if (DumbService.isDumb(project)) false
else if (classReference !is PhpTypedElement) false
else PhpIndex.getInstance(project)
.completeType(project, (classReference as PhpTypedElement).type, mutableSetOf())
.types
.flatMap { PhpIndex.getInstance(project).getClassesByFQN(it) }
.firstOrNull { it.isChildOfAny(*classes, orIsAny = false) } != null

fun PhpClass.isChildOfAny(vararg superFqn: String, orIsAny: Boolean = false): Boolean = superFqn.contains(superFQN) || (orIsAny && superFqn.contains(fqn)) || superClass?.isChildOfAny(*superFqn, orIsAny = orIsAny) == true

fun MethodReference.isClassFactoryStateMethod() =
this.name == "state" && this.getActualClassReference()?.getClass()?.isClassFactory() ?: (
this.name == "state" && (this.isMemberOfAny(CLASS_FACTORY_FQN) || (
this.firstPsiChild is ArrayAccessExpression
&& AttributeAccess(this.firstPsiChild as ArrayAccessExpression)
.getCompleteType(AttributesArrayValueTypeProvider())
.isClassFactory(this.project)
.isClassFactory(this.project))
)

fun PsiElement.isArrayHashValueOf(arrayHashElement: ArrayHashElement) = this == arrayHashElement.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,11 @@ internal class MakeMethodFactoryTest : EssentialTestCase() {
// assertTrue(usage.element?.textRange?.endOffset == usage.navigationRange.endOffset + 1)
// }
}

fun testItCompletesWhenCreatedFromStaticFactoryMethodOnClass() {
myFixture.configureByFile("makeWhenBuiltFromStaticFactoryMethod.php")
myFixture.completeBasic()

assertCompletionContains("value")
}
}
25 changes: 25 additions & 0 deletions src/test/testData/make/makeWhenBuiltFromStaticFactoryMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

class Age {
public function __construct(
public readonly int $value,
) {}

public static function factory(): AgeFactory
{
return AgeFactory::new();
}
}

class AgeFactory extends EKvedaras\ClassFactory\ClassFactory {
protected string $class = Age::class;

protected function definition(): array
{
return [
'value' => 1,
];
}
}

Age::factory()->make(['<caret>']);

0 comments on commit 8fe84a2

Please sign in to comment.