From 38e1d6454a01961c4873af0066bea49c9bcbcfa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3zsef=20Bart=C3=B3k?= Date: Mon, 27 Jan 2025 12:53:38 +0200 Subject: [PATCH] Make it possible to write tests --- gradle/libs.versions.toml | 4 +- lsp/build.gradle.kts | 1 + .../lsp/DeclarativeLanguageServer.kt | 2 +- .../lsp/DeclarativeTextDocumentService.kt | 17 +- .../declarative/lsp/TapiConnectionHandler.kt | 2 +- .../lsp/visitor/DocumentVisitor.kt | 3 +- .../lsp/DeclarativeTextDocumentServiceTest.kt | 199 ++++++++++++++++++ .../VersionedDocumentStoreTest.kt | 3 +- 8 files changed, 218 insertions(+), 13 deletions(-) create mode 100644 lsp/src/test/kotlin/org/gradle/declarative/lsp/DeclarativeTextDocumentServiceTest.kt rename lsp/src/test/kotlin/org/gradle/declarative/lsp/{storage => service}/VersionedDocumentStoreTest.kt (97%) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9ae75d8..0af4182 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,8 +2,8 @@ # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format [versions] -gradle-tooling = "8.12-20241112084018+0000" -declarative-dsl = "8.12-20241112084018+0000" +gradle-tooling = "8.13-20250127002038+0000" +declarative-dsl = "8.13-20250121001720+0000" detekt = "1.23.6" lsp4j = "0.23.1" logback = "1.5.6" diff --git a/lsp/build.gradle.kts b/lsp/build.gradle.kts index bd7f882..68e818e 100644 --- a/lsp/build.gradle.kts +++ b/lsp/build.gradle.kts @@ -36,6 +36,7 @@ dependencies { implementation(libs.gradle.declarative.dsl.tooling.models) testImplementation(libs.mockk) + testImplementation(libs.gradle.tooling.api) } detekt { diff --git a/lsp/src/main/kotlin/org/gradle/declarative/lsp/DeclarativeLanguageServer.kt b/lsp/src/main/kotlin/org/gradle/declarative/lsp/DeclarativeLanguageServer.kt index b313332..cc68af4 100644 --- a/lsp/src/main/kotlin/org/gradle/declarative/lsp/DeclarativeLanguageServer.kt +++ b/lsp/src/main/kotlin/org/gradle/declarative/lsp/DeclarativeLanguageServer.kt @@ -103,7 +103,7 @@ class DeclarativeLanguageServer : LanguageServer, LanguageClientAware { LOGGER.info("Fetching declarative model for workspace folder: $workspaceFolderFile") TapiConnectionHandler(workspaceFolderFile).let { - val declarativeResources = it.getDomPrequisites() + val declarativeResources = it.getDeclarativeResources() // Create services shared between the LSP services val documentStore = VersionedDocumentStore() diff --git a/lsp/src/main/kotlin/org/gradle/declarative/lsp/DeclarativeTextDocumentService.kt b/lsp/src/main/kotlin/org/gradle/declarative/lsp/DeclarativeTextDocumentService.kt index fa5be6c..378c3ac 100644 --- a/lsp/src/main/kotlin/org/gradle/declarative/lsp/DeclarativeTextDocumentService.kt +++ b/lsp/src/main/kotlin/org/gradle/declarative/lsp/DeclarativeTextDocumentService.kt @@ -16,8 +16,6 @@ package org.gradle.declarative.lsp -import org.eclipse.lsp4j.ClientCapabilities -import org.eclipse.lsp4j.ClientInfo import org.eclipse.lsp4j.CodeAction import org.eclipse.lsp4j.CodeActionParams import org.eclipse.lsp4j.Command @@ -42,7 +40,14 @@ import org.eclipse.lsp4j.SignatureInformation import org.eclipse.lsp4j.jsonrpc.messages.Either import org.eclipse.lsp4j.services.LanguageClient import org.eclipse.lsp4j.services.TextDocumentService -import org.gradle.declarative.dsl.schema.* +import org.gradle.declarative.dsl.schema.AnalysisSchema +import org.gradle.declarative.dsl.schema.DataClass +import org.gradle.declarative.dsl.schema.DataParameter +import org.gradle.declarative.dsl.schema.DataType +import org.gradle.declarative.dsl.schema.DataTypeRef +import org.gradle.declarative.dsl.schema.EnumClass +import org.gradle.declarative.dsl.schema.FunctionSemantics +import org.gradle.declarative.dsl.schema.SchemaFunction import org.gradle.declarative.lsp.build.model.DeclarativeResourcesModel import org.gradle.declarative.lsp.extension.indexBasedOverlayResultFromDocuments import org.gradle.declarative.lsp.extension.toLspRange @@ -168,12 +173,13 @@ class DeclarativeTextDocumentService : TextDocumentService { val completions = params?.let { param -> val uri = URI(param.textDocument.uri) withDom(uri) { dom, schema, _ -> - dom.document.visit( + val bestFittingNode = dom.document.visit( BestFittingNodeVisitor( params.position, DeclarativeDocument.DocumentNode.ElementNode::class ) ).bestFittingNode + bestFittingNode ?.getDataClass(dom.overlayResolutionContainer) .let { it ?: schema.topLevelReceiverType } .let { dataClass -> @@ -411,7 +417,8 @@ private fun computeCompletionInsertText( } /** - * Computes a [placeholder](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#placeholders) based on the given data type. + * Computes a [placeholder](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#placeholders) + * based on the given data type. * * If there is a specific placeholder for the given data type, it will be used. * Otherwise, a simple indexed will be used diff --git a/lsp/src/main/kotlin/org/gradle/declarative/lsp/TapiConnectionHandler.kt b/lsp/src/main/kotlin/org/gradle/declarative/lsp/TapiConnectionHandler.kt index 5ee49f5..d2c2c4a 100644 --- a/lsp/src/main/kotlin/org/gradle/declarative/lsp/TapiConnectionHandler.kt +++ b/lsp/src/main/kotlin/org/gradle/declarative/lsp/TapiConnectionHandler.kt @@ -29,7 +29,7 @@ private val LOGGER = LoggerFactory.getLogger(TapiConnectionHandler::class.java) class TapiConnectionHandler(val projectRoot: File): ProgressListener { - fun getDomPrequisites(): DeclarativeResourcesModel { + fun getDeclarativeResources(): DeclarativeResourcesModel { var connection: ProjectConnection? = null try { connection = GradleConnector diff --git a/lsp/src/main/kotlin/org/gradle/declarative/lsp/visitor/DocumentVisitor.kt b/lsp/src/main/kotlin/org/gradle/declarative/lsp/visitor/DocumentVisitor.kt index 5e97055..9dc4c9f 100644 --- a/lsp/src/main/kotlin/org/gradle/declarative/lsp/visitor/DocumentVisitor.kt +++ b/lsp/src/main/kotlin/org/gradle/declarative/lsp/visitor/DocumentVisitor.kt @@ -75,8 +75,7 @@ fun DeclarativeDocument.visit(visitor: T): T { is DeclarativeDocument.ValueNode.ValueFactoryNode -> visitor.visitValueFactoryNode(node) else -> {} } - } - is DeclarativeDocument.DocumentNode -> { + } else -> { visitor.visitDocumentNode(node) when (node) { is DeclarativeDocument.DocumentNode.ElementNode -> visitor.visitDocumentElementNode(node) diff --git a/lsp/src/test/kotlin/org/gradle/declarative/lsp/DeclarativeTextDocumentServiceTest.kt b/lsp/src/test/kotlin/org/gradle/declarative/lsp/DeclarativeTextDocumentServiceTest.kt new file mode 100644 index 0000000..c6867fd --- /dev/null +++ b/lsp/src/test/kotlin/org/gradle/declarative/lsp/DeclarativeTextDocumentServiceTest.kt @@ -0,0 +1,199 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.declarative.lsp + +import io.mockk.mockk +import org.eclipse.lsp4j.CompletionParams +import org.eclipse.lsp4j.DidOpenTextDocumentParams +import org.eclipse.lsp4j.Position +import org.eclipse.lsp4j.TextDocumentIdentifier +import org.eclipse.lsp4j.TextDocumentItem +import org.eclipse.lsp4j.services.LanguageClient +import org.gradle.declarative.lsp.build.model.DeclarativeResourcesModel +import org.gradle.declarative.lsp.service.MutationRegistry +import org.gradle.declarative.lsp.service.VersionedDocumentStore +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import java.io.File +import java.nio.file.Path +import kotlin.io.path.Path +import kotlin.io.path.createDirectories +import kotlin.io.path.readLines +import kotlin.io.path.readText +import kotlin.io.path.writeText +import kotlin.test.assertEquals + +class DeclarativeTextDocumentServiceTest { + + @field:TempDir + lateinit var buildFolder: File + + private lateinit var service: DeclarativeTextDocumentService + + private lateinit var settingsFile: Path + + @BeforeEach + fun setup() { + settingsFile = Path("$buildFolder/settings.gradle.dcl") + + val declarativeResources = setupGradleBuild(buildFolder) + + service = DeclarativeTextDocumentService() + service.initialize( + mockk(relaxed = true), + VersionedDocumentStore(), + MutationRegistry(declarativeResources, emptyList()), + DeclarativeFeatures(), + declarativeResources + ) + service.didOpen(DidOpenTextDocumentParams().apply { + textDocument = TextDocumentItem().apply { + uri = settingsFile.toUri().toString() + text = settingsFile.readText() + } + }) + } + + @Test + fun `code completion`() { + val completionParams = CompletionParams().apply { + textDocument = TextDocumentIdentifier(settingsFile.toUri().toString()) + position = Position(32, 16) + } + + assertEquals( + listOf( + " compileOptions {", + " sourceCompatibility = VERSION_17", + " targetCompatibility = VERSION_17", + " }" + ), + settingsFile.readLines().slice(31..34) + ) + + assertEquals( + listOf( + "encoding = String", + "isCoreLibraryDesugaringEnabled = Boolean", + "sourceCompatibility = JavaVersion", + "targetCompatibility = JavaVersion" + ), + service.completion(completionParams).get().left.map { it.label } + ) + } + + + @Suppress("LongMethod") + private fun setupGradleBuild(dir: File): DeclarativeResourcesModel { + Path("$dir/settings.gradle.dcl").writeText( + """ + pluginManagement { + repositories { + google() + mavenCentral() + maven { + url = uri("https://androidx.dev/studio/builds/12648882/artifacts/artifacts/repository") + } + } + } + + plugins { + id("com.android.ecosystem").version("8.9.0-dev") + } + + dependencyResolutionManagement { + repositories { + google() + mavenCentral() + maven { + url = uri("https://androidx.dev/studio/builds/12648882/artifacts/artifacts/repository") + } + } + } + + rootProject.name = "example-android-app" + + include("app") + + defaults { + androidApp { + compileSdk = 34 + compileOptions { + sourceCompatibility = VERSION_17 + targetCompatibility = VERSION_17 + } + defaultConfig { + minSdk = 30 + versionCode = 1 + versionName = "0.1" + applicationId = "org.gradle.experimental.android.app" + } + dependenciesDcl { + implementation("org.jetbrains.kotlin:kotlin-stdlib:2.0.21") + } + } + + androidLibrary { + compileSdk = 34 + compileOptions { + sourceCompatibility = VERSION_17 + targetCompatibility = VERSION_17 + } + defaultConfig { + minSdk = 30 + } + dependenciesDcl { + implementation("org.jetbrains.kotlin:kotlin-stdlib:2.0.21") + } + } + } + """.trimIndent() + ) + Path("$dir/app/").createDirectories().resolve("build.gradle.dcl").writeText( + """ + androidApp { + namespace = "org.example.app" + } + """.trimIndent() + ) + Path("$dir/app/src/main/kotlin/org/example/app/").createDirectories().resolve("MainActivity.kt").writeText( + """ + package org.example.app + + import org.apache.commons.text.WordUtils + + import android.widget.TextView + import android.os.Bundle + import android.app.Activity + + class MainActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + val textView = findViewById(R.id.textView) as TextView + textView.text = "Hello, World!" + } + } + """.trimIndent() + ) + + return TapiConnectionHandler(dir).getDeclarativeResources() + } + +} \ No newline at end of file diff --git a/lsp/src/test/kotlin/org/gradle/declarative/lsp/storage/VersionedDocumentStoreTest.kt b/lsp/src/test/kotlin/org/gradle/declarative/lsp/service/VersionedDocumentStoreTest.kt similarity index 97% rename from lsp/src/test/kotlin/org/gradle/declarative/lsp/storage/VersionedDocumentStoreTest.kt rename to lsp/src/test/kotlin/org/gradle/declarative/lsp/service/VersionedDocumentStoreTest.kt index ec9da9b..a426b8e 100644 --- a/lsp/src/test/kotlin/org/gradle/declarative/lsp/storage/VersionedDocumentStoreTest.kt +++ b/lsp/src/test/kotlin/org/gradle/declarative/lsp/service/VersionedDocumentStoreTest.kt @@ -14,11 +14,10 @@ * limitations under the License. */ -package org.gradle.declarative.lsp.storage +package org.gradle.declarative.lsp.service import io.mockk.mockk import org.gradle.declarative.dsl.schema.AnalysisSchema -import org.gradle.declarative.lsp.service.VersionedDocumentStore import org.gradle.internal.declarativedsl.dom.operations.overlay.DocumentOverlayResult import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotNull