Remove bunch files that are equal to base files
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
jvmTarget = "1.6"
|
||||
|
||||
dependencies {
|
||||
compile(project(":compiler:util"))
|
||||
compile(project(":compiler:backend"))
|
||||
compile(project(":compiler:frontend"))
|
||||
compile(project(":compiler:frontend.java"))
|
||||
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
|
||||
compileOnly(intellijDep()) { includeJars("annotations", "asm-all", "trove4j", "guava", rootProject = rootProject) }
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" {}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":idea"))
|
||||
compileOnly(project(":idea:idea-maven"))
|
||||
compileOnly(project(":idea:idea-gradle"))
|
||||
compileOnly(project(":idea:idea-jvm"))
|
||||
|
||||
compile(intellijDep())
|
||||
|
||||
runtimeOnly(files(toolsJar()))
|
||||
}
|
||||
|
||||
val ideaPluginDir: File by rootProject.extra
|
||||
val ideaSandboxDir: File by rootProject.extra
|
||||
|
||||
runIdeTask("runIde", ideaPluginDir, ideaSandboxDir) {
|
||||
dependsOn(":dist", ":ideaPlugin")
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":idea"))
|
||||
compileOnly(project(":idea:idea-maven"))
|
||||
compileOnly(project(":idea:idea-gradle"))
|
||||
compileOnly(project(":idea:idea-jvm"))
|
||||
|
||||
compile(intellijDep())
|
||||
|
||||
runtimeOnly(files(toolsJar()))
|
||||
}
|
||||
|
||||
val ideaPluginDir: File by rootProject.extra
|
||||
val ideaSandboxDir: File by rootProject.extra
|
||||
|
||||
runIdeTask("runIde", ideaPluginDir, ideaSandboxDir) {
|
||||
dependsOn(":dist", ":ideaPlugin")
|
||||
}
|
||||
-944
@@ -1,944 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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.jetbrains.kotlin.idea.search.usagesSearch
|
||||
|
||||
import com.intellij.ide.highlighter.JavaFileType
|
||||
import com.intellij.ide.highlighter.XmlFileType
|
||||
import com.intellij.lang.java.JavaLanguage
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference
|
||||
import com.intellij.psi.search.*
|
||||
import com.intellij.psi.search.searches.ClassInheritorsSearch
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.util.Processor
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.KtNodeTypes
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
|
||||
import org.jetbrains.kotlin.compatibility.ExecutorProcessor
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.PsiDiagnosticUtils
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.util.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
|
||||
import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference
|
||||
import org.jetbrains.kotlin.idea.search.excludeFileTypes
|
||||
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchOptions
|
||||
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReferencesSearchParameters
|
||||
import org.jetbrains.kotlin.idea.search.restrictToKotlinSources
|
||||
import org.jetbrains.kotlin.idea.util.FuzzyType
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
||||
import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import java.util.*
|
||||
|
||||
//TODO: check if smart search is too expensive
|
||||
|
||||
class ExpressionsOfTypeProcessor(
|
||||
private val typeToSearch: FuzzyType,
|
||||
private val classToSearch: PsiClass?,
|
||||
private val searchScope: SearchScope,
|
||||
private val project: Project,
|
||||
private val possibleMatchHandler: (KtExpression) -> Unit,
|
||||
private val possibleMatchesInScopeHandler: (SearchScope) -> Unit
|
||||
) {
|
||||
@TestOnly
|
||||
enum class Mode {
|
||||
ALWAYS_SMART,
|
||||
ALWAYS_PLAIN,
|
||||
PLAIN_WHEN_NEEDED // use plain search for LocalSearchScope and when unknown type of reference encountered
|
||||
}
|
||||
|
||||
companion object {
|
||||
@TestOnly
|
||||
var mode = if (ApplicationManager.getApplication().isUnitTestMode) Mode.ALWAYS_SMART else Mode.PLAIN_WHEN_NEEDED
|
||||
@TestOnly
|
||||
var testLog: MutableList<String>? = null
|
||||
|
||||
inline fun testLog(s: () -> String) {
|
||||
testLog?.add(s())
|
||||
}
|
||||
|
||||
val LOG = Logger.getInstance(ExpressionsOfTypeProcessor::class.java)
|
||||
|
||||
fun logPresentation(element: PsiElement): String? {
|
||||
return runReadAction {
|
||||
if (element !is KtDeclaration && element !is PsiMember) return@runReadAction element.text
|
||||
val fqName = element.getKotlinFqName()?.asString()
|
||||
?: (element as? KtNamedDeclaration)?.name
|
||||
when (element) {
|
||||
is PsiMethod -> fqName + element.parameterList.text
|
||||
is KtFunction -> fqName + element.valueParameterList!!.text
|
||||
is KtParameter -> {
|
||||
val owner = element.ownerFunction?.let { logPresentation(it) } ?: element.parent.toString()
|
||||
"parameter ${element.name} of $owner"
|
||||
}
|
||||
is KtDestructuringDeclaration -> element.entries.joinToString(", ", prefix = "(", postfix = ")") { it.text }
|
||||
else -> fqName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun PsiModifierListOwner.isPrivate() = hasModifierProperty(PsiModifier.PRIVATE)
|
||||
|
||||
private fun PsiModifierListOwner.isLocal() = parents.any { it is PsiCodeBlock }
|
||||
}
|
||||
|
||||
// note: a Task must define equals & hashCode!
|
||||
private interface Task {
|
||||
fun perform()
|
||||
}
|
||||
|
||||
private val tasks = ArrayDeque<Task>()
|
||||
private val taskSet = HashSet<Task>()
|
||||
|
||||
private val scopesToUsePlainSearch = LinkedHashMap<KtFile, ArrayList<PsiElement>>()
|
||||
|
||||
fun run() {
|
||||
val usePlainSearch = when (ExpressionsOfTypeProcessor.mode) {
|
||||
ExpressionsOfTypeProcessor.Mode.ALWAYS_SMART -> false
|
||||
ExpressionsOfTypeProcessor.Mode.ALWAYS_PLAIN -> true
|
||||
ExpressionsOfTypeProcessor.Mode.PLAIN_WHEN_NEEDED -> searchScope is LocalSearchScope // for local scope it's faster to use plain search
|
||||
}
|
||||
if (usePlainSearch || classToSearch == null) {
|
||||
possibleMatchesInScopeHandler(searchScope)
|
||||
return
|
||||
}
|
||||
|
||||
// optimization
|
||||
if (runReadAction { searchScope is GlobalSearchScope && !FileTypeIndex.containsFileOfType(KotlinFileType.INSTANCE, searchScope) }) return
|
||||
|
||||
// for class from library always use plain search because we cannot search usages in compiled code (we could though)
|
||||
if (!runReadAction { classToSearch.isValid && ProjectRootsUtil.isInProjectSource(classToSearch) }) {
|
||||
possibleMatchesInScopeHandler(searchScope)
|
||||
return
|
||||
}
|
||||
|
||||
addClassToProcess(classToSearch)
|
||||
|
||||
processTasks()
|
||||
|
||||
runReadAction {
|
||||
val scopeElements = scopesToUsePlainSearch.values
|
||||
.flatMap { it }
|
||||
.filter { it.isValid }
|
||||
.toTypedArray()
|
||||
if (scopeElements.isNotEmpty()) {
|
||||
possibleMatchesInScopeHandler(LocalSearchScope(scopeElements))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun addTask(task: Task) {
|
||||
if (taskSet.add(task)) {
|
||||
tasks.push(task)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun processTasks() {
|
||||
while (tasks.isNotEmpty()) {
|
||||
tasks.pop().perform()
|
||||
}
|
||||
}
|
||||
|
||||
private fun downShiftToPlainSearch(reference: PsiReference) {
|
||||
val message = getFallbackDiagnosticsMessage(reference)
|
||||
LOG.info("ExpressionsOfTypeProcessor: " + message)
|
||||
testLog { "Downgrade to plain text search: $message" }
|
||||
|
||||
tasks.clear()
|
||||
scopesToUsePlainSearch.clear()
|
||||
possibleMatchesInScopeHandler(searchScope)
|
||||
}
|
||||
|
||||
private fun checkPsiClass(psiClass: PsiClass): Boolean {
|
||||
// we don't filter out private classes because we can inherit public class from private inside the same visibility scope
|
||||
if (psiClass.isLocal()) {
|
||||
return false
|
||||
}
|
||||
|
||||
val qualifiedName = runReadAction { psiClass.qualifiedName }
|
||||
if (qualifiedName == null || qualifiedName.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun addNonKotlinClassToProcess(classToSearch: PsiClass) {
|
||||
if (!checkPsiClass(classToSearch)) {
|
||||
return
|
||||
}
|
||||
|
||||
addClassToProcess(classToSearch)
|
||||
}
|
||||
|
||||
private fun addClassToProcess(classToSearch: PsiClass) {
|
||||
data class ProcessClassUsagesTask(val classToSearch: PsiClass) : Task {
|
||||
override fun perform() {
|
||||
testLog { "Searched references to ${logPresentation(classToSearch)}" }
|
||||
val scope = GlobalSearchScope.allScope(project).excludeFileTypes(XmlFileType.INSTANCE) // ignore usages in XML - they don't affect us
|
||||
searchReferences(classToSearch, scope) { reference ->
|
||||
val element = reference.element
|
||||
val wasProcessed = when (element.language) {
|
||||
KotlinLanguage.INSTANCE -> processClassUsageInKotlin(element)
|
||||
JavaLanguage.INSTANCE -> processClassUsageInJava(element)
|
||||
else -> {
|
||||
when (element.language.displayName) {
|
||||
"Groovy" -> {
|
||||
processClassUsageInLanguageWithPsiClass(element)
|
||||
true
|
||||
}
|
||||
"Scala" -> false
|
||||
"Clojure" -> false
|
||||
else -> {
|
||||
// If there's no PsiClass - consider processed
|
||||
element.getParentOfType<PsiClass>(true) == null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wasProcessed) return@searchReferences true
|
||||
|
||||
if (mode != Mode.ALWAYS_SMART) {
|
||||
downShiftToPlainSearch(reference)
|
||||
return@searchReferences false
|
||||
}
|
||||
|
||||
error(getFallbackDiagnosticsMessage(reference))
|
||||
}
|
||||
|
||||
// we must use plain search inside our class (and inheritors) because implicit 'this' can happen anywhere
|
||||
(classToSearch as? KtLightClass)?.kotlinOrigin?.let { usePlainSearch(it) }
|
||||
}
|
||||
}
|
||||
addTask(ProcessClassUsagesTask(classToSearch))
|
||||
}
|
||||
|
||||
private fun getFallbackDiagnosticsMessage(reference: PsiReference): String {
|
||||
val element = reference.element
|
||||
val document = PsiDocumentManager.getInstance(project).getDocument(element.containingFile)
|
||||
val lineAndCol = PsiDiagnosticUtils.offsetToLineAndColumn(document, element.startOffset)
|
||||
return "Unsupported reference: '${element.text}' in ${element.containingFile.name} line ${lineAndCol.line} column ${lineAndCol.column}"
|
||||
}
|
||||
|
||||
private enum class ReferenceProcessor(val handler: (ExpressionsOfTypeProcessor, PsiReference) -> Boolean) {
|
||||
CallableOfOurType(ExpressionsOfTypeProcessor::processReferenceToCallableOfOurType),
|
||||
|
||||
ProcessLambdasInCalls({ processor, reference ->
|
||||
(reference.element as? KtReferenceExpression)?.let { processor.processLambdasForCallableReference(it) }
|
||||
true
|
||||
})
|
||||
}
|
||||
|
||||
private class StaticMemberRequestResultProcessor(val psiMember: PsiMember, classes: List<PsiClass>) : RequestResultProcessor(psiMember) {
|
||||
val possibleClassesNames: Set<String> = runReadAction { classes.map { it.qualifiedName }.filterNotNullTo(HashSet()) }
|
||||
|
||||
override fun processTextOccurrence(element: PsiElement, offsetInElement: Int, consumer: ExecutorProcessor<PsiReference>): Boolean {
|
||||
when (element) {
|
||||
is KtQualifiedExpression -> {
|
||||
val selectorExpression = element.selectorExpression ?: return true
|
||||
val selectorReference = element.findReferenceAt(selectorExpression.startOffsetInParent)
|
||||
|
||||
val references = when (selectorReference) {
|
||||
is PsiMultiReference -> selectorReference.references.toList()
|
||||
else -> listOf(selectorReference)
|
||||
}.filterNotNull()
|
||||
|
||||
for (ref in references) {
|
||||
ProgressManager.checkCanceled()
|
||||
|
||||
if (ref.isReferenceTo(psiMember)) {
|
||||
consumer.process(ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is KtImportDirective -> {
|
||||
if (element.isAllUnder) {
|
||||
val fqName = element.importedFqName?.asString()
|
||||
if (fqName != null && fqName in possibleClassesNames) {
|
||||
val ref = element.importedReference
|
||||
?.getQualifiedElementSelector()
|
||||
?.references
|
||||
?.firstOrNull()
|
||||
if (ref != null) {
|
||||
consumer.process(ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private fun classUseScope(psiClass: PsiClass) = runReadAction {
|
||||
if (!psiClass.isValid) {
|
||||
throw ProcessCanceledException()
|
||||
}
|
||||
|
||||
val file = psiClass.containingFile
|
||||
(file ?: psiClass).useScope
|
||||
}
|
||||
|
||||
private fun addStaticMemberToProcess(psiMember: PsiMember, scope: SearchScope, processor: ReferenceProcessor) {
|
||||
val declarationClass = runReadAction { psiMember.containingClass } ?: return
|
||||
val declarationName = runReadAction { psiMember.name } ?: return
|
||||
if (declarationName.isEmpty()) return
|
||||
|
||||
data class ProcessStaticCallableUsagesTask(val member: PsiMember, val memberScope: SearchScope, val taskProcessor: ReferenceProcessor) : Task {
|
||||
override fun perform() {
|
||||
// This class will look through the whole hierarchy anyway, so shouldn't be a big overhead here
|
||||
val inheritanceClasses = ClassInheritorsSearch.search(
|
||||
declarationClass,
|
||||
classUseScope(declarationClass),
|
||||
true, true, false).findAll()
|
||||
|
||||
val classes = (inheritanceClasses + declarationClass).filter {
|
||||
it !is KtLightClass
|
||||
}
|
||||
|
||||
val searchRequestCollector = SearchRequestCollector(SearchSession())
|
||||
val resultProcessor = StaticMemberRequestResultProcessor(member, classes)
|
||||
|
||||
val memberName = runReadAction { member.name }
|
||||
for (klass in classes) {
|
||||
val request = klass.name + "." + declarationName
|
||||
|
||||
testLog { "Searched references to static $memberName in non-Java files by request $request" }
|
||||
searchRequestCollector.searchWord(
|
||||
request,
|
||||
classUseScope(klass).intersectWith(memberScope), UsageSearchContext.IN_CODE, true, member, resultProcessor)
|
||||
|
||||
val qualifiedName = runReadAction { klass.qualifiedName }
|
||||
if (qualifiedName != null) {
|
||||
val importAllUnderRequest = qualifiedName + ".*"
|
||||
|
||||
testLog { "Searched references to static $memberName in non-Java files by request $importAllUnderRequest" }
|
||||
searchRequestCollector.searchWord(
|
||||
importAllUnderRequest,
|
||||
classUseScope(klass).intersectWith(memberScope), UsageSearchContext.IN_CODE, true, member, resultProcessor)
|
||||
}
|
||||
}
|
||||
|
||||
PsiSearchHelper.SERVICE.getInstance(project).processRequests(searchRequestCollector) { reference ->
|
||||
if (reference.element.parents.any { it is KtImportDirective }) {
|
||||
// Found declaration in import - process all file with an ordinal reference search
|
||||
val containingFile = reference.element.containingFile
|
||||
addCallableDeclarationToProcess(member, LocalSearchScope(containingFile), taskProcessor)
|
||||
|
||||
true
|
||||
}
|
||||
else {
|
||||
val processed = taskProcessor.handler(this@ExpressionsOfTypeProcessor, reference)
|
||||
if (!processed) { // we don't know how to handle this reference and down-shift to plain search
|
||||
downShiftToPlainSearch(reference)
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addTask(ProcessStaticCallableUsagesTask(psiMember, scope, processor))
|
||||
return
|
||||
}
|
||||
|
||||
private fun addCallableDeclarationToProcess(declaration: PsiElement, scope: SearchScope, processor: ReferenceProcessor) {
|
||||
if (scope !is LocalSearchScope && declaration is PsiMember && (declaration.modifierList?.hasModifierProperty(PsiModifier.STATIC) ?: false)) {
|
||||
addStaticMemberToProcess(declaration, scope, processor)
|
||||
return
|
||||
}
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
data class ProcessCallableUsagesTask(
|
||||
val declaration: PsiElement,
|
||||
val processor: ReferenceProcessor,
|
||||
val scope: SearchScope) : Task {
|
||||
override fun perform() {
|
||||
if (scope is LocalSearchScope) {
|
||||
testLog { "Searched imported static member $declaration in ${scope.scope.toList()}" }
|
||||
}
|
||||
else {
|
||||
testLog { "Searched references to ${logPresentation(declaration)} in non-Java files" }
|
||||
}
|
||||
|
||||
val searchParameters = KotlinReferencesSearchParameters(
|
||||
declaration, scope, kotlinOptions = KotlinReferencesSearchOptions(searchNamedArguments = false))
|
||||
searchReferences(searchParameters) { reference ->
|
||||
val processed = processor.handler(this@ExpressionsOfTypeProcessor, reference)
|
||||
if (!processed) { // we don't know how to handle this reference and down-shift to plain search
|
||||
downShiftToPlainSearch(reference)
|
||||
}
|
||||
processed
|
||||
}
|
||||
}
|
||||
}
|
||||
addTask(ProcessCallableUsagesTask(declaration, processor, scope))
|
||||
}
|
||||
|
||||
private fun addPsiMemberTask(member: PsiMember) {
|
||||
if (!member.isPrivate() && !member.isLocal()) {
|
||||
addCallableDeclarationOfOurType(member)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addCallableDeclarationOfOurType(declaration: PsiElement) {
|
||||
addCallableDeclarationToProcess(declaration, searchScope.restrictToKotlinSources(), ReferenceProcessor.CallableOfOurType)
|
||||
}
|
||||
|
||||
/**
|
||||
* Process references to declaration which has parameter of functional type with our class used inside
|
||||
*/
|
||||
private fun addCallableDeclarationToProcessLambdasInCalls(declaration: PsiElement) {
|
||||
// we don't need to search usages of declarations in Java because Java doesn't have implicitly typed declarations so such usages cannot affect Kotlin code
|
||||
val scope = GlobalSearchScope.projectScope(project).excludeFileTypes(JavaFileType.INSTANCE, XmlFileType.INSTANCE)
|
||||
addCallableDeclarationToProcess(declaration, scope, ReferenceProcessor.ProcessLambdasInCalls)
|
||||
}
|
||||
|
||||
/**
|
||||
* Process reference to declaration whose type is our class (or our class used anywhere inside that type)
|
||||
*/
|
||||
private fun processReferenceToCallableOfOurType(reference: PsiReference) = when (reference.element.language) {
|
||||
KotlinLanguage.INSTANCE -> {
|
||||
if (reference is KtDestructuringDeclarationReference) {
|
||||
// declaration usage in form of destructuring declaration entry
|
||||
addCallableDeclarationOfOurType(reference.element)
|
||||
}
|
||||
else {
|
||||
(reference.element as? KtReferenceExpression)?.let { processSuspiciousExpression(it) }
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
else -> false // reference in unknown language - we don't know how to handle it
|
||||
}
|
||||
|
||||
private fun addSamInterfaceToProcess(psiClass: PsiClass) {
|
||||
if (!checkPsiClass(psiClass)) {
|
||||
return
|
||||
}
|
||||
|
||||
data class ProcessSamInterfaceTask(val psiClass: PsiClass) : Task {
|
||||
override fun perform() {
|
||||
val scope = GlobalSearchScope.projectScope(project).excludeFileTypes(KotlinFileType.INSTANCE, XmlFileType.INSTANCE)
|
||||
testLog { "Searched references to ${logPresentation(psiClass)} in non-Kotlin files" }
|
||||
searchReferences(psiClass, scope) { reference ->
|
||||
if (reference.element.language != JavaLanguage.INSTANCE) { // reference in some JVM language can be method parameter (but we don't know)
|
||||
downShiftToPlainSearch(reference)
|
||||
return@searchReferences false
|
||||
}
|
||||
|
||||
// check if the reference is method parameter type
|
||||
val parameter = ((reference as? PsiJavaCodeReferenceElement)?.parent as? PsiTypeElement)?.parent as? PsiParameter
|
||||
val method = parameter?.declarationScope as? PsiMethod
|
||||
if (method != null) {
|
||||
addCallableDeclarationToProcessLambdasInCalls(method)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
addTask(ProcessSamInterfaceTask(psiClass))
|
||||
}
|
||||
|
||||
private fun processClassUsageInKotlin(element: PsiElement): Boolean {
|
||||
//TODO: type aliases
|
||||
|
||||
when (element) {
|
||||
is KtReferenceExpression -> {
|
||||
val parent = element.parent
|
||||
when (parent) {
|
||||
is KtUserType -> { // usage in type
|
||||
return processClassUsageInUserType(parent)
|
||||
}
|
||||
|
||||
is KtCallExpression -> {
|
||||
if (element == parent.calleeExpression) { // constructor invocation
|
||||
processSuspiciousExpression(parent)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
is KtContainerNode -> {
|
||||
if (parent.node.elementType == KtNodeTypes.LABEL_QUALIFIER) {
|
||||
return true // this@ClassName - it will be handled anyway because members and extensions are processed with plain search
|
||||
}
|
||||
}
|
||||
|
||||
is KtQualifiedExpression -> {
|
||||
// <class name>.memberName or some.<class name>.memberName
|
||||
if (element == parent.receiverExpression || parent.parent is KtQualifiedExpression) {
|
||||
return true // companion object member or static member access - ignore it
|
||||
}
|
||||
}
|
||||
|
||||
is KtCallableReferenceExpression -> {
|
||||
when (element) {
|
||||
parent.receiverExpression -> { // usage in receiver of callable reference (before "::") - ignore it
|
||||
return true
|
||||
}
|
||||
|
||||
parent.callableReference -> { // usage after "::" in callable reference - should be reference to constructor of our class
|
||||
processSuspiciousExpression(element)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is KtClassLiteralExpression -> {
|
||||
if (element == parent.receiverExpression) { // <class name>::class
|
||||
processSuspiciousExpression(element)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (element.getStrictParentOfType<KtImportDirective>() != null) return true // ignore usage in import
|
||||
|
||||
val bindingContext = element.analyze(BodyResolveMode.PARTIAL)
|
||||
val hasType = bindingContext.getType(element) != null
|
||||
if (hasType) { // access to object or companion object
|
||||
processSuspiciousExpression(element)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
is KDocName -> return true // ignore usage in doc-comment
|
||||
}
|
||||
|
||||
return false // unsupported type of reference
|
||||
}
|
||||
|
||||
private fun processClassUsageInUserType(userType: KtUserType): Boolean {
|
||||
val typeRef = userType.parents.lastOrNull { it is KtTypeReference }
|
||||
val typeRefParent = typeRef?.parent
|
||||
when (typeRefParent) {
|
||||
is KtCallableDeclaration -> {
|
||||
when (typeRef) {
|
||||
typeRefParent.typeReference -> { // usage in type of callable declaration
|
||||
addCallableDeclarationOfOurType(typeRefParent)
|
||||
|
||||
if (typeRefParent is KtParameter) { //TODO: what if functional type is declared with "FunctionN<...>"?
|
||||
val usedInsideFunctionalType = userType.parents.takeWhile { it != typeRef }.any { it is KtFunctionType }
|
||||
if (usedInsideFunctionalType) {
|
||||
val function = (typeRefParent.parent as? KtParameterList)?.parent as? KtFunction
|
||||
if (function != null) {
|
||||
addCallableDeclarationOfOurType(function)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
typeRefParent.receiverTypeReference -> { // usage in receiver type of callable declaration
|
||||
// we must use plain search inside extensions because implicit 'this' can happen anywhere
|
||||
usePlainSearch(typeRefParent)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is KtTypeProjection -> { // usage in type arguments of a call
|
||||
val callExpression = (typeRefParent.parent as? KtTypeArgumentList)?.parent as? KtCallExpression
|
||||
if (callExpression != null) {
|
||||
processSuspiciousExpression(callExpression)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
is KtConstructorCalleeExpression -> { // super-class name in the list of bases
|
||||
val parent = typeRefParent.parent
|
||||
if (parent is KtSuperTypeCallEntry) {
|
||||
val classOrObject = (parent.parent as KtSuperTypeList).parent as KtClassOrObject
|
||||
val psiClass = classOrObject.toLightClass()
|
||||
psiClass?.let { addClassToProcess(it) }
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
is KtSuperTypeListEntry -> { // super-interface name in the list of bases
|
||||
if (typeRef == typeRefParent.typeReference) {
|
||||
val classOrObject = (typeRefParent.parent as KtSuperTypeList).parent as KtClassOrObject
|
||||
val psiClass = classOrObject.toLightClass()
|
||||
psiClass?.let { addClassToProcess(it) }
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
is KtIsExpression -> { // <expr> is <class name>
|
||||
val scopeOfPossibleSmartCast = typeRefParent.getParentOfType<KtDeclarationWithBody>(true)
|
||||
scopeOfPossibleSmartCast?.let { usePlainSearch(it) }
|
||||
return true
|
||||
}
|
||||
|
||||
is KtWhenConditionIsPattern -> { // "is <class name>" or "!is <class name>" in when
|
||||
val whenEntry = typeRefParent.parent as KtWhenEntry
|
||||
if (typeRefParent.isNegated) {
|
||||
val whenExpression = whenEntry.parent as KtWhenExpression
|
||||
val entriesAfter = whenExpression.entries.dropWhile { it != whenEntry }.drop(1)
|
||||
entriesAfter.forEach { usePlainSearch(it) }
|
||||
}
|
||||
else {
|
||||
usePlainSearch(whenEntry)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
is KtBinaryExpressionWithTypeRHS -> { // <expr> as <class name>
|
||||
processSuspiciousExpression(typeRefParent)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false // unsupported case
|
||||
}
|
||||
|
||||
private fun processClassUsageInJava(element: PsiElement): Boolean {
|
||||
if (element !is PsiJavaCodeReferenceElement) return true // meaningless reference from Java
|
||||
|
||||
var prev = element
|
||||
ParentsLoop@
|
||||
for (parent in element.parents) {
|
||||
when (parent) {
|
||||
is PsiCodeBlock,
|
||||
is PsiExpression ->
|
||||
break@ParentsLoop // ignore local usages
|
||||
|
||||
is PsiMethod -> {
|
||||
if (prev == parent.returnTypeElement) { // usage in return type of a method
|
||||
addPsiMemberTask(parent)
|
||||
}
|
||||
break@ParentsLoop
|
||||
}
|
||||
|
||||
is PsiField -> {
|
||||
if (prev == parent.typeElement) { // usage in type of a field
|
||||
addPsiMemberTask(parent)
|
||||
}
|
||||
break@ParentsLoop
|
||||
}
|
||||
|
||||
is PsiReferenceList -> { // usage in extends/implements list
|
||||
if (parent.role == PsiReferenceList.Role.EXTENDS_LIST || parent.role == PsiReferenceList.Role.IMPLEMENTS_LIST) {
|
||||
val psiClass = parent.parent as PsiClass
|
||||
addNonKotlinClassToProcess(psiClass)
|
||||
}
|
||||
break@ParentsLoop
|
||||
}
|
||||
|
||||
//TODO: if Java parameter has Kotlin functional type then we should process method usages
|
||||
is PsiParameter -> {
|
||||
if (prev == parent.typeElement) { // usage in parameter type - check if the method is in SAM interface
|
||||
processParameterInSamClass(parent)
|
||||
}
|
||||
break@ParentsLoop
|
||||
}
|
||||
}
|
||||
|
||||
prev = parent
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun processClassUsageInLanguageWithPsiClass(element: PsiElement) {
|
||||
fun checkReferenceInTypeElement(typeElement: PsiTypeElement?, element: PsiElement): Boolean {
|
||||
val typeTextRange = typeElement?.textRange
|
||||
return (typeTextRange != null && element.textRange in typeTextRange)
|
||||
}
|
||||
|
||||
fun processParameter(parameter: PsiParameter): Boolean {
|
||||
if (checkReferenceInTypeElement(parameter.typeElement, element)) {
|
||||
processParameterInSamClass(parameter)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun processMethod(method: PsiMethod): Boolean {
|
||||
if (checkReferenceInTypeElement(method.returnTypeElement, element)) {
|
||||
addPsiMemberTask(method)
|
||||
return true
|
||||
}
|
||||
|
||||
val parameters = method.parameterList.parameters
|
||||
for (parameter in parameters) {
|
||||
if (processParameter(parameter)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun processField(field: PsiField): Boolean {
|
||||
if (checkReferenceInTypeElement(field.typeElement, element)) {
|
||||
addPsiMemberTask(field)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun processClass(psiClass: PsiClass) {
|
||||
if (!checkPsiClass(psiClass)) {
|
||||
return
|
||||
}
|
||||
|
||||
val elementTextRange: TextRange? = element.textRange
|
||||
if (elementTextRange != null) {
|
||||
val superList = listOf(psiClass.extendsList, psiClass.implementsList)
|
||||
for (psiReferenceList in superList) {
|
||||
val superListRange: TextRange? = psiReferenceList?.textRange
|
||||
if (superListRange != null && elementTextRange in superListRange) {
|
||||
addNonKotlinClassToProcess(psiClass)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (psiClass.fields.any { processField(it) }) {
|
||||
return
|
||||
}
|
||||
|
||||
if (psiClass.methods.any { processMethod(it) }) {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
val psiClass = element.getParentOfType<PsiClass>(true)
|
||||
if (psiClass != null) {
|
||||
processClass(psiClass)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processParameterInSamClass(psiParameter: PsiParameter): Boolean {
|
||||
val method = psiParameter.declarationScope as? PsiMethod ?: return false
|
||||
|
||||
if (method.hasModifierProperty(PsiModifier.ABSTRACT)) {
|
||||
val psiClass = method.containingClass
|
||||
if (psiClass != null) {
|
||||
testLog { "Resolved java class to descriptor: ${psiClass.qualifiedName}" }
|
||||
|
||||
val resolutionFacade = KotlinCacheService.getInstance(project).getResolutionFacadeByFile(psiClass.containingFile, JvmPlatform)
|
||||
val classDescriptor = psiClass.resolveToDescriptor(resolutionFacade) as? JavaClassDescriptor
|
||||
if (classDescriptor != null && SingleAbstractMethodUtils.getSingleAbstractMethodOrNull(classDescriptor) != null) {
|
||||
addSamInterfaceToProcess(psiClass)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Process expression which may have type of our class (or our class used anywhere inside that type)
|
||||
*/
|
||||
private fun processSuspiciousExpression(expression: KtExpression) {
|
||||
var inScope = expression in searchScope
|
||||
var affectedScope: PsiElement = expression
|
||||
ParentsLoop@
|
||||
for (element in expression.parentsWithSelf) {
|
||||
affectedScope = element
|
||||
if (element !is KtExpression) continue
|
||||
|
||||
if (searchScope is LocalSearchScope) { // optimization to not check every expression
|
||||
inScope = inScope && element in searchScope
|
||||
}
|
||||
if (inScope) {
|
||||
possibleMatchHandler(element)
|
||||
}
|
||||
|
||||
val parent = element.parent
|
||||
when (parent) {
|
||||
is KtDestructuringDeclaration -> { // "val (x, y) = <expr>"
|
||||
processSuspiciousDeclaration(parent)
|
||||
break@ParentsLoop
|
||||
}
|
||||
|
||||
is KtDeclarationWithInitializer -> { // "val x = <expr>" or "fun f() = <expr>"
|
||||
if (element == parent.initializer) {
|
||||
processSuspiciousDeclaration(parent)
|
||||
}
|
||||
break@ParentsLoop
|
||||
}
|
||||
|
||||
is KtContainerNode -> {
|
||||
if (parent.node.elementType == KtNodeTypes.LOOP_RANGE) { // "for (x in <expr>) ..."
|
||||
val forExpression = parent.parent as KtForExpression
|
||||
(forExpression.destructuringDeclaration ?: forExpression.loopParameter as KtDeclaration?)?.let {
|
||||
processSuspiciousDeclaration(it)
|
||||
}
|
||||
break@ParentsLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!element.mayTypeAffectAncestors()) break
|
||||
}
|
||||
|
||||
// use plain search in all lambdas and anonymous functions inside because they parameters or receiver can be implicitly typed with our class
|
||||
usePlainSearchInLambdas(affectedScope)
|
||||
}
|
||||
|
||||
private fun processLambdasForCallableReference(expression: KtReferenceExpression) {
|
||||
//TODO: receiver?
|
||||
usePlainSearchInLambdas(expression.parent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Process declaration which may have implicit type of our class (or our class used anywhere inside that type)
|
||||
*/
|
||||
private fun processSuspiciousDeclaration(declaration: KtDeclaration) {
|
||||
if (declaration is KtDestructuringDeclaration) {
|
||||
declaration.entries.forEach { processSuspiciousDeclaration(it) }
|
||||
}
|
||||
else {
|
||||
if (!isImplicitlyTyped(declaration)) return
|
||||
|
||||
testLog { "Checked type of ${logPresentation(declaration)}" }
|
||||
|
||||
val descriptor = declaration.resolveToDescriptorIfAny() as? CallableDescriptor ?: return
|
||||
val type = descriptor.returnType
|
||||
if (type != null && type.containsTypeOrDerivedInside(typeToSearch)) {
|
||||
addCallableDeclarationOfOurType(declaration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun usePlainSearchInLambdas(scope: PsiElement) {
|
||||
scope.forEachDescendantOfType<KtFunction> {
|
||||
if (it.nameIdentifier == null) {
|
||||
usePlainSearch(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun usePlainSearch(scope: KtElement) {
|
||||
runReadAction {
|
||||
if (!scope.isValid) return@runReadAction
|
||||
|
||||
val file = scope.containingKtFile
|
||||
val restricted = LocalSearchScope(scope).intersectWith(searchScope)
|
||||
if (restricted is LocalSearchScope) {
|
||||
ScopeLoop@
|
||||
for (element in restricted.scope) {
|
||||
val prevElements = scopesToUsePlainSearch.getOrPut(file) { ArrayList() }
|
||||
for ((index, prevElement) in prevElements.withIndex()) {
|
||||
if (!prevElement.isValid) continue@ScopeLoop
|
||||
if (prevElement.isAncestor(element, strict = false)) continue@ScopeLoop
|
||||
if (element.isAncestor(prevElement)) {
|
||||
prevElements[index] = element
|
||||
continue@ScopeLoop
|
||||
}
|
||||
}
|
||||
prevElements.add(element)
|
||||
}
|
||||
}
|
||||
else {
|
||||
assert(restricted == GlobalSearchScope.EMPTY_SCOPE)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: code is quite similar to PartialBodyResolveFilter.isValueNeeded
|
||||
private fun KtExpression.mayTypeAffectAncestors(): Boolean {
|
||||
val parent = this.parent
|
||||
when (parent) {
|
||||
is KtBlockExpression -> {
|
||||
return this == parent.statements.last() && parent.mayTypeAffectAncestors()
|
||||
}
|
||||
|
||||
is KtDeclarationWithBody -> {
|
||||
if (this == parent.bodyExpression) {
|
||||
return !parent.hasBlockBody() && !parent.hasDeclaredReturnType()
|
||||
}
|
||||
}
|
||||
|
||||
is KtContainerNode -> {
|
||||
val grandParent = parent.parent
|
||||
return when (parent.node.elementType) {
|
||||
KtNodeTypes.CONDITION, KtNodeTypes.BODY -> false
|
||||
KtNodeTypes.THEN, KtNodeTypes.ELSE -> (grandParent as KtExpression).mayTypeAffectAncestors()
|
||||
KtNodeTypes.LOOP_RANGE, KtNodeTypes.INDICES -> true
|
||||
else -> true // something else unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
return true // we don't know
|
||||
}
|
||||
|
||||
private fun KotlinType.containsTypeOrDerivedInside(type: FuzzyType): Boolean {
|
||||
return type.checkIsSuperTypeOf(this) != null || arguments.any { !it.isStarProjection && it.type.containsTypeOrDerivedInside(type) }
|
||||
}
|
||||
|
||||
private fun isImplicitlyTyped(declaration: KtDeclaration): Boolean {
|
||||
return when (declaration) {
|
||||
is KtFunction -> !declaration.hasDeclaredReturnType()
|
||||
is KtVariableDeclaration -> declaration.typeReference == null
|
||||
is KtParameter -> declaration.typeReference == null
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun searchReferences(element: PsiElement, scope: SearchScope, processor: (PsiReference) -> Boolean) {
|
||||
val parameters = ReferencesSearch.SearchParameters(element, scope, false)
|
||||
searchReferences(parameters, processor)
|
||||
}
|
||||
|
||||
private fun searchReferences(parameters: ReferencesSearch.SearchParameters, processor: (PsiReference) -> Boolean) {
|
||||
ReferencesSearch.search(parameters).forEach(Processor { ref ->
|
||||
runReadAction {
|
||||
if (ref.element.isValid) {
|
||||
processor(ref)
|
||||
}
|
||||
else {
|
||||
true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
-361
@@ -1,361 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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.jetbrains.kotlin.idea.codeInsight.gradle
|
||||
|
||||
import com.intellij.codeInspection.LocalInspectionTool
|
||||
import com.intellij.codeInspection.ProblemDescriptorBase
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import org.jetbrains.kotlin.idea.inspections.gradle.DeprecatedGradleDependencyInspection
|
||||
import org.jetbrains.kotlin.idea.inspections.gradle.DifferentKotlinGradleVersionInspection
|
||||
import org.jetbrains.kotlin.idea.inspections.gradle.DifferentStdlibGradleVersionInspection
|
||||
import org.jetbrains.kotlin.idea.inspections.runInspection
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
class GradleInspectionTest : GradleImportingTestCase() {
|
||||
@Test
|
||||
fun testDifferentStdlibGradleVersion() {
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.0.2")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:1.0.3"
|
||||
}
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DifferentStdlibGradleVersionInspection()
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.size == 1)
|
||||
Assert.assertEquals("Plugin version (1.0.2) is not the same as library version (1.0.3)", problems.single())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDifferentStdlibGradleVersionWithImplementation() {
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.0.2")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.0.3"
|
||||
}
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DifferentStdlibGradleVersionInspection()
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.size == 1)
|
||||
Assert.assertEquals("Plugin version (1.0.2) is not the same as library version (1.0.3)", problems.single())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDifferentStdlibJre7GradleVersion() {
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0-beta-17")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:1.1.0-beta-22"
|
||||
}
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DifferentStdlibGradleVersionInspection()
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.size == 1)
|
||||
Assert.assertEquals("Plugin version (1.1.0-beta-17) is not the same as library version (1.1.0-beta-22)", problems.single())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDifferentStdlibJdk7GradleVersion() {
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0-beta-17")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.1.0-beta-22"
|
||||
}
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DifferentStdlibGradleVersionInspection()
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.size == 1)
|
||||
Assert.assertEquals("Plugin version (1.1.0-beta-17) is not the same as library version (1.1.0-beta-22)", problems.single())
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testDifferentStdlibGradleVersionWithVariables() {
|
||||
createProjectSubFile(
|
||||
"gradle.properties", """
|
||||
|kotlin=1.0.1
|
||||
|lib_version=1.0.3""".trimMargin()
|
||||
)
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${'$'}kotlin")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: lib_version
|
||||
}
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DifferentStdlibGradleVersionInspection()
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.size == 1)
|
||||
Assert.assertEquals("Plugin version (1.0.1) is not the same as library version (1.0.3)", problems.single())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDifferentKotlinGradleVersion() {
|
||||
createProjectSubFile("gradle.properties", """test=1.0.1""")
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${'$'}{test}")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DifferentKotlinGradleVersionInspection()
|
||||
tool.testVersionMessage = "\$PLUGIN_VERSION"
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.size == 1)
|
||||
Assert.assertEquals(
|
||||
"Kotlin version that is used for building with Gradle (1.0.1) differs from the one bundled into the IDE plugin (\$PLUGIN_VERSION)",
|
||||
problems.single()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJreInOldVersion() {
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.60")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:1.1.60"
|
||||
}
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DeprecatedGradleDependencyInspection()
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJreIsDeprecated() {
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.60")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.0"
|
||||
}
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DeprecatedGradleDependencyInspection()
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.size == 1)
|
||||
Assert.assertEquals(
|
||||
"kotlin-stdlib-jre7 is deprecated since 1.2.0 and should be replaced with kotlin-stdlib-jdk7",
|
||||
problems.single()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testJreIsDeprecatedWithImplementation() {
|
||||
val localFile = createProjectSubFile(
|
||||
"build.gradle", """
|
||||
group 'Again'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.0")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.0"
|
||||
}
|
||||
"""
|
||||
)
|
||||
importProject()
|
||||
|
||||
val tool = DeprecatedGradleDependencyInspection()
|
||||
val problems = getInspectionResult(tool, localFile)
|
||||
|
||||
Assert.assertTrue(problems.size == 1)
|
||||
Assert.assertEquals(
|
||||
"kotlin-stdlib-jre7 is deprecated since 1.2.0 and should be replaced with kotlin-stdlib-jdk7",
|
||||
problems.single()
|
||||
)
|
||||
}
|
||||
|
||||
fun getInspectionResult(tool: LocalInspectionTool, file: VirtualFile): List<String> {
|
||||
val resultRef = Ref<List<String>>()
|
||||
invokeTestRunnable {
|
||||
val presentation = runInspection(tool, myProject, listOf(file))
|
||||
|
||||
val foundProblems = presentation.problemElements
|
||||
.values
|
||||
.mapNotNull { it as? ProblemDescriptorBase }
|
||||
.map { it.descriptionTemplate }
|
||||
|
||||
resultRef.set(foundProblems)
|
||||
}
|
||||
|
||||
return resultRef.get()
|
||||
}
|
||||
}
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.quickfix
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ex.ProjectRootManagerEx
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder
|
||||
import org.jetbrains.kotlin.idea.configuration.BuildSystemType
|
||||
import org.jetbrains.kotlin.idea.configuration.findApplicableConfigurator
|
||||
import org.jetbrains.kotlin.idea.configuration.getBuildSystemType
|
||||
import org.jetbrains.kotlin.idea.facet.KotlinFacet
|
||||
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
sealed class ChangeCoroutineSupportFix(
|
||||
element: PsiElement,
|
||||
protected val coroutineSupport: LanguageFeature.State
|
||||
) : KotlinQuickFixAction<PsiElement>(element) {
|
||||
protected val coroutineSupportEnabled: Boolean
|
||||
get() = coroutineSupport == LanguageFeature.State.ENABLED || coroutineSupport == LanguageFeature.State.ENABLED_WITH_WARNING
|
||||
|
||||
class InModule(element: PsiElement, coroutineSupport: LanguageFeature.State) : ChangeCoroutineSupportFix(element, coroutineSupport) {
|
||||
override fun getText() = "${super.getText()} in the current module"
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
val module = ModuleUtilCore.findModuleForPsiElement(file) ?: return
|
||||
|
||||
findApplicableConfigurator(module).changeCoroutineConfiguration(module, coroutineSupport)
|
||||
}
|
||||
}
|
||||
|
||||
class InProject(element: PsiElement, coroutineSupport: LanguageFeature.State) : ChangeCoroutineSupportFix(element, coroutineSupport) {
|
||||
override fun getText() = "${super.getText()} in the project"
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
if (coroutineSupportEnabled) {
|
||||
if (!checkUpdateRuntime(project, LanguageFeature.Coroutines.sinceApiVersion)) return
|
||||
}
|
||||
|
||||
KotlinCommonCompilerArgumentsHolder.getInstance(project).update {
|
||||
coroutinesState = when (coroutineSupport) {
|
||||
LanguageFeature.State.ENABLED -> CommonCompilerArguments.ENABLE
|
||||
LanguageFeature.State.ENABLED_WITH_WARNING -> CommonCompilerArguments.WARN
|
||||
LanguageFeature.State.ENABLED_WITH_ERROR, LanguageFeature.State.DISABLED -> CommonCompilerArguments.ERROR
|
||||
}
|
||||
}
|
||||
ProjectRootManagerEx.getInstanceEx(project).makeRootsChange({}, false, true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun getFamilyName() = "Enable/Disable coroutine support"
|
||||
|
||||
override fun getText(): String {
|
||||
return getFixText(coroutineSupport)
|
||||
}
|
||||
|
||||
companion object : KotlinIntentionActionsFactory() {
|
||||
fun getFixText(state: LanguageFeature.State): String {
|
||||
return when (state) {
|
||||
LanguageFeature.State.ENABLED -> "Enable coroutine support"
|
||||
LanguageFeature.State.ENABLED_WITH_WARNING -> "Enable coroutine support (with warning)"
|
||||
LanguageFeature.State.ENABLED_WITH_ERROR, LanguageFeature.State.DISABLED -> "Disable coroutine support"
|
||||
}
|
||||
}
|
||||
|
||||
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
|
||||
val newCoroutineSupports = when (diagnostic.factory) {
|
||||
Errors.EXPERIMENTAL_FEATURE_ERROR -> {
|
||||
if (Errors.EXPERIMENTAL_FEATURE_ERROR.cast(diagnostic).a.first != LanguageFeature.Coroutines) return emptyList()
|
||||
listOf(LanguageFeature.State.ENABLED_WITH_WARNING, LanguageFeature.State.ENABLED)
|
||||
}
|
||||
Errors.EXPERIMENTAL_FEATURE_WARNING -> {
|
||||
if (Errors.EXPERIMENTAL_FEATURE_WARNING.cast(diagnostic).a.first != LanguageFeature.Coroutines) return emptyList()
|
||||
listOf(LanguageFeature.State.ENABLED, LanguageFeature.State.ENABLED_WITH_ERROR)
|
||||
}
|
||||
else -> return emptyList()
|
||||
}
|
||||
val module = ModuleUtilCore.findModuleForPsiElement(diagnostic.psiElement) ?: return emptyList()
|
||||
val facetSettings = KotlinFacet.get(module)?.configuration?.settings
|
||||
|
||||
val configureInProject = (facetSettings == null || facetSettings.useProjectSettings) &&
|
||||
module.getBuildSystemType() == BuildSystemType.JPS
|
||||
val quickFixConstructor: (PsiElement, LanguageFeature.State) -> ChangeCoroutineSupportFix =
|
||||
if (configureInProject) ::InProject else ::InModule
|
||||
return newCoroutineSupports.map { quickFixConstructor(diagnostic.psiElement, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.quickfix
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ex.ProjectRootManagerEx
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder
|
||||
import org.jetbrains.kotlin.idea.configuration.BuildSystemType
|
||||
import org.jetbrains.kotlin.idea.configuration.findApplicableConfigurator
|
||||
import org.jetbrains.kotlin.idea.configuration.getBuildSystemType
|
||||
import org.jetbrains.kotlin.idea.facet.KotlinFacet
|
||||
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
sealed class ChangeCoroutineSupportFix(
|
||||
element: PsiElement,
|
||||
protected val coroutineSupport: LanguageFeature.State
|
||||
) : KotlinQuickFixAction<PsiElement>(element) {
|
||||
protected val coroutineSupportEnabled: Boolean
|
||||
get() = coroutineSupport == LanguageFeature.State.ENABLED || coroutineSupport == LanguageFeature.State.ENABLED_WITH_WARNING
|
||||
|
||||
class InModule(element: PsiElement, coroutineSupport: LanguageFeature.State) : ChangeCoroutineSupportFix(element, coroutineSupport) {
|
||||
override fun getText() = "${super.getText()} in the current module"
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
val module = ModuleUtilCore.findModuleForPsiElement(file) ?: return
|
||||
|
||||
findApplicableConfigurator(module).changeCoroutineConfiguration(module, coroutineSupport)
|
||||
}
|
||||
}
|
||||
|
||||
class InProject(element: PsiElement, coroutineSupport: LanguageFeature.State) : ChangeCoroutineSupportFix(element, coroutineSupport) {
|
||||
override fun getText() = "${super.getText()} in the project"
|
||||
|
||||
override fun invoke(project: Project, editor: Editor?, file: KtFile) {
|
||||
if (coroutineSupportEnabled) {
|
||||
if (!checkUpdateRuntime(project, LanguageFeature.Coroutines.sinceApiVersion)) return
|
||||
}
|
||||
|
||||
KotlinCommonCompilerArgumentsHolder.getInstance(project).update {
|
||||
coroutinesState = when (coroutineSupport) {
|
||||
LanguageFeature.State.ENABLED -> CommonCompilerArguments.ENABLE
|
||||
LanguageFeature.State.ENABLED_WITH_WARNING -> CommonCompilerArguments.WARN
|
||||
LanguageFeature.State.ENABLED_WITH_ERROR, LanguageFeature.State.DISABLED -> CommonCompilerArguments.ERROR
|
||||
}
|
||||
}
|
||||
ProjectRootManagerEx.getInstanceEx(project).makeRootsChange({}, false, true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun getFamilyName() = "Enable/Disable coroutine support"
|
||||
|
||||
override fun getText(): String {
|
||||
return getFixText(coroutineSupport)
|
||||
}
|
||||
|
||||
companion object : KotlinIntentionActionsFactory() {
|
||||
fun getFixText(state: LanguageFeature.State): String {
|
||||
return when (state) {
|
||||
LanguageFeature.State.ENABLED -> "Enable coroutine support"
|
||||
LanguageFeature.State.ENABLED_WITH_WARNING -> "Enable coroutine support (with warning)"
|
||||
LanguageFeature.State.ENABLED_WITH_ERROR, LanguageFeature.State.DISABLED -> "Disable coroutine support"
|
||||
}
|
||||
}
|
||||
|
||||
override fun doCreateActions(diagnostic: Diagnostic): List<IntentionAction> {
|
||||
val newCoroutineSupports = when (diagnostic.factory) {
|
||||
Errors.EXPERIMENTAL_FEATURE_ERROR -> {
|
||||
if (Errors.EXPERIMENTAL_FEATURE_ERROR.cast(diagnostic).a.first != LanguageFeature.Coroutines) return emptyList()
|
||||
listOf(LanguageFeature.State.ENABLED_WITH_WARNING, LanguageFeature.State.ENABLED)
|
||||
}
|
||||
Errors.EXPERIMENTAL_FEATURE_WARNING -> {
|
||||
if (Errors.EXPERIMENTAL_FEATURE_WARNING.cast(diagnostic).a.first != LanguageFeature.Coroutines) return emptyList()
|
||||
listOf(LanguageFeature.State.ENABLED, LanguageFeature.State.ENABLED_WITH_ERROR)
|
||||
}
|
||||
else -> return emptyList()
|
||||
}
|
||||
val module = ModuleUtilCore.findModuleForPsiElement(diagnostic.psiElement) ?: return emptyList()
|
||||
val facetSettings = KotlinFacet.get(module)?.configuration?.settings
|
||||
|
||||
val configureInProject = (facetSettings == null || facetSettings.useProjectSettings) &&
|
||||
module.getBuildSystemType() == BuildSystemType.JPS
|
||||
val quickFixConstructor: (PsiElement, LanguageFeature.State) -> ChangeCoroutineSupportFix =
|
||||
if (configureInProject) ::InProject else ::InModule
|
||||
return newCoroutineSupports.map { quickFixConstructor(diagnostic.psiElement, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
-131
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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.jetbrains.kotlin.idea.decompiler.navigation
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.idea.navigation.NavigationTestUtils
|
||||
import org.jetbrains.kotlin.idea.references.KtReference
|
||||
import org.jetbrains.kotlin.idea.test.*
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
abstract class AbstractNavigateToLibraryTest : KotlinCodeInsightTestCase() {
|
||||
|
||||
protected fun doTest(path: String): Unit = doTestEx(path)
|
||||
|
||||
protected fun doWithJSModuleTest(path: String): Unit = doTestEx(path) {
|
||||
val jsModule = this.createModule("js-module")
|
||||
jsModule.configureAs(ModuleKind.KOTLIN_JAVASCRIPT)
|
||||
}
|
||||
|
||||
abstract val withSource: Boolean
|
||||
abstract val expectedFileExt: String
|
||||
|
||||
protected fun doTestEx(path: String, additionalConfig: (() -> Unit)? = null) {
|
||||
module.configureAs(getProjectDescriptor())
|
||||
|
||||
if (additionalConfig != null) {
|
||||
additionalConfig()
|
||||
}
|
||||
|
||||
configureByFile(path)
|
||||
NavigationChecker.checkAnnotatedCode(file, File(path.replace(".kt", expectedFileExt)))
|
||||
}
|
||||
|
||||
override fun tearDown() {
|
||||
SourceNavigationHelper.setForceResolve(false)
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
override fun getTestDataPath(): String =
|
||||
KotlinTestUtils.getHomeDirectory() + File.separator
|
||||
|
||||
|
||||
open fun getProjectDescriptor(): KotlinLightProjectDescriptor =
|
||||
SdkAndMockLibraryProjectDescriptor(
|
||||
PluginTestCaseBase.getTestDataPathBase() + "/decompiler/navigation/library",
|
||||
withSource
|
||||
)
|
||||
}
|
||||
|
||||
abstract class AbstractNavigateToDecompiledLibraryTest : AbstractNavigateToLibraryTest() {
|
||||
override val withSource: Boolean get() = false
|
||||
override val expectedFileExt: String get() = ".decompiled.expected"
|
||||
}
|
||||
|
||||
abstract class AbstractNavigateToLibrarySourceTest : AbstractNavigateToLibraryTest() {
|
||||
override val withSource: Boolean get() = true
|
||||
override val expectedFileExt: String get() = ".source.expected"
|
||||
}
|
||||
|
||||
class NavigationChecker(val file: PsiFile, val referenceTargetChecker: (PsiElement) -> Unit) {
|
||||
fun annotatedLibraryCode(): String {
|
||||
return NavigationTestUtils.getNavigateElementsText(file.project, collectInterestingNavigationElements())
|
||||
}
|
||||
|
||||
private fun collectInterestingNavigationElements() =
|
||||
collectInterestingReferences().map {
|
||||
val target = it.resolve()
|
||||
TestCase.assertNotNull(target)
|
||||
target!!.navigationElement
|
||||
}
|
||||
|
||||
private fun collectInterestingReferences(): Collection<KtReference> {
|
||||
val referenceContainersToReferences = LinkedHashMap<PsiElement, KtReference>()
|
||||
for (offset in 0..file.textLength - 1) {
|
||||
val ref = file.findReferenceAt(offset)
|
||||
val refs = when (ref) {
|
||||
is KtReference -> listOf(ref)
|
||||
is PsiMultiReference -> ref.references.filterIsInstance<KtReference>()
|
||||
else -> emptyList<KtReference>()
|
||||
}
|
||||
|
||||
refs.forEach { referenceContainersToReferences.addReference(it) }
|
||||
}
|
||||
return referenceContainersToReferences.values
|
||||
}
|
||||
|
||||
private fun MutableMap<PsiElement, KtReference>.addReference(ref: KtReference) {
|
||||
if (containsKey(ref.element)) return
|
||||
val target = ref.resolve() ?: return
|
||||
|
||||
referenceTargetChecker(target)
|
||||
|
||||
val targetNavPsiFile = target.navigationElement.containingFile ?: return
|
||||
|
||||
val targetNavFile = targetNavPsiFile.virtualFile ?: return
|
||||
|
||||
if (!ProjectRootsUtil.isProjectSourceFile(target.project, targetNavFile)) {
|
||||
put(ref.element, ref)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun checkAnnotatedCode(file: PsiFile, expectedFile: File, referenceTargetChecker: (PsiElement) -> Unit = {}) {
|
||||
val navigationChecker = NavigationChecker(file, referenceTargetChecker)
|
||||
for (forceResolve in listOf(false, true)) {
|
||||
SourceNavigationHelper.setForceResolve(forceResolve)
|
||||
KotlinTestUtils.assertEqualsToFile(expectedFile, navigationChecker.annotatedLibraryCode())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* 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.jetbrains.kotlin.idea.intentions
|
||||
|
||||
import com.google.common.collect.Lists
|
||||
import com.intellij.codeInsight.intention.IntentionAction
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.Task
|
||||
import com.intellij.openapi.progress.util.ProgressIndicatorBase
|
||||
import com.intellij.openapi.util.Computable
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||
import com.intellij.testFramework.PlatformTestUtil
|
||||
import junit.framework.ComparisonFailure
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
||||
import org.jetbrains.kotlin.idea.test.DirectiveBasedActionUtils
|
||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.junit.Assert
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
abstract class AbstractIntentionTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
protected open fun intentionFileName(): String = ".intention"
|
||||
|
||||
protected open fun afterFileNameSuffix(): String = ".after"
|
||||
|
||||
protected open fun isApplicableDirectiveName(): String = "IS_APPLICABLE"
|
||||
|
||||
protected open fun intentionTextDirectiveName(): String = "INTENTION_TEXT"
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun createIntention(testDataFile: File): IntentionAction {
|
||||
val candidateFiles = Lists.newArrayList<File>()
|
||||
|
||||
var current: File? = testDataFile.parentFile
|
||||
while (current != null) {
|
||||
val candidate = File(current, intentionFileName())
|
||||
if (candidate.exists()) {
|
||||
candidateFiles.add(candidate)
|
||||
}
|
||||
current = current.parentFile
|
||||
}
|
||||
|
||||
if (candidateFiles.isEmpty()) {
|
||||
throw AssertionError(".intention file is not found for " + testDataFile +
|
||||
"\nAdd it to base directory of test data. It should contain fully-qualified name of intention class.")
|
||||
}
|
||||
if (candidateFiles.size > 1) {
|
||||
throw AssertionError("Several .intention files are available for " + testDataFile +
|
||||
"\nPlease remove some of them\n" + candidateFiles)
|
||||
}
|
||||
|
||||
val className = FileUtil.loadFile(candidateFiles[0]).trim { it <= ' ' }
|
||||
return Class.forName(className).newInstance() as IntentionAction
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
protected fun doTest(path: String) {
|
||||
val mainFile = File(path)
|
||||
val mainFileName = FileUtil.getNameWithoutExtension(mainFile)
|
||||
val intentionAction = createIntention(mainFile)
|
||||
val sourceFilePaths = ArrayList<String>()
|
||||
val parentDir = mainFile.parentFile
|
||||
var i = 1
|
||||
sourceFilePaths.add(mainFile.name)
|
||||
extraFileLoop@ while (true) {
|
||||
for (extension in EXTENSIONS) {
|
||||
val extraFile = File(parentDir, mainFileName + "." + i + extension)
|
||||
if (extraFile.exists()) {
|
||||
sourceFilePaths.add(extraFile.name)
|
||||
i++
|
||||
continue@extraFileLoop
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
val psiFiles = myFixture.configureByFiles(*sourceFilePaths.toTypedArray())
|
||||
val pathToFiles = mapOf(*(sourceFilePaths zip psiFiles).toTypedArray())
|
||||
|
||||
val fileText = FileUtil.loadFile(mainFile, true)
|
||||
|
||||
ConfigLibraryUtil.configureLibrariesByDirective(myModule, PlatformTestUtil.getCommunityPath(), fileText)
|
||||
|
||||
try {
|
||||
TestCase.assertTrue("\"<caret>\" is missing in file \"$mainFile\"", fileText.contains("<caret>"))
|
||||
|
||||
val minJavaVersion = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// MIN_JAVA_VERSION: ")
|
||||
if (minJavaVersion != null && !SystemInfo.isJavaVersionAtLeast(minJavaVersion)) return
|
||||
|
||||
if (file is KtFile && !InTextDirectivesUtils.isDirectiveDefined(fileText, "// SKIP_ERRORS_BEFORE")) {
|
||||
DirectiveBasedActionUtils.checkForUnexpectedErrors(file as KtFile)
|
||||
}
|
||||
|
||||
doTestFor(mainFile.name, pathToFiles, intentionAction, fileText)
|
||||
|
||||
if (file is KtFile && !InTextDirectivesUtils.isDirectiveDefined(fileText, "// SKIP_ERRORS_AFTER")) {
|
||||
DirectiveBasedActionUtils.checkForUnexpectedErrors(file as KtFile)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
ConfigLibraryUtil.unconfigureLibrariesByDirective(myModule, fileText)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> computeUnderProgressIndicatorAndWait(compute: () -> T): T {
|
||||
val result = CompletableFuture<T>()
|
||||
val progressIndicator = ProgressIndicatorBase()
|
||||
try {
|
||||
val task = object : Task.Backgroundable(project, "isApplicable", false) {
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
result.complete(compute())
|
||||
}
|
||||
}
|
||||
ProgressManager.getInstance().runProcessWithProgressAsynchronously(task, progressIndicator)
|
||||
return result.get(10, TimeUnit.SECONDS)
|
||||
}
|
||||
finally {
|
||||
progressIndicator.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun doTestFor(mainFilePath: String, pathToFiles: Map<String, PsiFile>, intentionAction: IntentionAction, fileText: String) {
|
||||
val isApplicableString = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// ${isApplicableDirectiveName()}: ")
|
||||
val isApplicableExpected = isApplicableString == null || isApplicableString == "true"
|
||||
|
||||
val isApplicableOnPooled = computeUnderProgressIndicatorAndWait { ApplicationManager.getApplication().runReadAction(Computable { intentionAction.isAvailable(project, editor, file) }) }
|
||||
|
||||
val isApplicableOnEdt = intentionAction.isAvailable(project, editor, file)
|
||||
|
||||
Assert.assertEquals("There should not be any difference what thread isApplicable is called from", isApplicableOnPooled, isApplicableOnEdt)
|
||||
|
||||
Assert.assertTrue(
|
||||
"isAvailable() for " + intentionAction.javaClass + " should return " + isApplicableExpected,
|
||||
isApplicableExpected == isApplicableOnEdt)
|
||||
|
||||
val intentionTextString = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// " + intentionTextDirectiveName() + ": ")
|
||||
|
||||
if (intentionTextString != null) {
|
||||
TestCase.assertEquals("Intention text mismatch.", intentionTextString, intentionAction.text)
|
||||
}
|
||||
|
||||
val shouldFailString = StringUtil.join(InTextDirectivesUtils.findListWithPrefixes(fileText, "// SHOULD_FAIL_WITH: "), ", ")
|
||||
|
||||
try {
|
||||
if (isApplicableExpected) {
|
||||
project.executeWriteCommand(
|
||||
intentionAction.text, null
|
||||
) {
|
||||
intentionAction.invoke(project, editor, file)
|
||||
null
|
||||
}
|
||||
// Don't bother checking if it should have failed.
|
||||
if (shouldFailString.isEmpty()) {
|
||||
for ((filePath, value) in pathToFiles) {
|
||||
val canonicalPathToExpectedFile = filePath + afterFileNameSuffix()
|
||||
if (filePath == mainFilePath) {
|
||||
try {
|
||||
myFixture.checkResultByFile(canonicalPathToExpectedFile)
|
||||
}
|
||||
catch (e: ComparisonFailure) {
|
||||
KotlinTestUtils
|
||||
.assertEqualsToFile(File(testDataPath, canonicalPathToExpectedFile), editor.document.text)
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
KotlinTestUtils.assertEqualsToFile(File(testDataPath, canonicalPathToExpectedFile), value.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TestCase.assertEquals("Expected test to fail.", "", shouldFailString)
|
||||
}
|
||||
catch (e: BaseRefactoringProcessor.ConflictsInTestsException) {
|
||||
TestCase.assertEquals("Failure message mismatch.", shouldFailString, StringUtil.join(e.messages.sorted(), ", "))
|
||||
}
|
||||
catch (e: CommonRefactoringUtil.RefactoringErrorHintException) {
|
||||
TestCase.assertEquals("Failure message mismatch.", shouldFailString, e.message?.replace('\n', ' '))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val EXTENSIONS = arrayOf(".kt", ".kts", ".java", ".groovy")
|
||||
}
|
||||
}
|
||||
|
||||
-128
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 JetBrains s.r.o.
|
||||
*
|
||||
* 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.jetbrains.kotlin.j2k
|
||||
|
||||
import com.intellij.codeInsight.ContainerProvider
|
||||
import com.intellij.codeInsight.NullableNotNullManager
|
||||
import com.intellij.codeInsight.runner.JavaMainMethodProvider
|
||||
import com.intellij.core.CoreApplicationEnvironment
|
||||
import com.intellij.core.JavaCoreApplicationEnvironment
|
||||
import com.intellij.core.JavaCoreProjectEnvironment
|
||||
import com.intellij.lang.MetaLanguage
|
||||
import com.intellij.lang.jvm.facade.JvmElementProvider
|
||||
import com.intellij.openapi.extensions.Extensions
|
||||
import com.intellij.openapi.extensions.ExtensionsArea
|
||||
import com.intellij.openapi.fileTypes.FileTypeExtensionPoint
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.augment.PsiAugmentProvider
|
||||
import com.intellij.psi.augment.TypeAnnotationModifier
|
||||
import com.intellij.psi.compiled.ClassFileDecompilers
|
||||
import com.intellij.psi.impl.JavaClassSupersImpl
|
||||
import com.intellij.psi.impl.PsiTreeChangePreprocessor
|
||||
import com.intellij.psi.impl.compiled.ClsCustomNavigationPolicy
|
||||
import com.intellij.psi.meta.MetaDataContributor
|
||||
import com.intellij.psi.stubs.BinaryFileStubBuilders
|
||||
import com.intellij.psi.util.JavaClassSupers
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.utils.PathUtil
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
|
||||
abstract class AbstractJavaToKotlinConverterForWebDemoTest : TestCase() {
|
||||
val DISPOSABLE = Disposer.newDisposable()
|
||||
|
||||
fun doTest(javaPath: String) {
|
||||
try {
|
||||
val fileContents = FileUtil.loadFile(File(javaPath), true)
|
||||
val javaCoreEnvironment: JavaCoreProjectEnvironment = setUpJavaCoreEnvironment()
|
||||
translateToKotlin(fileContents, javaCoreEnvironment.project)
|
||||
}
|
||||
finally {
|
||||
Disposer.dispose(DISPOSABLE)
|
||||
}
|
||||
}
|
||||
|
||||
fun setUpJavaCoreEnvironment(): JavaCoreProjectEnvironment {
|
||||
Extensions.cleanRootArea(DISPOSABLE)
|
||||
val area = Extensions.getRootArea()
|
||||
|
||||
registerExtensionPoints(area)
|
||||
|
||||
val applicationEnvironment = JavaCoreApplicationEnvironment(DISPOSABLE)
|
||||
val javaCoreEnvironment = object : JavaCoreProjectEnvironment(DISPOSABLE, applicationEnvironment) {
|
||||
override fun preregisterServices() {
|
||||
val projectArea = Extensions.getArea(project)
|
||||
CoreApplicationEnvironment.registerExtensionPoint(projectArea, PsiTreeChangePreprocessor.EP_NAME, PsiTreeChangePreprocessor::class.java)
|
||||
CoreApplicationEnvironment.registerExtensionPoint(projectArea, PsiElementFinder.EP_NAME, PsiElementFinder::class.java)
|
||||
CoreApplicationEnvironment.registerExtensionPoint(projectArea, JvmElementProvider.EP_NAME, JvmElementProvider::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
javaCoreEnvironment.project.registerService(NullableNotNullManager::class.java, object : NullableNotNullManager(javaCoreEnvironment.project) {
|
||||
override fun isNullable(owner: PsiModifierListOwner, checkBases: Boolean) = !isNotNull(owner, checkBases)
|
||||
override fun isNotNull(owner: PsiModifierListOwner, checkBases: Boolean) = true
|
||||
override fun hasHardcodedContracts(element: PsiElement): Boolean = false
|
||||
override fun getPredefinedNotNulls() = emptyList<String>()
|
||||
})
|
||||
|
||||
applicationEnvironment.application.registerService(JavaClassSupers::class.java, JavaClassSupersImpl::class.java)
|
||||
|
||||
for (root in PathUtil.getJdkClassesRootsFromCurrentJre()) {
|
||||
javaCoreEnvironment.addJarToClassPath(root)
|
||||
}
|
||||
val annotations: File? = findAnnotations()
|
||||
if (annotations != null && annotations.exists()) {
|
||||
javaCoreEnvironment.addJarToClassPath(annotations)
|
||||
}
|
||||
return javaCoreEnvironment
|
||||
}
|
||||
|
||||
private fun registerExtensionPoints(area: ExtensionsArea) {
|
||||
CoreApplicationEnvironment.registerExtensionPoint(area, BinaryFileStubBuilders.EP_NAME, FileTypeExtensionPoint::class.java)
|
||||
CoreApplicationEnvironment.registerExtensionPoint(area, FileContextProvider.EP_NAME, FileContextProvider::class.java)
|
||||
|
||||
CoreApplicationEnvironment.registerExtensionPoint(area, MetaDataContributor.EP_NAME, MetaDataContributor::class.java)
|
||||
CoreApplicationEnvironment.registerExtensionPoint(area, PsiAugmentProvider.EP_NAME, PsiAugmentProvider::class.java)
|
||||
CoreApplicationEnvironment.registerExtensionPoint(area, JavaMainMethodProvider.EP_NAME, JavaMainMethodProvider::class.java)
|
||||
|
||||
CoreApplicationEnvironment.registerExtensionPoint(area, ContainerProvider.EP_NAME, ContainerProvider::class.java)
|
||||
CoreApplicationEnvironment.registerExtensionPoint(area, ClsCustomNavigationPolicy.EP_NAME, ClsCustomNavigationPolicy::class.java)
|
||||
CoreApplicationEnvironment.registerExtensionPoint(area, ClassFileDecompilers.EP_NAME, ClassFileDecompilers.Decompiler::class.java)
|
||||
|
||||
CoreApplicationEnvironment.registerExtensionPoint(area, TypeAnnotationModifier.EP_NAME, TypeAnnotationModifier::class.java)
|
||||
CoreApplicationEnvironment.registerExtensionPoint(area, MetaLanguage.EP_NAME, MetaLanguage::class.java)
|
||||
CoreApplicationEnvironment.registerExtensionPoint(area, JavaModuleSystem.EP_NAME, JavaModuleSystem::class.java)
|
||||
}
|
||||
|
||||
fun findAnnotations(): File? {
|
||||
var classLoader = JavaToKotlinTranslator::class.java.classLoader
|
||||
while (classLoader != null) {
|
||||
val loader = classLoader
|
||||
if (loader is URLClassLoader) {
|
||||
for (url in loader.urLs) {
|
||||
if ("file" == url.protocol && url.file!!.endsWith("/annotations.jar")) {
|
||||
return File(url.file!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
classLoader = classLoader.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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.jetbrains.uast.test.env
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||
import org.jetbrains.uast.UElement
|
||||
import org.jetbrains.uast.UFile
|
||||
import org.jetbrains.uast.toUElementOfType
|
||||
import org.jetbrains.uast.visitor.UastVisitor
|
||||
import java.io.File
|
||||
import kotlin.test.fail
|
||||
|
||||
abstract class AbstractUastTest : AbstractTestWithCoreEnvironment() {
|
||||
protected companion object {
|
||||
val TEST_DATA_DIR = File("testData")
|
||||
}
|
||||
|
||||
abstract fun getVirtualFile(testName: String): VirtualFile
|
||||
abstract fun check(testName: String, file: UFile)
|
||||
|
||||
fun doTest(testName: String, checkCallback: (String, UFile) -> Unit = { testName, file -> check(testName, file) }) {
|
||||
val virtualFile = getVirtualFile(testName)
|
||||
|
||||
val psiFile = psiManager.findFile(virtualFile) ?: error("Can't get psi file for $testName")
|
||||
val uFile = uastContext.convertElementWithParent(psiFile, null) ?: error("Can't get UFile for $testName")
|
||||
checkCallback(testName, uFile as UFile)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> UElement.findElementByText(refText: String, cls: Class<T>): T {
|
||||
val matchingElements = mutableListOf<T>()
|
||||
accept(object : UastVisitor {
|
||||
override fun visitElement(node: UElement): Boolean {
|
||||
if (cls.isInstance(node) && node.psi?.text == refText) {
|
||||
matchingElements.add(node as T)
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (matchingElements.isEmpty()) {
|
||||
throw IllegalArgumentException("Reference '$refText' not found")
|
||||
}
|
||||
if (matchingElements.size != 1) {
|
||||
throw IllegalArgumentException("Reference '$refText' is ambiguous")
|
||||
}
|
||||
return matchingElements.single()
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> UElement.findElementByText(refText: String): T = findElementByText(refText, T::class.java)
|
||||
|
||||
inline fun <reified T : UElement> UElement.findElementByTextFromPsi(refText: String, strict: Boolean = true): T =
|
||||
(this.psi ?: fail("no psi for $this")).findUElementByTextFromPsi(refText, strict)
|
||||
|
||||
inline fun <reified T : UElement> PsiElement.findUElementByTextFromPsi(refText: String, strict: Boolean = true): T {
|
||||
val elementAtStart = this.findElementAt(this.text.indexOf(refText))
|
||||
?: throw AssertionError("requested text '$refText' was not found in $this")
|
||||
val uElementContainingText = elementAtStart.parentsWithSelf.let {
|
||||
if (strict) it.dropWhile { !it.text.contains(refText) } else it
|
||||
}.mapNotNull { it.toUElementOfType<T>() }.firstOrNull()
|
||||
?: throw AssertionError("requested text '$refText' not found as '${T::class.java.canonicalName}' in $this")
|
||||
if (strict && uElementContainingText.psi != null && uElementContainingText.psi?.text != refText) {
|
||||
throw AssertionError("requested text '$refText' found as '${uElementContainingText.psi?.text}' in $uElementContainingText")
|
||||
}
|
||||
return uElementContainingText;
|
||||
}
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* 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.jetbrains.uast.test.env
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||
import org.jetbrains.uast.UElement
|
||||
import org.jetbrains.uast.UFile
|
||||
import org.jetbrains.uast.toUElementOfType
|
||||
import org.jetbrains.uast.visitor.UastVisitor
|
||||
import java.io.File
|
||||
import kotlin.test.fail
|
||||
|
||||
abstract class AbstractUastTest : AbstractTestWithCoreEnvironment() {
|
||||
protected companion object {
|
||||
val TEST_DATA_DIR = File("testData")
|
||||
}
|
||||
|
||||
abstract fun getVirtualFile(testName: String): VirtualFile
|
||||
abstract fun check(testName: String, file: UFile)
|
||||
|
||||
fun doTest(testName: String, checkCallback: (String, UFile) -> Unit = { testName, file -> check(testName, file) }) {
|
||||
val virtualFile = getVirtualFile(testName)
|
||||
|
||||
val psiFile = psiManager.findFile(virtualFile) ?: error("Can't get psi file for $testName")
|
||||
val uFile = uastContext.convertElementWithParent(psiFile, null) ?: error("Can't get UFile for $testName")
|
||||
checkCallback(testName, uFile as UFile)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> UElement.findElementByText(refText: String, cls: Class<T>): T {
|
||||
val matchingElements = mutableListOf<T>()
|
||||
accept(object : UastVisitor {
|
||||
override fun visitElement(node: UElement): Boolean {
|
||||
if (cls.isInstance(node) && node.psi?.text == refText) {
|
||||
matchingElements.add(node as T)
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (matchingElements.isEmpty()) {
|
||||
throw IllegalArgumentException("Reference '$refText' not found")
|
||||
}
|
||||
if (matchingElements.size != 1) {
|
||||
throw IllegalArgumentException("Reference '$refText' is ambiguous")
|
||||
}
|
||||
return matchingElements.single()
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> UElement.findElementByText(refText: String): T = findElementByText(refText, T::class.java)
|
||||
|
||||
inline fun <reified T : UElement> UElement.findElementByTextFromPsi(refText: String, strict: Boolean = true): T =
|
||||
(this.psi ?: fail("no psi for $this")).findUElementByTextFromPsi(refText, strict)
|
||||
|
||||
inline fun <reified T : UElement> PsiElement.findUElementByTextFromPsi(refText: String, strict: Boolean = true): T {
|
||||
val elementAtStart = this.findElementAt(this.text.indexOf(refText))
|
||||
?: throw AssertionError("requested text '$refText' was not found in $this")
|
||||
val uElementContainingText = elementAtStart.parentsWithSelf.let {
|
||||
if (strict) it.dropWhile { !it.text.contains(refText) } else it
|
||||
}.mapNotNull { it.toUElementOfType<T>() }.firstOrNull()
|
||||
?: throw AssertionError("requested text '$refText' not found as '${T::class.java.canonicalName}' in $this")
|
||||
if (strict && uElementContainingText.psi != null && uElementContainingText.psi?.text != refText) {
|
||||
throw AssertionError("requested text '$refText' found as '${uElementContainingText.psi?.text}' in $uElementContainingText")
|
||||
}
|
||||
return uElementContainingText;
|
||||
}
|
||||
Reference in New Issue
Block a user