Skip to content

Commit 196124a

Browse files
committed
feat: add inspection for missing ActivityMethod attribute, consolidate code and improve documentation
1 parent bef9d08 commit 196124a

File tree

13 files changed

+114
-35
lines changed

13 files changed

+114
-35
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ When implementing a feature that should work across multiple languages (e.g., Ac
8383

8484
- **Kotlin First**: All new code should be written in Kotlin.
8585
- **Performance**: Use `CachedValue` and `DumbService.isDumb()` checks where appropriate. Use `lazyDumbAwareExtensions(project)` instead of `extensionList` when accessing Extension Points to ensure better performance and compatibility with dumb mode.
86+
- **PHP Utils**: For PHP support, use utility methods from `com.github.xepozz.temporal.languages.php.MixinKt` such as `isActivity()` and `isWorkflow()` on `PhpClass` and `Method` instead of manually checking attributes. Note that `Method.isActivity()` and `Method.isWorkflow()` are tolerant and return `true` for public methods even without explicit attributes if the containing class is an Activity or Workflow.
8687
- **Consistency**: Follow the existing package structure. For example, if a feature is implemented for PHP in `languages.php.navigation`, any future language implementations should follow the same sub-package structure (e.g., `languages.go.navigation`).
8788
- **Naming**:
8889
- Extension Points should **not** end with `EP`. They should represent the entity or feature (e.g., `ActivityCompletion`, `Workflow`).

src/main/kotlin/com/github/xepozz/temporal/languages/php/TemporalTypeProvider.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,9 @@ class TemporalTypeProvider : PhpTypeProvider4 {
3030
element.resolveGlobal(true)
3131
.mapNotNull { it as? Method }
3232
.mapNotNull {
33-
val containingClass = it.containingClass ?: return@mapNotNull null
34-
3533
return@mapNotNull when {
36-
containingClass.hasAttribute(TemporalClasses.ACTIVITY) -> it
37-
containingClass.hasAttribute(TemporalClasses.WORKFLOW) -> it
34+
it.isActivity() -> it
35+
it.isWorkflow() -> it
3836
else -> null
3937
}
4038
},

src/main/kotlin/com/github/xepozz/temporal/languages/php/endpoints/PhpActivity.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.github.xepozz.temporal.languages.php.endpoints
33
import com.github.xepozz.temporal.common.extensionPoints.Activity
44
import com.github.xepozz.temporal.languages.php.index.PhpActivityClassIndex
55
import com.github.xepozz.temporal.languages.php.index.PhpActivityMethodIndex
6+
import com.github.xepozz.temporal.languages.php.isActivity
67
import com.intellij.openapi.project.Project
78
import com.intellij.psi.SmartPointerManager
89
import com.intellij.util.indexing.FileBasedIndex
@@ -22,7 +23,7 @@ class PhpActivity : Activity {
2223
}, project)
2324

2425
classFqns.forEach { fqn ->
25-
phpIndex.getClassesByFQN(fqn).forEach { phpClass ->
26+
phpIndex.getClassesByFQN(fqn).filter { it.isActivity() }.forEach { phpClass ->
2627
results.add(
2728
ActivityModel(
2829
id = phpClass.name,
@@ -46,7 +47,7 @@ class PhpActivity : Activity {
4647
val classFqn = parts[0]
4748
val methodName = parts[1]
4849
phpIndex.getClassesByFQN(classFqn).forEach { phpClass ->
49-
phpClass.methods.find { it.name == methodName }?.let { method ->
50+
phpClass.methods.find { it.name == methodName && it.isActivity() }?.let { method ->
5051
results.add(
5152
ActivityModel(
5253
id = "$methodName (${phpClass.name})",

src/main/kotlin/com/github/xepozz/temporal/languages/php/endpoints/PhpWorkflow.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.github.xepozz.temporal.languages.php.endpoints
33
import com.github.xepozz.temporal.common.extensionPoints.Workflow
44
import com.github.xepozz.temporal.languages.php.index.PhpWorkflowClassIndex
55
import com.github.xepozz.temporal.languages.php.index.PhpWorkflowMethodIndex
6+
import com.github.xepozz.temporal.languages.php.isWorkflow
67
import com.intellij.openapi.project.Project
78
import com.intellij.psi.SmartPointerManager
89
import com.intellij.util.indexing.FileBasedIndex
@@ -22,7 +23,7 @@ class PhpWorkflow : Workflow {
2223
}, project)
2324

2425
classFqns.forEach { fqn ->
25-
phpIndex.getClassesByFQN(fqn).forEach { phpClass ->
26+
phpIndex.getClassesByFQN(fqn).filter { it.isWorkflow() }.forEach { phpClass ->
2627
results.add(
2728
WorkflowModel(
2829
id = phpClass.name,
@@ -46,7 +47,7 @@ class PhpWorkflow : Workflow {
4647
val classFqn = parts[0]
4748
val methodName = parts[1]
4849
phpIndex.getClassesByFQN(classFqn).forEach { phpClass ->
49-
phpClass.methods.find { it.name == methodName }?.let { method ->
50+
phpClass.methods.find { it.name == methodName && it.isWorkflow() }?.let { method ->
5051
results.add(
5152
WorkflowModel(
5253
id = "$methodName (${phpClass.name})",

src/main/kotlin/com/github/xepozz/temporal/languages/php/index/PhpActivityClassIndex.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package com.github.xepozz.temporal.languages.php.index
22

33
import com.github.xepozz.temporal.common.index.AbstractIndex
4-
import com.github.xepozz.temporal.languages.php.TemporalClasses
5-
import com.github.xepozz.temporal.languages.php.hasAttribute
4+
import com.github.xepozz.temporal.languages.php.isActivity
65
import com.intellij.psi.util.PsiTreeUtil
76
import com.intellij.util.indexing.DataIndexer
87
import com.intellij.util.indexing.FileBasedIndex
@@ -27,7 +26,7 @@ class PhpActivityClassIndex : AbstractIndex<String>() {
2726
val result = mutableMapOf<String, String>()
2827
val classes = PsiTreeUtil.findChildrenOfType(phpFile, PhpClass::class.java)
2928
for (phpClass in classes) {
30-
if (phpClass.hasAttribute(TemporalClasses.ACTIVITY)) {
29+
if (phpClass.isActivity()) {
3130
val fqn = phpClass.fqn
3231
result[fqn] = fqn
3332
}

src/main/kotlin/com/github/xepozz/temporal/languages/php/index/PhpActivityMethodIndex.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package com.github.xepozz.temporal.languages.php.index
22

33
import com.github.xepozz.temporal.common.index.AbstractIndex
4-
import com.github.xepozz.temporal.languages.php.TemporalClasses
5-
import com.github.xepozz.temporal.languages.php.hasAttribute
4+
import com.github.xepozz.temporal.languages.php.isActivity
65
import com.intellij.psi.util.PsiTreeUtil
76
import com.intellij.util.indexing.DataIndexer
87
import com.intellij.util.indexing.FileBasedIndex
@@ -28,9 +27,9 @@ class PhpActivityMethodIndex : AbstractIndex<String>() {
2827
val classes = PsiTreeUtil.findChildrenOfType(phpFile, PhpClass::class.java)
2928
for (phpClass in classes) {
3029
val classFqn = phpClass.fqn
31-
for (method in phpClass.methods) {
32-
if (method.hasAttribute(TemporalClasses.ACTIVITY_METHOD)) {
33-
result["$classFqn::${method.name}"] = TemporalClasses.ACTIVITY_METHOD
30+
for (method in phpClass.ownMethods) {
31+
if (method.isActivity()) {
32+
result["$classFqn::${method.name}"] = ""
3433
}
3534
}
3635
}

src/main/kotlin/com/github/xepozz/temporal/languages/php/index/PhpWorkflowClassIndex.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package com.github.xepozz.temporal.languages.php.index
22

33
import com.github.xepozz.temporal.common.index.AbstractIndex
4-
import com.github.xepozz.temporal.languages.php.TemporalClasses
5-
import com.github.xepozz.temporal.languages.php.hasAttribute
4+
import com.github.xepozz.temporal.languages.php.isWorkflow
65
import com.intellij.psi.util.PsiTreeUtil
76
import com.intellij.util.indexing.DataIndexer
87
import com.intellij.util.indexing.FileBasedIndex
@@ -27,7 +26,7 @@ class PhpWorkflowClassIndex : AbstractIndex<String>() {
2726
val result = mutableMapOf<String, String>()
2827
val classes = PsiTreeUtil.findChildrenOfType(phpFile, PhpClass::class.java)
2928
for (phpClass in classes) {
30-
if (phpClass.hasAttribute(TemporalClasses.WORKFLOW)) {
29+
if (phpClass.isWorkflow()) {
3130
val fqn = phpClass.fqn
3231
result[fqn] = fqn
3332
}

src/main/kotlin/com/github/xepozz/temporal/languages/php/index/PhpWorkflowMethodIndex.kt

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package com.github.xepozz.temporal.languages.php.index
22

33
import com.github.xepozz.temporal.common.index.AbstractIndex
4-
import com.github.xepozz.temporal.languages.php.TemporalClasses
5-
import com.github.xepozz.temporal.languages.php.hasAttribute
4+
import com.github.xepozz.temporal.languages.php.isWorkflow
65
import com.intellij.psi.util.PsiTreeUtil
76
import com.intellij.util.indexing.DataIndexer
87
import com.intellij.util.indexing.FileBasedIndex
@@ -17,14 +16,6 @@ import com.jetbrains.php.lang.psi.elements.PhpClass
1716
class PhpWorkflowMethodIndex : AbstractIndex<String>() {
1817
companion object {
1918
val NAME = ID.create<String, String>("temporal.workflow.methods")
20-
val ATTRIBUTES = listOf(
21-
TemporalClasses.WORKFLOW_METHOD,
22-
TemporalClasses.WORKFLOW_INIT,
23-
TemporalClasses.UPDATE_VALIDATOR_METHOD,
24-
TemporalClasses.UPDATE_METHOD,
25-
TemporalClasses.SIGNAL_METHOD,
26-
TemporalClasses.QUERY_METHOD
27-
)
2819
}
2920

3021
override fun getName(): ID<String, String> = NAME
@@ -36,12 +27,9 @@ class PhpWorkflowMethodIndex : AbstractIndex<String>() {
3627
val classes = PsiTreeUtil.findChildrenOfType(phpFile, PhpClass::class.java)
3728
for (phpClass in classes) {
3829
val classFqn = phpClass.fqn
39-
for (method in phpClass.methods) {
40-
for (attribute in ATTRIBUTES) {
41-
if (method.hasAttribute(attribute)) {
42-
result["$classFqn::${method.name}"] = attribute
43-
break
44-
}
30+
for (method in phpClass.ownMethods) {
31+
if (method.isWorkflow()) {
32+
result["$classFqn::${method.name}"] = ""
4533
}
4634
}
4735
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.github.xepozz.temporal.languages.php.inspections
2+
3+
import com.github.xepozz.temporal.TemporalBundle
4+
import com.github.xepozz.temporal.languages.php.TemporalClasses
5+
import com.github.xepozz.temporal.languages.php.hasAttribute
6+
import com.github.xepozz.temporal.languages.php.isActivity
7+
import com.intellij.codeInspection.LocalQuickFix
8+
import com.intellij.codeInspection.ProblemDescriptor
9+
import com.intellij.codeInspection.ProblemsHolder
10+
import com.intellij.openapi.project.Project
11+
import com.intellij.psi.PsiElementVisitor
12+
import com.intellij.psi.codeStyle.CodeStyleManager
13+
import com.jetbrains.php.lang.inspections.PhpInspection
14+
import com.jetbrains.php.lang.psi.PhpPsiElementFactory
15+
import com.jetbrains.php.lang.psi.elements.Method
16+
import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor
17+
18+
class PhpActivityMethodInspection : PhpInspection() {
19+
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
20+
return object : PhpElementVisitor() {
21+
override fun visitPhpMethod(method: Method) {
22+
if (!method.isActivity()) return
23+
if (method.hasAttribute(TemporalClasses.ACTIVITY_METHOD)) return
24+
25+
holder.registerProblem(
26+
method.nameIdentifier ?: method,
27+
TemporalBundle.message("inspection.php.activity.method.attribute.missing.problem.description"),
28+
AddActivityMethodAttributeQuickFix()
29+
)
30+
}
31+
}
32+
}
33+
34+
private class AddActivityMethodAttributeQuickFix : LocalQuickFix {
35+
override fun getFamilyName(): String {
36+
return TemporalBundle.message("inspection.php.activity.method.attribute.missing.quick.fix")
37+
}
38+
39+
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
40+
val element = descriptor.psiElement
41+
val method = (element as? Method ?: element.parent) as? Method ?: return
42+
43+
val newAttributesList = PhpPsiElementFactory.createAttributesList(project, TemporalClasses.ACTIVITY_METHOD)
44+
val anchor = method.firstChild
45+
method.addBefore(newAttributesList, anchor)
46+
47+
CodeStyleManager.getInstance(project).reformat(method)
48+
}
49+
}
50+
}

src/main/kotlin/com/github/xepozz/temporal/languages/php/mixin.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.github.xepozz.temporal.languages.php
33
import com.intellij.openapi.util.TextRange
44
import com.intellij.psi.ElementManipulators
55
import com.intellij.psi.PsiLanguageInjectionHost
6+
import com.jetbrains.php.lang.psi.elements.Method
67
import com.jetbrains.php.lang.psi.elements.PhpAttributesOwner
78
import com.jetbrains.php.lang.psi.elements.PhpClass
89

@@ -15,3 +16,24 @@ val PsiLanguageInjectionHost.contentRange: TextRange
1516

1617
//fun PhpReference.getSignatures(): Collection<String> = signature.split('|')
1718
//fun PhpReference.hasSignature(signatureToFind: String): Boolean = getSignatures().any { it==signatureToFind }
19+
20+
fun PhpClass.isActivity(): Boolean = hasAttribute(TemporalClasses.ACTIVITY)
21+
fun PhpClass.isWorkflow(): Boolean = hasAttribute(TemporalClasses.WORKFLOW)
22+
23+
fun Method.isActivity(): Boolean = when {
24+
isStatic -> false
25+
modifier.isAbstract -> false
26+
!modifier.isPublic -> false
27+
name.startsWith("__") -> false
28+
containingClass?.isActivity() != true -> false
29+
else -> true
30+
}
31+
32+
fun Method.isWorkflow(): Boolean = when {
33+
isStatic -> false
34+
modifier.isAbstract -> false
35+
!modifier.isPublic -> false
36+
name.startsWith("__") -> false
37+
containingClass?.isWorkflow() != true -> false
38+
else -> true
39+
}

0 commit comments

Comments
 (0)