-
Notifications
You must be signed in to change notification settings - Fork 506
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add experimental rule to lint/format the spacing after the type param…
…eter list in a function signature (#1366) Rule op-spacing should not handle characters like '<', '>' and '*' when not used as operator. This logic is moved to the TypeParameterListSpacingRule and new rule TypeArgumentListSpacingRule. This rule is required to create a rule which can rewrite the function signature automatically as is described in #1341
- Loading branch information
1 parent
2098cff
commit 57e0cbe
Showing
13 changed files
with
712 additions
and
53 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
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
117 changes: 117 additions & 0 deletions
117
.../src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/TypeArgumentListSpacingRule.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,117 @@ | ||
package com.pinterest.ktlint.ruleset.experimental | ||
|
||
import com.pinterest.ktlint.core.Rule | ||
import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION | ||
import com.pinterest.ktlint.core.ast.ElementType.GT | ||
import com.pinterest.ktlint.core.ast.ElementType.LAMBDA_ARGUMENT | ||
import com.pinterest.ktlint.core.ast.ElementType.LT | ||
import com.pinterest.ktlint.core.ast.ElementType.SUPER_EXPRESSION | ||
import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_LIST | ||
import com.pinterest.ktlint.core.ast.ElementType.TYPE_ARGUMENT_LIST | ||
import com.pinterest.ktlint.core.ast.ElementType.TYPE_REFERENCE | ||
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE | ||
import com.pinterest.ktlint.core.ast.findCompositeElementOfType | ||
import com.pinterest.ktlint.core.ast.isPartOfCompositeElementOfType | ||
import com.pinterest.ktlint.core.ast.nextLeaf | ||
import com.pinterest.ktlint.core.ast.nextSibling | ||
import com.pinterest.ktlint.core.ast.parent | ||
import com.pinterest.ktlint.core.ast.prevLeaf | ||
import com.pinterest.ktlint.core.ast.prevSibling | ||
import org.jetbrains.kotlin.com.intellij.lang.ASTNode | ||
|
||
/** | ||
* Lints and formats the spacing before and after the angle brackets of a type argument list. | ||
*/ | ||
public class TypeArgumentListSpacingRule : Rule("type-argument-list-spacing") { | ||
override fun visit( | ||
node: ASTNode, | ||
autoCorrect: Boolean, | ||
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit | ||
) { | ||
when (node.elementType) { | ||
TYPE_ARGUMENT_LIST -> { | ||
visitFunctionDeclaration(node, autoCorrect, emit) | ||
visitInsideTypeArgumentList(node, autoCorrect, emit) | ||
} | ||
SUPER_TYPE_LIST, SUPER_EXPRESSION -> | ||
visitInsideTypeArgumentList(node, autoCorrect, emit) | ||
} | ||
} | ||
|
||
private fun visitFunctionDeclaration( | ||
node: ASTNode, | ||
autoCorrect: Boolean, | ||
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit | ||
) { | ||
// No whitespace expected before type argument list of function call | ||
// val list = listOf <String>() | ||
node | ||
.prevLeaf(includeEmpty = true) | ||
?.takeIf { it.elementType == WHITE_SPACE } | ||
?.let { noWhitespaceExpected(it, autoCorrect, emit) } | ||
|
||
// No whitespace expected after type argument list of function call | ||
// val list = listOf<String> () | ||
node | ||
.takeUnless { | ||
// unless it is part of a type reference: | ||
// fun foo(): List<Foo> { ... } | ||
// var bar: List<Bar> = emptyList() | ||
it.isPartOfCompositeElementOfType(TYPE_REFERENCE) | ||
} | ||
?.takeUnless { | ||
// unless it is part of a call expression followed by lambda: | ||
// bar<Foo> { ... } | ||
it.isPartOfCallExpressionFolledByLambda() | ||
} | ||
?.lastChildNode | ||
?.nextLeaf(includeEmpty = true) | ||
?.takeIf { it.elementType == WHITE_SPACE } | ||
?.let { noWhitespaceExpected(it, autoCorrect, emit) } | ||
} | ||
|
||
private fun visitInsideTypeArgumentList( | ||
node: ASTNode, | ||
autoCorrect: Boolean, | ||
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit | ||
) { | ||
// No whitespace expected after opening angle bracket of type argument list | ||
// val list = listOf< String>() | ||
node | ||
.findChildByType(LT) | ||
?.nextSibling { true } | ||
?.takeIf { it.elementType == WHITE_SPACE } | ||
?.let { noWhitespaceExpected(it, autoCorrect, emit) } | ||
|
||
// No whitespace expected before closing angle bracket of type argument list | ||
// val list = listOf<String >() | ||
node | ||
.findChildByType(GT) | ||
?.prevSibling { true } | ||
?.takeIf { it.elementType == WHITE_SPACE } | ||
?.let { noWhitespaceExpected(it, autoCorrect, emit) } | ||
} | ||
|
||
private fun noWhitespaceExpected( | ||
node: ASTNode, | ||
autoCorrect: Boolean, | ||
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit | ||
) { | ||
if (node.text != "") { | ||
emit( | ||
node.startOffset, | ||
"No whitespace expected at this position", | ||
true | ||
) | ||
if (autoCorrect) { | ||
node.treeParent.removeChild(node) | ||
} | ||
} | ||
} | ||
} | ||
|
||
private fun ASTNode.isPartOfCallExpressionFolledByLambda(): Boolean = | ||
parent(findCompositeElementOfType(CALL_EXPRESSION)) | ||
?.takeIf { it.elementType == CALL_EXPRESSION } | ||
?.findChildByType(LAMBDA_ARGUMENT) | ||
.let { it != null } |
176 changes: 176 additions & 0 deletions
176
...src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/TypeParameterListSpacingRule.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,176 @@ | ||
package com.pinterest.ktlint.ruleset.experimental | ||
|
||
import com.pinterest.ktlint.core.Rule | ||
import com.pinterest.ktlint.core.ast.ElementType.CLASS | ||
import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY | ||
import com.pinterest.ktlint.core.ast.ElementType.CONSTRUCTOR_KEYWORD | ||
import com.pinterest.ktlint.core.ast.ElementType.GT | ||
import com.pinterest.ktlint.core.ast.ElementType.LT | ||
import com.pinterest.ktlint.core.ast.ElementType.PRIMARY_CONSTRUCTOR | ||
import com.pinterest.ktlint.core.ast.ElementType.TYPE_PARAMETER_LIST | ||
import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE | ||
import com.pinterest.ktlint.core.ast.nextCodeSibling | ||
import com.pinterest.ktlint.core.ast.nextLeaf | ||
import com.pinterest.ktlint.core.ast.nextSibling | ||
import com.pinterest.ktlint.core.ast.prevLeaf | ||
import com.pinterest.ktlint.core.ast.prevSibling | ||
import com.pinterest.ktlint.core.ast.upsertWhitespaceBeforeMe | ||
import org.jetbrains.kotlin.com.intellij.lang.ASTNode | ||
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement | ||
|
||
/** | ||
* Lints and formats the spacing before and after the angle brackets of a type parameter list. | ||
*/ | ||
public class TypeParameterListSpacingRule : Rule("type-parameter-list-spacing") { | ||
override fun visit( | ||
node: ASTNode, | ||
autoCorrect: Boolean, | ||
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit | ||
) { | ||
if (node.elementType != TYPE_PARAMETER_LIST) { | ||
return | ||
} | ||
|
||
if (node.treeParent.elementType == CLASS) { | ||
visitClassDeclaration(node, autoCorrect, emit) | ||
} else { | ||
visitFunctionDeclaration(node, autoCorrect, emit) | ||
} | ||
visitInsideTypeParameterList(node, autoCorrect, emit) | ||
} | ||
|
||
private fun visitClassDeclaration( | ||
node: ASTNode, | ||
autoCorrect: Boolean, | ||
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit | ||
) { | ||
// No white space expected between class name and parameter list | ||
// class Bar <T> | ||
node | ||
.prevSibling { true } | ||
?.takeIf { it.elementType == WHITE_SPACE } | ||
?.let { noWhitespaceExpected(it, autoCorrect, emit) } | ||
|
||
// No white space expected between parameter type list and the constructor except when followed by compound | ||
// constructor | ||
// class Bar<T> (...) | ||
node | ||
.nextSibling { true } | ||
?.takeIf { it.elementType == WHITE_SPACE && it.nextCodeSibling()?.elementType == PRIMARY_CONSTRUCTOR } | ||
?.let { whiteSpace -> | ||
if (whiteSpace.nextCodeSibling()?.findChildByType(CONSTRUCTOR_KEYWORD) != null) { | ||
// Single space expect before (modifier list of) constructor | ||
// class Bar<T> constructor(...) | ||
// class Bar<T> actual constructor(...) | ||
// class Bar<T> @SomeAnnotation constructor(...) | ||
singleSpaceExpected(whiteSpace, autoCorrect, emit) | ||
} else { | ||
noWhitespaceExpected(whiteSpace, autoCorrect, emit) | ||
} | ||
} | ||
|
||
// No white space expected between parameter type list and class body when constructor is missing | ||
// class Bar<T> { | ||
node | ||
.nextSibling { true } | ||
?.takeIf { it.elementType == WHITE_SPACE && it.nextCodeSibling()?.elementType == CLASS_BODY } | ||
?.let { singleSpaceExpected(it, autoCorrect, emit) } | ||
} | ||
|
||
private fun visitFunctionDeclaration( | ||
node: ASTNode, | ||
autoCorrect: Boolean, | ||
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit | ||
) { | ||
// Single space expected before type parameter list of function | ||
// fun<T> foo(...) | ||
node | ||
.prevLeaf(includeEmpty = true) | ||
?.let { prevLeaf -> | ||
if (prevLeaf.elementType == WHITE_SPACE) { | ||
singleSpaceExpected(prevLeaf, autoCorrect, emit) | ||
} else { | ||
singleSpaceExpected(node.firstChildNode, autoCorrect, emit) | ||
} | ||
} | ||
|
||
// Single space expected after type parameter list of function | ||
// fun <T>foo(...) | ||
// fun <T>List<T>foo(...) | ||
node | ||
.lastChildNode | ||
.nextLeaf(includeEmpty = true) | ||
?.let { nextSibling -> | ||
singleSpaceExpected(nextSibling, autoCorrect, emit) | ||
} | ||
} | ||
|
||
private fun visitInsideTypeParameterList( | ||
node: ASTNode, | ||
autoCorrect: Boolean, | ||
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit | ||
) { | ||
node | ||
.findChildByType(LT) | ||
?.nextSibling { true } | ||
?.takeIf { it.elementType == WHITE_SPACE } | ||
?.let { noWhitespaceExpected(it, autoCorrect, emit) } | ||
|
||
node | ||
.findChildByType(GT) | ||
?.prevSibling { true } | ||
?.takeIf { it.elementType == WHITE_SPACE } | ||
?.let { noWhitespaceExpected(it, autoCorrect, emit) } | ||
} | ||
|
||
private fun noWhitespaceExpected( | ||
node: ASTNode, | ||
autoCorrect: Boolean, | ||
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit | ||
) { | ||
if (node.text != "") { | ||
emit( | ||
node.startOffset, | ||
"No whitespace expected at this position", | ||
true | ||
) | ||
if (autoCorrect) { | ||
node.treeParent.removeChild(node) | ||
} | ||
} | ||
} | ||
|
||
private fun singleSpaceExpected( | ||
node: ASTNode, | ||
autoCorrect: Boolean, | ||
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit | ||
) { | ||
when { | ||
node.text == " " -> Unit | ||
node.textContains('\n') -> { | ||
emit( | ||
node.startOffset, | ||
"Expected a single space instead of newline", | ||
true | ||
) | ||
if (autoCorrect) { | ||
(node as LeafPsiElement).rawReplaceWithText(" ") | ||
} | ||
} | ||
else -> { | ||
emit( | ||
node.startOffset, | ||
"Expected a single space", | ||
true | ||
) | ||
if (autoCorrect) { | ||
if (node.elementType == WHITE_SPACE) { | ||
(node as LeafPsiElement).rawReplaceWithText(" ") | ||
} else { | ||
(node as LeafPsiElement).upsertWhitespaceBeforeMe(" ") | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.