From d304792c037b068429d84132d29ec679097e99ef Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Thu, 14 Nov 2024 13:53:52 +0300 Subject: [PATCH] Proper support for `settings.gradle.dcl` * Copy and reuse utilities from `gradle-client` for merging analysis results from multiple interpretation sequence steps. * Produce an approximate union schema for type lookup in `settings.gradle.dcl` where there is more than one schema from the multiple interpretation sequence steps. * Track the analysis schemas along with the documents in the language server state. * Use the schema's top-level receiver type for completion at the top-level when outside any matching element. * Simplify the `DeclarativeResourcesModel`, as the schemas and files are actually tracked by the DCL analysis utilities. --- .../lsp/DeclarativeTextDocumentService.kt | 67 ++++----- .../lsp/extension/AnalysisSchemaExtensions.kt | 88 +++++++++++- .../extension/ResolutionResultExtensions.kt | 127 ++++++++++++++++++ .../lsp/service/MutationRegistry.kt | 5 +- .../lsp/service/VersionedDocumentStore.kt | 25 +++- .../lsp/storage/VersionedDocumentStoreTest.kt | 29 ++-- .../action/GetDeclarativeResourcesModel.java | 57 +------- .../model/DeclarativeResourcesModel.java | 8 -- 8 files changed, 296 insertions(+), 110 deletions(-) create mode 100644 lsp/src/main/kotlin/org/gradle/declarative/lsp/extension/ResolutionResultExtensions.kt 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 578f508..efdb441 100644 --- a/lsp/src/main/kotlin/org/gradle/declarative/lsp/DeclarativeTextDocumentService.kt +++ b/lsp/src/main/kotlin/org/gradle/declarative/lsp/DeclarativeTextDocumentService.kt @@ -42,6 +42,7 @@ import org.eclipse.lsp4j.services.LanguageClient import org.eclipse.lsp4j.services.TextDocumentService import org.gradle.declarative.dsl.schema.* import org.gradle.declarative.lsp.build.model.DeclarativeResourcesModel +import org.gradle.declarative.lsp.extension.indexBasedOverlayResultFromDocuments import org.gradle.declarative.lsp.extension.toLspRange import org.gradle.declarative.lsp.service.MutationRegistry import org.gradle.declarative.lsp.service.VersionedDocumentStore @@ -53,11 +54,9 @@ import org.gradle.internal.declarativedsl.analysis.SchemaTypeRefContext import org.gradle.internal.declarativedsl.dom.DeclarativeDocument import org.gradle.internal.declarativedsl.dom.DocumentResolution import org.gradle.internal.declarativedsl.dom.mutation.MutationParameterKind -import org.gradle.internal.declarativedsl.dom.operations.overlay.DocumentOverlay import org.gradle.internal.declarativedsl.dom.operations.overlay.DocumentOverlayResult import org.gradle.internal.declarativedsl.dom.resolution.DocumentResolutionContainer import org.gradle.internal.declarativedsl.evaluator.main.AnalysisDocumentUtils.resolvedDocument -import org.gradle.internal.declarativedsl.evaluator.main.AnalysisSequenceResult import org.gradle.internal.declarativedsl.evaluator.main.SimpleAnalysisEvaluator import org.gradle.internal.declarativedsl.evaluator.runner.stepResultOrPartialResult import org.slf4j.LoggerFactory @@ -72,7 +71,6 @@ class DeclarativeTextDocumentService : TextDocumentService { private lateinit var documentStore: VersionedDocumentStore private lateinit var mutationRegistry: MutationRegistry private lateinit var declarativeResources: DeclarativeResourcesModel - private lateinit var analysisSchema: AnalysisSchema private lateinit var schemaAnalysisEvaluator: SimpleAnalysisEvaluator fun initialize( @@ -85,8 +83,7 @@ class DeclarativeTextDocumentService : TextDocumentService { this.documentStore = documentStore this.mutationRegistry = mutationRegistry this.declarativeResources = declarativeResources - - this.analysisSchema = declarativeResources.analysisSchema + this.schemaAnalysisEvaluator = SimpleAnalysisEvaluator.withSchema( declarativeResources.settingsInterpretationSequence, declarativeResources.projectInterpretationSequence @@ -100,9 +97,9 @@ class DeclarativeTextDocumentService : TextDocumentService { params?.let { val uri = URI(it.textDocument.uri) val text = it.textDocument.text - val dom = parse(uri, text) + val parsed = parse(uri, text) run { - documentStore.storeInitial(uri, text, dom) + documentStore.storeInitial(uri, text, parsed.documentOverlayResult, parsed.analysisSchemas) processDocument(uri) } } @@ -115,8 +112,8 @@ class DeclarativeTextDocumentService : TextDocumentService { it.contentChanges.forEach { change -> val version = it.textDocument.version val text = change.text - val dom = parse(uri, change.text) - documentStore.storeVersioned(uri, version, text, dom) + val parsed = parse(uri, change.text) + documentStore.storeVersioned(uri, version, text, parsed.documentOverlayResult, parsed.analysisSchemas) processDocument(uri) } } @@ -138,7 +135,7 @@ class DeclarativeTextDocumentService : TextDocumentService { LOGGER.trace("Hover requested for position: {}", params) val hover = params?.let { val uri = URI(it.textDocument.uri) - withDom(uri) { dom, _ -> + withDom(uri) { dom, _, _ -> // LSPs are supplying 0-based line and column numbers, while the DSL model is 1-based val visitor = BestFittingNodeVisitor( params.position, @@ -163,9 +160,9 @@ class DeclarativeTextDocumentService : TextDocumentService { override fun completion(params: CompletionParams?): CompletableFuture, CompletionList>> { LOGGER.trace("Completion requested for position: {}", params) - val completions = params?.let { - val uri = URI(it.textDocument.uri) - withDom(uri) { dom, _ -> + val completions = params?.let { param -> + val uri = URI(param.textDocument.uri) + withDom(uri) { dom, schema, _ -> dom.document.visit( BestFittingNodeVisitor( params.position, @@ -173,9 +170,10 @@ class DeclarativeTextDocumentService : TextDocumentService { ) ).bestFittingNode ?.getDataClass(dom.overlayResolutionContainer) - ?.let { dataClass -> - computePropertyCompletions(dataClass, analysisSchema) + - computeFunctionCompletions(dataClass, analysisSchema) + .let { it ?: schema.topLevelReceiverType } + .let { dataClass -> + computePropertyCompletions(dataClass, schema) + + computeFunctionCompletions(dataClass, schema) } } }.orEmpty().toMutableList() @@ -187,7 +185,7 @@ class DeclarativeTextDocumentService : TextDocumentService { val signatureInformationList = params?.let { val uri = URI(it.textDocument.uri) - withDom(uri) { dom, _ -> + withDom(uri) { dom, _, _ -> val position = it.position val matchingNodes = dom.document.visit( BestFittingNodeVisitor( @@ -252,10 +250,10 @@ class DeclarativeTextDocumentService : TextDocumentService { // Utility and other member functions ------------------------------------------------------------------------------ - private fun processDocument(uri: URI) = withDom(uri) { dom, _ -> + private fun processDocument(uri: URI) = withDom(uri) { dom, schema, _ -> reportSyntaxErrors(uri, dom) reportSemanticErrors(uri, dom) - mutationRegistry.registerDocument(uri, dom.result) + mutationRegistry.registerDocument(uri, schema, dom.result) } /** @@ -294,30 +292,37 @@ class DeclarativeTextDocumentService : TextDocumentService { ) ) } + + data class ParsedDocument( + val documentOverlayResult: DocumentOverlayResult, + val analysisSchemas: List + ) - private fun parse(uri: URI, text: String): DocumentOverlayResult { - fun AnalysisSequenceResult.lastStepDocument() = - stepResults.values.last().stepResultOrPartialResult.resolvedDocument() - + private fun parse(uri: URI, text: String): ParsedDocument { val fileName = uri.path.substringAfterLast('/') - val document = schemaAnalysisEvaluator.evaluate(fileName, text).lastStepDocument() + val analysisResult = schemaAnalysisEvaluator.evaluate(fileName, text) // Workaround: for now, the mutation utilities cannot handle mutations that touch the underlay document content. - // To avoid that, use an empty document as an underlay instead of the real document produced from the - // settings file. + // To avoid that, use the utility that produces an overlay result with no real underlay content. + // This utility also takes care of multi-step resolution results and merges them, presenting . // TODO: carry both the real overlay and the document produced from just the current file, run the mutations // against the latter for now. // TODO: once the mutation utilities start handling mutations across the overlay, pass them the right overlay. - val emptyUnderlay = schemaAnalysisEvaluator.evaluate("empty-underlay/build.gradle.dcl", "").lastStepDocument() - + val overlay = indexBasedOverlayResultFromDocuments( + analysisResult.stepResults.map { it.value.stepResultOrPartialResult.resolvedDocument() } + ) + LOGGER.trace("Parsed declarative model for document: {}", uri) - return DocumentOverlay.overlayResolvedDocuments(emptyUnderlay, document) + return ParsedDocument( + overlay, + analysisResult.stepResults.map { it.key.evaluationSchemaForStep.analysisSchema } + ) } - private fun withDom(uri: URI, work: (DocumentOverlayResult, String) -> T): T? { + private fun withDom(uri: URI, work: (DocumentOverlayResult, AnalysisSchema, String) -> T): T? { return documentStore[uri]?.let { entry -> - work(entry.dom, entry.document) + work(entry.dom, entry.unionSchema, entry.document) } } diff --git a/lsp/src/main/kotlin/org/gradle/declarative/lsp/extension/AnalysisSchemaExtensions.kt b/lsp/src/main/kotlin/org/gradle/declarative/lsp/extension/AnalysisSchemaExtensions.kt index 7a06131..6c68d44 100644 --- a/lsp/src/main/kotlin/org/gradle/declarative/lsp/extension/AnalysisSchemaExtensions.kt +++ b/lsp/src/main/kotlin/org/gradle/declarative/lsp/extension/AnalysisSchemaExtensions.kt @@ -1,6 +1,10 @@ package org.gradle.declarative.lsp.extension -import org.gradle.declarative.dsl.schema.AnalysisSchema +import org.gradle.declarative.dsl.schema.* +import org.gradle.internal.declarativedsl.analysis.DefaultAnalysisSchema +import org.gradle.internal.declarativedsl.analysis.DefaultDataClass +import org.gradle.internal.declarativedsl.analysis.DefaultEnumClass +import org.gradle.internal.declarativedsl.analysis.DefaultFqName /* * Copyright 2024 the original author or authors. @@ -24,4 +28,84 @@ inline fun AnalysisSchema.findType(name: String): T? = dataClassType dataClass.name.qualifiedName == name && dataClass is T }?.let { it as T - } \ No newline at end of file + } + + +/** + * Produces an [AnalysisSchema] that approximates the [schemas] merged together. + * Namely, it has [AnalysisSchema.dataClassTypesByFqName] from all the schemas, and if a type appears in more than + * one of the schemas, its contents get merged, too. + * + * The top level receiver is either the merged type from the [AnalysisSchema.topLevelReceiverType]s from the schemas, if + * it has the same name in all of them, or a type with a synthetic name that has the content from the top level + * receiver types from [schemas]. + */ +fun unionAnalysisSchema(schemas: List): AnalysisSchema = if (schemas.size == 1) + schemas.single() +else { + fun mergeDataClasses(newName: String?, dataClasses: List): DataClass { + // Can't have properties with the same name but different types anyway: + val properties = dataClasses.flatMap { it.properties }.distinctBy { it.name } + + val functions = dataClasses.flatMap { it.memberFunctions } + .distinctBy { listOf(it.simpleName) + it.parameters.map { it.name to typeIdentityName(it.type) } } + + val constructors = + dataClasses.flatMap { it.constructors }.distinctBy { it.parameters.map { typeIdentityName(it.type) } } + + val supertypes = dataClasses.flatMap { it.supertypes }.toSet() + + return DefaultDataClass( + newName?.let { DefaultFqName.parse(it) } ?: dataClasses.first().name, + dataClasses.first().javaTypeName, + dataClasses.first().javaTypeArgumentTypeNames, + supertypes, + properties, + functions, + constructors + ) + } + + val dataClassesByFqName = run { + fun mergeEnums(enumTypes: List): EnumClass = + DefaultEnumClass( + enumTypes.first().name, + enumTypes.first().javaTypeName, + enumTypes.flatMap { it.entryNames }.distinct() + ) + + schemas.flatMap { it.dataClassTypesByFqName.values }.groupBy { it.name } + .mapValues { (_, dataClasses) -> + when { + dataClasses.all { it is DataClass } -> mergeDataClasses(null, dataClasses.map { it as DataClass }) + dataClasses.all { it is EnumClass } -> mergeEnums(dataClasses.map { it as EnumClass }) + else -> error("mixed enum and data classes") + } + } + } + + val newTopLevelReceiver = run { + val topLevelReceivers = schemas.map { it.topLevelReceiverType } + if (topLevelReceivers.map { it.name.qualifiedName }.distinct().size == 1) { + dataClassesByFqName.getValue(topLevelReceivers.first().name) as DataClass + } else { + mergeDataClasses("\$top-level-receiver\$", topLevelReceivers) + } + } + + DefaultAnalysisSchema( + newTopLevelReceiver, + dataClassesByFqName, + emptyMap(), + emptyMap(), + emptySet() + ) +} + +private fun typeIdentityName(typeRef: DataTypeRef) = when (typeRef) { + is DataTypeRef.Name -> typeRef.fqName.qualifiedName + is DataTypeRef.Type -> when (val type = typeRef.dataType) { + is DataType.ClassDataType -> type.name.qualifiedName + else -> type.toString() + } +} \ No newline at end of file diff --git a/lsp/src/main/kotlin/org/gradle/declarative/lsp/extension/ResolutionResultExtensions.kt b/lsp/src/main/kotlin/org/gradle/declarative/lsp/extension/ResolutionResultExtensions.kt new file mode 100644 index 0000000..6392f9f --- /dev/null +++ b/lsp/src/main/kotlin/org/gradle/declarative/lsp/extension/ResolutionResultExtensions.kt @@ -0,0 +1,127 @@ +/* + * Copyright 2024 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.extension + +import org.gradle.internal.declarativedsl.dom.DeclarativeDocument +import org.gradle.internal.declarativedsl.dom.DocumentResolution +import org.gradle.internal.declarativedsl.dom.UnresolvedBase +import org.gradle.internal.declarativedsl.dom.operations.overlay.DocumentOverlay.overlayResolvedDocuments +import org.gradle.internal.declarativedsl.dom.operations.overlay.DocumentOverlayResult +import org.gradle.internal.declarativedsl.dom.resolution.DocumentResolutionContainer +import org.gradle.internal.declarativedsl.dom.resolution.DocumentWithResolution +import org.gradle.internal.declarativedsl.language.SourceData +import org.gradle.internal.declarativedsl.language.SyntheticallyProduced + +/** + * Utilities copied from `gradle-client`. + * TODO: expose some of them, or their replacements, in the Gradle DCL libs. + */ + +/** + * Packs multiple instances of the same document with different resolution results into a [DocumentOverlayResult] in a + * way that they appear as a single document (all in the overlay, no underlay part). The resolution results get merged + * so that if any of the resolution results container has [UnresolvedBase] for a part of the document, it is checked + * against the other resolution result containers. + */ +internal fun indexBasedOverlayResultFromDocuments(docs: List): DocumentOverlayResult { + val emptyDoc = DocumentWithResolution( + object : DeclarativeDocument { + override val content: List = emptyList() + override val sourceData: SourceData = SyntheticallyProduced + }, + indexBasedMultiResolutionContainer(emptyList()) + ) + + val lastDocWithAllResolutionResults = DocumentWithResolution( + docs.last().document, + indexBasedMultiResolutionContainer(docs) + ) + + /** + * NB: No real overlay origin data is going to be present, as we are overlaying the doc with all the resolution + * results collected over the empty document. + */ + return overlayResolvedDocuments(emptyDoc, lastDocWithAllResolutionResults) +} + +/** + * A resolution results container collected from multiple resolved instances of the same document (or multiple + * different instances of the same document, no referential equality required). + * + * The document parts are matched based on indices. + * + * If any of the [docs] is different from the others, the result is undefined (likely to be a broken container). + */ +internal fun indexBasedMultiResolutionContainer(docs: List): DocumentResolutionContainer { + val indicesMaps: Map> = docs.associateWith { + buildMap { + fun visitValue(valueNode: DeclarativeDocument.ValueNode) { + put(valueNode.sourceData.indexRange, valueNode) + when (valueNode) { + is DeclarativeDocument.ValueNode.ValueFactoryNode -> valueNode.values.forEach(::visitValue) + is DeclarativeDocument.ValueNode.LiteralValueNode, + is DeclarativeDocument.ValueNode.NamedReferenceNode -> Unit + } + } + + fun visitDocumentNode(documentNode: DeclarativeDocument.DocumentNode) { + put(documentNode.sourceData.indexRange, documentNode) + when (documentNode) { + is DeclarativeDocument.DocumentNode.ElementNode -> { + documentNode.elementValues.forEach(::visitValue) + documentNode.content.forEach(::visitDocumentNode) + } + + is DeclarativeDocument.DocumentNode.PropertyNode -> visitValue(documentNode.value) + is DeclarativeDocument.DocumentNode.ErrorNode -> Unit + } + } + + it.document.content.forEach(::visitDocumentNode) + } + } + + /** + * The resolution containers work with node identities. + * Querying resolution results using nodes from a different document is prohibited. + * Given that all documents are the same, we can map the node indices and use them to find matching nodes in + * the documents that we are merging. + */ + return object : DocumentResolutionContainer { + inline fun retryOverContainers( + node: N, + noinline get: DocumentResolutionContainer.(N) -> T + ) = docs.map { doc -> + val matchingNode = indicesMaps.getValue(doc)[node.sourceData.indexRange] + ?: error("index not found in index map") + get(doc.resolutionContainer, matchingNode as N) + }.let { results -> + results.firstOrNull { + it !is DocumentResolution.UnsuccessfulResolution || !it.reasons.contains(UnresolvedBase) + } ?: results.first() + } + + override fun data(node: DeclarativeDocument.DocumentNode.ElementNode) = retryOverContainers(node) { data(it) } + override fun data(node: DeclarativeDocument.DocumentNode.ErrorNode) = retryOverContainers(node) { data(it) } + override fun data(node: DeclarativeDocument.DocumentNode.PropertyNode) = retryOverContainers(node) { data(it) } + override fun data(node: DeclarativeDocument.ValueNode.LiteralValueNode) = retryOverContainers(node) { data(it) } + override fun data(node: DeclarativeDocument.ValueNode.NamedReferenceNode) = + retryOverContainers(node) { data(it) } + + override fun data(node: DeclarativeDocument.ValueNode.ValueFactoryNode) = retryOverContainers(node) { data(it) } + } +} diff --git a/lsp/src/main/kotlin/org/gradle/declarative/lsp/service/MutationRegistry.kt b/lsp/src/main/kotlin/org/gradle/declarative/lsp/service/MutationRegistry.kt index a859794..4ee907b 100644 --- a/lsp/src/main/kotlin/org/gradle/declarative/lsp/service/MutationRegistry.kt +++ b/lsp/src/main/kotlin/org/gradle/declarative/lsp/service/MutationRegistry.kt @@ -18,6 +18,7 @@ package org.gradle.declarative.lsp.service import org.eclipse.lsp4j.Range import org.eclipse.lsp4j.util.Ranges +import org.gradle.declarative.dsl.schema.AnalysisSchema import org.gradle.declarative.lsp.build.model.DeclarativeResourcesModel import org.gradle.declarative.lsp.extension.toLspRange import org.gradle.internal.declarativedsl.dom.mutation.MutationApplicability @@ -37,9 +38,9 @@ class MutationRegistry( return possibleMutations.firstOrNull { it.id == name } } - fun registerDocument(uri: URI, documentWithResolution: DocumentWithResolution) { + fun registerDocument(uri: URI, schema: AnalysisSchema, documentWithResolution: DocumentWithResolution) { val applicabilityChecker = - MutationApplicabilityChecker(declarativeResources.analysisSchema, documentWithResolution) + MutationApplicabilityChecker(schema, documentWithResolution) val applicableMutations = possibleMutations.flatMap { mutation -> applicabilityChecker.checkApplicability(mutation).map { applicability -> Pair(mutation, applicability) } } diff --git a/lsp/src/main/kotlin/org/gradle/declarative/lsp/service/VersionedDocumentStore.kt b/lsp/src/main/kotlin/org/gradle/declarative/lsp/service/VersionedDocumentStore.kt index 8cef631..09535d5 100644 --- a/lsp/src/main/kotlin/org/gradle/declarative/lsp/service/VersionedDocumentStore.kt +++ b/lsp/src/main/kotlin/org/gradle/declarative/lsp/service/VersionedDocumentStore.kt @@ -16,6 +16,8 @@ package org.gradle.declarative.lsp.service +import org.gradle.declarative.dsl.schema.AnalysisSchema +import org.gradle.declarative.lsp.extension.unionAnalysisSchema import org.gradle.internal.declarativedsl.dom.operations.overlay.DocumentOverlayResult import java.net.URI @@ -33,12 +35,18 @@ class VersionedDocumentStore { return store[uri] } - fun storeInitial(uri: URI, document: String, dom: DocumentOverlayResult) { - store(uri, DocumentStoreEntry.Initial(document, dom)) + fun storeInitial(uri: URI, document: String, dom: DocumentOverlayResult, analysisSchemas: List) { + store(uri, DocumentStoreEntry.Initial(document, dom, analysisSchemas)) } - fun storeVersioned(uri: URI, version: Int, document: String, dom: DocumentOverlayResult) { - store(uri, DocumentStoreEntry.Versioned(version, document, dom)) + fun storeVersioned( + uri: URI, + version: Int, + document: String, + dom: DocumentOverlayResult, + analysisSchemas: List + ) { + store(uri, DocumentStoreEntry.Versioned(version, document, dom, analysisSchemas)) } /** @@ -73,6 +81,11 @@ class VersionedDocumentStore { abstract val document: String abstract val dom: DocumentOverlayResult + abstract val analysisSchemas: List + + val unionSchema by lazy { + unionAnalysisSchema(analysisSchemas) + } // Component 1 operator fun component1(): String = document @@ -80,13 +93,15 @@ class VersionedDocumentStore { class Initial( override val document: String, - override val dom: DocumentOverlayResult + override val dom: DocumentOverlayResult, + override val analysisSchemas: List ) : DocumentStoreEntry() class Versioned( val version: Int, override val document: String, override val dom: DocumentOverlayResult, + override val analysisSchemas: List ) : DocumentStoreEntry() } } \ 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/storage/VersionedDocumentStoreTest.kt index 9d67d9e..ec9da9b 100644 --- a/lsp/src/test/kotlin/org/gradle/declarative/lsp/storage/VersionedDocumentStoreTest.kt +++ b/lsp/src/test/kotlin/org/gradle/declarative/lsp/storage/VersionedDocumentStoreTest.kt @@ -17,6 +17,7 @@ package org.gradle.declarative.lsp.storage 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 @@ -24,6 +25,7 @@ import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import java.net.URI +import kotlin.test.assertSame class VersionedDocumentStoreTest { @@ -36,30 +38,32 @@ class VersionedDocumentStoreTest { @Test fun `null to initial works`() { - store.storeInitial(TEST_URI, "v0", NEW_DOM) + store.storeInitial(TEST_URI, "v0", NEW_DOM, NEW_SCHEMAS) store[TEST_URI].let { assertNotNull(it) assertEquals(it!!.document, "v0") assertEquals(it.dom, NEW_DOM) + assertSame(it.analysisSchemas, NEW_SCHEMAS) } } @Test fun `initialized to initialized is stored`() { - store.storeInitial(TEST_URI, "v0", STORED_DOM) - store.storeInitial(TEST_URI, "v0", NEW_DOM) + store.storeInitial(TEST_URI, "v0", STORED_DOM, STORED_SCHEMAS) + store.storeInitial(TEST_URI, "v0", NEW_DOM, NEW_SCHEMAS) store[TEST_URI].let { it!! assertEquals(it.document, "v0") assertEquals(it.dom, NEW_DOM) + assertSame(it.analysisSchemas, NEW_SCHEMAS) } } @Test fun `null to versioned is stored`() { - store.storeVersioned(TEST_URI, 1, "v1", NEW_DOM) + store.storeVersioned(TEST_URI, 1, "v1", NEW_DOM, NEW_SCHEMAS) store[TEST_URI].let { it!! @@ -69,37 +73,40 @@ class VersionedDocumentStoreTest { @Test fun `initialized to versioned is stored`() { - store.storeInitial(TEST_URI, "v0", STORED_DOM) - store.storeVersioned(TEST_URI, 1, "v1", NEW_DOM) + store.storeInitial(TEST_URI, "v0", STORED_DOM, STORED_SCHEMAS) + store.storeVersioned(TEST_URI, 1, "v1", NEW_DOM, NEW_SCHEMAS) store[TEST_URI].let { it!! assertEquals(it.document, "v1") assertEquals(it.dom, NEW_DOM) + assertSame(it.analysisSchemas, NEW_SCHEMAS) } } @Test fun `higher versions are stored`() { - store.storeVersioned(TEST_URI, 1, "v2", STORED_DOM) - store.storeVersioned(TEST_URI, 2, "v1", NEW_DOM) + store.storeVersioned(TEST_URI, 1, "v2", STORED_DOM, STORED_SCHEMAS) + store.storeVersioned(TEST_URI, 2, "v1", NEW_DOM, NEW_SCHEMAS) store[TEST_URI].let { it!! assertEquals(it.document, "v1") assertEquals(it.dom, NEW_DOM) + assertSame(it.analysisSchemas, NEW_SCHEMAS) } } @Test fun `version is not replaced if lower`() { - store.storeVersioned(TEST_URI, 2, "v2", STORED_DOM) - store.storeVersioned(TEST_URI, 1, "v1", NEW_DOM) + store.storeVersioned(TEST_URI, 2, "v2", STORED_DOM, STORED_SCHEMAS) + store.storeVersioned(TEST_URI, 1, "v1", NEW_DOM, NEW_SCHEMAS) store[TEST_URI].let { it!! assertEquals(it.document, "v2") assertEquals(it.dom, STORED_DOM) + assertSame(it.analysisSchemas, STORED_SCHEMAS) } } @@ -108,6 +115,8 @@ class VersionedDocumentStoreTest { private val STORED_DOM = mockk() private val NEW_DOM = mockk() + private val STORED_SCHEMAS = mockk>() + private val NEW_SCHEMAS = mockk>() } } \ No newline at end of file diff --git a/tapi-model/src/main/java/org/gradle/declarative/lsp/build/action/GetDeclarativeResourcesModel.java b/tapi-model/src/main/java/org/gradle/declarative/lsp/build/action/GetDeclarativeResourcesModel.java index f9dd0aa..b7cba69 100644 --- a/tapi-model/src/main/java/org/gradle/declarative/lsp/build/action/GetDeclarativeResourcesModel.java +++ b/tapi-model/src/main/java/org/gradle/declarative/lsp/build/action/GetDeclarativeResourcesModel.java @@ -17,20 +17,13 @@ package org.gradle.declarative.lsp.build.action; import org.gradle.declarative.dsl.evaluation.InterpretationSequence; -import org.gradle.declarative.dsl.schema.AnalysisSchema; import org.gradle.declarative.dsl.tooling.models.DeclarativeSchemaModel; import org.gradle.declarative.lsp.build.model.DeclarativeResourcesModel; -import org.gradle.internal.Pair; import org.gradle.tooling.BuildAction; import org.gradle.tooling.BuildController; import org.gradle.tooling.model.gradle.GradleBuild; import java.io.File; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; public class GetDeclarativeResourcesModel implements BuildAction { @@ -39,23 +32,13 @@ public DeclarativeResourcesModel execute(BuildController controller) { DeclarativeSchemaModel declarativeSchemaModel = controller.getModel(DeclarativeSchemaModel.class); InterpretationSequence settingsSchema = declarativeSchemaModel.getSettingsSequence(); InterpretationSequence projectSchema = declarativeSchemaModel.getProjectSequence(); - Pair> buildFiles = getDeclarativeBuildFiles(controller); - return new DeclarativeResourcesModelImpl(settingsSchema, projectSchema, buildFiles.getLeft(), buildFiles.getRight()); + File rootDir = getRootDir(controller); + return new DeclarativeResourcesModelImpl(settingsSchema, projectSchema, rootDir); } - private static Pair> getDeclarativeBuildFiles(BuildController controller) { + private static File getRootDir(BuildController controller) { GradleBuild gradleBuild = controller.getModel(GradleBuild.class); - File rootProjectDirectory = gradleBuild.getRootProject().getProjectDirectory(); - List declarativeBuildFiles = gradleBuild - .getProjects() - .getAll() - .stream() - .map(p -> new File(p.getProjectDirectory(), "build.gradle.dcl")) - .filter(File::exists).collect(Collectors.toList()); - if (declarativeBuildFiles.isEmpty()) { - throw new RuntimeException("No declarative project file found"); - } - return Pair.of(rootProjectDirectory, declarativeBuildFiles); + return gradleBuild.getRootProject().getProjectDirectory(); } @@ -64,18 +47,15 @@ private static final class DeclarativeResourcesModelImpl implements DeclarativeR private final InterpretationSequence settingsSequence; private final InterpretationSequence projectSequence; private final File rootDir; - private final List declarativeBuildFiles; public DeclarativeResourcesModelImpl( InterpretationSequence settingsSequence, InterpretationSequence projectSequence, - File rootDir, - List declarativeBuildFiles + File rootDir ) { this.settingsSequence = settingsSequence; this.projectSequence = projectSequence; this.rootDir = rootDir; - this.declarativeBuildFiles = declarativeBuildFiles; } @Override @@ -88,36 +68,9 @@ public InterpretationSequence getProjectInterpretationSequence() { return projectSequence; } - @Override - public AnalysisSchema getAnalysisSchema() { - return StreamSupport.stream(getProjectInterpretationSequence().getSteps().spliterator(), false) - .findFirst() - .orElseThrow(() -> new RuntimeException("no schema step available for project")) - .getEvaluationSchemaForStep() - .getAnalysisSchema(); - } - @Override public File getRootDir() { return rootDir; } - - @Override - public File getSettingsFile() { - // TODO: this is an assumption about the location of the settings file – get it from Gradle instead. - List candidateFileNames = Arrays.asList("settings.gradle.dcl", "settings.gradle.kts"); - Function asFileInRootDirectory = it -> new File(getRootDir(), it); - - return candidateFileNames.stream() - .map(asFileInRootDirectory) - .filter(File::exists) - .findFirst() - .orElse(asFileInRootDirectory.apply(candidateFileNames.get(0))); - } - - @Override - public List getDeclarativeBuildFiles() { - return declarativeBuildFiles; - } } } \ No newline at end of file diff --git a/tapi-model/src/main/java/org/gradle/declarative/lsp/build/model/DeclarativeResourcesModel.java b/tapi-model/src/main/java/org/gradle/declarative/lsp/build/model/DeclarativeResourcesModel.java index f33e0a0..81e1d45 100644 --- a/tapi-model/src/main/java/org/gradle/declarative/lsp/build/model/DeclarativeResourcesModel.java +++ b/tapi-model/src/main/java/org/gradle/declarative/lsp/build/model/DeclarativeResourcesModel.java @@ -17,11 +17,9 @@ package org.gradle.declarative.lsp.build.model; import org.gradle.declarative.dsl.evaluation.InterpretationSequence; -import org.gradle.declarative.dsl.schema.AnalysisSchema; import java.io.File; import java.io.Serializable; -import java.util.List; /**w * Model holding all required information to make a DOM out of the declarative resources in the build. @@ -32,11 +30,5 @@ public interface DeclarativeResourcesModel extends Serializable { InterpretationSequence getProjectInterpretationSequence(); - AnalysisSchema getAnalysisSchema(); - File getRootDir(); - - File getSettingsFile(); - - List getDeclarativeBuildFiles(); }