generated from JetBrains/intellij-platform-plugin-template
-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #27 from olivernybroe/closure-to-arrow
Closure to arrow function
- Loading branch information
Showing
20 changed files
with
343 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,38 @@ | ||
package dev.nybroe.collector | ||
|
||
import com.jetbrains.php.lang.psi.elements.Function | ||
import com.jetbrains.php.lang.psi.elements.FunctionReference | ||
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.PhpReference | ||
import com.jetbrains.php.lang.psi.elements.impl.FunctionImpl | ||
import com.jetbrains.php.lang.psi.resolve.types.PhpType | ||
|
||
fun FunctionReference.isGlobalFunctionCallWithName(name: String): Boolean { | ||
return this.name == name | ||
} | ||
|
||
fun PhpClass.isCollectionClass(): Boolean { | ||
return this.fqn == "\\Illuminate\\Support\\Collection" | ||
} | ||
val PhpClass.isCollectionClass: Boolean | ||
get() = this.fqn == "\\Illuminate\\Support\\Collection" | ||
|
||
fun Method.isCollectionMethod(): Boolean { | ||
return this.containingClass?.isCollectionClass() ?: false | ||
} | ||
val Method.isCollectionMethod: Boolean | ||
get() = this.containingClass?.isCollectionClass ?: false | ||
|
||
fun MethodReference.isCollectionMethod(): Boolean { | ||
return (this.resolve() as? Method)?.isCollectionMethod() ?: false | ||
} | ||
val MethodReference.isCollectionMethod: Boolean | ||
get() = (this.resolve() as? Method)?.isCollectionMethod ?: false | ||
|
||
val Function.returnsCollection: Boolean | ||
get() = this.type.global(this.project).isCollection | ||
|
||
val FunctionReference.returnsCollection: Boolean | ||
get() = (this.resolve() as? Function)?.returnsCollection ?: false | ||
|
||
val PhpReference.isCollectionType: Boolean | ||
get() = this.type.global(this.project).types.contains("\\Illuminate\\Support\\Collection") | ||
get() = this.type.global(this.project).isCollection | ||
|
||
val Function.isShortArrowFunction: Boolean | ||
get() = FunctionImpl.isShortArrowFunction(this) | ||
|
||
val PhpType.isCollection: Boolean | ||
get() = this.types.contains("\\Illuminate\\Support\\Collection") |
85 changes: 85 additions & 0 deletions
85
src/main/kotlin/dev/nybroe/collector/inspections/ClosureToArrowFunctionInspection.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package dev.nybroe.collector.inspections | ||
|
||
import com.intellij.codeInspection.ProblemHighlightType | ||
import com.intellij.codeInspection.ProblemsHolder | ||
import com.intellij.psi.PsiElement | ||
import com.intellij.psi.PsiElementVisitor | ||
import com.intellij.psi.util.elementType | ||
import com.jetbrains.php.config.PhpLanguageFeature | ||
import com.jetbrains.php.lang.inspections.PhpInspection | ||
import com.jetbrains.php.lang.lexer.PhpTokenTypes | ||
import com.jetbrains.php.lang.psi.PhpPsiUtil | ||
import com.jetbrains.php.lang.psi.elements.Function | ||
import com.jetbrains.php.lang.psi.elements.FunctionReference | ||
import com.jetbrains.php.lang.psi.elements.GroupStatement | ||
import com.jetbrains.php.lang.psi.elements.ParameterList | ||
import com.jetbrains.php.lang.psi.elements.PhpUseList | ||
import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor | ||
import dev.nybroe.collector.MyBundle | ||
import dev.nybroe.collector.isShortArrowFunction | ||
import dev.nybroe.collector.quickFixes.ClosureToArrowFunctionQuickFix | ||
import dev.nybroe.collector.returnsCollection | ||
|
||
class ClosureToArrowFunctionInspection : PhpInspection() { | ||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { | ||
return object : PhpElementVisitor() { | ||
override fun visitPhpFunction(closure: Function) { | ||
// Check that the PHP version supports arrow functions | ||
if (!PhpLanguageFeature.ARROW_FUNCTION_SYNTAX.isSupported(closure.project)) { | ||
return | ||
} | ||
|
||
// Make sure we are in a closure | ||
if (!closure.isClosure) { | ||
return | ||
} | ||
|
||
// And not already using arrow functions | ||
if (closure.isShortArrowFunction) { | ||
return | ||
} | ||
|
||
// And no use arguments are by reference | ||
if (useByReferenceExists(closure)) { | ||
return | ||
} | ||
|
||
// And closure is inside a collection. | ||
val methodReference = PhpPsiUtil | ||
.getParentByCondition<ParameterList>(closure, ParameterList.INSTANCEOF)?.parent ?: return | ||
val functionReference = PhpPsiUtil | ||
.getChildByCondition<FunctionReference>(methodReference, FunctionReference.INSTANCEOF) ?: return | ||
if (!functionReference.returnsCollection) { | ||
return | ||
} | ||
|
||
// Get the closure body and make sure the size is 1 | ||
val body = PhpPsiUtil.getChildByCondition<GroupStatement>(closure, GroupStatement.INSTANCEOF) ?: return | ||
|
||
if (body.statements.size != 1) { | ||
return | ||
} | ||
|
||
// And cannot be done on echo call | ||
if (body.statements[0].firstChild.elementType === PhpTokenTypes.kwECHO) { | ||
return | ||
} | ||
|
||
holder.registerProblem( | ||
closure, | ||
MyBundle.message("closureToArrowFunctionDescription"), | ||
ProblemHighlightType.WEAK_WARNING, | ||
ClosureToArrowFunctionQuickFix() | ||
) | ||
} | ||
} | ||
} | ||
|
||
private fun useByReferenceExists(closure: Function): Boolean { | ||
val useList = PhpPsiUtil | ||
.getChildByCondition<PsiElement>(closure, PhpUseList.INSTANCEOF) as PhpUseList? ?: return false | ||
|
||
return useList.children | ||
.any { it.prevSibling.elementType === PhpTokenTypes.opBIT_AND } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
src/main/kotlin/dev/nybroe/collector/quickFixes/ClosureToArrowFunctionQuickFix.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package dev.nybroe.collector.quickFixes | ||
|
||
import com.intellij.codeInspection.LocalQuickFix | ||
import com.intellij.codeInspection.ProblemDescriptor | ||
import com.intellij.openapi.project.Project | ||
import com.intellij.psi.PsiElement | ||
import com.intellij.psi.util.elementType | ||
import com.jetbrains.php.lang.lexer.PhpTokenTypes | ||
import com.jetbrains.php.lang.psi.PhpPsiElementFactory | ||
import com.jetbrains.php.lang.psi.PhpPsiUtil | ||
import com.jetbrains.php.lang.psi.elements.Function | ||
import com.jetbrains.php.lang.psi.elements.GroupStatement | ||
import com.jetbrains.php.lang.psi.elements.PhpUseList | ||
|
||
class ClosureToArrowFunctionQuickFix : LocalQuickFix { | ||
companion object { | ||
const val QUICK_FIX_NAME = "Closure can be converted to arrow function" | ||
} | ||
|
||
override fun getFamilyName(): String { | ||
return QUICK_FIX_NAME | ||
} | ||
|
||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) { | ||
val closure = descriptor.psiElement as? Function ?: return | ||
|
||
val body = PhpPsiUtil.getChildByCondition<GroupStatement>(closure, GroupStatement.INSTANCEOF) ?: return | ||
|
||
// Replace `function` with `fn` | ||
val functionKeyword = closure.node.findChildByType(PhpTokenTypes.kwFUNCTION)?.psi ?: return | ||
functionKeyword.replace(PhpPsiElementFactory.createFromText(project, PhpTokenTypes.kwFN, "fn()")) | ||
|
||
// Replace body with dummy arrow function | ||
val arrow = body.replace(PhpPsiElementFactory.createFromText(project, PhpTokenTypes.opHASH_ARRAY, "fn() => 1")) | ||
|
||
// Add body | ||
arrow.parent.addAfter(copyBody(body), arrow) | ||
|
||
// Delete use list | ||
PhpPsiUtil.getChildByCondition<PhpUseList>(closure, PhpUseList.INSTANCEOF)?.delete() | ||
} | ||
|
||
private fun copyBody(body: GroupStatement): PsiElement { | ||
val statement = body.statements[0].copy() | ||
|
||
// Delete semi colon | ||
if (statement.lastChild.elementType === PhpTokenTypes.opSEMICOLON) { | ||
statement.lastChild.delete() | ||
} | ||
|
||
// Delete return keyword if exist | ||
if (statement.firstChild.elementType === PhpTokenTypes.kwRETURN) { | ||
statement.firstChild.delete() | ||
} | ||
|
||
return statement | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
46 changes: 46 additions & 0 deletions
46
src/test/kotlin/dev/nybroe/collector/inspections/ClosureToArrowFunctionInspectionTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package dev.nybroe.collector.inspections | ||
|
||
import com.intellij.codeInspection.InspectionProfileEntry | ||
import com.jetbrains.php.config.PhpLanguageLevel | ||
import com.jetbrains.php.config.PhpProjectConfigurationFacade | ||
import dev.nybroe.collector.quickFixes.ClosureToArrowFunctionQuickFix | ||
|
||
internal class ClosureToArrowFunctionInspectionTest : InspectionTest() { | ||
override fun defaultInspection(): InspectionProfileEntry = ClosureToArrowFunctionInspection() | ||
override fun defaultAction(): String = ClosureToArrowFunctionQuickFix.QUICK_FIX_NAME | ||
|
||
fun testEachFunctionCallToArrow() { | ||
PhpProjectConfigurationFacade.getInstance(project).languageLevel = PhpLanguageLevel.PHP740 | ||
doTest("each-function-call-to-arrow-function") | ||
} | ||
|
||
fun testEachFunctionCallToArrowWithUses() { | ||
PhpProjectConfigurationFacade.getInstance(project).languageLevel = PhpLanguageLevel.PHP740 | ||
doTest("each-function-call-to-arrow-function-with-uses") | ||
} | ||
|
||
fun testEachFunctionCallToArrowNotOnPHP730() { | ||
PhpProjectConfigurationFacade.getInstance(project).languageLevel = PhpLanguageLevel.PHP730 | ||
doNotMatchTest("each-function-call-to-arrow-function-php730") | ||
} | ||
|
||
fun testEachFunctionCallToArrowWithReturn() { | ||
PhpProjectConfigurationFacade.getInstance(project).languageLevel = PhpLanguageLevel.PHP740 | ||
doTest("each-function-call-to-arrow-function-with-return") | ||
} | ||
|
||
fun testEachFunctionCallToArrowNotOnUsesByReference() { | ||
PhpProjectConfigurationFacade.getInstance(project).languageLevel = PhpLanguageLevel.PHP740 | ||
doNotMatchTest("each-function-call-to-arrow-function-with-uses-by-reference") | ||
} | ||
|
||
fun testEachFunctionCallToArrowNotOnEchoCall() { | ||
PhpProjectConfigurationFacade.getInstance(project).languageLevel = PhpLanguageLevel.PHP740 | ||
doNotMatchTest("each-function-call-to-arrow-function-echo") | ||
} | ||
|
||
fun testFunctionCallToArrowFunctionNotOnNonCollectionFunctions() { | ||
PhpProjectConfigurationFacade.getInstance(project).languageLevel = PhpLanguageLevel.PHP740 | ||
doNotMatchTest("function-call-to-arrow-function-not-in-collection") | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
...osureToArrowFunctionInspection/each-function-call-to-arrow-function-with-return.after.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?php | ||
|
||
$array = [ | ||
'one', | ||
'two', | ||
'tree' | ||
]; | ||
|
||
collect($array)->each(fn($item) => doAction($item)); |
11 changes: 11 additions & 0 deletions
11
...ons/ClosureToArrowFunctionInspection/each-function-call-to-arrow-function-with-return.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
$array = [ | ||
'one', | ||
'two', | ||
'tree' | ||
]; | ||
|
||
collect($array)->each(<weak_warning descr="Closure can be converted to arrow function">function ($item) { | ||
return doAction($item); | ||
}</weak_warning>); |
11 changes: 11 additions & 0 deletions
11
...ClosureToArrowFunctionInspection/each-function-call-to-arrow-function-with-uses.after.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
$array = [ | ||
'one', | ||
'two', | ||
'tree' | ||
]; | ||
|
||
$name = 'one'; | ||
|
||
collect($array)->each(fn($item) => doAction($item, $name)); |
13 changes: 13 additions & 0 deletions
13
...tions/ClosureToArrowFunctionInspection/each-function-call-to-arrow-function-with-uses.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php | ||
|
||
$array = [ | ||
'one', | ||
'two', | ||
'tree' | ||
]; | ||
|
||
$name = 'one'; | ||
|
||
collect($array)->each(<weak_warning descr="Closure can be converted to arrow function">function ($item) use ($name) { | ||
doAction($item, $name); | ||
}</weak_warning>); |
9 changes: 9 additions & 0 deletions
9
...spections/ClosureToArrowFunctionInspection/each-function-call-to-arrow-function.after.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?php | ||
|
||
$array = [ | ||
'one', | ||
'two', | ||
'tree' | ||
]; | ||
|
||
collect($array)->each(fn($item) => doAction($item)); |
11 changes: 11 additions & 0 deletions
11
...ces/inspections/ClosureToArrowFunctionInspection/each-function-call-to-arrow-function.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
$array = [ | ||
'one', | ||
'two', | ||
'tree' | ||
]; | ||
|
||
collect($array)->each(<weak_warning descr="Closure can be converted to arrow function">function ($item) { | ||
doAction($item); | ||
}</weak_warning>); |
11 changes: 11 additions & 0 deletions
11
...ClosureToArrowFunctionInspection/nonMatches/each-function-call-to-arrow-function-echo.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
$array = [ | ||
'one', | ||
'two', | ||
'tree' | ||
]; | ||
|
||
collect($array)->each(function ($item) { | ||
echo doAction($item); | ||
}); |
Oops, something went wrong.