Skip to content

Instantly share code, notes, and snippets.

@h0tk3y
Last active July 4, 2024 16:00
Show Gist options
  • Save h0tk3y/a5f2b4e81acc51d07f2a2248755d89c7 to your computer and use it in GitHub Desktop.
Save h0tk3y/a5f2b4e81acc51d07f2a2248755d89c7 to your computer and use it in GitHub Desktop.
DCL IDE experiment

Experimenting with DCL support in Android Studio

This set of patches shows some experiments in Android Studio to add features in the DCL support. It is based on the approach of directly converting DCL PSI elements to the DCL language tree and using that as an input to the Gradle analysis implementation. Therefore, it does not need to run the Gradle parser on the source text at all.

Subject: [PATCH] Update and add more utilities for DCL analysis
---
Index: gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeFileResolution.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeFileResolution.kt b/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeFileResolution.kt
new file mode 100644
--- /dev/null (revision 27b8f491fdad7ee8b4b5fa994b8abb86b60323ed)
+++ b/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeFileResolution.kt (revision 27b8f491fdad7ee8b4b5fa994b8abb86b60323ed)
@@ -0,0 +1,210 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.android.tools.idea.gradle.declarative
+
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeAssignment
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeBare
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeBlock
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeEntry
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeFactory
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeFile
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeLiteral
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeProperty
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeQualified
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeValue
+import com.intellij.psi.PsiElement
+import com.intellij.psi.util.CachedValueProvider
+import com.intellij.psi.util.CachedValuesManager
+import com.intellij.psi.util.endOffset
+import com.intellij.psi.util.startOffset
+import org.gradle.internal.declarativedsl.analysis.AnalysisSchema
+import org.gradle.internal.declarativedsl.analysis.DataClass
+import org.gradle.internal.declarativedsl.analysis.DataProperty
+import org.gradle.internal.declarativedsl.analysis.SchemaMemberFunction
+import org.gradle.internal.declarativedsl.analysis.tracingCodeResolver
+import org.gradle.internal.declarativedsl.dom.DocumentResolution
+import org.gradle.internal.declarativedsl.dom.ResolvedDeclarativeDocument
+import org.gradle.internal.declarativedsl.dom.resolvedDocument
+import org.gradle.internal.declarativedsl.dom.toDocument
+import org.gradle.internal.declarativedsl.language.Assignment
+import org.gradle.internal.declarativedsl.language.Block
+import org.gradle.internal.declarativedsl.language.BlockElement
+import org.gradle.internal.declarativedsl.language.ErroneousStatement
+import org.gradle.internal.declarativedsl.language.Expr
+import org.gradle.internal.declarativedsl.language.FunctionArgument
+import org.gradle.internal.declarativedsl.language.FunctionCall
+import org.gradle.internal.declarativedsl.language.LanguageTreeElement
+import org.gradle.internal.declarativedsl.language.LanguageTreeResult
+import org.gradle.internal.declarativedsl.language.Literal
+import org.gradle.internal.declarativedsl.language.ParsingError
+import org.gradle.internal.declarativedsl.language.PropertyAccess
+import org.gradle.internal.declarativedsl.language.SourceData
+import org.gradle.internal.declarativedsl.language.SourceIdentifier
+
+object DeclarativeFileResolution {
+ fun resolvedFile(declarativeFile: DeclarativeFile, schema: AnalysisSchema): ResolvedDeclarativeFile {
+ return CachedValuesManager.getCachedValue(declarativeFile, CachedValueProvider {
+ val toLanguageTree = declarativeFile.toLanguageTree()
+ val resolver = tracingCodeResolver()
+ resolver.resolve(schema, emptyList(), toLanguageTree.tree.topLevelBlock)
+ val doc = toLanguageTree.tree.toDocument()
+ val resolved = resolvedDocument(schema, resolver.trace, doc)
+ CachedValueProvider.Result.create(resolved.collectResolutionResultsWithIndices(schema), declarativeFile)
+ })
+ }
+}
+
+class ResolvedDeclarativeFile(
+ private val schema: AnalysisSchema,
+ private val resolutionResultsByIndex: List<ResolutionResultAtIndices>,
+) {
+ fun errorsAt(psiElement: PsiElement): List<DocumentResolution.UnsuccessfulResolution> =
+ resolutionResultsByIndex
+ .filter { it.indices == psiElement.sourceData.indexRange }
+ .mapNotNull { it.resolution as? DocumentResolution.UnsuccessfulResolution }
+
+ fun typeOfElementAt(psiElement: PsiElement): DataClass? =
+ resolutionResultsByIndex.filter { it.indices == psiElement.sourceData.indexRange }.firstNotNullOfOrNull {
+ (it.resolution as? DocumentResolution.ElementResolution.SuccessfulElementResolution)?.elementType as? DataClass
+ }
+
+ fun methodAt(psiElement: PsiElement): SchemaMemberFunction? =
+ resolutionResultsByIndex.filter { it.indices == psiElement.sourceData.indexRange }.firstNotNullOfOrNull {
+ (it.resolution as? DocumentResolution.ElementResolution.SuccessfulElementResolution.ContainerElementResolved)?.elementFactoryFunction
+ }
+
+ fun propertyAt(psiElement: PsiElement): Pair<DataClass, DataProperty>? =
+ resolutionResultsByIndex.filter { it.indices == psiElement.sourceData.indexRange }.firstNotNullOfOrNull {
+ val prop = (it.resolution as? DocumentResolution.PropertyResolution.PropertyAssignmentResolved)?.property
+ val owner = schema.dataClassesByFqName.values.find { prop in it.properties }
+ if (prop != null && owner != null)
+ owner to prop
+ else null
+ }
+}
+
+data class ResolutionResultAtIndices(
+ val indices: IntRange,
+ val resolution: DocumentResolution,
+)
+
+private data class DeclarativeFileMappedToTree(
+ val tree: LanguageTreeResult,
+ val sourceIndexMapping: List<LanguageTreeElementAtIndices>,
+)
+
+private data class LanguageTreeElementAtIndices(
+ val indices: IntRange,
+ val element: LanguageTreeElement,
+)
+
+private fun DeclarativeFile.toLanguageTree(): DeclarativeFileMappedToTree {
+ val indexMapping = mutableListOf<LanguageTreeElementAtIndices>()
+ fun <T : LanguageTreeElement> T.rememberIndices(): T {
+ indexMapping.add(LanguageTreeElementAtIndices(sourceData.indexRange, this))
+ return this
+ }
+
+ fun DeclarativeProperty.toPropertyAccess(): PropertyAccess {
+ val name = field.name ?: "<ERROR>"
+ val receiver = receiver?.toPropertyAccess()
+ return PropertyAccess(receiver, name, sourceData).rememberIndices()
+ }
+
+ fun DeclarativeValue.toExpr(): Expr {
+ val psiSourceData = sourceData
+ return when (this) {
+ is DeclarativeBare -> PropertyAccess(null, identifier.name ?: "<ERROR>", psiSourceData)
+ is DeclarativeQualified -> PropertyAccess(property.toPropertyAccess(), name, psiSourceData)
+ is DeclarativeFactory -> FunctionCall(
+ null,
+ identifier.name ?: "<ERROR>",
+ argumentsList?.arguments?.map { FunctionArgument.Positional(it.toExpr(), it.sourceData) }.orEmpty(),
+ psiSourceData
+ )
+ is DeclarativeLiteral ->
+ number?.let { Literal.IntLiteral(it.text.toInt(), psiSourceData) }
+ ?: this.string?.let { Literal.StringLiteral(it.text.removeSurrounding("\""), psiSourceData) }
+ ?: this.boolean?.let { Literal.BooleanLiteral(it.text.toBoolean(), psiSourceData) }
+ ?: Literal.StringLiteral("<ERROR>", psiSourceData)
+ is DeclarativeProperty -> toPropertyAccess()
+ else -> error("unexpected subtype ${this.javaClass}")
+ }.rememberIndices()
+ }
+
+ fun DeclarativeEntry.asElement(): BlockElement {
+ val psiSourceData = sourceData
+ return when (this) {
+ is DeclarativeAssignment -> {
+ val lhs = PropertyAccess(null, identifier.name ?: return badPsi, psiSourceData)
+ val rhs = value?.toExpr() ?: return badPsi
+ Assignment(lhs, rhs, psiSourceData)
+ }
+ is DeclarativeBlock -> {
+ val content = entries.map { it.asElement() }
+ FunctionCall(
+ null, identifier?.name ?: return badPsi, listOf(FunctionArgument.Lambda(Block(content, psiSourceData), psiSourceData)), psiSourceData
+ )
+ }
+ is DeclarativeFactory -> this.toExpr()
+ else -> error("unexpected subtype")
+ }.rememberIndices()
+ }
+
+ val tree = LanguageTreeResult(emptyList(), Block(getEntries().map { it.asElement() }, sourceData), emptyList(), emptyList())
+ return DeclarativeFileMappedToTree(tree, indexMapping)
+}
+
+// TODO: with modern DCL APIs, it should be possible not to rely on source offsets to associate the PSI elements with the DOM content.
+// Instead, the following should work:
+// 1. When creating the language tree, store the `Map<LanguageTreeElement, DeclarativeElement>` instead of using `ResolutionResultAtIndices`
+// 2. Create a DeclarativeDocument using the `LanguageTreeResult.toDocument(): LanguageTreeBackedDocument` extension.
+// 3. Use the `LanguageTreeBackedDocument.languageTreeMappingContainer` from the result to match the document items with the language tree,
+// and then with the original PSI elements.
+
+fun ResolvedDeclarativeDocument.collectResolutionResultsWithIndices(schema: AnalysisSchema): ResolvedDeclarativeFile = ResolvedDeclarativeFile(
+ schema,
+ buildList {
+ fun visitValue(resolvedValueNode: ResolvedDeclarativeDocument.ResolvedValueNode) {
+ add(ResolutionResultAtIndices(resolvedValueNode.sourceData.indexRange, resolvedValueNode.resolution))
+ when (resolvedValueNode) {
+ is ResolvedDeclarativeDocument.ResolvedValueNode.ResolvedLiteralValueNode -> Unit
+ is ResolvedDeclarativeDocument.ResolvedValueNode.ResolvedValueFactoryNode -> {
+ resolvedValueNode.values.forEach(::visitValue)
+ }
+ }
+ }
+
+ fun visitNode(resolvedDocumentNode: ResolvedDeclarativeDocument.ResolvedDocumentNode) {
+ add(ResolutionResultAtIndices(resolvedDocumentNode.sourceData.indexRange, resolvedDocumentNode.resolution))
+ when (resolvedDocumentNode) {
+ is ResolvedDeclarativeDocument.ResolvedDocumentNode.ResolvedElementNode -> {
+ resolvedDocumentNode.elementValues.forEach(::visitValue)
+ resolvedDocumentNode.content.forEach(::visitNode)
+ }
+ is ResolvedDeclarativeDocument.ResolvedDocumentNode.ResolvedPropertyNode -> {
+ visitValue(resolvedDocumentNode.value)
+ }
+ else -> Unit
+ }
+ }
+
+ content.forEach(::visitNode)
+})
+
+private val PsiElement.sourceData: SourceData
+ get() {
+ val psi = this
+ return object : SourceData {
+ override val indexRange: IntRange
+ get() = psi.startOffset..psi.endOffset
+
+ override val lineRange: IntRange = IntRange.EMPTY
+ override val sourceIdentifier: SourceIdentifier = SourceIdentifier(psi.containingFile.name)
+ override val endColumn: Int = -1 // todo: column?
+ override val startColumn: Int = -1
+
+ override fun text(): String = psi.text
+ }
+ }
+
+private val PsiElement.badPsi get() = ErroneousStatement(ParsingError(sourceData, sourceData, "bad PSI"))
Index: gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeService.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeService.kt b/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeService.kt
--- a/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeService.kt (revision bde8cacfe4735f65b7df2341cde491aadb3d2b3d)
+++ b/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeService.kt (revision 27b8f491fdad7ee8b4b5fa994b8abb86b60323ed)
@@ -45,11 +45,8 @@
return map.getOrPut(module) {
val parentPath = module.guessModuleDir()?.path
val project = File(parentPath, ".gradle/declarative-schema/project.dcl.schema")
- val plugins = File(parentPath, ".gradle/declarative-schema/plugins.dcl.schema")
try {
- val projectSchema = SchemaSerialization.schemaFromJsonString(project.readText())
- val pluginSchema = SchemaSerialization.schemaFromJsonString(plugins.readText())
- DeclarativeSchema(projectSchema, pluginSchema)
+ DeclarativeSchema(SchemaSerialization.schemaFromJsonString(project.readText()))
}
catch (e: Exception) {
return null
@@ -58,10 +55,10 @@
}
}
-class DeclarativeSchema(private val project: AnalysisSchema, private val plugin: AnalysisSchema) {
- fun getDataClassesByFqName(): Map<FqName, DataClass> = project.dataClassesByFqName + plugin.dataClassesByFqName
+class DeclarativeSchema(val project: AnalysisSchema) {
+ fun getDataClassesByFqName(): Map<FqName, DataClass> = project.dataClassesByFqName
fun getRootMemberFunctions(): List<SchemaMemberFunction> =
- project.topLevelReceiverType.memberFunctions + plugin.topLevelReceiverType.memberFunctions
+ project.topLevelReceiverType.memberFunctions
}
fun getTopLevelReceiverByName(name: String, schema: DeclarativeSchema): FqName? =
getReceiverByName(name, schema.getRootMemberFunctions())
Subject: [PATCH] Add DCL analysis errors to the `DeclarativeAnnotator`
---
Index: gradle-dsl-declarative/resources/intellij.android.gradle.dsl.declarative.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/gradle-dsl-declarative/resources/intellij.android.gradle.dsl.declarative.xml b/gradle-dsl-declarative/resources/intellij.android.gradle.dsl.declarative.xml
--- a/gradle-dsl-declarative/resources/intellij.android.gradle.dsl.declarative.xml (revision 27b8f491fdad7ee8b4b5fa994b8abb86b60323ed)
+++ b/gradle-dsl-declarative/resources/intellij.android.gradle.dsl.declarative.xml (revision e55f827281b4a15bc8cd7e6bcf9fb739ed328bfc)
@@ -25,6 +25,8 @@
<psi.referenceContributor language="Declarative"
implementation="com.android.tools.idea.gradle.declarative.DeclarativeVersionCatalogReferenceContributor"/>
+ <psi.referenceContributor language="Declarative"
+ implementation="com.android.tools.idea.gradle.declarative.DeclarativeToJavaReferenceContributor"/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains.uast">
<uastLanguagePlugin implementation="com.android.tools.idea.gradle.declarative.DeclarativeUastLanguagePlugin"/>
Index: gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeAnnotator.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeAnnotator.kt b/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeAnnotator.kt
--- a/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeAnnotator.kt (revision 27b8f491fdad7ee8b4b5fa994b8abb86b60323ed)
+++ b/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeAnnotator.kt (revision e55f827281b4a15bc8cd7e6bcf9fb739ed328bfc)
@@ -17,6 +17,7 @@
import com.android.tools.idea.flags.StudioFlags
import com.android.tools.idea.gradle.declarative.psi.DeclarativeElement
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeFile
import com.android.tools.idea.gradle.declarative.psi.DeclarativeIdentifier
import com.android.tools.idea.gradle.declarative.psi.DeclarativeIdentifierOwner
import com.intellij.lang.annotation.AnnotationHolder
@@ -30,18 +31,37 @@
class DeclarativeAnnotator : Annotator {
override fun annotate(element: PsiElement, holder: AnnotationHolder) {
- if (!StudioFlags.GRADLE_DECLARATIVE_IDE_SUPPORT.get()) return
+ val module = ModuleUtil.findModuleForPsiElement(element.containingFile) ?: return
+ val service = DeclarativeService.getInstance(element.project)
+ val schema = service.getSchema(module) ?: return
+
+ (element.containingFile as? DeclarativeFile)?.let { file ->
+ val resolved = DeclarativeFileResolution.resolvedFile(file, schema.project)
+ annotateWithAnalysisErrors(element, resolved, holder)
+ }
+
+ // FIXME: this line is commented because the IJ lacks the StudioFlags class and would crash at runtime:
+ // if (!StudioFlags.GRADLE_DECLARATIVE_IDE_SUPPORT.get()) return
+
if (element.parent !is DeclarativeElement) return
val parent = element.parent
if (parent is DeclarativeIdentifier) {
val path = getPath(parent)
- val service = DeclarativeService.getInstance(element.project)
- val module = ModuleUtil.findModuleForPsiElement(element.containingFile) ?: return
- val schema = service.getSchema(module) ?: return
verifyPath(path, schema, holder)
}
}
+ private fun annotateWithAnalysisErrors(element: PsiElement, resolvedDeclarativeFile: ResolvedDeclarativeFile, holder: AnnotationHolder) {
+ resolvedDeclarativeFile.errorsAt(element).forEach { error ->
+ error.reasons.forEach { reason ->
+ holder.newAnnotation(
+ HighlightSeverity.ERROR,
+ reason.toString()
+ ).create()
+ }
+ }
+ }
+
private fun verifyPath(path: List<String>, schema: DeclarativeSchema, holder: AnnotationHolder) {
if (path.isEmpty()) return
var currentName: FqName? = null
Subject: [PATCH] Implement basic DCL-to-JVM references and "Go to Declaration" to the JVM types
---
Index: gradle-dsl-declarative/gen/com/android/tools/idea/gradle/declarative/psi/DeclarativeAssignment.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/gradle-dsl-declarative/gen/com/android/tools/idea/gradle/declarative/psi/DeclarativeAssignment.java b/gradle-dsl-declarative/gen/com/android/tools/idea/gradle/declarative/psi/DeclarativeAssignment.java
--- a/gradle-dsl-declarative/gen/com/android/tools/idea/gradle/declarative/psi/DeclarativeAssignment.java (revision e55f827281b4a15bc8cd7e6bcf9fb739ed328bfc)
+++ b/gradle-dsl-declarative/gen/com/android/tools/idea/gradle/declarative/psi/DeclarativeAssignment.java (revision 0cadd39c73246a35de8fd5ff9de38684a4b51c21)
@@ -18,10 +18,11 @@
package com.android.tools.idea.gradle.declarative.psi;
import java.util.List;
+import com.intellij.psi.ContributedReferenceHost;
import org.jetbrains.annotations.*;
import com.intellij.psi.PsiElement;
-public interface DeclarativeAssignment extends DeclarativeEntry, DeclarativeIdentifierOwner {
+public interface DeclarativeAssignment extends DeclarativeEntry, DeclarativeIdentifierOwner, ContributedReferenceHost {
@Nullable
DeclarativeFactory getFactory();
Index: gradle-dsl-declarative/gen/com/android/tools/idea/gradle/declarative/psi/DeclarativeBlock.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/gradle-dsl-declarative/gen/com/android/tools/idea/gradle/declarative/psi/DeclarativeBlock.java b/gradle-dsl-declarative/gen/com/android/tools/idea/gradle/declarative/psi/DeclarativeBlock.java
--- a/gradle-dsl-declarative/gen/com/android/tools/idea/gradle/declarative/psi/DeclarativeBlock.java (revision e55f827281b4a15bc8cd7e6bcf9fb739ed328bfc)
+++ b/gradle-dsl-declarative/gen/com/android/tools/idea/gradle/declarative/psi/DeclarativeBlock.java (revision 0cadd39c73246a35de8fd5ff9de38684a4b51c21)
@@ -18,10 +18,12 @@
package com.android.tools.idea.gradle.declarative.psi;
import java.util.List;
+import com.intellij.psi.ContributedReferenceHost;
+import com.intellij.psi.NavigatablePsiElement;
import org.jetbrains.annotations.*;
import com.intellij.psi.PsiElement;
-public interface DeclarativeBlock extends DeclarativeEntry, DeclarativeIdentifierOwner {
+public interface DeclarativeBlock extends DeclarativeEntry, DeclarativeIdentifierOwner, ContributedReferenceHost {
@NotNull
DeclarativeBlockGroup getBlockGroup();
Index: gradle-dsl-declarative/gen/com/android/tools/idea/gradle/declarative/psi/DeclarativeFactory.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/gradle-dsl-declarative/gen/com/android/tools/idea/gradle/declarative/psi/DeclarativeFactory.java b/gradle-dsl-declarative/gen/com/android/tools/idea/gradle/declarative/psi/DeclarativeFactory.java
--- a/gradle-dsl-declarative/gen/com/android/tools/idea/gradle/declarative/psi/DeclarativeFactory.java (revision e55f827281b4a15bc8cd7e6bcf9fb739ed328bfc)
+++ b/gradle-dsl-declarative/gen/com/android/tools/idea/gradle/declarative/psi/DeclarativeFactory.java (revision 0cadd39c73246a35de8fd5ff9de38684a4b51c21)
@@ -18,10 +18,11 @@
package com.android.tools.idea.gradle.declarative.psi;
import java.util.List;
+import com.intellij.psi.ContributedReferenceHost;
import org.jetbrains.annotations.*;
import com.intellij.psi.PsiElement;
-public interface DeclarativeFactory extends DeclarativeEntry, DeclarativeIdentifierOwner, DeclarativeValue {
+public interface DeclarativeFactory extends DeclarativeEntry, DeclarativeIdentifierOwner, DeclarativeValue, ContributedReferenceHost {
@Nullable
DeclarativeArgumentsList getArgumentsList();
Index: gradle-dsl-declarative/resources/intellij.android.gradle.dsl.declarative.xml
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/gradle-dsl-declarative/resources/intellij.android.gradle.dsl.declarative.xml b/gradle-dsl-declarative/resources/intellij.android.gradle.dsl.declarative.xml
--- a/gradle-dsl-declarative/resources/intellij.android.gradle.dsl.declarative.xml (revision e55f827281b4a15bc8cd7e6bcf9fb739ed328bfc)
+++ b/gradle-dsl-declarative/resources/intellij.android.gradle.dsl.declarative.xml (revision 0cadd39c73246a35de8fd5ff9de38684a4b51c21)
@@ -23,6 +23,8 @@
implementationClass="com.android.tools.idea.gradle.declarative.EnableAutoPopupInDeclarativeCompletion"/>
<annotator language="Declarative" implementationClass="com.android.tools.idea.gradle.declarative.DeclarativeAnnotator"/>
+ <gotoDeclarationHandler implementation="com.android.tools.idea.gradle.declarative.DeclarativeGotoDeclarationHandler"/>
+
<psi.referenceContributor language="Declarative"
implementation="com.android.tools.idea.gradle.declarative.DeclarativeVersionCatalogReferenceContributor"/>
<psi.referenceContributor language="Declarative"
Index: gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeGotoDeclarationHandler.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeGotoDeclarationHandler.kt b/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeGotoDeclarationHandler.kt
new file mode 100644
--- /dev/null (revision 0cadd39c73246a35de8fd5ff9de38684a4b51c21)
+++ b/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeGotoDeclarationHandler.kt (revision 0cadd39c73246a35de8fd5ff9de38684a4b51c21)
@@ -0,0 +1,40 @@
+// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.android.tools.idea.gradle.declarative
+
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeAssignment
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeBlock
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeFactory
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeFile
+import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler
+import com.intellij.openapi.editor.Editor
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiReferenceService
+import com.intellij.psi.util.PsiTreeUtil
+
+class DeclarativeGotoDeclarationHandler : GotoDeclarationHandler {
+ override fun getGotoDeclarationTargets(sourceElement: PsiElement?, offset: Int, editor: Editor): Array<PsiElement>? {
+ if (sourceElement?.containingFile !is DeclarativeFile) {
+ return null
+ }
+
+ val references = PsiReferenceService.getService()
+ val hints = PsiReferenceService.Hints()
+
+ val factoryTargets = PsiTreeUtil.getNonStrictParentOfType(sourceElement, DeclarativeFactory::class.java)?.let { factory ->
+ references.getReferences(factory, hints).mapNotNull { it.resolve() }
+ } ?: emptyList()
+
+ val propertyTargets = PsiTreeUtil.getNonStrictParentOfType(sourceElement, DeclarativeAssignment::class.java)?.let { factory ->
+ references.getReferences(factory, hints).mapNotNull { it.resolve() }
+ } ?: emptyList()
+
+ if (factoryTargets.isNotEmpty() || propertyTargets.isNotEmpty())
+ return (factoryTargets + propertyTargets).toTypedArray()
+
+ val blockTargets = PsiTreeUtil.getNonStrictParentOfType(sourceElement, DeclarativeBlock::class.java)?.let { block ->
+ references.getReferences(block, hints).mapNotNull { it.resolve() }
+ } ?: emptyList()
+ return blockTargets.toTypedArray()
+
+ }
+}
\ No newline at end of file
Index: gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeToJavaReferenceContributor.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeToJavaReferenceContributor.kt b/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeToJavaReferenceContributor.kt
new file mode 100644
--- /dev/null (revision 0cadd39c73246a35de8fd5ff9de38684a4b51c21)
+++ b/gradle-dsl-declarative/src/com/android/tools/idea/gradle/declarative/DeclarativeToJavaReferenceContributor.kt (revision 0cadd39c73246a35de8fd5ff9de38684a4b51c21)
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * 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 com.android.tools.idea.gradle.declarative
+
+import com.android.tools.idea.gradle.declarative.DeclarativeFileResolution.resolvedFile
+import com.android.tools.idea.gradle.declarative.DeclarativeService.Companion.getInstance
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeElement
+import com.android.tools.idea.gradle.declarative.psi.DeclarativeFile
+import com.intellij.openapi.module.ModuleUtil
+import com.intellij.openapi.util.TextRange
+import com.intellij.openapi.util.text.Strings
+import com.intellij.patterns.PlatformPatterns
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiElementResolveResult
+import com.intellij.psi.PsiPolyVariantReference
+import com.intellij.psi.PsiReference
+import com.intellij.psi.PsiReferenceBase
+import com.intellij.psi.PsiReferenceContributor
+import com.intellij.psi.PsiReferenceProvider
+import com.intellij.psi.PsiReferenceRegistrar
+import com.intellij.psi.ResolveResult
+import com.intellij.psi.search.GlobalSearchScope
+import com.intellij.util.ProcessingContext
+import org.gradle.internal.declarativedsl.analysis.DataTypeRef
+
+
+class DeclarativeToJavaReferenceContributor : PsiReferenceContributor() {
+ override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
+ registrar.registerReferenceProvider(
+ PlatformPatterns.psiElement(DeclarativeElement::class.java),
+ DeclarativeToJavaReferenceProvider()
+ )
+ }
+}
+
+class DeclarativeToJavaReferenceProvider : PsiReferenceProvider() {
+ override fun getReferencesByElement(element: PsiElement, context: ProcessingContext): Array<PsiReference> {
+ if (element !is DeclarativeElement) return emptyArray()
+
+ val module = ModuleUtil.findModuleForPsiElement(element.containingFile) ?: return emptyArray()
+
+ val file = resolvedFile(
+ element.containingFile as DeclarativeFile, getInstance(element.project).getSchema(module)?.project ?: return emptyArray()
+ )
+
+ return listOfNotNull(
+ file.typeOfElementAt(element)?.let { DeclarativeToJavaClassReference(element, element.textRange, it.name.qualifiedName) },
+ file.methodAt(element)?.let { method ->
+ val typeName = (method.receiver as? DataTypeRef.Name)?.fqName?.qualifiedName
+ typeName?.let { DeclarativeToJavaMethodReference(element, element.textRange, typeName, method.simpleName) }
+ },
+ file.propertyAt(element)?.let { (owner, property) ->
+ val typeName = owner.name.qualifiedName
+ DeclarativeToPropertyReference(element, element.textRange, typeName, property.name)
+ }
+ ).toTypedArray()
+ }
+
+ class DeclarativeToJavaClassReference(
+ element: PsiElement,
+ textRange: TextRange?,
+ private val className: String,
+ ) : PsiReferenceBase<PsiElement?>(element, textRange), PsiPolyVariantReference {
+ override fun resolve(): PsiElement? =
+ multiResolve(false).firstOrNull()?.element
+
+ override fun multiResolve(incompleteCode: Boolean): Array<ResolveResult> {
+ val project = myElement?.project ?: return arrayOf()
+ val scope = GlobalSearchScope.allScope(project)
+ val javaPsiFacade = JavaPsiFacade.getInstance(project)
+ val classes = javaPsiFacade.findClasses(className, scope)
+ return classes.map { PsiElementResolveResult(it) }.toTypedArray()
+ }
+
+ override fun getVariants(): Array<Any> {
+ return emptyArray()
+ }
+ }
+
+
+ class DeclarativeToJavaMethodReference(
+ element: PsiElement,
+ textRange: TextRange?,
+ private val className: String,
+ private val methodName: String,
+ ) : PsiReferenceBase<PsiElement?>(element, textRange), PsiPolyVariantReference {
+ override fun resolve(): PsiElement? =
+ multiResolve(false).firstOrNull()?.element
+
+ override fun multiResolve(incompleteCode: Boolean): Array<ResolveResult> {
+ val project = myElement?.project ?: return arrayOf()
+ val scope = GlobalSearchScope.allScope(project)
+ val javaPsiFacade = JavaPsiFacade.getInstance(project)
+ val classes = javaPsiFacade.findClasses(className, scope)
+ return classes.flatMap { psiClass ->
+ psiClass.allMethods.filter { it.name == methodName }.map { PsiElementResolveResult(it) }
+ }.toTypedArray()
+ }
+
+ override fun getVariants(): Array<Any> {
+ return emptyArray()
+ }
+ }
+
+
+ class DeclarativeToPropertyReference(
+ element: PsiElement,
+ textRange: TextRange?,
+ private val className: String,
+ private val propertyName: String,
+ ) : PsiReferenceBase<PsiElement?>(element, textRange), PsiPolyVariantReference {
+ override fun resolve(): PsiElement? =
+ multiResolve(false).firstOrNull()?.element
+
+ override fun multiResolve(incompleteCode: Boolean): Array<ResolveResult> {
+ val project = myElement?.project ?: return arrayOf()
+ val scope = GlobalSearchScope.allScope(project)
+ val javaPsiFacade = JavaPsiFacade.getInstance(project)
+ val classes = javaPsiFacade.findClasses(className, scope)
+ return classes.flatMap { psiClass ->
+ val nameCapitalized = Strings.capitalize(propertyName)
+ psiClass.allMethods.filter { it.name == "get$nameCapitalized" || it.name == "is$nameCapitalized" }.map {
+ PsiElementResolveResult(it)
+ }
+ }.toTypedArray()
+ }
+
+ override fun getVariants(): Array<Any> {
+ return emptyArray()
+ }
+ }
+
+
+}
\ No newline at end of file
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment