From 413819bff2943bc8a96b7323e35d14a5a5a85c4f Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Tue, 16 May 2023 17:51:00 +0200 Subject: [PATCH 1/8] Investigation of impact of unavailability of extension point "org.jetbrains.kotlin.com.intellij.treeCopyHandler" in kotlin 1.9 Some tests are failing as a result of this. DO NOT MERGE TO MASTER --- build-logic/build.gradle.kts | 7 + gradle/libs.versions.toml | 5 +- .../Issue1981-kotlin-1dot9-investigation.md | 422 ++++++++++++++++++ .../engine/internal/KotlinPsiFileFactory.kt | 38 +- settings.gradle.kts | 8 + 5 files changed, 471 insertions(+), 9 deletions(-) create mode 100644 ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/Issue1981-kotlin-1dot9-investigation.md diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 193bee34ed..85bdaa8bad 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -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 { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 953dd97532..618c349a8e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/Issue1981-kotlin-1dot9-investigation.md b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/Issue1981-kotlin-1dot9-investigation.md new file mode 100644 index 0000000000..278d35dacb --- /dev/null +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/Issue1981-kotlin-1dot9-investigation.md @@ -0,0 +1,422 @@ +@goodwinnk Tnx for the pointers. I am not sure whether I understand all that you wrote. I will come back to that on later point in time if needed, and I hope that is ok with you. You may ignore, the analysis below. It is just here, to keep track of what I have done sofar. + +Sofar, the initial investigation revealed that when running on the [kotlin 1.9.0-dev-6976 bootstrap](https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap/org/jetbrains/kotlin/kotlin-compiler-embeddable/1.9.0-dev-6976/): +* 247 tests out of 1705 fail when removing when *not* calling `enableASTMutations()` + * If `enableASTMutations()` is called and, only the registration of the TreeCopyHandler class is skipped, the number of failed tests drops to 47. So the absence of the registration of the TreeCopyHandler results in 47 errors. +* Replacing the current implementation of method below in `KotlinPsiFileFactory`: +``` +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) + } + } + + registerService(PomModel::class.java, FormatPomModel()) +} +``` +with below (analog to [suggested replacement](https://github.com/JetBrains/kotlin/commit/3caa1d13492fef61a47132c17475dcde02a47624)): +``` +private fun MockProject.enableASTMutations() { + val extensionPointName: ExtensionPointName = ExtensionPointName.create("org.jetbrains.kotlin.com.intellij.treeCopyHandler") + val extensionClass = TreeCopyHandler::class.java + for (area in arrayOf(extensionArea, getRootArea())) { + CoreApplicationEnvironment.registerExtensionPoint( + extensionArea, + extensionPointName, + extensionClass + ) + } + + registerService(PomModel::class.java, FormatPomModel()) +} +``` +does not have any effect as an IllegalArgumentException like below is thrown: +```text + +Rule 'test:auto-correct' throws exception in file '' at position (0:0) + Rule maintainer: Not specified (and not maintained by the Ktlint project) + Issue tracker : Not specified + Repository : Not specified +com.pinterest.ktlint.rule.engine.api.KtLintRuleException: Rule 'test:auto-correct' throws exception in file '' at position (0:0) + Rule maintainer: Not specified (and not maintained by the Ktlint project) + Issue tracker : Not specified + Repository : Not specified + at app//com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRule(RuleExecutionContext.kt:64) + at app//com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine$format$3.invoke(KtLintRuleEngine.kt:136) + at app//com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine$format$3.invoke(KtLintRuleEngine.kt:135) + at app//com.pinterest.ktlint.rule.engine.internal.VisitorProvider$visitor$3.invoke(VisitorProvider.kt:46) + at app//com.pinterest.ktlint.rule.engine.internal.VisitorProvider$visitor$3.invoke(VisitorProvider.kt:44) + at app//com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.format(KtLintRuleEngine.kt:135) + at app//com.pinterest.ktlint.rule.engine.api.KtLintTest$Given an API consumer$Given that format is invoked via the KtLintRuleEngine.Given a rule returning errors which can and can not be autocorrected than that state of the error can be retrieved in the callback(KtLintTest.kt:146) + at java.base@17.0.3/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at java.base@17.0.3/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) + at java.base@17.0.3/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.base@17.0.3/java.lang.reflect.Method.invoke(Method.java:568) + at app//org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727) + at app//org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) + at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) + at app//org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156) + at app//org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147) + at app//org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86) + at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103) + at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93) + at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) + at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) + at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) + at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) + at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92) + at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86) + at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213) + at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138) + at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.base@17.0.3/java.util.ArrayList.forEach(ArrayList.java:1511) + at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.base@17.0.3/java.util.ArrayList.forEach(ArrayList.java:1511) + at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.base@17.0.3/java.util.ArrayList.forEach(ArrayList.java:1511) + at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.base@17.0.3/java.util.ArrayList.forEach(ArrayList.java:1511) + at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) + at app//org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) + at app//org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) + at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107) + at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88) + at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54) + at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67) + at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52) + at app//org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114) + at app//org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86) + at app//org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86) + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:110) + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:90) + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:85) + at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:62) + at java.base@17.0.3/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at java.base@17.0.3/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) + at java.base@17.0.3/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.base@17.0.3/java.lang.reflect.Method.invoke(Method.java:568) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) + at jdk.proxy1/jdk.proxy1.$Proxy2.stop(Unknown Source) + at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60) + at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65) + at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) + at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) +Caused by: java.lang.IllegalArgumentException: Missing extension point: org.jetbrains.kotlin.com.intellij.treeCopyHandler in container {} + at org.jetbrains.kotlin.com.intellij.openapi.extensions.impl.ExtensionsAreaImpl.getExtensionPoint(ExtensionsAreaImpl.java:250) + at org.jetbrains.kotlin.com.intellij.openapi.extensions.BaseExtensionPointName.getPointImpl(BaseExtensionPointName.java:28) + at org.jetbrains.kotlin.com.intellij.openapi.extensions.ExtensionPointName.getExtensionList(ExtensionPointName.java:39) + at org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.ChangeUtil.encodeInformation(ChangeUtil.java:43) + at org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.ChangeUtil.lambda$encodeInformation$0(ChangeUtil.java:39) + at org.jetbrains.kotlin.com.intellij.psi.impl.DebugUtil.performPsiModification(DebugUtil.java:481) + at org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.ChangeUtil.encodeInformation(ChangeUtil.java:39) + at org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.ChangeUtil.copyLeafWithText(ChangeUtil.java:78) + at org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement.replaceWithText(LeafElement.java:153) + at com.pinterest.ktlint.rule.engine.api.AutoCorrectErrorRule.beforeVisitChildNodes(KtLintTest.kt:506) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$1.invoke(RuleExecutionContext.kt:93) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$1.invoke(RuleExecutionContext.kt:92) + at com.pinterest.ktlint.rule.engine.internal.SuppressHandler.handle-dhiVX_g(SuppressHandler.kt:28) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:92) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.access$executeRuleOnNodeRecursively(RuleExecutionContext.kt:29) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:100) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:99) + at com.pinterest.ktlint.rule.engine.internal.SuppressHandler.handle-dhiVX_g(SuppressHandler.kt:28) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:99) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.access$executeRuleOnNodeRecursively(RuleExecutionContext.kt:29) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:100) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:99) + at com.pinterest.ktlint.rule.engine.internal.SuppressHandler.handle-dhiVX_g(SuppressHandler.kt:28) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:99) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.access$executeRuleOnNodeRecursively(RuleExecutionContext.kt:29) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:100) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:99) + at com.pinterest.ktlint.rule.engine.internal.SuppressHandler.handle-dhiVX_g(SuppressHandler.kt:28) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:99) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.access$executeRuleOnNodeRecursively(RuleExecutionContext.kt:29) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:100) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:99) + at com.pinterest.ktlint.rule.engine.internal.SuppressHandler.handle-dhiVX_g(SuppressHandler.kt:28) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:99) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRule(RuleExecutionContext.kt:61) + ... 110 more +``` +* The 47 failing tests have 10 distinct root causes in 8 ktlint rules which need further investigation: +``` +org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement.replaceWithText(LeafElement.java:153) + at com.pinterest.ktlint.rule.engine.api.AutoCorrectErrorRule.beforeVisitChildNodes(KtLintTest.kt:506) + +org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement.replaceWithText(LeafElement.java:153) + at com.pinterest.ktlint.ruleset.standard.rules.NoTrailingSpacesRule.removeTrailingSpacesBeforeNewline(NoTrailingSpacesRule.kt:76) + +org.jetbrains.kotlin.com.intellij.extapi.psi.ASTDelegatePsiElement.addAfter(ASTDelegatePsiElement.java:292) + at com.pinterest.ktlint.ruleset.standard.rules.SpacingAroundColonRule.beforeVisitChildNodes(SpacingAroundColonRule.kt:63) + +org.jetbrains.kotlin.psi.KtExpressionImplStub.replace(KtExpressionImplStub.java:43) + at com.pinterest.ktlint.ruleset.standard.rules.StringTemplateRule.beforeVisitChildNodes(StringTemplateRule.kt:47) + +org.jetbrains.kotlin.com.intellij.extapi.psi.ASTDelegatePsiElement.addAfter(ASTDelegatePsiElement.java:292) + at com.pinterest.ktlint.ruleset.standard.rules.NoLineBreakBeforeAssignmentRule.beforeVisitChildNodes(NoLineBreakBeforeAssignmentRule.kt:38) + +org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement.replaceWithText(LeafElement.java:153) + at com.pinterest.ktlint.ruleset.standard.rules.ParameterListSpacingRule.replaceWithSingleSpace(ParameterListSpacingRule.kt:252) + +org.jetbrains.kotlin.com.intellij.extapi.psi.ASTDelegatePsiElement.addAfter(ASTDelegatePsiElement.java:292) + at com.pinterest.ktlint.ruleset.standard.rules.TrailingCommaOnCallSiteRule.reportAndCorrectTrailingCommaNodeBefore(TrailingCommaOnCallSiteRule.kt:177) + +org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement.replace(LeafPsiElement.java:198) + at com.pinterest.ktlint.ruleset.standard.rules.TrailingCommaOnDeclarationSiteRule.reportAndCorrectTrailingCommaNodeBefore(TrailingCommaOnDeclarationSiteRule.kt:326) + +org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement.replace(LeafPsiElement.java:198) + at com.pinterest.ktlint.ruleset.standard.rules.TrailingCommaOnDeclarationSiteRule.reportAndCorrectTrailingCommaNodeBefore(TrailingCommaOnDeclarationSiteRule.kt:341) + +org.jetbrains.kotlin.com.intellij.extapi.psi.ASTDelegatePsiElement.addAfter(ASTDelegatePsiElement.java:292) + at com.pinterest.ktlint.ruleset.standard.rules.TrailingCommaOnDeclarationSiteRule.reportAndCorrectTrailingCommaNodeBefore(TrailingCommaOnDeclarationSiteRule.kt:348) +``` +* More worrisome is that similar problems can occur in custom rule sets and will need to be investigated and fixed by maintainers of those rulesets. + + + +=== + +Summary for new issue to be created: + + +Ktlint lint and formats Kotlin code. For formatting of the code it is necessary that Ktlint is able to mutate the AST of files in which lint violations are found. For this an extension point on class `org.jetbrains.kotlin.com.intellij.treeCopyHandler` is registered in https://github.com/pinterest/ktlint/blob/cb0de4c3c848d1f1f61d53ed86475e165dd517ba/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KotlinPsiFileFactory.kt#L116. + +In [ktlint issue 1981](https://github.com/pinterest/ktlint/issues/1981) it was reported that ktlint uses a deprecated endpoint which is to be removed in Kotlin 1.9 resulting in runtime failures in Ktlint. We have applied a fix similar to this suggestion for the [kotlin embeddable compiler](https://github.com/JetBrains/kotlin/commit/3caa1d13492fef61a47132c17475dcde02a47624) and compiled and run Ktlint with [kotlin 1.9.0-dev-6976 bootstrap](https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap/org/jetbrains/kotlin/kotlin-compiler-embeddable/1.9.0-dev-6976/). Some tests of Ktlint fail with exceptions like below: +```text +Rule 'test:auto-correct' throws exception in file '' at position (0:0) + Rule maintainer: Not specified (and not maintained by the Ktlint project) + Issue tracker : Not specified + Repository : Not specified +com.pinterest.ktlint.rule.engine.api.KtLintRuleException: Rule 'test:auto-correct' throws exception in file '' at position (0:0) + Rule maintainer: Not specified (and not maintained by the Ktlint project) + Issue tracker : Not specified + Repository : Not specified + at app//com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRule(RuleExecutionContext.kt:64) + at app//com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine$format$3.invoke(KtLintRuleEngine.kt:136) + at app//com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine$format$3.invoke(KtLintRuleEngine.kt:135) + at app//com.pinterest.ktlint.rule.engine.internal.VisitorProvider$visitor$3.invoke(VisitorProvider.kt:46) + at app//com.pinterest.ktlint.rule.engine.internal.VisitorProvider$visitor$3.invoke(VisitorProvider.kt:44) + at app//com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine.format(KtLintRuleEngine.kt:135) + at app//com.pinterest.ktlint.rule.engine.api.KtLintTest$Given an API consumer$Given that format is invoked via the KtLintRuleEngine.Given a rule returning errors which can and can not be autocorrected than that state of the error can be retrieved in the callback(KtLintTest.kt:146) + at java.base@17.0.3/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at java.base@17.0.3/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) + at java.base@17.0.3/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.base@17.0.3/java.lang.reflect.Method.invoke(Method.java:568) + at app//org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727) + at app//org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) + at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) + at app//org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156) + at app//org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147) + at app//org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86) + at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103) + at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93) + at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) + at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) + at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) + at app//org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) + at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92) + at app//org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86) + at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213) + at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138) + at app//org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.base@17.0.3/java.util.ArrayList.forEach(ArrayList.java:1511) + at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.base@17.0.3/java.util.ArrayList.forEach(ArrayList.java:1511) + at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.base@17.0.3/java.util.ArrayList.forEach(ArrayList.java:1511) + at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at java.base@17.0.3/java.util.ArrayList.forEach(ArrayList.java:1511) + at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) + at app//org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) + at app//org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) + at app//org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95) + at app//org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35) + at app//org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57) + at app//org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54) + at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107) + at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88) + at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54) + at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67) + at app//org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52) + at app//org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114) + at app//org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86) + at app//org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86) + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:110) + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:90) + at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:85) + at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:62) + at java.base@17.0.3/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at java.base@17.0.3/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) + at java.base@17.0.3/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.base@17.0.3/java.lang.reflect.Method.invoke(Method.java:568) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36) + at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) + at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33) + at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94) + at jdk.proxy1/jdk.proxy1.$Proxy2.stop(Unknown Source) + at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100) + at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60) + at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113) + at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65) + at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) + at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) +Caused by: java.lang.IllegalArgumentException: Missing extension point: org.jetbrains.kotlin.com.intellij.treeCopyHandler in container {} + at org.jetbrains.kotlin.com.intellij.openapi.extensions.impl.ExtensionsAreaImpl.getExtensionPoint(ExtensionsAreaImpl.java:250) + at org.jetbrains.kotlin.com.intellij.openapi.extensions.BaseExtensionPointName.getPointImpl(BaseExtensionPointName.java:28) + at org.jetbrains.kotlin.com.intellij.openapi.extensions.ExtensionPointName.getExtensionList(ExtensionPointName.java:39) + at org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.ChangeUtil.encodeInformation(ChangeUtil.java:43) + at org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.ChangeUtil.lambda$encodeInformation$0(ChangeUtil.java:39) + at org.jetbrains.kotlin.com.intellij.psi.impl.DebugUtil.performPsiModification(DebugUtil.java:481) + at org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.ChangeUtil.encodeInformation(ChangeUtil.java:39) + at org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.ChangeUtil.copyLeafWithText(ChangeUtil.java:78) + at org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement.replaceWithText(LeafElement.java:153) + at com.pinterest.ktlint.rule.engine.api.AutoCorrectErrorRule.beforeVisitChildNodes(KtLintTest.kt:506) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$1.invoke(RuleExecutionContext.kt:93) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$1.invoke(RuleExecutionContext.kt:92) + at com.pinterest.ktlint.rule.engine.internal.SuppressHandler.handle-dhiVX_g(SuppressHandler.kt:28) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:92) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.access$executeRuleOnNodeRecursively(RuleExecutionContext.kt:29) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:100) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:99) + at com.pinterest.ktlint.rule.engine.internal.SuppressHandler.handle-dhiVX_g(SuppressHandler.kt:28) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:99) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.access$executeRuleOnNodeRecursively(RuleExecutionContext.kt:29) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:100) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:99) + at com.pinterest.ktlint.rule.engine.internal.SuppressHandler.handle-dhiVX_g(SuppressHandler.kt:28) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:99) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.access$executeRuleOnNodeRecursively(RuleExecutionContext.kt:29) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:100) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:99) + at com.pinterest.ktlint.rule.engine.internal.SuppressHandler.handle-dhiVX_g(SuppressHandler.kt:28) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:99) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.access$executeRuleOnNodeRecursively(RuleExecutionContext.kt:29) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:100) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext$executeRuleOnNodeRecursively$2$1.invoke(RuleExecutionContext.kt:99) + at com.pinterest.ktlint.rule.engine.internal.SuppressHandler.handle-dhiVX_g(SuppressHandler.kt:28) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRuleOnNodeRecursively(RuleExecutionContext.kt:99) + at com.pinterest.ktlint.rule.engine.internal.RuleExecutionContext.executeRule(RuleExecutionContext.kt:61) + ... 110 more +``` + +The relevant part of this stacktrace seems to be the following: +```text +Caused by: java.lang.IllegalArgumentException: Missing extension point: org.jetbrains.kotlin.com.intellij.treeCopyHandler in container {} + at org.jetbrains.kotlin.com.intellij.openapi.extensions.impl.ExtensionsAreaImpl.getExtensionPoint(ExtensionsAreaImpl.java:250) + at org.jetbrains.kotlin.com.intellij.openapi.extensions.BaseExtensionPointName.getPointImpl(BaseExtensionPointName.java:28) + at org.jetbrains.kotlin.com.intellij.openapi.extensions.ExtensionPointName.getExtensionList(ExtensionPointName.java:39) + at org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.ChangeUtil.encodeInformation(ChangeUtil.java:43) + at org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.ChangeUtil.lambda$encodeInformation$0(ChangeUtil.java:39) + at org.jetbrains.kotlin.com.intellij.psi.impl.DebugUtil.performPsiModification(DebugUtil.java:481) + at org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.ChangeUtil.encodeInformation(ChangeUtil.java:39) + at org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.ChangeUtil.copyLeafWithText(ChangeUtil.java:78) + at org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement.replaceWithText(LeafElement.java:153) +``` + +See below for the list of root causes found by the unit tests of ktlint: +``` +org.jetbrains.kotlin.psi.KtExpressionImplStub.replace(KtExpressionImplStub.java:43) +org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement.replaceWithText(LeafElement.java:153) +org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement.replace(LeafPsiElement.java:198) +org.jetbrains.kotlin.com.intellij.extapi.psi.ASTDelegatePsiElement.addAfter(ASTDelegatePsiElement.java:292) +``` +Most likely we can work around those root causes as the majority of Ktlint's rules that modify the AST are not using above methods. But as Ktlint has the format of custom rulesets, similar analysis and fixes have to be applies by all maintainers of such ruleset. + +Ideally, the extension point for class `org.jetbrains.kotlin.com.intellij.treeCopyHandler` keeps working in kotlin 1.9 and beyond. If that is not possible, it would be great if another extension point can be used that provides for similar functionality. diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KotlinPsiFileFactory.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KotlinPsiFileFactory.kt index 52b7224050..60f5a34025 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KotlinPsiFileFactory.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KotlinPsiFileFactory.kt @@ -5,9 +5,11 @@ import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.com.intellij.core.CoreApplicationEnvironment 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.ExtensionPointName 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 @@ -110,20 +112,40 @@ private class LoggerFactory : DiagnosticLogger.Factory { } } +// FIXME: DO NOT MERGE WITH MASTER. THIS IS SOLELY NEEDED FOR INVESTIGATION OF KOTLIN 1.9 IMPACT +// https://github.com/pinterest/ktlint/issues/1981 +/** + * Enables AST mutations (`ktlint -F ...`). Works for Kotlin 1.8 and before. + */ +//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) +// } +// } +// +// registerService(PomModel::class.java, FormatPomModel()) +//} + /** * 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) - } - } - +// val extensionPointName: ExtensionPointName = ExtensionPointName.create("org.jetbrains.kotlin.com.intellij.treeCopyHandler") +// val extensionClass = TreeCopyHandler::class.java +// if (!extensionArea.hasExtensionPoint(extensionPointName)) { +// CoreApplicationEnvironment.registerExtensionPoint( +// extensionArea, +// extensionPointName, +// extensionClass +// ) +// } +// registerService(PomModel::class.java, FormatPomModel()) } +// END OF FIXME: DO NOT MERGE WITH MASTER. THIS IS SOLELY NEEDED FOR INVESTIGATION OF KOTLIN 1.9 IMPACT private class FormatPomModel : UserDataHolderBase(), PomModel { override fun runTransaction(transaction: PomTransaction) { diff --git a/settings.gradle.kts b/settings.gradle.kts index 802a76d912..cd918a3d30 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,6 +10,14 @@ pluginManagement { dependencyResolutionManagement { 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 } } From ac2f0dcd4f0d826181152f8e79295cb3ed862035 Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Tue, 16 May 2023 18:38:47 +0200 Subject: [PATCH 2/8] Replace `LeafElement.replaceWithText(String)` with `LeafElement.rawReplaceWithText(String)` Up until Kotlin 1.8 the KotlinPsiFactory registered the treeCopyHandler extension point which is needed in order to use the `LeafElement.replaceWithText(String)`. With the Kotlin 1.9 it is no longer possible to register this extension point which results in exception. The exception does not occur when using method `LeafElement.rawReplaceWithText(String)`. --- .../kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt | 2 +- .../ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt | 2 +- .../ktlint/ruleset/standard/rules/ParameterListSpacingRule.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt index 13bf1476ed..d1904f576f 100644 --- a/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt +++ b/ktlint-rule-engine/src/test/kotlin/com/pinterest/ktlint/rule/engine/api/KtLintTest.kt @@ -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 -> diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt index eae7c01721..e1a44b7518 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoTrailingSpacesRule.kt @@ -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) == " " diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListSpacingRule.kt index a16bd83ac5..ca8a7c63a1 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListSpacingRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ParameterListSpacingRule.kt @@ -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(" ") } } From 53c082f9bac62f22ee89b1841f7fbc13504f0258 Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Wed, 17 May 2023 20:51:46 +0200 Subject: [PATCH 3/8] Replace `PsiElement.addAfter(PsiElement, PsiElement)` on `LeafElement` with `AstNode.addChild(AstNode, AstNode)` Up until Kotlin 1.8 the KotlinPsiFactory registered the treeCopyHandler extension point which is needed in order to use the `PsiElement.addAfter(PsiLeaf, PsiLeaf)`. With the Kotlin 1.9 it is no longer possible to register this extension point which results in exception. The exception does not occur when using method `AstNode.addChild(AstNode, AstNode)`. Note that this method inserts the new node (first) argument *before* the second argument node and as of that is not a simple replacement of the `PsiElement.addAfter(PsiElement, PsiElement)`. --- .../rules/NoLineBreakBeforeAssignmentRule.kt | 61 ++++++++++++------- .../standard/rules/SpacingAroundColonRule.kt | 60 +++++++++++++++--- .../rules/TrailingCommaOnCallSiteRule.kt | 12 ++-- .../TrailingCommaOnDeclarationSiteRule.kt | 54 +++++++++------- 4 files changed, 130 insertions(+), 57 deletions(-) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakBeforeAssignmentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakBeforeAssignmentRule.kt index e5823839c4..701bb767e7 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakBeforeAssignmentRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakBeforeAssignmentRule.kt @@ -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") { @@ -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) } } - } } } diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRule.kt index b5ee35c302..876c272813 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRule.kt @@ -4,12 +4,14 @@ 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.EQ import com.pinterest.ktlint.rule.engine.core.api.RuleId +import com.pinterest.ktlint.rule.engine.core.api.children 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.upsertWhitespaceAfterMe import com.pinterest.ktlint.rule.engine.core.api.upsertWhitespaceBeforeMe @@ -17,6 +19,7 @@ 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.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl import org.jetbrains.kotlin.psi.KtBlockExpression import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtConstructor @@ -51,31 +54,70 @@ public class SpacingAroundColonRule : StandardRule("colon-spacing") { val prevNonCodeElements = node .siblings(forward = false, withItself = false) - .takeWhile { it.node.isWhiteSpace() || it.node.isPartOfComment() }.toList() + .takeWhile { it.node.isWhiteSpace() || it.node.isPartOfComment() } + .toList() + .reversed() when { parent is KtProperty || parent is KtNamedFunction -> { val equalsSignElement = node - .siblings(forward = true, withItself = false) - .firstOrNull { it.node.elementType == EQ } + .node + .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 { + parent.node.addChild(it.node, treeNext) + } + if (treeNext.isWhiteSpace()) { + equalsSignElement.treeParent.removeChild(treeNext) + } + Unit + } } val blockElement = node .siblings(forward = true, withItself = false) .firstIsInstanceOrNull() + ?.node 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().node.isWhiteSpace()) { + blockElement.treeParent.removeChild(it.first().node) + it.drop(1) + } else { + blockElement.addChild(PsiWhiteSpaceImpl("#"), before) + it + } + if (it.last().node.isWhiteSpaceWithNewline()) { + blockElement.treeParent.removeChild(it.last().node) + it.dropLast(1) + } else { + it + } + }.forEach { + blockElement.addChild(it.node, before) + } + } + if (prevNonCodeElements.first() != prevNonCodeElements.last()) { +// parent.deleteChildRange(prevNonCodeElements.first(), prevNonCodeElements.last()) +// parent.node.removeRange(prevNonCodeElements.first().node, prevNonCodeElements.last().node) + Unit + } else { +// parent.node.removeChild(prevNonCodeElements.first().node) + Unit } - parent.deleteChildRange(prevNonCodeElements.last(), prevNonCodeElements.first()) } prevLeaf.prevLeaf()?.isPartOfComment() == true -> { val nextLeaf = node.nextLeaf() - prevNonCodeElements.reversed().forEach { + prevNonCodeElements.forEach { node.treeParent.addChild(it.node, nextLeaf) } if (nextLeaf != null && nextLeaf.isWhiteSpace()) { diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt index 8cde204494..20a5888f6c 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt @@ -2,6 +2,7 @@ package com.pinterest.ktlint.ruleset.standard.rules import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.COLLECTION_LITERAL_EXPRESSION +import com.pinterest.ktlint.rule.engine.core.api.ElementType.COMMA import com.pinterest.ktlint.rule.engine.core.api.ElementType.INDICES import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_ARGUMENT_LIST import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_ARGUMENT @@ -21,6 +22,7 @@ import com.pinterest.ktlint.ruleset.standard.StandardRule import org.ec4j.core.model.PropertyType import org.ec4j.core.model.PropertyType.PropertyValueParser import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet import org.jetbrains.kotlin.psi.KtPsiFactory @@ -171,10 +173,12 @@ public class TrailingCommaOnCallSiteRule : true, ) if (autoCorrect) { - val prevPsi = inspectNode.prevCodeSibling()!!.psi - val parentPsi = prevPsi.parent - val psiFactory = KtPsiFactory(prevPsi) - parentPsi.addAfter(psiFactory.createComma(), prevPsi) + inspectNode + .prevCodeSibling() + ?.nextSibling() + ?.let { before -> + before.treeParent.addChild(LeafPsiElement(COMMA, ","), before) + } } } TrailingCommaState.REDUNDANT -> { diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt index 8da16f1086..99673941aa 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt @@ -3,10 +3,12 @@ package com.pinterest.ktlint.ruleset.standard.rules import com.pinterest.ktlint.rule.engine.core.api.ElementType import com.pinterest.ktlint.rule.engine.core.api.ElementType.ARROW import com.pinterest.ktlint.rule.engine.core.api.ElementType.CLASS +import com.pinterest.ktlint.rule.engine.core.api.ElementType.COMMA import com.pinterest.ktlint.rule.engine.core.api.ElementType.DESTRUCTURING_DECLARATION import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUNCTION_LITERAL import com.pinterest.ktlint.rule.engine.core.api.ElementType.FUNCTION_TYPE import com.pinterest.ktlint.rule.engine.core.api.ElementType.RBRACE +import com.pinterest.ktlint.rule.engine.core.api.ElementType.SEMICOLON import com.pinterest.ktlint.rule.engine.core.api.ElementType.TYPE_PARAMETER_LIST import com.pinterest.ktlint.rule.engine.core.api.ElementType.VALUE_PARAMETER_LIST import com.pinterest.ktlint.rule.engine.core.api.ElementType.WHEN_ENTRY @@ -18,6 +20,7 @@ import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfigProperty import com.pinterest.ktlint.rule.engine.core.api.indent import com.pinterest.ktlint.rule.engine.core.api.isCodeLeaf +import com.pinterest.ktlint.rule.engine.core.api.nextLeaf import com.pinterest.ktlint.rule.engine.core.api.noNewLineInClosedRange import com.pinterest.ktlint.rule.engine.core.api.prevCodeLeaf import com.pinterest.ktlint.rule.engine.core.api.prevLeaf @@ -29,6 +32,8 @@ import org.ec4j.core.model.PropertyType.PropertyValueParser import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.PsiElement import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtCollectionLiteralExpression @@ -316,36 +321,39 @@ public class TrailingCommaOnDeclarationSiteRule : } if (autoCorrect) { if (addNewLine) { - val newLine = - KtPsiFactory(prevNode.psi).createWhiteSpace( - prevNode - .treeParent - .indent(), - ) - if (leafBeforeArrowOrNull != null && leafBeforeArrowOrNull is PsiWhiteSpace) { - leafBeforeArrowOrNull.replace(newLine) + val indent = + prevNode + .treeParent + .indent() + if (leafBeforeArrowOrNull is PsiWhiteSpace) { + (leafBeforeArrowOrNull as LeafPsiElement).rawReplaceWithText(indent) } else { - prevNode.psi.parent.addAfter(newLine, prevNode.psi) + inspectNode + .prevCodeLeaf() + ?.nextLeaf() + ?.let { before -> + before.treeParent.addChild(PsiWhiteSpaceImpl(indent), before) + } } } - val comma = KtPsiFactory(prevNode.psi).createComma() if (inspectNode.treeParent.elementType == ElementType.ENUM_ENTRY) { - with(KtPsiFactory(prevNode.psi)) { - val parentIndent = - (prevNode.psi.parent.prevLeaf() as? PsiWhiteSpace)?.text - ?: prevNode.indent() - val newline = createWhiteSpace(parentIndent) - val enumEntry = inspectNode.treeParent.psi - enumEntry.apply { - inspectNode.psi.replace(comma) - add(newline) - add(createSemicolon()) - } + val parentIndent = + (prevNode.psi.parent.prevLeaf() as? PsiWhiteSpace)?.text + ?: prevNode.indent() + (inspectNode as LeafPsiElement).apply { + this.treeParent.addChild(LeafPsiElement(COMMA, ","), this) + this.treeParent.addChild(PsiWhiteSpaceImpl(parentIndent), null) + this.treeParent.addChild(LeafPsiElement(SEMICOLON, ";"), null) } - Unit + inspectNode.treeParent.removeChild(inspectNode) } else { - prevNode.psi.parent.addAfter(comma, prevNode.psi) + inspectNode + .prevCodeLeaf() + ?.nextLeaf() + ?.let { before -> + before.treeParent.addChild(LeafPsiElement(COMMA, ","), before) + } } } } From 8e929ece0c0b63dc9c543efcefecec38aa079e04 Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Thu, 18 May 2023 19:01:04 +0200 Subject: [PATCH 4/8] Refactor - Keep main element as type ASTNode and convert to psi when needed --- .../standard/rules/SpacingAroundColonRule.kt | 87 +++++++++---------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRule.kt index 876c272813..a692053882 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRule.kt @@ -2,24 +2,22 @@ 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.children 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.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl import org.jetbrains.kotlin.psi.KtBlockExpression import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtConstructor @@ -36,32 +34,26 @@ 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() } + .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 - .node .siblings(forward = true) .firstOrNull { it.elementType == EQ } if (equalsSignElement != null) { @@ -69,7 +61,7 @@ public class SpacingAroundColonRule : StandardRule("colon-spacing") { .treeNext ?.let { treeNext -> prevNonCodeElements.forEach { - parent.node.addChild(it.node, treeNext) + node.treeParent.addChild(it, treeNext) } if (treeNext.isWhiteSpace()) { equalsSignElement.treeParent.removeChild(treeNext) @@ -79,9 +71,8 @@ public class SpacingAroundColonRule : StandardRule("colon-spacing") { } val blockElement = node - .siblings(forward = true, withItself = false) + .siblings(forward = true) .firstIsInstanceOrNull() - ?.node if (blockElement != null) { val before = blockElement @@ -89,36 +80,27 @@ public class SpacingAroundColonRule : StandardRule("colon-spacing") { .nextSibling() prevNonCodeElements .let { - if (it.first().node.isWhiteSpace()) { - blockElement.treeParent.removeChild(it.first().node) + if (it.first().isWhiteSpace()) { + blockElement.treeParent.removeChild(it.first()) it.drop(1) } else { - blockElement.addChild(PsiWhiteSpaceImpl("#"), before) it } - if (it.last().node.isWhiteSpaceWithNewline()) { - blockElement.treeParent.removeChild(it.last().node) + if (it.last().isWhiteSpaceWithNewline()) { + blockElement.treeParent.removeChild(it.last()) it.dropLast(1) } else { it } }.forEach { - blockElement.addChild(it.node, before) + blockElement.addChild(it, before) } } - if (prevNonCodeElements.first() != prevNonCodeElements.last()) { -// parent.deleteChildRange(prevNonCodeElements.first(), prevNonCodeElements.last()) -// parent.node.removeRange(prevNonCodeElements.first().node, prevNonCodeElements.last().node) - Unit - } else { -// parent.node.removeChild(prevNonCodeElements.first().node) - Unit - } } prevLeaf.prevLeaf()?.isPartOfComment() == true -> { val nextLeaf = node.nextLeaf() prevNonCodeElements.forEach { - node.treeParent.addChild(it.node, nextLeaf) + node.treeParent.addChild(it, nextLeaf) } if (nextLeaf != null && nextLeaf.isWhiteSpace()) { node.treeParent.removeChild(nextLeaf) @@ -126,52 +108,67 @@ public class SpacingAroundColonRule : StandardRule("colon-spacing") { } 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 From 90cb01bff9211ee5cc8bf3aead292ae2a58ec6bd Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Thu, 18 May 2023 19:00:20 +0200 Subject: [PATCH 5/8] Refactor - Split test and add some test cases --- .../rules/SpacingAroundColonRuleTest.kt | 156 ++++++++++++------ 1 file changed, 101 insertions(+), 55 deletions(-) diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRuleTest.kt index 4ff4036135..d1daa3fcbd 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRuleTest.kt @@ -2,6 +2,7 @@ package com.pinterest.ktlint.ruleset.standard.rules import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule import com.pinterest.ktlint.test.LintViolation +import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test class SpacingAroundColonRuleTest { @@ -273,96 +274,141 @@ class SpacingAroundColonRuleTest { ).isFormattedAs(formattedCode) } - @Test - fun `Issue 1057 - Given some declaration with an unexpected newline before the colon`() { - val code = - """ - fun test() { + @Nested + inner class `Issue 1057 - Given some declaration with an unexpected newline before the colon` { + @Test + fun `Property with colon on next line`() { + val code = + """ val v1 : Int = 1 - val v2 // comment + val v2// comment : Int = 1 - val v3 - // comment + val v3 // comment : Int = 1 - fun f1() + val v4 + // comment : Int = 1 + """.trimIndent() + val formattedCode = + """ + val v1: Int = + 1 - fun f2() // comment - : Int = 1 + val v2: Int =// comment + 1 - fun f3() + val v3: Int = // comment + 1 + + val v4: Int = // comment + 1 + """.trimIndent() + spacingAroundColonRuleAssertThat(code) + .hasLintViolations( + LintViolation(1, 7, "Unexpected newline before \":\""), + LintViolation(4, 17, "Unexpected newline before \":\""), + LintViolation(7, 18, "Unexpected newline before \":\""), + LintViolation(11, 15, "Unexpected newline before \":\""), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Function with colon on next line followed by body expression`() { + val code = + """ + fun foo1() : Int = 1 - fun g1() - : Int { - return 1 - } + fun foo2()// comment + : Int = 1 - fun g2() // comment - : Int { - return 1 - } + fun foo3() // comment + : Int = 1 - fun g3() + fun foo4() // comment - : Int { - return 1 - } - } - """.trimIndent() - val formattedCode = - """ - fun test() { - val v1: Int = + : Int = 1 + """.trimIndent() + val formattedCode = + """ + fun foo1(): Int = + 1 + + fun foo2(): Int =// comment 1 - val v2: Int = // comment + fun foo3(): Int = // comment 1 - val v3: Int = + fun foo4(): Int = // comment 1 + """.trimIndent() + spacingAroundColonRuleAssertThat(code) + .hasLintViolations( + LintViolation(1, 11, "Unexpected newline before \":\""), + LintViolation(4, 21, "Unexpected newline before \":\""), + LintViolation(7, 22, "Unexpected newline before \":\""), + LintViolation(11, 15, "Unexpected newline before \":\""), + ).isFormattedAs(formattedCode) + } + + @Test + fun `Function with colon on next line followed by body block`() { + val code = + """ + fun foo1() + : Int { + 1 + } - fun f1(): Int = + fun foo2()// comment + : Int { 1 + } - fun f2(): Int = // comment + fun foo3() // comment + : Int { 1 + } - fun f3(): Int = + fun foo4() // comment + : Int { 1 + } + """.trimIndent() + val formattedCode = + """ + fun foo1(): Int { + 1 + } - fun g1(): Int { - return 1 + fun foo2(): Int {// comment + 1 } - fun g2(): Int { // comment - return 1 + fun foo3(): Int { // comment + 1 } - fun g3(): Int { + fun foo4(): Int { // comment - return 1 + 1 } - } - """.trimIndent() - spacingAroundColonRuleAssertThat(code) - .hasLintViolations( - LintViolation(2, 11, "Unexpected newline before \":\""), - LintViolation(5, 22, "Unexpected newline before \":\""), - LintViolation(9, 19, "Unexpected newline before \":\""), - LintViolation(12, 13, "Unexpected newline before \":\""), - LintViolation(15, 24, "Unexpected newline before \":\""), - LintViolation(19, 19, "Unexpected newline before \":\""), - LintViolation(22, 13, "Unexpected newline before \":\""), - LintViolation(27, 24, "Unexpected newline before \":\""), - LintViolation(33, 19, "Unexpected newline before \":\""), - ).isFormattedAs(formattedCode) + """.trimIndent() + spacingAroundColonRuleAssertThat(code) + .hasLintViolations( + LintViolation(1, 11, "Unexpected newline before \":\""), + LintViolation(6, 21, "Unexpected newline before \":\""), + LintViolation(11, 22, "Unexpected newline before \":\""), + LintViolation(17, 15, "Unexpected newline before \":\""), + ).isFormattedAs(formattedCode) + } } } From d6a17fcddd79a046e3670f03a20a57770a6f1cbb Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Thu, 18 May 2023 21:19:49 +0200 Subject: [PATCH 6/8] Replace `PsiElement.replace(PsiElement)` with a sequence of `AstNode.addChild(AstNode, AstNode)` and `AstNode.removeChild(AstNode)` Up until Kotlin 1.8 the KotlinPsiFactory registered the treeCopyHandler extension point which is needed in order to use the `PsiElement.replace(PsiElement)`. With the Kotlin 1.9 it is no longer possible to register this extension point which results in exception. --- .../standard/rules/StringTemplateRule.kt | 56 ++++++++++++------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule.kt index add82e8dea..0a2cf8bfd7 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule.kt @@ -44,44 +44,60 @@ public class StringTemplateRule : StandardRule("string-template") { true, ) if (autoCorrect) { - entryExpression.replace(receiver) - entryExpression = receiver + entryExpression + .node + .let { entryExpressionNode -> + entryExpressionNode.treeParent.addChild(receiver.node, entryExpressionNode) + entryExpressionNode.treeParent.removeChild(entryExpressionNode) + } + node + .takeIf { it.isStringTemplate() } + ?.removeCurlyBracesIfRedundant() } } } - if (node.text.startsWith("${'$'}{") && - node.text.let { it.substring(2, it.length - 1) }.all { it.isPartOfIdentifier() } && + if (node.isStringTemplate() && (entryExpression is KtNameReferenceExpression || entryExpression is KtThisExpression) && node.treeNext.let { nextSibling -> nextSibling.elementType == CLOSING_QUOTE || ( nextSibling.elementType == LITERAL_STRING_TEMPLATE_ENTRY && - !nextSibling.text[0].isPartOfIdentifier() + !nextSibling.text.isPartOfIdentifier() ) } ) { emit(node.treePrev.startOffset + 2, "Redundant curly braces", true) if (autoCorrect) { - val leftCurlyBraceNode = node.findChildByType(LONG_TEMPLATE_ENTRY_START) - val rightCurlyBraceNode = node.findChildByType(LONG_TEMPLATE_ENTRY_END) - if (leftCurlyBraceNode != null && rightCurlyBraceNode != null) { - node.removeChild(leftCurlyBraceNode) - node.removeChild(rightCurlyBraceNode) - val remainingNode = node.firstChildNode - val newNode = - if (remainingNode.elementType == DOT_QUALIFIED_EXPRESSION) { - LeafPsiElement(REGULAR_STRING_PART, "\$${remainingNode.text}") - } else { - LeafPsiElement(remainingNode.elementType, "\$${remainingNode.text}") - } - node.replaceChild(node.firstChildNode, newNode) - } + node.removeCurlyBracesIfRedundant() } } } } - private fun Char.isPartOfIdentifier() = this == '_' || this.isLetterOrDigit() + private fun ASTNode.removeCurlyBracesIfRedundant() { + if (isStringTemplate()) { + val leftCurlyBraceNode = findChildByType(LONG_TEMPLATE_ENTRY_START) + val rightCurlyBraceNode = findChildByType(LONG_TEMPLATE_ENTRY_END) + if (leftCurlyBraceNode != null && rightCurlyBraceNode != null) { + removeChild(leftCurlyBraceNode) + removeChild(rightCurlyBraceNode) + val remainingNode = firstChildNode + val newNode = + if (remainingNode.elementType == DOT_QUALIFIED_EXPRESSION) { + LeafPsiElement(REGULAR_STRING_PART, "\$${remainingNode.text}") + } else { + LeafPsiElement(remainingNode.elementType, "\$${remainingNode.text}") + } + replaceChild(firstChildNode, newNode) + } + } + } + + private fun ASTNode.isStringTemplate() = + text.startsWith("${'$'}{") && + text.substring(2, text.length - 1).isPartOfIdentifier() + + private fun String.isPartOfIdentifier() = this == "_" || this.all { it.isLetterOrDigit() } } public val STRING_TEMPLATE_RULE_ID: RuleId = StringTemplateRule().ruleId From dac1a986b6cb0f7bb98681f39e3fd7109c1b2728 Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Thu, 18 May 2023 21:43:27 +0200 Subject: [PATCH 7/8] Replace `PsiElement.replace(PsiElement)` with a sequence of `AstNode.addChild(AstNode, AstNode)` and `AstNode.removeChild(AstNode)` [bugfix] Up until Kotlin 1.8 the KotlinPsiFactory registered the treeCopyHandler extension point which is needed in order to use the `PsiElement.replace(PsiElement)`. With the Kotlin 1.9 it is no longer possible to register this extension point which results in exception. --- .../ruleset/standard/rules/StringTemplateRule.kt | 2 +- .../ruleset/standard/rules/StringTemplateRuleTest.kt | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule.kt index 0a2cf8bfd7..52335cd8de 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRule.kt @@ -62,7 +62,7 @@ public class StringTemplateRule : StandardRule("string-template") { nextSibling.elementType == CLOSING_QUOTE || ( nextSibling.elementType == LITERAL_STRING_TEMPLATE_ENTRY && - !nextSibling.text.isPartOfIdentifier() + !nextSibling.text.substring(0, 1).isPartOfIdentifier() ) } ) { diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRuleTest.kt index fcb9920a5c..f87b49ad0e 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/StringTemplateRuleTest.kt @@ -354,6 +354,17 @@ class StringTemplateRuleTest { .hasLintViolation(3, 21, "Redundant \"toString()\" call in string template") .isFormattedAs(formattedCode) } + + @Test + fun `Given a string template immediately followed by non-whitespace character`() { + val code = + """ + fun test(foo: String, bar: String) { + println("${'$'}{foo}text:${'$'}bar") + } + """.trimIndent() + stringTemplateRuleAssertThat(code).hasNoLintViolations() + } } // Replace the "$." placeholder with an actual "$" so that string "$.{expression}" is transformed to a String template From e8405693302b28731dda3293e78a144e75587a61 Mon Sep 17 00:00:00 2001 From: paul-dingemans Date: Thu, 18 May 2023 21:43:57 +0200 Subject: [PATCH 8/8] Fix lint violations --- .../engine/internal/KotlinPsiFileFactory.kt | 35 ++----------------- .../rules/NoLineBreakBeforeAssignmentRule.kt | 2 +- .../rules/TrailingCommaOnCallSiteRule.kt | 1 - .../TrailingCommaOnDeclarationSiteRule.kt | 1 - .../rules/SpacingAroundColonRuleTest.kt | 4 +-- 5 files changed, 5 insertions(+), 38 deletions(-) diff --git a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KotlinPsiFileFactory.kt b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KotlinPsiFileFactory.kt index 60f5a34025..ce2afeeb3d 100644 --- a/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KotlinPsiFileFactory.kt +++ b/ktlint-rule-engine/src/main/kotlin/com/pinterest/ktlint/rule/engine/internal/KotlinPsiFileFactory.kt @@ -5,12 +5,8 @@ import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment -import org.jetbrains.kotlin.com.intellij.core.CoreApplicationEnvironment 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.ExtensionPointName -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 @@ -19,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 @@ -112,40 +107,14 @@ private class LoggerFactory : DiagnosticLogger.Factory { } } -// FIXME: DO NOT MERGE WITH MASTER. THIS IS SOLELY NEEDED FOR INVESTIGATION OF KOTLIN 1.9 IMPACT -// https://github.com/pinterest/ktlint/issues/1981 -/** - * Enables AST mutations (`ktlint -F ...`). Works for Kotlin 1.8 and before. - */ -//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) -// } -// } -// -// registerService(PomModel::class.java, FormatPomModel()) -//} - /** * Enables AST mutations (`ktlint -F ...`). */ private fun MockProject.enableASTMutations() { -// val extensionPointName: ExtensionPointName = ExtensionPointName.create("org.jetbrains.kotlin.com.intellij.treeCopyHandler") -// val extensionClass = TreeCopyHandler::class.java -// if (!extensionArea.hasExtensionPoint(extensionPointName)) { -// CoreApplicationEnvironment.registerExtensionPoint( -// extensionArea, -// extensionPointName, -// extensionClass -// ) -// } -// + // 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()) } -// END OF FIXME: DO NOT MERGE WITH MASTER. THIS IS SOLELY NEEDED FOR INVESTIGATION OF KOTLIN 1.9 IMPACT private class FormatPomModel : UserDataHolderBase(), PomModel { override fun runTransaction(transaction: PomTransaction) { diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakBeforeAssignmentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakBeforeAssignmentRule.kt index 701bb767e7..fe34da7e58 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakBeforeAssignmentRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/NoLineBreakBeforeAssignmentRule.kt @@ -27,7 +27,7 @@ public class NoLineBreakBeforeAssignmentRule : StandardRule("no-line-break-befor private fun visitEquals( assignmentNode: ASTNode, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, - autoCorrect: Boolean + autoCorrect: Boolean, ) { assignmentNode .prevSibling() diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt index 20a5888f6c..5ca274a435 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnCallSiteRule.kt @@ -24,7 +24,6 @@ import org.ec4j.core.model.PropertyType.PropertyValueParser import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet -import org.jetbrains.kotlin.psi.KtPsiFactory /** * Linting trailing comma for call site. diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt index 99673941aa..79cb5a2e52 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/TrailingCommaOnDeclarationSiteRule.kt @@ -41,7 +41,6 @@ import org.jetbrains.kotlin.psi.KtDestructuringDeclaration import org.jetbrains.kotlin.psi.KtEnumEntry import org.jetbrains.kotlin.psi.KtFunctionLiteral import org.jetbrains.kotlin.psi.KtParameterList -import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtValueArgumentList import org.jetbrains.kotlin.psi.KtWhenEntry import org.jetbrains.kotlin.psi.KtWhenExpression diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRuleTest.kt index d1daa3fcbd..af7413fd58 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/SpacingAroundColonRuleTest.kt @@ -307,7 +307,7 @@ class SpacingAroundColonRuleTest { val v4: Int = // comment 1 - """.trimIndent() + """.trimIndent() spacingAroundColonRuleAssertThat(code) .hasLintViolations( LintViolation(1, 7, "Unexpected newline before \":\""), @@ -348,7 +348,7 @@ class SpacingAroundColonRuleTest { fun foo4(): Int = // comment 1 - """.trimIndent() + """.trimIndent() spacingAroundColonRuleAssertThat(code) .hasLintViolations( LintViolation(1, 11, "Unexpected newline before \":\""),