Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

GH-41 refactor major components #42

Merged
merged 1 commit into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.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
Loading