Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Investigation of impact of kotlin 1.9 treeCopyHandler #2031

Closed
wants to merge 8 commits into from
7 changes: 7 additions & 0 deletions build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ plugins {

repositories {
mavenCentral()
// FIXME: DO NOT MERGE WITH MASTER. THIS IS SOLELY NEEDED FOR INVESTIGATION OF KOTLIN 1.9 IMPACT
// https://github.com/pinterest/ktlint/issues/1981
maven {
url = uri("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap")
artifactUrls("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap")
}
// END OF FIXME: DO NOT MERGE WITH MASTER. THIS IS SOLELY NEEDED FOR INVESTIGATION OF KOTLIN 1.9 IMPACT
}

dependencies {
Expand Down
5 changes: 4 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ shadow = "com.github.johnrengelman.shadow:8.1.1"
sdkman = "io.sdkman.vendors:3.0.0"

[libraries]
kotlin-compiler = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlin" }
# FIXME: DO NOT COMMIT THIS IS SOLELY NEEDED FOR INVESTIGATION OF KOTLIN 1.9 IMPACT (https://github.com/pinterest/ktlint/issues/1981)
kotlin-compiler = "org.jetbrains.kotlin:kotlin-compiler-embeddable:1.9.0-dev-6976"
#kotlin-compiler = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlin" }
# END OF FIXME: DO NOT MERGE WITH MASTER. THIS IS SOLELY NEEDED FOR INVESTIGATION OF KOTLIN 1.9 IMPACT
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-plugin-dev = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinDev" }
dokka = "org.jetbrains.dokka:dokka-gradle-plugin:1.8.10"
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.com.intellij.mock.MockProject
import org.jetbrains.kotlin.com.intellij.openapi.diagnostic.DefaultLogger
import org.jetbrains.kotlin.com.intellij.openapi.extensions.ExtensionPoint
import org.jetbrains.kotlin.com.intellij.openapi.extensions.Extensions.getRootArea
import org.jetbrains.kotlin.com.intellij.openapi.util.Disposer
import org.jetbrains.kotlin.com.intellij.openapi.util.UserDataHolderBase
import org.jetbrains.kotlin.com.intellij.pom.PomModel
Expand All @@ -17,7 +15,6 @@ import org.jetbrains.kotlin.com.intellij.pom.PomTransaction
import org.jetbrains.kotlin.com.intellij.pom.impl.PomTransactionBase
import org.jetbrains.kotlin.com.intellij.pom.tree.TreeAspect
import org.jetbrains.kotlin.com.intellij.psi.PsiFileFactory
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.TreeCopyHandler
import org.jetbrains.kotlin.config.CompilerConfiguration
import sun.reflect.ReflectionFactory
import java.nio.file.Files
Expand Down Expand Up @@ -114,14 +111,8 @@ private class LoggerFactory : DiagnosticLogger.Factory {
* Enables AST mutations (`ktlint -F ...`).
*/
private fun MockProject.enableASTMutations() {
val extensionPoint = "org.jetbrains.kotlin.com.intellij.treeCopyHandler"
val extensionClassName = TreeCopyHandler::class.java.name
for (area in arrayOf(extensionArea, getRootArea())) {
if (!area.hasExtensionPoint(extensionPoint)) {
area.registerExtensionPoint(extensionPoint, extensionClassName, ExtensionPoint.Kind.INTERFACE)
}
}

// FIXME: DO NOT MERGE WITH MASTER. THIS IS SOLELY NEEDED FOR INVESTIGATION OF KOTLIN 1.9 IMPACT
// https://github.com/pinterest/ktlint/issues/1981
registerService(PomModel::class.java, FormatPomModel())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ private class AutoCorrectErrorRule : Rule(
STRING_VALUE_TO_BE_AUTOCORRECTED -> {
emit(node.startOffset, ERROR_MESSAGE_CAN_BE_AUTOCORRECTED, true)
if (autoCorrect) {
(node as LeafElement).replaceWithText(STRING_VALUE_AFTER_AUTOCORRECT)
(node as LeafElement).rawReplaceWithText(STRING_VALUE_AFTER_AUTOCORRECT)
}
}
STRING_VALUE_NOT_TO_BE_CORRECTED ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import com.pinterest.ktlint.rule.engine.core.api.RuleId
import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment
import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace
import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline
import com.pinterest.ktlint.rule.engine.core.api.prevCodeSibling
import com.pinterest.ktlint.rule.engine.core.api.nextSibling
import com.pinterest.ktlint.rule.engine.core.api.prevSibling
import com.pinterest.ktlint.ruleset.standard.StandardRule
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
import org.jetbrains.kotlin.psi.psiUtil.siblings

public class NoLineBreakBeforeAssignmentRule : StandardRule("no-line-break-before-assignment") {
Expand All @@ -20,27 +20,46 @@ public class NoLineBreakBeforeAssignmentRule : StandardRule("no-line-break-befor
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
) {
if (node.elementType == EQ) {
val prevCodeSibling = node.prevCodeSibling()
val unexpectedLinebreak =
prevCodeSibling
?.siblings()
?.takeWhile { it.isWhiteSpace() || it.isPartOfComment() }
?.lastOrNull { it.isWhiteSpaceWithNewline() }
if (unexpectedLinebreak != null) {
emit(unexpectedLinebreak.startOffset, "Line break before assignment is not allowed", true)
visitEquals(node, emit, autoCorrect)
}
}

private fun visitEquals(
assignmentNode: ASTNode,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
autoCorrect: Boolean,
) {
assignmentNode
.prevSibling()
.takeIf { it.isWhiteSpaceWithNewline() }
?.let { unexpectedNewlineBeforeAssignment ->
emit(unexpectedNewlineBeforeAssignment.startOffset, "Line break before assignment is not allowed", true)
if (autoCorrect) {
val prevPsi = prevCodeSibling.psi
val parentPsi = prevPsi.parent
val psiFactory = KtPsiFactory(prevPsi)
if (prevPsi.nextSibling !is PsiWhiteSpace) {
parentPsi.addAfter(psiFactory.createWhiteSpace(), prevPsi)
}
parentPsi.addAfter(psiFactory.createEQ(), prevPsi)
parentPsi.addAfter(psiFactory.createWhiteSpace(), prevPsi)
(node as? LeafPsiElement)?.delete()
val parent = assignmentNode.treeParent
// Insert assigment surrounded by whitespaces at new position
assignmentNode
.siblings(false)
.takeWhile { it.isWhiteSpace() || it.isPartOfComment() }
.last()
.let { before ->
if (!before.prevSibling().isWhiteSpace()) {
parent.addChild(PsiWhiteSpaceImpl(" "), before)
}
parent.addChild(LeafPsiElement(EQ, "="), before)
if (!before.isWhiteSpace()) {
parent.addChild(PsiWhiteSpaceImpl(" "), before)
}
}
// Cleanup old assignment and whitespace after it. The indent before the old assignment is kept unchanged
assignmentNode
.nextSibling()
.takeIf { it.isWhiteSpace() }
?.let { whiteSpaceAfterEquals ->
parent.removeChild(whiteSpaceAfterEquals)
}
parent.removeChild(assignmentNode)
}
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public class NoTrailingSpacesRule : StandardRule("no-trailing-spaces") {
regex = SPACE_OR_TAB_BEFORE_NEWLINE_REGEX,
replacement = "\n",
)
(this as LeafPsiElement).replaceWithText(newText)
(this as LeafPsiElement).rawReplaceWithText(newText)
}

private fun String.hasTrailingSpace() = takeLast(1) == " "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ public class ParameterListSpacingRule :
) {
emit(node.startOffset, "Expected a single space", true)
if (autoCorrect) {
(node as LeafPsiElement).replaceWithText(" ")
(node as LeafPsiElement).rawReplaceWithText(" ")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@ package com.pinterest.ktlint.ruleset.standard.rules

import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION
import com.pinterest.ktlint.rule.engine.core.api.ElementType.ANNOTATION_ENTRY
import com.pinterest.ktlint.rule.engine.core.api.ElementType.COLON
import com.pinterest.ktlint.rule.engine.core.api.ElementType.EQ
import com.pinterest.ktlint.rule.engine.core.api.RuleId
import com.pinterest.ktlint.rule.engine.core.api.isPartOf
import com.pinterest.ktlint.rule.engine.core.api.isPartOfComment
import com.pinterest.ktlint.rule.engine.core.api.isPartOfString
import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpace
import com.pinterest.ktlint.rule.engine.core.api.isWhiteSpaceWithNewline
import com.pinterest.ktlint.rule.engine.core.api.nextLeaf
import com.pinterest.ktlint.rule.engine.core.api.nextSibling
import com.pinterest.ktlint.rule.engine.core.api.prevLeaf
import com.pinterest.ktlint.rule.engine.core.api.prevSibling
import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceAfterMe
import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe
import com.pinterest.ktlint.ruleset.standard.StandardRule
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtClassOrObject
Expand All @@ -33,103 +34,141 @@ public class SpacingAroundColonRule : StandardRule("colon-spacing") {
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
) {
if (node is LeafPsiElement && node.textMatches(":") && !node.isPartOfString() && !node.isPartOfComment()) {
if (node.elementType == COLON) {
val psiParent = node.psi.parent
if (node.isPartOf(ANNOTATION) || node.isPartOf(ANNOTATION_ENTRY)) {
// todo: enforce "no spacing"
return
}
val removeSpacingBefore =
node.parent !is KtClassOrObject &&
node.parent !is KtConstructor<*> && // constructor : this/super
node.parent !is KtTypeConstraint && // where T : S
node.parent?.parent !is KtTypeParameterList
val prevLeaf = node.prevLeaf()
if (prevLeaf != null && prevLeaf.isWhiteSpaceWithNewline()) {
emit(prevLeaf.startOffset, "Unexpected newline before \":\"", true)
if (autoCorrect) {
val parent = node.parent
val prevNonCodeElements =
node
.siblings(forward = false, withItself = false)
.takeWhile { it.node.isWhiteSpace() || it.node.isPartOfComment() }.toList()
.siblings(forward = false)
.takeWhile { it.isWhiteSpace() || it.isPartOfComment() }
.toList()
.reversed()
when {
parent is KtProperty || parent is KtNamedFunction -> {
psiParent is KtProperty || psiParent is KtNamedFunction -> {
val equalsSignElement =
node
.siblings(forward = true, withItself = false)
.firstOrNull { it.node.elementType == EQ }
.siblings(forward = true)
.firstOrNull { it.elementType == EQ }
if (equalsSignElement != null) {
equalsSignElement.nextSibling?.takeIf { it.node.isWhiteSpace() }?.delete()
prevNonCodeElements.forEach { parent.addAfter(it, equalsSignElement) }
equalsSignElement
.treeNext
?.let { treeNext ->
prevNonCodeElements.forEach {
node.treeParent.addChild(it, treeNext)
}
if (treeNext.isWhiteSpace()) {
equalsSignElement.treeParent.removeChild(treeNext)
}
Unit
}
}
val blockElement =
node
.siblings(forward = true, withItself = false)
.siblings(forward = true)
.firstIsInstanceOrNull<KtBlockExpression>()
if (blockElement != null) {
val before =
blockElement
.firstChildNode
.nextSibling()
prevNonCodeElements
.let { if (it.first().node.isWhiteSpace()) it.drop(1) else it }
.forEach { blockElement.addAfter(it, blockElement.lBrace) }
.let {
if (it.first().isWhiteSpace()) {
blockElement.treeParent.removeChild(it.first())
it.drop(1)
} else {
it
}
if (it.last().isWhiteSpaceWithNewline()) {
blockElement.treeParent.removeChild(it.last())
it.dropLast(1)
} else {
it
}
}.forEach {
blockElement.addChild(it, before)
}
}
parent.deleteChildRange(prevNonCodeElements.last(), prevNonCodeElements.first())
}
prevLeaf.prevLeaf()?.isPartOfComment() == true -> {
val nextLeaf = node.nextLeaf()
prevNonCodeElements.reversed().forEach {
node.treeParent.addChild(it.node, nextLeaf)
prevNonCodeElements.forEach {
node.treeParent.addChild(it, nextLeaf)
}
if (nextLeaf != null && nextLeaf.isWhiteSpace()) {
node.treeParent.removeChild(nextLeaf)
}
}
else -> {
val text = prevLeaf.text
if (removeSpacingBefore) {
if (node.removeSpacingBefore) {
prevLeaf.treeParent.removeChild(prevLeaf)
} else {
(prevLeaf as LeafPsiElement).rawReplaceWithText(" ")
}
(node as ASTNode).upsertWhitespaceAfterMe(text)
node.upsertWhitespaceAfterMe(text)
}
}
}
}
if (node.prevSibling is PsiWhiteSpace && removeSpacingBefore && !prevLeaf.isWhiteSpaceWithNewline()) {
if (node.prevSibling().isWhiteSpace() && node.removeSpacingBefore && !prevLeaf.isWhiteSpaceWithNewline()) {
emit(node.startOffset, "Unexpected spacing before \":\"", true)
if (autoCorrect) {
node.prevSibling.node.treeParent.removeChild(node.prevSibling.node)
node
.prevSibling()
?.let { prevSibling ->
prevSibling.treeParent.removeChild(prevSibling)
}
}
}
val missingSpacingBefore =
node.prevSibling !is PsiWhiteSpace &&
!node.prevSibling().isWhiteSpace() &&
(
node.parent is KtClassOrObject || node.parent is KtConstructor<*> ||
node.parent is KtTypeConstraint || node.parent.parent is KtTypeParameterList
psiParent is KtClassOrObject || psiParent is KtConstructor<*> ||
psiParent is KtTypeConstraint || psiParent.parent is KtTypeParameterList
)
val missingSpacingAfter = node.nextSibling !is PsiWhiteSpace
val missingSpacingAfter = !node.nextSibling().isWhiteSpace()
when {
missingSpacingBefore && missingSpacingAfter -> {
emit(node.startOffset, "Missing spacing around \":\"", true)
if (autoCorrect) {
(node as ASTNode).upsertWhitespaceBeforeMe(" ")
(node as ASTNode).upsertWhitespaceAfterMe(" ")
node.upsertWhitespaceBeforeMe(" ")
node.upsertWhitespaceAfterMe(" ")
}
}
missingSpacingBefore -> {
emit(node.startOffset, "Missing spacing before \":\"", true)
if (autoCorrect) {
(node as ASTNode).upsertWhitespaceBeforeMe(" ")
node.upsertWhitespaceBeforeMe(" ")
}
}
missingSpacingAfter -> {
emit(node.startOffset + 1, "Missing spacing after \":\"", true)
if (autoCorrect) {
(node as ASTNode).upsertWhitespaceAfterMe(" ")
node.upsertWhitespaceAfterMe(" ")
}
}
}
}
}

private inline val ASTNode.removeSpacingBefore: Boolean
get() =
psi
.parent
.let { psiParent ->
psiParent !is KtClassOrObject &&
psiParent !is KtConstructor<*> && // constructor : this/super
psiParent !is KtTypeConstraint && // where T : S
psiParent?.parent !is KtTypeParameterList
}
}

public val SPACING_AROUND_COLON_RULE_ID: RuleId = SpacingAroundColonRule().ruleId
Loading