From ab8634144db2bd70acac08217a2e71af028e236d Mon Sep 17 00:00:00 2001 From: vldf Date: Wed, 23 Jul 2025 12:56:49 +0300 Subject: [PATCH 01/11] Introduced the AddImmutableClassAnnotationIntention --- .../inspections/helpers/PhpDocPsiBuilder.kt | 9 ++++ .../AddImmutableClassAnnotationIntention.kt | 46 +++++++++++++++++++ src/main/resources/META-INF/plugin.xml | 7 ++- .../after.php.template | 7 +++ .../before.php.template | 6 +++ .../description.html | 1 + .../immutable_class_intention-1.fixture.php | 5 ++ .../immutable_class_intention-1.qf.php | 8 ++++ ...mmutable_class_intention-2.nointention.php | 8 ++++ ...mmutable_class_intention-3.nointention.php | 7 +++ ...mmutable_class_intention-4.nointention.php | 6 +++ .../infrastructure/IntentionTestBase.kt | 15 ++++++ .../tests/AddImmutableAnnoIntentionTest.kt | 22 +++++++++ 13 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt create mode 100644 src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/after.php.template create mode 100644 src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/before.php.template create mode 100644 src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/description.html create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-1.fixture.php create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-1.qf.php create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-2.nointention.php create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-3.nointention.php create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-4.nointention.php create mode 100644 src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt diff --git a/src/main/kotlin/com/vk/kphpstorm/inspections/helpers/PhpDocPsiBuilder.kt b/src/main/kotlin/com/vk/kphpstorm/inspections/helpers/PhpDocPsiBuilder.kt index 8d77eece..efc99c6b 100644 --- a/src/main/kotlin/com/vk/kphpstorm/inspections/helpers/PhpDocPsiBuilder.kt +++ b/src/main/kotlin/com/vk/kphpstorm/inspections/helpers/PhpDocPsiBuilder.kt @@ -13,8 +13,10 @@ import com.jetbrains.php.lang.psi.PhpPsiElementFactory import com.jetbrains.php.lang.psi.elements.Field import com.jetbrains.php.lang.psi.elements.Function import com.jetbrains.php.lang.psi.elements.Parameter +import com.jetbrains.php.lang.psi.elements.PhpClass import com.jetbrains.php.lang.psi.elements.PhpNamedElement import com.vk.kphpstorm.helpers.parentDocComment +import com.vk.kphpstorm.kphptags.KphpDocTag import com.vk.kphpstorm.kphptags.KphpSerializedFieldDocTag /** @@ -68,6 +70,13 @@ object PhpDocPsiBuilder { return field.docComment!!.addTag(project, "@var", "type") } + fun addTagToClass(klass: PhpClass, annotation: KphpDocTag): PhpDocTag { + val project = klass.project + val docComment = klass.docComment ?: createDocComment(project, klass) + + return docComment.transformToMultiline(project).addTag(project, annotation.nameWithAt) + } + /** * Create empty phpdoc and insert it before function/class/field diff --git a/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt b/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt new file mode 100644 index 00000000..0ea4709b --- /dev/null +++ b/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt @@ -0,0 +1,46 @@ +package com.vk.kphpstorm.intentions + +import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction +import com.intellij.codeInspection.util.IntentionFamilyName +import com.intellij.codeInspection.util.IntentionName +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.jetbrains.php.lang.psi.elements.PhpClass +import com.vk.kphpstorm.inspections.helpers.PhpDocPsiBuilder +import com.vk.kphpstorm.kphptags.KphpImmutableClassDocTag + +class AddImmutableClassAnnotationIntention : PsiElementBaseIntentionAction() { + override fun getText(): @IntentionName String = "Add @kphp-immutable-class" + override fun getFamilyName(): @IntentionFamilyName String = getText() + + override fun isAvailable( + project: Project, + editor: Editor?, + element: PsiElement + ): Boolean { + if (!element.isClassNameNode()) { + return false + } + + val klass = element.parent as PhpClass + val klassDocNode = klass.docComment ?: return true + + // do not suggest if already present + return !KphpImmutableClassDocTag.existsInDocComment(klassDocNode) + } + + override fun invoke( + project: Project, + editor: Editor?, + element: PsiElement + ) { + val klass = element.parent as PhpClass + PhpDocPsiBuilder.addTagToClass(klass, KphpImmutableClassDocTag) + } + + private fun PsiElement.isClassNameNode(): Boolean { + val klass = this.parent as? PhpClass ?: return false + return klass.nameIdentifier == this + } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index f455ec5a..a8184dca 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -77,7 +77,12 @@ PHP com.vk.kphpstorm.intentions.PrettifyPhpdocBlockIntention PHP - PrettifyPhpdocBlockIntention + + + + PHP + com.vk.kphpstorm.intentions.AddImmutableClassAnnotationIntention + PHP diff --git a/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/after.php.template b/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/after.php.template new file mode 100644 index 00000000..23a4a7a9 --- /dev/null +++ b/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/after.php.template @@ -0,0 +1,7 @@ +/** + * My favorite class + * @kphp-immutable-class + */ +class MyClass { + // ... +} \ No newline at end of file diff --git a/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/before.php.template b/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/before.php.template new file mode 100644 index 00000000..016a2668 --- /dev/null +++ b/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/before.php.template @@ -0,0 +1,6 @@ +/** + * My favorite class + */ +class MyClass { + // ... +} \ No newline at end of file diff --git a/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/description.html b/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/description.html new file mode 100644 index 00000000..27adb8d7 --- /dev/null +++ b/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/description.html @@ -0,0 +1 @@ +Add @kphp-immutable-class annotation to the class \ No newline at end of file diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-1.fixture.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-1.fixture.php new file mode 100644 index 00000000..85a48864 --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-1.fixture.php @@ -0,0 +1,5 @@ +C1 { + // ... +} diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-1.qf.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-1.qf.php new file mode 100644 index 00000000..8d2e25bf --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-1.qf.php @@ -0,0 +1,8 @@ +C1 { + // ... +} diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-3.nointention.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-3.nointention.php new file mode 100644 index 00000000..d17bc32a --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-3.nointention.php @@ -0,0 +1,7 @@ + + // ... +} diff --git a/src/test/kotlin/com/vk/kphpstorm/testing/infrastructure/IntentionTestBase.kt b/src/test/kotlin/com/vk/kphpstorm/testing/infrastructure/IntentionTestBase.kt index 0a5ecc88..cc39a302 100644 --- a/src/test/kotlin/com/vk/kphpstorm/testing/infrastructure/IntentionTestBase.kt +++ b/src/test/kotlin/com/vk/kphpstorm/testing/infrastructure/IntentionTestBase.kt @@ -34,4 +34,19 @@ abstract class IntentionTestBase( val qfFile = fixtureFile.replace(".fixture.php", ".qf.php") myFixture.checkResultByFile(qfFile) } + + /** + * Assert there are no intention [intentionToExecute] in file [fixtureFile] + */ + protected fun assertNoIntention(fixtureFile: String) { + setupLanguageLevel() + + KphpStormConfiguration.saveThatSetupForProjectDone(project) + myFixture.configureByFile(fixtureFile) + val availableIntentions = myFixture + .availableIntentions + .filter { intentionAction -> intentionAction.familyName == intentionToExecute.familyName } + + assertEmpty(availableIntentions) + } } diff --git a/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt b/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt new file mode 100644 index 00000000..231f0f38 --- /dev/null +++ b/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt @@ -0,0 +1,22 @@ +package com.vk.kphpstorm.testing.tests + +import com.vk.kphpstorm.intentions.AddImmutableClassAnnotationIntention +import com.vk.kphpstorm.testing.infrastructure.IntentionTestBase + +class AddImmutableAnnoIntentionTest : IntentionTestBase(AddImmutableClassAnnotationIntention()) { + fun testAddImmutableAnno1() { + runIntention("kphp_intentions/immutable_class_intention-1.fixture.php") + } + + fun testAddImmutableAnno2() { + assertNoIntention("kphp_intentions/immutable_class_intention-2.nointention.php") + } + + fun testAddImmutableAnno3() { + assertNoIntention("kphp_intentions/immutable_class_intention-3.nointention.php") + } + + fun testAddImmutableAnno4() { + assertNoIntention("kphp_intentions/immutable_class_intention-4.nointention.php") + } +} \ No newline at end of file From f19903ae96f8f824c3f3992618fdafabbda2c689 Mon Sep 17 00:00:00 2001 From: vldf Date: Wed, 23 Jul 2025 15:08:38 +0300 Subject: [PATCH 02/11] Added missing trailing empty lines --- .../intentions/AddImmutableClassAnnotationIntention.kt | 2 +- .../AddImmutableClassAnnotationIntention/after.php.template | 2 +- .../AddImmutableClassAnnotationIntention/before.php.template | 2 +- .../AddImmutableClassAnnotationIntention/description.html | 2 +- .../kphp_intentions/immutable_class_intention-3.nointention.php | 2 +- .../vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt b/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt index 0ea4709b..59dd6dc2 100644 --- a/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt +++ b/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt @@ -43,4 +43,4 @@ class AddImmutableClassAnnotationIntention : PsiElementBaseIntentionAction() { val klass = this.parent as? PhpClass ?: return false return klass.nameIdentifier == this } -} \ No newline at end of file +} diff --git a/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/after.php.template b/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/after.php.template index 23a4a7a9..732a8060 100644 --- a/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/after.php.template +++ b/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/after.php.template @@ -4,4 +4,4 @@ */ class MyClass { // ... -} \ No newline at end of file +} diff --git a/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/before.php.template b/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/before.php.template index 016a2668..b73f8840 100644 --- a/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/before.php.template +++ b/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/before.php.template @@ -3,4 +3,4 @@ */ class MyClass { // ... -} \ No newline at end of file +} diff --git a/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/description.html b/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/description.html index 27adb8d7..4b59262a 100644 --- a/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/description.html +++ b/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/description.html @@ -1 +1 @@ -Add @kphp-immutable-class annotation to the class \ No newline at end of file +Add @kphp-immutable-class annotation to the class diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-3.nointention.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-3.nointention.php index d17bc32a..a82d9777 100644 --- a/src/test/fixtures/kphp_intentions/immutable_class_intention-3.nointention.php +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-3.nointention.php @@ -4,4 +4,4 @@ class C1 extends C2 { // ... } -class C2 {} \ No newline at end of file +class C2 {} diff --git a/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt b/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt index 231f0f38..a85ad5bf 100644 --- a/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt +++ b/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt @@ -19,4 +19,4 @@ class AddImmutableAnnoIntentionTest : IntentionTestBase(AddImmutableClassAnnotat fun testAddImmutableAnno4() { assertNoIntention("kphp_intentions/immutable_class_intention-4.nointention.php") } -} \ No newline at end of file +} From abd478fd37491dde4b02507924acc5adc08e3419 Mon Sep 17 00:00:00 2001 From: vldf Date: Wed, 23 Jul 2025 17:20:15 +0300 Subject: [PATCH 03/11] Add some local mutability analysis to hide the intention when class is 100% mutable --- .../AddImmutableClassAnnotationIntention.kt | 44 ++++++++++++++++++- ...mmutable_class_intention-5.nointention.php | 9 ++++ .../immutable_class_intention-6.fixture.php | 10 +++++ .../immutable_class_intention-6.qf.php | 13 ++++++ .../immutable_class_intention-7.fixture.php | 11 +++++ .../immutable_class_intention-7.qf.php | 14 ++++++ .../tests/AddImmutableAnnoIntentionTest.kt | 12 +++++ 7 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-5.nointention.php create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-6.fixture.php create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-6.qf.php create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-7.fixture.php create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-7.qf.php diff --git a/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt b/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt index 59dd6dc2..ef21de2d 100644 --- a/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt +++ b/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt @@ -6,6 +6,10 @@ import com.intellij.codeInspection.util.IntentionName import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement +import com.intellij.psi.search.LocalSearchScope +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.psi.util.findParentInFile +import com.jetbrains.php.lang.psi.elements.AssignmentExpression import com.jetbrains.php.lang.psi.elements.PhpClass import com.vk.kphpstorm.inspections.helpers.PhpDocPsiBuilder import com.vk.kphpstorm.kphptags.KphpImmutableClassDocTag @@ -24,10 +28,46 @@ class AddImmutableClassAnnotationIntention : PsiElementBaseIntentionAction() { } val klass = element.parent as PhpClass - val klassDocNode = klass.docComment ?: return true + val klassDocNode = klass.docComment // do not suggest if already present - return !KphpImmutableClassDocTag.existsInDocComment(klassDocNode) + if (klassDocNode != null && KphpImmutableClassDocTag.existsInDocComment(klassDocNode)) { + return false + } + + return !isClassLocallyImmutable(klass) + } + + /** + * Simple local class mutability check. If there is any field mutation in class, + * the class is mutable. The only exception is the class constructor + */ + private fun isClassLocallyImmutable(klass: PhpClass): Boolean { + val searchScope = LocalSearchScope(klass) + for (field in klass.fields) { + val hasAnyMutation = ReferencesSearch.search(field, searchScope).any { ref -> + val element = ref.element + + isMutatingOp(element) && !isInClassConstructor(klass, element) + } + + if (hasAnyMutation) { + return true + } + } + + return false + } + + private fun isMutatingOp(psiElement: PsiElement): Boolean { + val parent = psiElement.parent + + return parent is AssignmentExpression + } + + private fun isInClassConstructor(klass: PhpClass, psiElement: PsiElement): Boolean { + val classConstructor = klass.constructor + return classConstructor != null && psiElement.findParentInFile { e -> e == classConstructor } != null } override fun invoke( diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-5.nointention.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-5.nointention.php new file mode 100644 index 00000000..541c5712 --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-5.nointention.php @@ -0,0 +1,9 @@ +C1 { + public int $field; + + public function foo() { + $this->field = 1; // mutate the field + } +} diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-6.fixture.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-6.fixture.php new file mode 100644 index 00000000..db93a28d --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-6.fixture.php @@ -0,0 +1,10 @@ +C1 { + public int $field; + + public function __construct(int $arg) + { + $this->field = $arg; // field mutation in constructor, that's ok + } +} diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-6.qf.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-6.qf.php new file mode 100644 index 00000000..7a3a88d3 --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-6.qf.php @@ -0,0 +1,13 @@ +C1 { + public int $field; + + public function __construct(int $arg) + { + $this->field = $arg; // field mutation in constructor, that's ok + } +} diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-7.fixture.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-7.fixture.php new file mode 100644 index 00000000..6033b029 --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-7.fixture.php @@ -0,0 +1,11 @@ +C1 { + public int $field; +} + +public function foo() +{ + $v = new C1(); + $v->field = 1; // mutation outside of a class, that's ok +} diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-7.qf.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-7.qf.php new file mode 100644 index 00000000..0eab696d --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-7.qf.php @@ -0,0 +1,14 @@ +C1 { + public int $field; +} + +public function foo() +{ + $v = new C1(); + $v->field = 1; // mutation outside of a class, that's ok +} diff --git a/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt b/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt index a85ad5bf..86bb86c7 100644 --- a/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt +++ b/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt @@ -19,4 +19,16 @@ class AddImmutableAnnoIntentionTest : IntentionTestBase(AddImmutableClassAnnotat fun testAddImmutableAnno4() { assertNoIntention("kphp_intentions/immutable_class_intention-4.nointention.php") } + + fun testAddImmutableAnno5() { + assertNoIntention("kphp_intentions/immutable_class_intention-5.nointention.php") + } + + fun testAddImmutableAnno6() { + runIntention("kphp_intentions/immutable_class_intention-6.fixture.php") + } + + fun testAddImmutableAnno7() { + runIntention("kphp_intentions/immutable_class_intention-7.fixture.php") + } } From 3df73b93523b1986dd40ac626b27c1e4f82ef209 Mon Sep 17 00:00:00 2001 From: vldf Date: Thu, 24 Jul 2025 10:51:30 +0300 Subject: [PATCH 04/11] Add missing --- .../kphp_intentions/immutable_class_intention-3.nointention.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-3.nointention.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-3.nointention.php index a82d9777..fc153494 100644 --- a/src/test/fixtures/kphp_intentions/immutable_class_intention-3.nointention.php +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-3.nointention.php @@ -1,6 +1,6 @@ C2 { // ... } From 2da4b8ce44ef365817276d5909b84f11e0de8a02 Mon Sep 17 00:00:00 2001 From: vldf Date: Thu, 24 Jul 2025 10:59:31 +0300 Subject: [PATCH 05/11] Add to the new intention description --- .../AddImmutableClassAnnotationIntention/description.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/description.html b/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/description.html index 4b59262a..3efe6e20 100644 --- a/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/description.html +++ b/src/main/resources/intentionDescriptions/AddImmutableClassAnnotationIntention/description.html @@ -1 +1 @@ -Add @kphp-immutable-class annotation to the class +Add @kphp-immutable-class annotation to the class From 40f74b62e60e4cc3b2750eb39bc48ea01a4a051b Mon Sep 17 00:00:00 2001 From: vldf Date: Fri, 25 Jul 2025 10:55:30 +0300 Subject: [PATCH 06/11] code style fixes --- .../AddImmutableClassAnnotationIntention.kt | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt b/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt index ef21de2d..d4c94876 100644 --- a/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt +++ b/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt @@ -1,8 +1,6 @@ package com.vk.kphpstorm.intentions import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction -import com.intellij.codeInspection.util.IntentionFamilyName -import com.intellij.codeInspection.util.IntentionName import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement @@ -15,14 +13,10 @@ import com.vk.kphpstorm.inspections.helpers.PhpDocPsiBuilder import com.vk.kphpstorm.kphptags.KphpImmutableClassDocTag class AddImmutableClassAnnotationIntention : PsiElementBaseIntentionAction() { - override fun getText(): @IntentionName String = "Add @kphp-immutable-class" - override fun getFamilyName(): @IntentionFamilyName String = getText() + override fun getText(): String = "Add @kphp-immutable-class" + override fun getFamilyName(): String = getText() - override fun isAvailable( - project: Project, - editor: Editor?, - element: PsiElement - ): Boolean { + override fun isAvailable(project: Project, editor: Editor?, element: PsiElement): Boolean { if (!element.isClassNameNode()) { return false } @@ -70,11 +64,7 @@ class AddImmutableClassAnnotationIntention : PsiElementBaseIntentionAction() { return classConstructor != null && psiElement.findParentInFile { e -> e == classConstructor } != null } - override fun invoke( - project: Project, - editor: Editor?, - element: PsiElement - ) { + override fun invoke(project: Project, editor: Editor?, element: PsiElement) { val klass = element.parent as PhpClass PhpDocPsiBuilder.addTagToClass(klass, KphpImmutableClassDocTag) } From e066ed883753e0c41a21964da000f0af1253a8ef Mon Sep 17 00:00:00 2001 From: vldf Date: Fri, 25 Jul 2025 10:59:12 +0300 Subject: [PATCH 07/11] fix immutable_class_intention-7 --- .../kphp_intentions/immutable_class_intention-7.fixture.php | 2 +- .../fixtures/kphp_intentions/immutable_class_intention-7.qf.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-7.fixture.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-7.fixture.php index 6033b029..0b3dee26 100644 --- a/src/test/fixtures/kphp_intentions/immutable_class_intention-7.fixture.php +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-7.fixture.php @@ -4,7 +4,7 @@ class C1 { public int $field; } -public function foo() +function foo() { $v = new C1(); $v->field = 1; // mutation outside of a class, that's ok diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-7.qf.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-7.qf.php index 0eab696d..70116c8e 100644 --- a/src/test/fixtures/kphp_intentions/immutable_class_intention-7.qf.php +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-7.qf.php @@ -7,7 +7,7 @@ class C1 { public int $field; } -public function foo() +function foo() { $v = new C1(); $v->field = 1; // mutation outside of a class, that's ok From 81986391a8a00ce811c36aea9f2a28fa0453c47c Mon Sep 17 00:00:00 2001 From: vldf Date: Fri, 25 Jul 2025 11:35:27 +0300 Subject: [PATCH 08/11] add extra tests --- .../immutable_class_intention-10.fixture.php | 7 +++++++ .../immutable_class_intention-10.qf.php | 8 ++++++++ .../immutable_class_intention-8.fixture.php | 9 +++++++++ .../immutable_class_intention-8.qf.php | 12 ++++++++++++ .../immutable_class_intention-9.fixture.php | 4 ++++ .../immutable_class_intention-9.qf.php | 7 +++++++ .../testing/tests/AddImmutableAnnoIntentionTest.kt | 12 ++++++++++++ 7 files changed, 59 insertions(+) create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-10.fixture.php create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-10.qf.php create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-8.fixture.php create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-8.qf.php create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-9.fixture.php create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-9.qf.php diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-10.fixture.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-10.fixture.php new file mode 100644 index 00000000..1c3eede2 --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-10.fixture.php @@ -0,0 +1,7 @@ +C1 { } diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-10.qf.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-10.qf.php new file mode 100644 index 00000000..7b1edbb7 --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-10.qf.php @@ -0,0 +1,8 @@ +C1 { } diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-8.fixture.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-8.fixture.php new file mode 100644 index 00000000..57d70c26 --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-8.fixture.php @@ -0,0 +1,9 @@ +C1 { + public int $field = 1; + + public function foo() { + $tmp = $this->field; // no field mutation + } +} diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-8.qf.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-8.qf.php new file mode 100644 index 00000000..1d725fdc --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-8.qf.php @@ -0,0 +1,12 @@ +C1 { + public int $field = 1; + + public function foo() { + $tmp = $this->field; // no field mutation + } +} diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-9.fixture.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-9.fixture.php new file mode 100644 index 00000000..09bc0a4d --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-9.fixture.php @@ -0,0 +1,4 @@ +C1 { } diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-9.qf.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-9.qf.php new file mode 100644 index 00000000..2209f3df --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-9.qf.php @@ -0,0 +1,7 @@ +C1 { } diff --git a/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt b/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt index 86bb86c7..faf9b836 100644 --- a/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt +++ b/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt @@ -31,4 +31,16 @@ class AddImmutableAnnoIntentionTest : IntentionTestBase(AddImmutableClassAnnotat fun testAddImmutableAnno7() { runIntention("kphp_intentions/immutable_class_intention-7.fixture.php") } + + fun testAddImmutableAnno8() { + runIntention("kphp_intentions/immutable_class_intention-8.fixture.php") + } + + fun testAddImmutableAnno9() { + runIntention("kphp_intentions/immutable_class_intention-9.fixture.php") + } + + fun testAddImmutableAnno10() { + runIntention("kphp_intentions/immutable_class_intention-10.fixture.php") + } } From 5b21d951701a0c57c22a4f9701dc33a256a90a8f Mon Sep 17 00:00:00 2001 From: vldf Date: Fri, 25 Jul 2025 11:35:48 +0300 Subject: [PATCH 09/11] add review issue with lhs --- .../intentions/AddImmutableClassAnnotationIntention.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt b/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt index d4c94876..0e3897f8 100644 --- a/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt +++ b/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt @@ -56,7 +56,7 @@ class AddImmutableClassAnnotationIntention : PsiElementBaseIntentionAction() { private fun isMutatingOp(psiElement: PsiElement): Boolean { val parent = psiElement.parent - return parent is AssignmentExpression + return parent is AssignmentExpression && parent.variable == psiElement } private fun isInClassConstructor(klass: PhpClass, psiElement: PsiElement): Boolean { From dacb76846dc4718b7e813b7c69e196c7709beb4b Mon Sep 17 00:00:00 2001 From: vldf Date: Mon, 28 Jul 2025 10:48:52 +0300 Subject: [PATCH 10/11] do not show the intention on interfaces and abstract classes --- .../intentions/AddImmutableClassAnnotationIntention.kt | 4 ++++ .../immutable_class_intention-11.nointent.php | 3 +++ .../immutable_class_intention-12.nointent.php | 3 +++ .../testing/tests/AddImmutableAnnoIntentionTest.kt | 8 ++++++++ 4 files changed, 18 insertions(+) create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-11.nointent.php create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-12.nointent.php diff --git a/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt b/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt index 0e3897f8..df09c350 100644 --- a/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt +++ b/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt @@ -22,6 +22,10 @@ class AddImmutableClassAnnotationIntention : PsiElementBaseIntentionAction() { } val klass = element.parent as PhpClass + if (klass.isAbstract || klass.isInterface) { + return false + } + val klassDocNode = klass.docComment // do not suggest if already present diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-11.nointent.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-11.nointent.php new file mode 100644 index 00000000..de0d4de0 --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-11.nointent.php @@ -0,0 +1,3 @@ +C1 { } diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-12.nointent.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-12.nointent.php new file mode 100644 index 00000000..2d4abd6b --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-12.nointent.php @@ -0,0 +1,3 @@ +C1 { } diff --git a/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt b/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt index faf9b836..e2c3667f 100644 --- a/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt +++ b/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt @@ -43,4 +43,12 @@ class AddImmutableAnnoIntentionTest : IntentionTestBase(AddImmutableClassAnnotat fun testAddImmutableAnno10() { runIntention("kphp_intentions/immutable_class_intention-10.fixture.php") } + + fun testAddImmutableAnno11() { + assertNoIntention("kphp_intentions/immutable_class_intention-11.nointent.php") + } + + fun testAddImmutableAnno12() { + assertNoIntention("kphp_intentions/immutable_class_intention-12.nointent.php") + } } From 60c53c6b769002b04f7ccb8438e9d3dab6e7ba03 Mon Sep 17 00:00:00 2001 From: vldf Date: Mon, 28 Jul 2025 16:52:46 +0300 Subject: [PATCH 11/11] disable the intention on traits and anon classes --- .../intentions/AddImmutableClassAnnotationIntention.kt | 2 +- .../kphp_intentions/immutable_class_intention-13.nointent.php | 3 +++ .../kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 src/test/fixtures/kphp_intentions/immutable_class_intention-13.nointent.php diff --git a/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt b/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt index df09c350..472c3771 100644 --- a/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt +++ b/src/main/kotlin/com/vk/kphpstorm/intentions/AddImmutableClassAnnotationIntention.kt @@ -22,7 +22,7 @@ class AddImmutableClassAnnotationIntention : PsiElementBaseIntentionAction() { } val klass = element.parent as PhpClass - if (klass.isAbstract || klass.isInterface) { + if (klass.isAbstract || klass.isInterface || klass.isTrait || klass.isAnonymous) { return false } diff --git a/src/test/fixtures/kphp_intentions/immutable_class_intention-13.nointent.php b/src/test/fixtures/kphp_intentions/immutable_class_intention-13.nointent.php new file mode 100644 index 00000000..725e5487 --- /dev/null +++ b/src/test/fixtures/kphp_intentions/immutable_class_intention-13.nointent.php @@ -0,0 +1,3 @@ +C1 { } diff --git a/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt b/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt index e2c3667f..c25888a5 100644 --- a/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt +++ b/src/test/kotlin/com/vk/kphpstorm/testing/tests/AddImmutableAnnoIntentionTest.kt @@ -51,4 +51,8 @@ class AddImmutableAnnoIntentionTest : IntentionTestBase(AddImmutableClassAnnotat fun testAddImmutableAnno12() { assertNoIntention("kphp_intentions/immutable_class_intention-12.nointent.php") } + + fun testAddImmutableAnno13() { + assertNoIntention("kphp_intentions/immutable_class_intention-13.nointent.php") + } }