Skip to content

Commit

Permalink
Add value factories to property value completions
Browse files Browse the repository at this point in the history
  • Loading branch information
jbartok committed Feb 3, 2025
1 parent 38e1d64 commit cab7378
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 68 deletions.
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format

[versions]
gradle-tooling = "8.13-20250127002038+0000"
declarative-dsl = "8.13-20250121001720+0000"
gradle-tooling = "8.13-20250128002155+0000"
declarative-dsl = "8.13-20250128002155+0000"
detekt = "1.23.6"
lsp4j = "0.23.1"
logback = "1.5.6"
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions-snapshots/gradle-8.13-20250128002155+0000-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ 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.FqName
import org.gradle.declarative.dsl.schema.FunctionSemantics
import org.gradle.declarative.dsl.schema.SchemaFunction
import org.gradle.declarative.lsp.build.model.DeclarativeResourcesModel
Expand Down Expand Up @@ -428,12 +429,40 @@ private fun computeTypedPlaceholder(
type: DataTypeRef,
analysisSchema: AnalysisSchema
): String {
fun indexValueFactories(analysisSchema: AnalysisSchema, type: DataClass, namePrefix: String): Map<FqName, List<String>> {
val factoryIndex = mutableMapOf<FqName, List<String>>()
type.memberFunctions
.filter { it.semantics is FunctionSemantics.Pure && it.returnValueType is DataTypeRef.Name}
.forEach {
val indexKey = (it.returnValueType as DataTypeRef.Name).fqName
factoryIndex.merge(indexKey, listOf("$namePrefix${it.simpleName}()")) { oldVal, newVal -> oldVal + newVal }
}
type.properties.filter {it.valueType is DataTypeRef.Name}.forEach {
when(val propType = analysisSchema.dataClassTypesByFqName[(it.valueType as DataTypeRef.Name).fqName]) {
is DataClass -> {
val propName = it.name
val propIndex = indexValueFactories(analysisSchema, propType, "$namePrefix${propName}.")
propIndex.forEach { (key, value) ->
factoryIndex.merge(key, value) { oldVal, newVal -> oldVal + newVal}
}
}
is EnumClass -> Unit
null -> Unit
}
}
return factoryIndex
}

return when (val resolvedType = SchemaTypeRefContext(analysisSchema).resolveRef(type)) {
is DataType.BooleanDataType -> "\${$index|true,false|}"
is EnumClass -> "\${$index|${resolvedType.entryNames.joinToString(",")}|}"
is DataType.IntDataType -> "\${$index:0}"
is DataType.LongDataType -> "\${$index:0L}"
is DataType.StringDataType -> "\"\${$index}\""
is DataType.ClassDataType -> {
val factories = indexValueFactories(analysisSchema, analysisSchema.topLevelReceiverType, "")[resolvedType.name]
factories?.joinToString(prefix = "\${\$index|", separator = ",", postfix = "|}") ?: "\$$index"
}
else -> "\$$index"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import kotlin.io.path.readText
import kotlin.io.path.writeText
import kotlin.test.assertEquals

@Suppress("MaxLineLength")
class DeclarativeTextDocumentServiceTest {

@field:TempDir
Expand All @@ -46,10 +47,12 @@ class DeclarativeTextDocumentServiceTest {
private lateinit var service: DeclarativeTextDocumentService

private lateinit var settingsFile: Path
private lateinit var buildFile: Path

@BeforeEach
fun setup() {
settingsFile = Path("$buildFolder/settings.gradle.dcl")
buildFile = Path("$buildFolder/app/build.gradle.dcl")

val declarativeResources = setupGradleBuild(buildFolder)

Expand All @@ -61,114 +64,154 @@ class DeclarativeTextDocumentServiceTest {
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)
}
fun `code completion inside dependencies block`() {
val script = settingsFile
openFile(script)

assertEquals(
listOf(
" compileOptions {",
" sourceCompatibility = VERSION_17",
" targetCompatibility = VERSION_17",
" }"
" dependencies {",
" implementation(\"org.junit.jupiter:junit-jupiter:5.10.2\")",
" runtimeOnly(\"org.junit.platform:junit-platform-launcher\")",
" }",
),
settingsFile.readLines().slice(31..34)
script.readLines().slice(26..29)
)

assertCompletion(
script, 27, 15, listOf(
"""androidImplementation(dependency: ProjectDependency), androidImplementation(${'$'}1)${'$'}0""",
"""androidImplementation(dependency: String), androidImplementation("${'$'}{1}")${'$'}0""",
"""compileOnly(dependency: ProjectDependency), compileOnly(${'$'}1)${'$'}0""",
"""compileOnly(dependency: String), compileOnly("${'$'}{1}")${'$'}0""",
"""implementation(dependency: ProjectDependency), implementation(${'$'}1)${'$'}0""",
"""implementation(dependency: String), implementation("${'$'}{1}")${'$'}0""",
"""project(projectPath: String), project("${'$'}{1}")${'$'}0""",
"""runtimeOnly(dependency: ProjectDependency), runtimeOnly(${'$'}1)${'$'}0""",
"""runtimeOnly(dependency: String), runtimeOnly("${'$'}{1}")${'$'}0""",
)
)
}

@Test
fun `code completion inside block with properties`() {
val script = buildFile
openFile(script)

assertEquals(
listOf(
"encoding = String",
"isCoreLibraryDesugaringEnabled = Boolean",
"sourceCompatibility = JavaVersion",
"targetCompatibility = JavaVersion"
"androidLibrary {",
" secrets {",
" defaultPropertiesFile = layout.settingsDirectory.file(\"somefile\")",
" }",
"}",
),
service.completion(completionParams).get().left.map { it.label }
script.readLines().slice(3..7)
)

assertCompletion(
script, 5, 16, listOf(
"defaultPropertiesFile = RegularFile, defaultPropertiesFile = \${\$index|layout.projectDirectory.file(),layout.settingsDirectory.file()|}",
"enabled = Boolean, enabled = \${1|true,false|}"
)
)
}

private fun assertCompletion(script: Path, line: Int, column: Int, expectedCompletions: List<String>) {
val actualCompletionItems = service.completion(completionParams(script, line, column)).get().left
assertEquals(
expectedCompletions.sorted(),
actualCompletionItems.map { "${it.label}, ${it.insertText}" }.sorted()
)
}

private fun openFile(script: Path) {
service.didOpen(DidOpenTextDocumentParams().apply {
textDocument = TextDocumentItem().apply {
uri = script.toUri().toString()
text = script.readText()
}
})
}

private fun completionParams(script: Path, line: Int, column: Int): CompletionParams {
val completionParams = CompletionParams().apply {
textDocument = TextDocumentIdentifier(script.toUri().toString())
position = Position(line, column)
}
return completionParams
}

@Suppress("LongMethod")
private fun setupGradleBuild(dir: File): DeclarativeResourcesModel {
Path("$dir/settings.gradle.dcl").writeText(
settingsFile.writeText(
"""
pluginManagement {
repositories {
google()
mavenCentral()
maven {
url = uri("https://androidx.dev/studio/builds/12648882/artifacts/artifacts/repository")
}
google() // Needed for the Android plugin, applied by the unified plugin
gradlePluginPortal()
}
}
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")
}
}
id("org.gradle.experimental.android-ecosystem").version("0.1.37")
}
rootProject.name = "example-android-app"
include("app")
defaults {
androidApp {
androidApplication {
jdkVersion = 17
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")
minSdk = 30
versionCode = 1
versionName = "0.1"
applicationId = "org.gradle.experimental.android.app"
testing {
dependencies {
implementation("org.junit.jupiter:junit-jupiter:5.10.2")
runtimeOnly("org.junit.platform:junit-platform-launcher")
}
}
}
androidLibrary {
jdkVersion = 17
compileSdk = 34
compileOptions {
sourceCompatibility = VERSION_17
targetCompatibility = VERSION_17
}
defaultConfig {
minSdk = 30
}
dependenciesDcl {
implementation("org.jetbrains.kotlin:kotlin-stdlib:2.0.21")
minSdk = 30
testing {
dependencies {
implementation("org.junit.jupiter:junit-jupiter:5.10.2")
runtimeOnly("org.junit.platform:junit-platform-launcher")
}
}
}
}
""".trimIndent()
)
Path("$dir/gradle").createDirectories().resolve("gradle-daemon-jvm.properties").writeText(
"""
toolchainVersion=17
""".trimIndent()
)
Path("$dir/app/").createDirectories().resolve("build.gradle.dcl").writeText(
"""
androidApp {
androidApplication {
namespace = "org.example.app"
}
androidLibrary {
secrets {
defaultPropertiesFile = layout.settingsDirectory.file("somefile")
}
}
""".trimIndent()
)
Path("$dir/app/src/main/kotlin/org/example/app/").createDirectories().resolve("MainActivity.kt").writeText(
Expand All @@ -184,10 +227,6 @@ class DeclarativeTextDocumentServiceTest {
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()
Expand Down

0 comments on commit cab7378

Please sign in to comment.