[gradle] IDEA-171199 IDEA-197590 absolute project references in project("...")

GitOrigin-RevId: 37e62b43c880f5d5dc7b91624a819dba6e9fa0ef
This commit is contained in:
Daniil Ovchinnikov
2020-06-01 16:47:18 +03:00
committed by intellij-monorepo-bot
parent 6a8055fb0f
commit 30c4bfb9ff
8 changed files with 243 additions and 3 deletions
@@ -47,6 +47,14 @@
implementationClass="org.jetbrains.plugins.gradle.execution.GradleGroovyRunLineMarkerProvider"/>
<fileTypeUsageSchemaDescriptor schema="Gradle" implementationClass="org.jetbrains.plugins.gradle.config.GradleScriptTypeDetector"/>
<psi.symbolReferenceProvider hostLanguage="Groovy"
hostElementClass="org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral"
targetClass="org.jetbrains.plugins.gradle.service.resolve.GradleProjectSymbol"
implementationClass="org.jetbrains.plugins.gradle.service.resolve.GradleProjectReferenceProvider"/>
<searcher forClass="com.intellij.model.search.PsiSymbolReferenceSearchParameters"
implementationClass="org.jetbrains.plugins.gradle.service.resolve.GradleProjectReferenceSearcher"/>
<lang.symbolTextSearcher forClass="org.jetbrains.plugins.gradle.service.resolve.GradleProjectSymbol"
implementationClass="org.jetbrains.plugins.gradle.service.resolve.GradleProjectTextSearcher"/>
</extensions>
<extensions defaultExtensionNs="org.intellij.groovy">
@@ -0,0 +1,62 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.gradle.service.resolve
import com.intellij.model.SingleTargetReference
import com.intellij.model.Symbol
import com.intellij.model.psi.PsiCompletableReference
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.plugins.gradle.settings.GradleExtensionsSettings
import org.jetbrains.plugins.gradle.settings.GradleExtensionsSettings.GradleProject
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral
@Internal
class GradleProjectReference(
private val literal: GrLiteral,
private val range: TextRange,
private val projectPath: List<String>
) : SingleTargetReference(), PsiCompletableReference {
init {
require(projectPath.isNotEmpty())
}
private val projectPathString = projectPath.joinToString(":")
override fun getElement(): PsiElement = literal
override fun getRangeInElement(): TextRange = range
override fun resolveSingleTarget(): Symbol? {
val gradleProject = GradleExtensionsSettings.getRootProject(literal) ?: return null
if (projectPathString in gradleProject.extensions) {
return GradleProjectSymbol(projectPath)
}
return null
}
/**
* This could be much easier if we could query list of sub-projects by project fqn.
*/
override fun getCompletionVariants(): Collection<Any> {
val gradleProject: GradleProject = GradleExtensionsSettings.getRootProject(literal) ?: return emptyList()
val allProjectFqns: Set<String> = gradleProject.extensions.keys
val parentProjectFqn: List<String> = projectPath // ["com", "IntellijIdeaRulezzz"]
.run { take(size - 1) } // ["com"]
val parentProjectPrefix: String = parentProjectFqn.joinToString("") { "$it:" } // "com:"
val result = LinkedHashSet<String>()
for (projectFqn in allProjectFqns) { // "com:foo:bar"
if (!projectFqn.startsWith(parentProjectPrefix)) {
continue
}
val relativeFqn = projectFqn.removePrefix(parentProjectPrefix) // "foo:bar"
val childProjectName = relativeFqn.split(':').firstOrNull() // "foo"
if (childProjectName.isNullOrEmpty()) {
continue
}
result += childProjectName
}
return result
}
}
@@ -0,0 +1,67 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.gradle.service.resolve
import com.intellij.model.Symbol
import com.intellij.model.psi.PsiExternalReferenceHost
import com.intellij.model.psi.PsiSymbolReference
import com.intellij.model.psi.PsiSymbolReferenceHints
import com.intellij.model.psi.PsiSymbolReferenceProvider
import com.intellij.model.search.SearchRequest
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.psi.ElementManipulators
import com.intellij.util.SmartList
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral
import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyMethodCallPattern
import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyPatterns
import org.jetbrains.plugins.groovy.lang.psi.patterns.groovyElement
import org.jetbrains.plugins.groovy.lang.psi.patterns.psiMethod
@Internal
class GradleProjectReferenceProvider : PsiSymbolReferenceProvider {
private val myPattern = GroovyPatterns.stringLiteral().withParent(
groovyElement<GrArgumentList>().withParent(
GroovyMethodCallPattern.resolvesTo(psiMethod(GradleCommonClassNames.GRADLE_API_PROJECT, "project"))
)
)
/**
* Handled by [GradleProjectReferenceSearcher].
*/
override fun getSearchRequests(project: Project, target: Symbol): Collection<SearchRequest> = emptyList()
override fun getReferences(element: PsiExternalReferenceHost, hints: PsiSymbolReferenceHints): Collection<PsiSymbolReference> {
if (element !is GrLiteral || !myPattern.accepts(element)) {
return emptyList()
}
val escaper = element.createLiteralTextEscaper()
val manipulator = ElementManipulators.getManipulator(element)
val value = StringBuilder()
val rangeInHost = manipulator.getRangeInElement(element)
if (!escaper.decode(rangeInHost, value)) {
return emptyList()
}
if (!value.startsWith(":")) {
return emptyList()
}
val path = value.split(":")
val result = SmartList<PsiSymbolReference>()
val subPath = ArrayList<String>()
var currentOffsetInPath = 0
for (part in path) {
subPath.add(part)
val partStart = escaper.getOffsetInHost(currentOffsetInPath, rangeInHost)
currentOffsetInPath += part.length
val partEnd = escaper.getOffsetInHost(currentOffsetInPath, rangeInHost)
currentOffsetInPath += 1 // ":"
if (partStart < 0 || partEnd < 0) {
continue
}
result += GradleProjectReference(element, TextRange(partStart, partEnd), ArrayList(subPath))
}
return result
}
}
@@ -0,0 +1,26 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.gradle.service.resolve
import com.intellij.model.psi.PsiSymbolReference
import com.intellij.model.search.PsiSymbolReferenceSearchParameters
import com.intellij.model.search.PsiSymbolReferenceSearcher
import com.intellij.model.search.SearchContext
import com.intellij.model.search.SearchService
import com.intellij.model.search.impl.ExternalReferenceMapper
import com.intellij.util.Query
import org.jetbrains.plugins.groovy.GroovyLanguage
class GradleProjectReferenceSearcher : PsiSymbolReferenceSearcher {
override fun collectSearchRequests(parameters: PsiSymbolReferenceSearchParameters): Collection<Query<out PsiSymbolReference>> {
val projectSymbol = parameters.symbol as? GradleProjectSymbol ?: return emptyList()
val projectName = projectSymbol.projectName.takeIf(String::isNotEmpty) ?: return emptyList()
val query = SearchService.getInstance()
.searchWord(parameters.project, projectName)
.inScope(parameters.searchScope)
.inFilesWithLanguage(GroovyLanguage)
.inContexts(SearchContext.IN_STRINGS)
.buildQuery(ExternalReferenceMapper(projectSymbol.createPointer()))
return listOf(query)
}
}
@@ -0,0 +1,47 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.gradle.service.resolve
import com.intellij.model.Pointer
import com.intellij.model.presentation.PresentableSymbol
import com.intellij.model.presentation.SymbolPresentation
import icons.GradleIcons
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.plugins.gradle.util.GradleBundle
import javax.swing.Icon
@Internal
class GradleProjectSymbol(private val myQualifiedName: List<String>) : PresentableSymbol {
init {
require(myQualifiedName.isNotEmpty())
}
override fun createPointer(): Pointer<GradleProjectSymbol> = Pointer.hardPointer(this)
val projectName: String get() = myQualifiedName.last()
val qualifiedName: String get() = myQualifiedName.joinToString(separator = ":")
private val myPresentation = object : SymbolPresentation {
override fun getIcon(): Icon? = GradleIcons.Gradle
override fun getShortNameString(): String = projectName
override fun getShortDescription(): String = GradleBundle.message("gradle.project.0", projectName)
override fun getLongDescription(): String = GradleBundle.message("gradle.project.0", qualifiedName)
}
override fun getSymbolPresentation(): SymbolPresentation = myPresentation
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as GradleProjectSymbol
if (myQualifiedName != other.myQualifiedName) return false
return true
}
override fun hashCode(): Int {
return myQualifiedName.hashCode()
}
}
@@ -0,0 +1,9 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.gradle.service.resolve
import com.intellij.find.usages.SymbolTextSearcher
class GradleProjectTextSearcher : SymbolTextSearcher<GradleProjectSymbol> {
override fun getStringsToSearch(symbol: GradleProjectSymbol): Collection<String> = listOf(symbol.qualifiedName)
}
@@ -165,3 +165,4 @@ compiler.build.messages.gradle.configuration.not.found=Unable to make ''{0}''.\n
gradle.project.generation.wrapper.progress.title=Generating Gradle Wrapper
gradle.commandline.description=Configures Gradle projects under the given folder
gradle.project.0=Gradle project ''{0}''
@@ -12,10 +12,14 @@ import com.intellij.openapi.externalSystem.service.project.ProjectDataManager;
import com.intellij.openapi.externalSystem.service.project.manage.ExternalProjectsManager;
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.psi.CommonClassNames;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.config.GradleSettingsListenerAdapter;
@@ -188,14 +192,20 @@ public class GradleExtensionsSettings {
*/
@Nullable
public GradleExtensionsData getExtensionsFor(@Nullable String rootProjectPath, @Nullable String gradlePath) {
if (rootProjectPath == null || gradlePath == null) return null;
GradleProject gradleProject = projects.get(rootProjectPath);
GradleProject gradleProject = getRootGradleProject(rootProjectPath);
if (gradleProject == null) return null;
return gradleProject.extensions.get(gradlePath);
}
@Contract("null -> null")
@Nullable
public GradleProject getRootGradleProject(@Nullable String rootProjectPath) {
if (rootProjectPath == null) return null;
return projects.get(rootProjectPath);
}
}
static class GradleProject {
public static class GradleProject {
public Map<String, GradleExtensionsData> extensions = new HashMap<>();
}
@@ -219,6 +229,7 @@ public class GradleExtensionsSettings {
public final Map<String, GradleConfiguration> configurations = new HashMap<>();
@NotNull
public final Map<String, GradleConfiguration> buildScriptConfigurations = new HashMap<>();
@Nullable
public GradleExtensionsData getParent() {
if (myGradleProject == null) return null;
@@ -315,4 +326,13 @@ public class GradleExtensionsSettings {
public boolean scriptClasspath;
public String description;
}
@Nullable
public static GradleProject getRootProject(@NotNull PsiElement element) {
final PsiFile containingFile = element.getContainingFile().getOriginalFile();
final Project project = containingFile.getProject();
final Module module = ModuleUtilCore.findModuleForFile(containingFile);
final String rootProjectPath = ExternalSystemApiUtil.getExternalRootProjectPath(module);
return getInstance(project).getRootGradleProject(rootProjectPath);
}
}