blob: 753d942403373a8b85c65d560df8e55be7eb76b8 [file]
// Copyright 2000-2019 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.psi.CommonClassNames.JAVA_LANG_STRING
import com.intellij.psi.PsiType
import groovy.lang.Closure
import org.jetbrains.plugins.gradle.service.resolve.GradleCommonClassNames.*
import org.jetbrains.plugins.groovy.lang.GroovyElementFilter
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement
import org.jetbrains.plugins.groovy.lang.psi.api.GrFunctionalExpression
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrBinaryExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral
import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil.findCall
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil.createType
import org.jetbrains.plugins.groovy.lang.psi.patterns.GroovyPatterns.methodCall
import org.jetbrains.plugins.groovy.lang.psi.patterns.psiMethod
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil.unwrapClassType
import org.jetbrains.plugins.groovy.lang.resolve.delegatesTo.DelegatesToInfo
import org.jetbrains.plugins.groovy.lang.resolve.delegatesTo.GrDelegatesToProvider
import org.jetbrains.plugins.groovy.lang.typing.GrTypeCalculator
class GradleTaskDeclarationElementFilter : GroovyElementFilter {
override fun isFake(element: GroovyPsiElement): Boolean {
return isFakeInner(element) && element.containingFile.isGradleScript()
}
}
class GradleTaskDeclarationTypeCalculator : GrTypeCalculator<GrReferenceExpression> {
override fun getType(expression: GrReferenceExpression): PsiType? = when {
isTaskIdExpression(expression) -> createType(JAVA_LANG_STRING, expression.containingFile)
isTaskIdInOperator(expression) -> createType(GRADLE_API_TASK, expression.containingFile)
else -> null
}
}
class GradleTaskDeclarationClosureDelegateProvider : GrDelegatesToProvider {
companion object {
private val projectTaskMethod = methodCall().resolvesTo(psiMethod(GRADLE_API_PROJECT, "task"))
private val taskContainerCreateMethod = methodCall().resolvesTo(psiMethod(GRADLE_API_TASK_CONTAINER, "create"))
}
override fun getDelegatesToInfo(expression: GrFunctionalExpression): DelegatesToInfo? {
val methodCall = findCall(expression) ?: return null
val taskType = if (isTaskIdCall(methodCall) || projectTaskMethod.accepts(methodCall)) {
getFromNamedArgument(methodCall)
?: createType(GRADLE_API_TASK, expression)
}
else if (taskContainerCreateMethod.accepts(methodCall)) {
getFromNamedArgument(methodCall)
?: getFromExpressionArgument(methodCall)
?: createType(GRADLE_API_TASK, expression)
}
else {
return null
}
return DelegatesToInfo(taskType, Closure.DELEGATE_FIRST)
}
private fun getFromNamedArgument(methodCall: GrMethodCall): PsiType? {
val namedArgument = getNamedArgument(methodCall)
return unwrapClassType(namedArgument?.expression?.type)
}
private fun getNamedArgument(methodCall: GrMethodCall): GrNamedArgument? {
val namedArgument = methodCall.argumentList.findNamedArgument("type")
if (namedArgument != null) {
return namedArgument
}
val mapExpression = methodCall.expressionArguments.firstOrNull() as? GrListOrMap
if (mapExpression == null || !mapExpression.isMap) {
return null
}
return mapExpression.findNamedArgument("type")
}
private fun getFromExpressionArgument(methodCall: GrMethodCall): PsiType? {
val type = methodCall.expressionArguments.getOrNull(1)?.type
return unwrapClassType(type)
}
}
private fun isFakeInner(element: GroovyPsiElement): Boolean {
return when (element) {
is GrMethodCall -> isFakeTopTaskCall(element) ||
isTaskIdCall(element)
is GrReferenceExpression -> isTaskIdExpression(element) ||
isTaskIdInvoked(element) ||
isTaskIdInOperator(element)
else -> false
}
}
private fun isFakeTopTaskCall(methodCall: GrMethodCall): Boolean {
if (!checkTaskCall(methodCall)) return false
if (methodCall.namedArguments.isNotEmpty()) return false
if (methodCall.hasClosureArguments()) return false
val singleArgument = methodCall.expressionArguments.singleOrNull() ?: return false
return isTaskIdCallDown(singleArgument) ||
isTaskIdOperatorDown(singleArgument)
}
/**
* Matches:
* - `id()` in `task id()`;
* - `id {}` in `task id {}`;
* - `id(namedArg: 42)` in `task id(namedArg: 42)`.
* - `id(namedArg: 42) {}` in `task id(namedArg: 42) {}`.
*/
private fun isTaskIdCall(methodCall: GrMethodCall): Boolean {
return isFirstArgumentOfTaskMethod(methodCall) &&
isTaskIdCallDown(methodCall)
}
private fun isTaskIdCallDown(expression: GrExpression): Boolean {
return expression is GrMethodCall &&
hasTaskIdCallArguments(expression) &&
expression.isDynamicVariableInvoked()
}
private fun hasTaskIdCallArguments(expression: GrMethodCall): Boolean {
val expressions = expression.expressionArguments + expression.closureArguments
return when (expressions.size) {
0 -> true
1 -> expressions[0] is GrClosableBlock
2 -> {
val (first, second) = expressions
first is GrListOrMap &&
first.isMap &&
second is GrClosableBlock &&
expression.namedArguments.isEmpty()
}
else -> false
}
}
private fun isTaskIdOperatorDown(expression: GrExpression): Boolean {
if (expression !is GrBinaryExpression) return false
val left = expression.leftOperand
return left is GrLiteral || left is GrReferenceExpression && !left.isQualified
}
/**
* Matches:
* - `task id`
* - `task(id) {}`
* - `task(id, namedArg: 42)`
* - `task(id, namedArg: 42) {}`
*/
private fun isTopTaskCall(methodCall: GrMethodCall): Boolean {
if (!checkTaskCall(methodCall)) {
return false
}
val expressions = methodCall.expressionArguments
val expressionsCount = expressions.size
if (expressionsCount == 0) {
return false
}
val argsCount = expressionsCount + methodCall.closureArguments.size + (if (methodCall.namedArguments.isEmpty()) 0 else 1)
if (argsCount > 3) {
return false
}
val taskId = expressions[0] as? GrReferenceExpression ?: return false
return taskId.isDynamicVariable()
}
/**
* Matches:
* - `id` in `task id`
* - `id` in `task(id) {}`
* - `id` in `task(id, namedArg: 42)`
* - `id` in `task(id, namedArg: 42) {}`
*/
private fun isTaskIdExpression(expression: GrReferenceExpression): Boolean {
val methodCall = findMethodCallByFirstArgument(expression) ?: return false
return isTopTaskCall(methodCall)
}
/**
* Matches:
* - `id` in `task id()`
* - `id` in `task id {}`
* - `id` in `task id(namedArg: 42)`
* - `id` in `task id(namedArg: 42) {}`
*/
private fun isTaskIdInvoked(expression: GrReferenceExpression): Boolean {
val methodCall = expression.parent as? GrMethodCall ?: return false
return methodCall.invokedExpression == expression && isTaskIdCall(methodCall)
}
/**
* Matches `id` in: `task id <op> right`
* which gets transformed to: `task("id") <op> right`
*/
private fun isTaskIdInOperator(expression: GrReferenceExpression): Boolean {
if (expression.isQualified) return false
val binary = expression.parent as? GrBinaryExpression ?: return false
if (expression != binary.leftOperand) return false
return isFirstArgumentOfTaskMethod(binary)
}
private fun isFirstArgumentOfTaskMethod(expression: GrExpression): Boolean {
val methodCall = findMethodCallByFirstArgument(expression) ?: return false
return checkTaskCall(methodCall)
}
private fun findMethodCallByFirstArgument(expression: GrExpression): GrMethodCall? {
val argumentList = expression.parent as? GrArgumentList ?: return null
if (expression != argumentList.expressionArguments[0]) return null
return argumentList.parent as? GrMethodCall
}
private fun checkTaskCall(methodCall: GrMethodCall): Boolean {
val invokedExpression = methodCall.invokedExpression as? GrReferenceExpression ?: return false
return invokedExpression.referenceName == "task" && !invokedExpression.isQualified
}
private fun GrMethodCall.isDynamicVariableInvoked(): Boolean {
val invoked = invokedExpression
return invoked is GrReferenceExpression && invoked.isDynamicVariable()
}
private fun GrReferenceExpression.isDynamicVariable(): Boolean {
return !isQualified && staticReference.resolve() == null
}