Skip to content

Commit

Permalink
GH-41 refactor major components
Browse files Browse the repository at this point in the history
  • Loading branch information
akefirad committed Jan 19, 2025
1 parent edbd2e1 commit 914a3f8
Show file tree
Hide file tree
Showing 30 changed files with 1,500 additions and 540 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ jobs:

- name: Inspect code
uses: JetBrains/[email protected]
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN_205267377 }}
QODANA_ENDPOINT: 'https://qodana.cloud'
with:
cache-default-branch-only: true

Expand Down
10 changes: 10 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import org.jetbrains.changelog.Changelog
import org.jetbrains.changelog.markdownToHTML
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
java
groovy

alias(libs.plugins.kotlin) // Kotlin support
alias(libs.plugins.intelliJPlatform) // IntelliJ Platform Gradle Plugin
Expand Down Expand Up @@ -34,6 +36,10 @@ repositories {
dependencies {
testImplementation(libs.junit)
testImplementation(libs.mockk)
testImplementation(libs.groovy)

// TODO: use Mockito!
testImplementation("org.easymock:easymock:5.5.0")

// IntelliJ Platform Gradle Plugin Dependencies Extension - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html
intellijPlatform {
Expand Down Expand Up @@ -128,6 +134,10 @@ kover {
}

tasks {
withType<KotlinCompile> {
kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.contracts.ExperimentalContracts"
}

wrapper {
gradleVersion = providers.gradleProperty("gradleVersion").get()
}
Expand Down
6 changes: 6 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ qodana = "2024.3.4"
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
mockk = { group = "io.mockk", name = "mockk", version = "1.13.16" }
mockito-core = { group = "org.mockito", name = "mockito-core", version = "5.15.2" }
mockito-inline = { group = "org.mockito", name = "mockito-inline", version = "5.2.0" }
groovy = { group = "org.codehaus.groovy", name = "groovy-test", version = "3.0.19" } # update if you can!

[bundles]
mockito = ["mockito-core", "mockito-inline"]

[plugins]
changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package com.akefirad.groom.spock

import com.akefirad.groom.spock.SpockSpecUtils.hasAnySpecification
import com.akefirad.groom.spock.SpockSpecUtils.isContinuationLabel
import com.akefirad.groom.spock.SpockSpecUtils.isExpectationLabel
import com.akefirad.groom.spock.SpockSpecUtils.isSpockLabel
import com.intellij.codeInsight.hints.declarative.InlayHintsCollector
import com.akefirad.groom.spock.SpockSpecUtils.isSpeckLabel
import com.intellij.codeInsight.hints.declarative.InlayHintsProvider
import com.intellij.codeInsight.hints.declarative.InlayTreeSink
import com.intellij.codeInsight.hints.declarative.InlineInlayPosition
Expand All @@ -20,53 +17,72 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssign
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod
import org.jetbrains.plugins.groovy.lang.psi.util.isWhiteSpaceOrNewLine

class AssertInlayHintsProvider : InlayHintsProvider, PossiblyDumbAware {

override fun createCollector(file: PsiFile, editor: Editor): InlayHintsCollector? {
if (file.hasAnySpecification() == false) return null
return AssertInlayHintsCollector()
}

override fun isDumbAware() = false
override fun createCollector(file: PsiFile, editor: Editor) =
if (file.hasAnySpecification()) AssertInlayHintsCollector() else null
}

class AssertInlayHintsCollector : SharedBypassCollector {

override fun collectFromElement(element: PsiElement, sink: InlayTreeSink) {
if (element !is GrMethod) return
val body = element.children.find { it is GrOpenBlock } as? GrOpenBlock ?: return
val children = body.children.iterator()
while (children.hasNext()) {
while (children.hasNext() && children.next().isExpectationLabel() == false) continue
collectFromExpectationBlockChildren(children, sink)
}
if (element is GrMethod)
collectFromMethod(element, sink)
}

private fun collectFromExpectationBlockChildren(children: Iterator<PsiElement>, sink: InlayTreeSink) {
class AssertInlayHint : (PresentationTreeBuilder) -> Unit {
override fun invoke(builder: PresentationTreeBuilder) = builder.text("assert")
private fun collectFromMethod(m: GrMethod, sink: InlayTreeSink) {
m.children.forEach {
if (it is GrOpenBlock) collectFromBlock(it, sink)
}
}

while (children.hasNext()) {
val e = children.next()
if (e.isSpockLabel() && e.isContinuationLabel() == false) return
if (e !is GrExpression || e is GrAssignmentExpression) continue
if (e.isInteractionExpression()) continue
if (e.isVerifyAllExpression() || e.isWithExpression()) {
collectFromMethodCallExpression(e, sink)
} else {
val position = InlineInlayPosition(e.textRange.startOffset, relatedToPrevious = false)
sink.addPresentation(position, hasBackground = true, builder = AssertInlayHint())
private fun collectFromBlock(b: GrOpenBlock, sink: InlayTreeSink) {
var label: SpecLabelElement? = null
for (c in b.children) {
if (c.isWhiteSpaceOrNewLine()) {
continue
}

if (c.isSpeckLabel()) {
val current = SpecLabelElement.ofLabel(c)
if (current.isContinuation && label?.isExpectation == true) {

Check notice on line 49 in src/main/kotlin/com/akefirad/groom/spock/AssertInlayHintsProvider.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Constant conditions

Value of 'isExpectation' is always true
if (current.hasTitle == false) {

Check notice on line 50 in src/main/kotlin/com/akefirad/groom/spock/AssertInlayHintsProvider.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Boolean expression can be simplified

Boolean expression can be simplified
collectFromExpectationBlockChildren(c.lastChild, sink)
}
} else if (current.isExpectation) {
if (current.hasTitle == false) {

Check notice on line 54 in src/main/kotlin/com/akefirad/groom/spock/AssertInlayHintsProvider.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Boolean expression can be simplified

Boolean expression can be simplified
collectFromExpectationBlockChildren(c.lastChild, sink)
}
label = current
} else {
label = null
}
} else if (label?.isExpectation == true) {

Check notice on line 61 in src/main/kotlin/com/akefirad/groom/spock/AssertInlayHintsProvider.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Constant conditions

Value of 'isExpectation' is always true
collectFromExpectationBlockChildren(c, sink)
}

}
}

private fun collectFromMethodCallExpression(e: GrExpression, sink: InlayTreeSink) {
val closure = e.lastChild as? GrClosableBlock ?: return
collectFromExpectationBlockChildren(closure.children.iterator(), sink)
private fun collectFromExpectationBlockChildren(e: PsiElement, sink: InlayTreeSink) {
class AssertInlayHint : (PresentationTreeBuilder) -> Unit {
override fun invoke(builder: PresentationTreeBuilder) = builder.text("assert")
}

if (e !is GrExpression || e is GrAssignmentExpression) return
if (e.isSpockInteractionExpression()) return

if (e.isVerifyAllExpression() || e.isWithExpression()) {
val closure = e.lastChild as? GrClosableBlock ?: return
closure.children.forEach { collectFromExpectationBlockChildren(it, sink) }
} else {
val position = InlineInlayPosition(e.textRange.startOffset, relatedToPrevious = false)
sink.addPresentation(position, hasBackground = true, builder = AssertInlayHint())
}
}

private fun GrExpression.isInteractionExpression() = isMethodCallExpression("interaction")
private fun GrExpression.isSpockInteractionExpression() = isMethodCallExpression("interaction")
private fun GrExpression.isVerifyAllExpression() = isMethodCallExpression("verifyAll")
private fun GrExpression.isWithExpression() = isMethodCallExpression("with")

Expand Down
67 changes: 67 additions & 0 deletions src/main/kotlin/com/akefirad/groom/spock/SpecLabelElement.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.akefirad.groom.spock

import com.intellij.openapi.util.TextRange
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrLabeledStatement
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral
import java.util.EnumSet

enum class SpecLabel {
AND,
CLEANUP,
EXPECT,
GIVEN,
SETUP,
THEN,
WHEN,
WHERE,
;

val label = name.lowercase()
val isContinuation get() = this == AND
val isExpectation get() = this == EXPECT || this == THEN

val successors: EnumSet<SpecLabel>
get() = when (this) {
AND -> labels(AND, EXPECT, WHEN, THEN, CLEANUP, WHERE)
CLEANUP -> labels(AND, WHERE)
EXPECT -> labels(AND, WHEN, CLEANUP, WHERE)
GIVEN -> labels(AND, EXPECT, WHEN, CLEANUP, WHERE)
SETUP -> labels(AND, EXPECT, WHEN, CLEANUP, WHERE)
THEN -> labels(AND, EXPECT, WHEN, THEN, CLEANUP, WHERE)
WHEN -> labels(AND, THEN)
WHERE -> labels(AND)
}

override fun toString() = label

companion object {
private fun labels(e: SpecLabel, vararg es: SpecLabel): EnumSet<SpecLabel> = EnumSet.of(e, *es)

fun isLabel(text: String): Boolean = entries.any { it.label == text }

fun ofLabel(text: String): SpecLabel = valueOf(text.uppercase())
}
}

data class SpecLabelElement(val element: GrLabeledStatement) {
val name = SpecLabel.ofLabel(element.name)
val range: TextRange = element.textRange
val title = element.title
val hasTitle = title != null
val isContinuation = name.isContinuation
val isExpectation = name.isExpectation

override fun toString() = "$name${if (hasTitle) ": '$title'" else ""}"

companion object {
@JvmStatic
fun ofLabel(e: GrLabeledStatement): SpecLabelElement {
return SpecLabelElement(e)
}

val GrLabeledStatement.title
get() = if (text.contains('\n')) null else lastChild.let {
if (it is GrLiteral && it.isString) it.value as String else null
}
}
}
Loading

0 comments on commit 914a3f8

Please sign in to comment.