Skip to content

Commit 372ae93

Browse files
authored
introduce an intention to add @kphp-immutable-class annotation (#67)
1 parent 8b6092e commit 372ae93

27 files changed

Lines changed: 329 additions & 1 deletion

src/main/kotlin/com/vk/kphpstorm/inspections/helpers/PhpDocPsiBuilder.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ import com.jetbrains.php.lang.psi.PhpPsiElementFactory
1313
import com.jetbrains.php.lang.psi.elements.Field
1414
import com.jetbrains.php.lang.psi.elements.Function
1515
import com.jetbrains.php.lang.psi.elements.Parameter
16+
import com.jetbrains.php.lang.psi.elements.PhpClass
1617
import com.jetbrains.php.lang.psi.elements.PhpNamedElement
1718
import com.vk.kphpstorm.helpers.parentDocComment
19+
import com.vk.kphpstorm.kphptags.KphpDocTag
1820
import com.vk.kphpstorm.kphptags.KphpSerializedFieldDocTag
1921

2022
/**
@@ -68,6 +70,13 @@ object PhpDocPsiBuilder {
6870
return field.docComment!!.addTag(project, "@var", "type")
6971
}
7072

73+
fun addTagToClass(klass: PhpClass, annotation: KphpDocTag): PhpDocTag {
74+
val project = klass.project
75+
val docComment = klass.docComment ?: createDocComment(project, klass)
76+
77+
return docComment.transformToMultiline(project).addTag(project, annotation.nameWithAt)
78+
}
79+
7180

7281
/**
7382
* Create empty phpdoc and insert it before function/class/field
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.vk.kphpstorm.intentions
2+
3+
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction
4+
import com.intellij.openapi.editor.Editor
5+
import com.intellij.openapi.project.Project
6+
import com.intellij.psi.PsiElement
7+
import com.intellij.psi.search.LocalSearchScope
8+
import com.intellij.psi.search.searches.ReferencesSearch
9+
import com.intellij.psi.util.findParentInFile
10+
import com.jetbrains.php.lang.psi.elements.AssignmentExpression
11+
import com.jetbrains.php.lang.psi.elements.PhpClass
12+
import com.vk.kphpstorm.inspections.helpers.PhpDocPsiBuilder
13+
import com.vk.kphpstorm.kphptags.KphpImmutableClassDocTag
14+
15+
class AddImmutableClassAnnotationIntention : PsiElementBaseIntentionAction() {
16+
override fun getText(): String = "Add @kphp-immutable-class"
17+
override fun getFamilyName(): String = getText()
18+
19+
override fun isAvailable(project: Project, editor: Editor?, element: PsiElement): Boolean {
20+
if (!element.isClassNameNode()) {
21+
return false
22+
}
23+
24+
val klass = element.parent as PhpClass
25+
if (klass.isAbstract || klass.isInterface || klass.isTrait || klass.isAnonymous) {
26+
return false
27+
}
28+
29+
val klassDocNode = klass.docComment
30+
31+
// do not suggest if already present
32+
if (klassDocNode != null && KphpImmutableClassDocTag.existsInDocComment(klassDocNode)) {
33+
return false
34+
}
35+
36+
return !isClassLocallyImmutable(klass)
37+
}
38+
39+
/**
40+
* Simple local class mutability check. If there is any field mutation in class,
41+
* the class is mutable. The only exception is the class constructor
42+
*/
43+
private fun isClassLocallyImmutable(klass: PhpClass): Boolean {
44+
val searchScope = LocalSearchScope(klass)
45+
for (field in klass.fields) {
46+
val hasAnyMutation = ReferencesSearch.search(field, searchScope).any { ref ->
47+
val element = ref.element
48+
49+
isMutatingOp(element) && !isInClassConstructor(klass, element)
50+
}
51+
52+
if (hasAnyMutation) {
53+
return true
54+
}
55+
}
56+
57+
return false
58+
}
59+
60+
private fun isMutatingOp(psiElement: PsiElement): Boolean {
61+
val parent = psiElement.parent
62+
63+
return parent is AssignmentExpression && parent.variable == psiElement
64+
}
65+
66+
private fun isInClassConstructor(klass: PhpClass, psiElement: PsiElement): Boolean {
67+
val classConstructor = klass.constructor
68+
return classConstructor != null && psiElement.findParentInFile { e -> e == classConstructor } != null
69+
}
70+
71+
override fun invoke(project: Project, editor: Editor?, element: PsiElement) {
72+
val klass = element.parent as PhpClass
73+
PhpDocPsiBuilder.addTagToClass(klass, KphpImmutableClassDocTag)
74+
}
75+
76+
private fun PsiElement.isClassNameNode(): Boolean {
77+
val klass = this.parent as? PhpClass ?: return false
78+
return klass.nameIdentifier == this
79+
}
80+
}

src/main/resources/META-INF/plugin.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,12 @@
7777
<language>PHP</language>
7878
<className>com.vk.kphpstorm.intentions.PrettifyPhpdocBlockIntention</className>
7979
<category>PHP</category>
80-
<descriptionDirectoryName>PrettifyPhpdocBlockIntention</descriptionDirectoryName>
80+
</intentionAction>
81+
82+
<intentionAction>
83+
<language>PHP</language>
84+
<className>com.vk.kphpstorm.intentions.AddImmutableClassAnnotationIntention</className>
85+
<category>PHP</category>
8186
</intentionAction>
8287

8388
<additionalTextAttributes scheme="Default" file="colorSchemes/KphpAddonsDefault.xml"/>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* My favorite class
3+
* <spot>@kphp-immutable-class</spot>
4+
*/
5+
class MyClass {
6+
// ...
7+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/**
2+
* My favorite class
3+
*/
4+
class MyClass {
5+
// ...
6+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add <i>@kphp-immutable-class annotation</i> to the class
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
class <caret>C1 {
4+
// ...
5+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
/**
4+
* @kphp-immutable-class
5+
*/
6+
class C1 {
7+
// ...
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
/**
4+
* @kphp-serializable
5+
* @kphp-color slow-ignore
6+
*/
7+
class <caret>C1 { }
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
/**
4+
* @kphp-immutable-class
5+
* @kphp-serializable
6+
* @kphp-color slow-ignore
7+
*/
8+
class <caret>C1 { }

0 commit comments

Comments
 (0)