diff --git a/build.gradle.kts b/build.gradle.kts index 034606cb0..104b65371 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { compile("com.google.protobuf:protobuf-java:$protobufVersion") compile("io.grpc:grpc-stub:$grpcVersion") compile("io.grpc:grpc-protobuf:$grpcVersion") + compile("org.assertj:assertj-core:3.18.1") runtimeOnly("io.grpc:grpc-netty-shaded:$grpcVersion") protobuf(files("protos/", "grammars/")) } diff --git a/gen/org/jetbrains/r/psi/api/RCallExpression.java b/gen/org/jetbrains/r/psi/api/RCallExpression.java index 8490555a1..34f842f78 100644 --- a/gen/org/jetbrains/r/psi/api/RCallExpression.java +++ b/gen/org/jetbrains/r/psi/api/RCallExpression.java @@ -1,9 +1,12 @@ // This is a generated file. Not intended for manual editing. package org.jetbrains.r.psi.api; +import java.util.List; import org.jetbrains.annotations.*; +import com.intellij.psi.PsiElement; import com.intellij.psi.StubBasedPsiElement; import org.jetbrains.r.psi.stubs.RCallExpressionStub; +import org.jetbrains.r.classes.r6.R6ClassInfo; import org.jetbrains.r.classes.s4.RS4ClassInfo; public interface RCallExpression extends RExpression, StubBasedPsiElement { @@ -17,4 +20,7 @@ public interface RCallExpression extends RExpression, StubBasedPsiElement - - messages.RPluginBundle @@ -432,7 +429,8 @@ You can find the source code in the following repositories: - + + @@ -679,13 +677,19 @@ You can find the source code in the following repositories: bundle="messages.RPluginBundle" key="inspection.deprecated.double.starts.name" implementationClass="org.jetbrains.r.inspections.DeprecatedDoubleStarts"/> + + implementationClass="org.jetbrains.r.inspections.classes.s4.DeprecatedSetClassArgsInspection"/> + implementationClass="org.jetbrains.r.inspections.classes.s4.InstanceOfVirtualS4ClassInspection"/> + + + R @@ -727,10 +731,6 @@ You can find the source code in the following repositories: - - - - diff --git a/resources/inspectionDescriptions/UnmatchingR6ClassNameInspection.html b/resources/inspectionDescriptions/UnmatchingR6ClassNameInspection.html new file mode 100644 index 000000000..e0656c7ec --- /dev/null +++ b/resources/inspectionDescriptions/UnmatchingR6ClassNameInspection.html @@ -0,0 +1,6 @@ + + +Variable and argument class name in expression 'UserClass <- R6Class("UserClass")' should match. +It’s not strictly needed, but it improves error messages and makes it possible to use R6 objects with S3 generics + + \ No newline at end of file diff --git a/resources/messages/RPluginBundle.properties b/resources/messages/RPluginBundle.properties index ba7bc5088..ffd38803e 100644 --- a/resources/messages/RPluginBundle.properties +++ b/resources/messages/RPluginBundle.properties @@ -381,6 +381,8 @@ inspection.deprecated.setClass.args.description=Argument ''{0}'' is deprecated f inspection.virtual.s4class.instance.name=Trying to generate an object from a virtual class inspection.virtual.s4class.instance.description=Class ''{0}'' is virtual and object of this class cannot be created +inspection.r6class.naming.convention.classname=Classname should match the variable assignee name + install.libraries.fix.name=Install {0} install.libraries.fix.family.name=Install packages diff --git a/src/org/jetbrains/r/classes/common/context/ILibraryClassContext.kt b/src/org/jetbrains/r/classes/common/context/ILibraryClassContext.kt new file mode 100644 index 000000000..19efef86c --- /dev/null +++ b/src/org/jetbrains/r/classes/common/context/ILibraryClassContext.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2000-2021 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.r.classes.common.context + +import org.jetbrains.r.hints.parameterInfo.RArgumentInfo +import org.jetbrains.r.psi.api.RCallExpression +import org.jetbrains.r.psi.api.RPsiElement + +interface ILibraryClassContext { + val functionName: String + val functionCall: RCallExpression + val argumentInfo: RArgumentInfo? + val originalElement: RPsiElement +} \ No newline at end of file diff --git a/src/org/jetbrains/r/classes/r6/R6ClassInfo.kt b/src/org/jetbrains/r/classes/r6/R6ClassInfo.kt new file mode 100644 index 000000000..2906bc41f --- /dev/null +++ b/src/org/jetbrains/r/classes/r6/R6ClassInfo.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2000-2021 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.r.classes.r6 + +import com.intellij.openapi.util.io.DataInputOutputUtilRt +import com.intellij.psi.stubs.StubInputStream +import com.intellij.psi.stubs.StubOutputStream +import com.intellij.util.io.StringRef + +// no need to care about overloads because R6 lib doesn't support it: +// "All items in public, private, and active must have unique names." +interface IR6ClassMember { val name: String } +data class R6ClassField(override val name: String, val isPublic: Boolean = true) : IR6ClassMember +data class R6ClassMethod(override val name: String, val isPublic: Boolean = true) : IR6ClassMember +data class R6ClassActiveBinding(override val name: String) : IR6ClassMember + +data class R6ClassInfo(val className: String, + val superClasses: List, + val fields: List, + val methods: List, + val activeBindings: List) { + + fun containsMember(memberName: String) : Boolean { + return ((fields + methods).map { it.name }.contains(memberName) || + activeBindings.map { it.name }.contains(memberName)) + } + + fun serialize(dataStream: StubOutputStream) { + dataStream.writeName(className) + DataInputOutputUtilRt.writeSeq(dataStream, superClasses) { dataStream.writeName(it) } + + DataInputOutputUtilRt.writeSeq(dataStream, fields) { + dataStream.writeName(it.name); + dataStream.writeBoolean(it.isPublic) + } + + DataInputOutputUtilRt.writeSeq(dataStream, methods) { + dataStream.writeName(it.name); + dataStream.writeBoolean(it.isPublic) + } + + DataInputOutputUtilRt.writeSeq(dataStream, activeBindings) { + dataStream.writeName(it.name) + } + } + + companion object { + fun deserialize(dataStream: StubInputStream): R6ClassInfo { + val className = StringRef.toString(dataStream.readName()) + val superClasses = DataInputOutputUtilRt.readSeq(dataStream) { StringRef.toString(dataStream.readName()) } + + val fields = DataInputOutputUtilRt.readSeq(dataStream) { + val name = StringRef.toString(dataStream.readName()) + val isPublic = dataStream.readBoolean() + R6ClassField(name, isPublic) + } + + val methods = DataInputOutputUtilRt.readSeq(dataStream) { + val name = StringRef.toString(dataStream.readName()) + val isPublic = dataStream.readBoolean() + R6ClassMethod(name, isPublic) + } + + val activeBindings = DataInputOutputUtilRt.readSeq(dataStream) { + val name = StringRef.toString(dataStream.readName()) + R6ClassActiveBinding(name) + } + + return R6ClassInfo(className, superClasses, fields, methods, activeBindings) + } + } +} + +class R6ClassKeywordsProvider { + companion object { + val predefinedClassMethods = listOf( + R6ClassMethod("clone", true) + ) + + val visibilityModifiers = listOf( + R6ClassInfoUtil.argumentPrivate, + R6ClassInfoUtil.argumentPublic, + R6ClassInfoUtil.argumentActive, + ) + } +} \ No newline at end of file diff --git a/src/org/jetbrains/r/classes/r6/R6ClassInfoUtil.kt b/src/org/jetbrains/r/classes/r6/R6ClassInfoUtil.kt new file mode 100644 index 000000000..0a77015da --- /dev/null +++ b/src/org/jetbrains/r/classes/r6/R6ClassInfoUtil.kt @@ -0,0 +1,240 @@ +/* + * Copyright 2000-2021 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.r.classes.r6 + +import com.intellij.openapi.util.Key +import com.intellij.psi.util.CachedValuesManager +import org.jetbrains.r.hints.parameterInfo.RArgumentInfo +import org.jetbrains.r.hints.parameterInfo.RParameterInfoUtil +import org.jetbrains.r.psi.RElementFactory +import org.jetbrains.r.psi.api.* +import org.jetbrains.r.psi.impl.RCallExpressionImpl +import org.jetbrains.r.psi.impl.RMemberExpressionImpl +import org.jetbrains.r.psi.isFunctionFromLibrarySoft +import org.jetbrains.r.psi.references.RSearchScopeUtil +import org.jetbrains.r.psi.stubs.classes.R6ClassNameIndex + +object R6ClassInfoUtil { + const val R6PackageName = "R6" + const val R6CreateClassMethod = "R6Class" + + const val R6ClassThisKeyword = "self" + const val functionNew = "new" + const val functionSet = "set" + + const val argumentClassName = "classname" + const val argumentSuperClass = "inherit" + const val argumentPublic = "public" + const val argumentPrivate = "private" + const val argumentActive = "active" + + private val INSTANTIATE_CLASS_DEFINITION_KEY: Key = Key.create("R6_INSTANTIATE_CLASS_DEFINITION") + + private val INSTANTIATE_CLASS_DEFINITION = + """R6Class <- function (classname = NULL, public = list(), private = NULL, + active = NULL, inherit = NULL, lock_objects = TRUE, class = TRUE, + portable = TRUE, lock_class = FALSE, cloneable = TRUE, + parent_env = parent.frame(), lock) {}""".trimIndent() + + /** + * @param call expression `MyClass$new()` + * @return class name which type is instantiated + */ + fun getAssociatedClassNameFromInstantiationCall(call: RCallExpression): String? { + val callExpression = call.expression as? RMemberExpressionImpl ?: return null + if (callExpression.rightExpr?.text != functionNew) return null + return callExpression.leftExpr?.name + } + + /** + * @param rMemberExpression expression `self$someMember` or `obj$someMember` + * @return className of class where `self$...` is used or of which object is called + */ + fun getClassNameFromInternalClassMemberUsageExpression(rMemberExpression: RMemberExpression?): String? { + if (rMemberExpression == null) return null + val classDefinitionCall = R6ClassPsiUtil.getClassDefinitionCallFromMemberUsage(rMemberExpression) ?: return null + return getAssociatedClassNameFromR6ClassCall(classDefinitionCall) + } + + /** + * @param callExpression expression `R6Class("MyClass", ...)` + * @return + */ + fun getAssociatedClassNameFromR6ClassCall(callExpression: RCallExpression, + argumentInfo: RArgumentInfo? = RParameterInfoUtil.getArgumentInfo(callExpression)): String? { + argumentInfo ?: return null + if (!callExpression.isFunctionFromLibrarySoft(R6CreateClassMethod, R6PackageName)) return null + val rAssignmentStatement = callExpression.parent as? RAssignmentStatement ?: return null + return rAssignmentStatement.assignee?.name + } + + /** + * @param callExpression expression `R6Class("MyClass", ...)` + * @return names of all inherited chain of parents + */ + fun getAssociatedSuperClassesHierarchy(callExpression: RCallExpression, + argumentInfo: RArgumentInfo? = RParameterInfoUtil.getArgumentInfo(callExpression)): MutableList? { + argumentInfo ?: return null + + var classDeclarationExpression = callExpression + val classNamesHierarchy = mutableListOf() + + while (true) { + val directInherit = getAssociatedSuperClassName(classDeclarationExpression) ?: break + classNamesHierarchy.add(directInherit) + classDeclarationExpression = getSuperClassDefinitionCallExpression(classDeclarationExpression) ?: break + } + + return classNamesHierarchy + } + + /** + * @param callExpression expression `R6Class("MyClass", ...)` + * @return direct parent classname + */ + private fun getAssociatedSuperClassName(callExpression: RCallExpression, + argumentInfo: RArgumentInfo? = RParameterInfoUtil.getArgumentInfo(callExpression)): String? { + argumentInfo ?: return null + if (!callExpression.isFunctionFromLibrarySoft(R6CreateClassMethod, R6PackageName)) return null + return (argumentInfo.getArgumentPassedToParameter(argumentSuperClass) as? RIdentifierExpression)?.name + } + + private fun getSuperClassDefinitionCallExpression(callExpression: RCallExpression, + argumentInfo: RArgumentInfo? = RParameterInfoUtil.getArgumentInfo(callExpression)): RCallExpression? { + if (!callExpression.isFunctionFromLibrarySoft(R6CreateClassMethod, R6PackageName)) return null + val inheritClassDefinition = argumentInfo?.getArgumentPassedToParameter(argumentSuperClass) as? RIdentifierExpression + return (inheritClassDefinition?.reference?.resolve() as? RAssignmentStatement)?.assignedValue as? RCallExpression + } + + fun getAllClassMembers(callExpression: RCallExpression): List { + val r6ClassInfo = CachedValuesManager.getProjectPsiDependentCache(callExpression) { callExpression.associatedR6ClassInfo } ?: return emptyList() + val allSuperClasses = getAssociatedSuperClassesHierarchy(callExpression) + + val callSearchScope = RSearchScopeUtil.getScope(callExpression) + val project = callExpression.project + + if (allSuperClasses != null) { + return (r6ClassInfo.fields + r6ClassInfo.methods + r6ClassInfo.activeBindings + allSuperClasses.flatMap { superClassName -> + R6ClassNameIndex.findClassInfos(superClassName, project, callSearchScope).flatMap { it.fields + it.methods + it.activeBindings } + }).distinctBy { it.name } + } + + return emptyList() + } + + fun getAssociatedMembers(callExpression: RCallExpression, + argumentInfo: RArgumentInfo? = RParameterInfoUtil.getArgumentInfo(callExpression), + onlyPublic: Boolean = false): List? { + argumentInfo ?: return null + if (!callExpression.isFunctionFromLibrarySoft(R6CreateClassMethod, R6PackageName)) return null + + val r6ClassFields = getAssociatedFields(callExpression, argumentInfo, onlyPublic) + val r6ClassMethods = getAssociatedMethods(callExpression, argumentInfo, onlyPublic) + + val r6ClassMembers = mutableListOf() + if (r6ClassFields != null) r6ClassMembers.addAll(r6ClassFields) + if (r6ClassMethods != null) r6ClassMembers.addAll(r6ClassMethods) + + return r6ClassMembers + } + + fun getAssociatedFields(callExpression: RCallExpression, + argumentInfo: RArgumentInfo? = RParameterInfoUtil.getArgumentInfo(callExpression), + onlyPublic: Boolean = false): List? { + argumentInfo ?: return null + if (!callExpression.isFunctionFromLibrarySoft(R6CreateClassMethod, R6PackageName)) return null + + val r6ClassFields = mutableListOf() + val publicContents = (argumentInfo.getArgumentPassedToParameter(argumentPublic) as? RCallExpressionImpl)?.argumentList?.expressionList + if (!publicContents.isNullOrEmpty()) getFieldsFromExpressionList(r6ClassFields, publicContents, true) + + if (!onlyPublic) { + val privateContents = (argumentInfo.getArgumentPassedToParameter(argumentPrivate) as? RCallExpressionImpl)?.argumentList?.expressionList + if (!privateContents.isNullOrEmpty()) getFieldsFromExpressionList(r6ClassFields, privateContents, false) + } + + return r6ClassFields + } + + fun getAssociatedMethods(callExpression: RCallExpression, + argumentInfo: RArgumentInfo? = RParameterInfoUtil.getArgumentInfo(callExpression), + onlyPublic: Boolean = false): List? { + argumentInfo ?: return null + if (!callExpression.isFunctionFromLibrarySoft(R6CreateClassMethod, R6PackageName)) return null + + val r6ClassMethods = mutableListOf() + val publicContents = (argumentInfo.getArgumentPassedToParameter(argumentPublic) as? RCallExpressionImpl)?.argumentList?.expressionList + if (!publicContents.isNullOrEmpty()) getMethodsFromExpressionList(r6ClassMethods, publicContents, true) + + if (!onlyPublic) { + val privateContents = (argumentInfo.getArgumentPassedToParameter(argumentPrivate) as? RCallExpressionImpl)?.argumentList?.expressionList + if (!privateContents.isNullOrEmpty()) getMethodsFromExpressionList(r6ClassMethods, privateContents, false) + } + + return r6ClassMethods + } + + fun getAssociatedActiveBindings(callExpression: RCallExpression, + argumentInfo: RArgumentInfo? = RParameterInfoUtil.getArgumentInfo( + callExpression)): List? { + argumentInfo ?: return null + if (!callExpression.isFunctionFromLibrarySoft(R6CreateClassMethod, R6PackageName)) return null + val r6ClassActiveBindings = mutableListOf() + + val activeBindings = (argumentInfo.getArgumentPassedToParameter(argumentActive) as? RCallExpressionImpl)?.argumentList?.expressionList + if (!activeBindings.isNullOrEmpty()) getActiveBindingsFromExpressionList(r6ClassActiveBindings, activeBindings) + return r6ClassActiveBindings + } + + fun parseR6ClassInfo(callExpression: RCallExpression): R6ClassInfo? { + if (!callExpression.isFunctionFromLibrarySoft(R6CreateClassMethod, R6PackageName)) return null + val project = callExpression.project + var definition = project.getUserData(INSTANTIATE_CLASS_DEFINITION_KEY) + + if (definition == null || !definition.isValid) { + val instantiateClassDefinition = + RElementFactory.createRPsiElementFromText(callExpression.project, INSTANTIATE_CLASS_DEFINITION) as RAssignmentStatement + definition = instantiateClassDefinition.also { project.putUserData(INSTANTIATE_CLASS_DEFINITION_KEY, it) } + } + + val argumentInfo = RParameterInfoUtil.getArgumentInfo(callExpression, definition) ?: return null + val className = getAssociatedClassNameFromR6ClassCall(callExpression, argumentInfo) ?: return null + val superClassesHierarchy = getAssociatedSuperClassesHierarchy(callExpression, argumentInfo) ?: emptyList() + val fields = getAssociatedFields(callExpression, argumentInfo) ?: emptyList() + val methods = getAssociatedMethods(callExpression, argumentInfo) ?: emptyList() + val activeBindings = getAssociatedActiveBindings(callExpression, argumentInfo) ?: emptyList() + + return R6ClassInfo(className, superClassesHierarchy, fields, methods, activeBindings) + } + + private fun getFieldsFromExpressionList(r6ClassFields: MutableList, + callExpressions: List, + isPublicScope: Boolean) { + callExpressions.forEach { + if (it.lastChild !is RFunctionExpression && !it.name.isNullOrEmpty()) { + r6ClassFields.add(R6ClassField(it.name!!, isPublicScope)) + } + } + } + + private fun getMethodsFromExpressionList(r6ClassMethods: MutableList, + callExpressions: List, + isPublicScope: Boolean) { + callExpressions.forEach { + if (it.lastChild is RFunctionExpression && !it.name.isNullOrEmpty()) { + r6ClassMethods.add(R6ClassMethod(it.name!!, isPublicScope)) + } + } + } + + private fun getActiveBindingsFromExpressionList(r6ClassActiveBindings: MutableList, + callExpressions: List) { + callExpressions.forEach { + if (it.lastChild is RFunctionExpression && !it.name.isNullOrEmpty()) { + r6ClassActiveBindings.add(R6ClassActiveBinding(it.name!!)) + } + } + } +} \ No newline at end of file diff --git a/src/org/jetbrains/r/classes/r6/R6ClassPsiUtil.kt b/src/org/jetbrains/r/classes/r6/R6ClassPsiUtil.kt new file mode 100644 index 000000000..e02273ccb --- /dev/null +++ b/src/org/jetbrains/r/classes/r6/R6ClassPsiUtil.kt @@ -0,0 +1,203 @@ +/* + * Copyright 2000-2021 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.r.classes.r6 + +import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.elementType +import org.jetbrains.r.hints.parameterInfo.RArgumentInfo +import org.jetbrains.r.hints.parameterInfo.RParameterInfoUtil +import org.jetbrains.r.parsing.RElementTypes.* +import org.jetbrains.r.psi.api.* +import org.jetbrains.r.psi.impl.RCallExpressionImpl +import org.jetbrains.r.psi.isFunctionFromLibrarySoft +import org.jetbrains.r.psi.references.RSearchScopeUtil +import org.jetbrains.r.psi.stubs.classes.R6ClassNameIndex + +object R6ClassPsiUtil { + + /** + * @param dependantIdentifier `someMember` psi-element of expression `obj$someMember` + * @return RPsiElement with name of `someMember` + */ + fun getSearchedIdentifier(dependantIdentifier: RIdentifierExpression?) : RPsiElement? { + if (dependantIdentifier == null) return null + + val classDefinitionCall = getClassDefinitionCallFromMemberUsage(dependantIdentifier) ?: return null + val className = R6ClassInfoUtil.getAssociatedClassNameFromR6ClassCall(classDefinitionCall) ?: return null + val classNamesHierarchy = R6ClassInfoUtil.getAssociatedSuperClassesHierarchy(classDefinitionCall) + classNamesHierarchy?.add(0, className) + + val callSearchScope = RSearchScopeUtil.getScope(classDefinitionCall) + val project = classDefinitionCall.project + + val r6ClassInfo = run findMemberDefinition@ { + (classNamesHierarchy)?.reversed()?.forEach { + val r6ClassInfo = R6ClassNameIndex.findClassInfos(it, project, callSearchScope).firstOrNull() + + if (r6ClassInfo != null) { + if (r6ClassInfo.containsMember(dependantIdentifier.name)) return@findMemberDefinition r6ClassInfo + } + } + } as R6ClassInfo? + + r6ClassInfo ?: return null + val r6ClassDefinitionCall = R6ClassNameIndex.findClassDefinitions(r6ClassInfo.className, project, callSearchScope).firstOrNull() + val argumentInfo = getClassDefinitionArgumentInfo(r6ClassDefinitionCall) ?: return null + + val publicMembers = getClassMemberExpressionsOfArgument(argumentInfo, R6ClassInfoUtil.argumentPublic) + val privateMembers = getClassMemberExpressionsOfArgument(argumentInfo, R6ClassInfoUtil.argumentPrivate) + val activeMembers = getClassMemberExpressionsOfArgument(argumentInfo, R6ClassInfoUtil.argumentActive) + + return extractNamedArgumentByName(dependantIdentifier.name, publicMembers?.mapNotNull { it as? RNamedArgument }) + ?: extractNamedArgumentByName(dependantIdentifier.name, privateMembers?.mapNotNull { it as? RNamedArgument }) + ?: extractNamedArgumentByName(dependantIdentifier.name, activeMembers?.mapNotNull { it as? RNamedArgument }) + } + + /** + * @param dependantIdentifier `someMember` psi-element of expression `obj$someMember` + * @param objectInstantiationCall RAssignmentStatement expression `obj <- MyClass$new()` + * @return class definition expression `MyClass <- R6Class("MyClass", list( someField = 0))` + */ + private fun getClassDefinitionExpression(dependantIdentifier: RIdentifierExpression?, objectInstantiationCall: RAssignmentStatement?): RAssignmentStatement? { + if (dependantIdentifier == null) return null + + // handling search request from inside of class usage with `self$field` + if (objectInstantiationCall == null) { + if (dependantIdentifier.parent?.firstChild?.text == R6ClassInfoUtil.R6ClassThisKeyword){ + var currentParent = dependantIdentifier.parent + var currentCall = currentParent as? RCallExpression + + while (dependantIdentifier.parent != null){ + if (currentCall?.isFunctionFromLibrarySoft(R6ClassInfoUtil.R6CreateClassMethod, R6ClassInfoUtil.R6PackageName) == true) break + currentParent = currentParent.parent + currentCall = currentParent as? RCallExpression + } + + return currentCall?.parent as? RAssignmentStatement + } + + return null + } + + // handling search request from classic out-of-class-definition usage + val objectCreationCall = objectInstantiationCall.lastChild // MyClass$new() + val classElement = objectCreationCall?.firstChild?.firstChild // MyClass + + return classElement?.reference?.resolve() as? RAssignmentStatement + } + + /** + * @param rIdentifierExpression `someMember` of expression like `classObject$someMember` or `self$someMember` + * @return `R6Class` function call, which defines class containing `someMember` + */ + fun getClassDefinitionCallFromMemberUsage(rIdentifierExpression: RIdentifierExpression?) : RCallExpression? { + if (rIdentifierExpression == null) return null + val usedClassVariable = getClassIdentifierFromChainedUsages(rIdentifierExpression.parent as? RMemberExpression) + return getClassDefinitionFromClassVariableUsage(usedClassVariable) + } + + /** + * @param rMemberExpression expression like `classObject$someMember` or `self$someMember` + * @return `R6Class` function call, which defines class containing `someMember` + */ + fun getClassDefinitionCallFromMemberUsage(rMemberExpression: RMemberExpression?) : RCallExpression? { + if (rMemberExpression == null) return null + val classObject = getClassIdentifierFromChainedUsages(rMemberExpression) + return getClassDefinitionFromClassVariableUsage(classObject) + } + + private fun getClassDefinitionFromClassVariableUsage(classObject: PsiElement?) : RCallExpression? { + // `self$someMember` + if (classObject?.text == R6ClassInfoUtil.R6ClassThisKeyword) { + val parentFunction = PsiTreeUtil.getStubOrPsiParentOfType(classObject, RCallExpression::class.java) + val r6ClassDefinitionCall = PsiTreeUtil.getStubOrPsiParentOfType(parentFunction, RCallExpression::class.java) + + if (r6ClassDefinitionCall?.isFunctionFromLibrarySoft(R6ClassInfoUtil.R6CreateClassMethod, R6ClassInfoUtil.R6PackageName) == true) { + return r6ClassDefinitionCall + } + } + // `classObject$someMember` + else { + // `classObject <- MyClass$new()` from `classObject$someMember$someMethod()$someMethod2()$someMember` + val r6ObjectCreationExpression = classObject?.reference?.resolve() as? RAssignmentStatement + // `MyClass` from `classObject <- MyClass$new()` + val usedClassVariable = r6ObjectCreationExpression?.assignedValue?.firstChild?.firstChild + // `MyClass <- R6Class(...)` from `MyClass` + val classDefinitionAssignment = usedClassVariable?.reference?.resolve() as? RAssignmentStatement + + return classDefinitionAssignment?.assignedValue as? RCallExpression + } + + return null + } + + /** + * @param classDefinitionAssignment class definition expression `MyClass <- R6Class("MyClass", list( someField = 0))` + * @return argument info containing all internal members of class + */ + fun getClassDefinitionArgumentInfo(classDefinitionAssignment: RAssignmentStatement?) : RArgumentInfo? { + if (classDefinitionAssignment == null) return null + val classDefinitionCall = classDefinitionAssignment.children.last() as? RCallExpression + return getClassDefinitionArgumentInfo(classDefinitionCall) + } + + /** + * @param classDefinitionCall class definition expression `R6Class("MyClass", list( someField = 0))` + * @return argument info containing all internal members of class + */ + fun getClassDefinitionArgumentInfo(classDefinitionCall: RCallExpression?) : RArgumentInfo? { + if (classDefinitionCall == null) return null + return RParameterInfoUtil.getArgumentInfo(classDefinitionCall) + } + + /** + * @param argumentInfo information about arguments of R6 class definition call + * @param argumentName name of argument (i.e. `public`, `private`, `active`) from where to pick class members + */ + private fun getClassMemberExpressionsOfArgument(argumentInfo: RArgumentInfo, argumentName: String) : List? { + val members = argumentInfo.getArgumentPassedToParameter(argumentName) as? RCallExpressionImpl + return members?.argumentList?.expressionList + } + + /** + * @param dependantIdentifier `someMember` psi-element of expression `classObject$someMember$someMethod()$someActive$someMethod2()` + * @return RIdentifier of R6-class object + */ + private fun getR6ObjectIdentifierFromChainedUsage(dependantIdentifier: RIdentifierExpression?): RIdentifierExpression? { + if (dependantIdentifier == null) return null + + val usageExpression = dependantIdentifier.parent + if (usageExpression.elementType != R_MEMBER_EXPRESSION) return null + + var r6Object = usageExpression.firstChild + while (r6Object != null && r6Object.elementType != R_IDENTIFIER_EXPRESSION){ + r6Object = r6Object.firstChild + } + + return r6Object as RIdentifierExpression + } + + /** + * @param rMemberExpression `classObject$someMember$someMethod()$someActive()` + * @return `classObject` identifier as the most left psi-element in chained usage expression + */ + private fun getClassIdentifierFromChainedUsages(rMemberExpression: RMemberExpression?) : RIdentifierExpression? { + if (rMemberExpression == null) return null + val classIdentifier = PsiTreeUtil.firstChild(rMemberExpression).parent as? RIdentifierExpression + if (classIdentifier?.parent.elementType != R_MEMBER_EXPRESSION) return null + return classIdentifier + } + + private fun extractNamedArgumentByName(elementName: String, namedArguments: List?) : RPsiElement? { + namedArguments?.forEach { + if (it != null) { + if (it.name == elementName) return it + } + } + + return null + } +} \ No newline at end of file diff --git a/src/org/jetbrains/r/classes/r6/context/R6ContextProvider.kt b/src/org/jetbrains/r/classes/r6/context/R6ContextProvider.kt new file mode 100644 index 000000000..a7b6870bf --- /dev/null +++ b/src/org/jetbrains/r/classes/r6/context/R6ContextProvider.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2000-2021 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.r.classes.r6.context + +import com.intellij.openapi.extensions.ExtensionPointName +import org.jetbrains.r.classes.common.context.ILibraryClassContext +import org.jetbrains.r.psi.api.RPsiElement +import java.lang.reflect.ParameterizedType + +abstract class R6ContextProvider { + abstract fun getR6ContextInner(element: RPsiElement): T? + abstract fun getContext(element: RPsiElement): T? + + @Suppress("UNCHECKED_CAST") + private val contextClass = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class + + companion object { + fun getProviders(): List> = listOf( + R6CreateClassContextProvider(), + R6SetClassMembersContextProvider() + ) + + fun getR6Context(element: RPsiElement): ILibraryClassContext? { + return getR6Context(element, ILibraryClassContext::class.java) + } + + fun getR6Context(element: RPsiElement, vararg searchedContexts: Class): T? { + for (provider in getProviders()) { + if (searchedContexts.any { it.isAssignableFrom(provider.contextClass) }) { + val s4Context = provider.getContext(element) + if (s4Context != null) { + @Suppress("UNCHECKED_CAST") + return s4Context as T? + } + } + } + return null + } + } +} \ No newline at end of file diff --git a/src/org/jetbrains/r/classes/r6/context/R6CreateClassContextProvider.kt b/src/org/jetbrains/r/classes/r6/context/R6CreateClassContextProvider.kt new file mode 100644 index 000000000..5cbd6bed7 --- /dev/null +++ b/src/org/jetbrains/r/classes/r6/context/R6CreateClassContextProvider.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2000-2021 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.r.classes.r6.context + +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.r.classes.common.context.ILibraryClassContext +import org.jetbrains.r.classes.r6.R6ClassInfoUtil +import org.jetbrains.r.classes.s4.context.RS4ContextProvider +import org.jetbrains.r.hints.parameterInfo.RArgumentInfo +import org.jetbrains.r.hints.parameterInfo.RParameterInfoUtil +import org.jetbrains.r.psi.api.RCallExpression +import org.jetbrains.r.psi.api.RNamedArgument +import org.jetbrains.r.psi.api.RPsiElement +import org.jetbrains.r.psi.isFunctionFromLibrary + +sealed class R6CreateClassContext : ILibraryClassContext { + override val functionName = R6ClassInfoUtil.R6CreateClassMethod +} + +// R6Class(, ) +// R6Class("", ) +data class R6CreateClassNameContext(override val originalElement: RPsiElement, + override val functionCall: RCallExpression, + override val argumentInfo: RArgumentInfo) : R6CreateClassContext() + +// R6Class("MyClass", inherit = ) +data class R6CreateClassInheritContext(override val originalElement: RPsiElement, + override val functionCall: RCallExpression, + override val argumentInfo: RArgumentInfo) : R6CreateClassContext() + +// R6Class("MyClass", , public = ) +// R6Class("MyClass", , public = list()) +// R6Class("MyClass", , private = ) +// R6Class("MyClass", , private = list()) +// R6Class("MyClass", , active = ) +// R6Class("MyClass", , active = list()) +data class R6CreateClassMembersContext(override val originalElement: RPsiElement, + override val functionCall: RCallExpression, + override val argumentInfo: RArgumentInfo) : R6CreateClassContext() + +class R6CreateClassContextProvider : R6ContextProvider() { + override fun getContext(element: RPsiElement): R6CreateClassContext? { + return CachedValuesManager.getCachedValue(element) { + CachedValueProvider.Result.create(getR6ContextInner(element), element) + } + } + + override fun getR6ContextInner(element: RPsiElement): R6CreateClassContext? { + val parentCall = PsiTreeUtil.getParentOfType(element, RCallExpression::class.java) ?: return null + return if (parentCall.isFunctionFromLibrary(R6ClassInfoUtil.R6CreateClassMethod, R6ClassInfoUtil.R6PackageName)) { + val parentArgumentInfo = RParameterInfoUtil.getArgumentInfo(parentCall) ?: return null + when (element) { + parentArgumentInfo.getArgumentPassedToParameter(R6ClassInfoUtil.argumentClassName) -> { + // R6Class("") + R6CreateClassNameContext(element, parentCall, parentArgumentInfo) + } + + else -> null + } + } else { + val superParentCall = PsiTreeUtil.getParentOfType(parentCall, RCallExpression::class.java) ?: return null + if (!superParentCall.isFunctionFromLibrary(R6ClassInfoUtil.R6CreateClassMethod, R6ClassInfoUtil.R6PackageName)) return null + val superParentArgumentInfo = RParameterInfoUtil.getArgumentInfo(superParentCall) ?: return null + + return when { + // R6Class("MyClass", inherit = "") + PsiTreeUtil.isAncestor(superParentArgumentInfo.getArgumentPassedToParameter(R6ClassInfoUtil.argumentSuperClass), element, + false) -> { + val parent = element.parent + if (parent is RNamedArgument && parent.nameIdentifier == element) null + else R6CreateClassInheritContext(element, superParentCall, superParentArgumentInfo) + } + + // R6Class("MyClass", public = "" + // R6Class("MyClass", public = list("") + // R6Class("MyClass", public = list(smt = "") + + // R6Class("MyClass", private = "" + // R6Class("MyClass", private = list("") + // R6Class("MyClass", private = list(smt = "") + + // R6Class("MyClass", active = "" + // R6Class("MyClass", active = list("") + // R6Class("MyClass", active = list(smt = "") + PsiTreeUtil.isAncestor(superParentArgumentInfo.getArgumentPassedToParameter(R6ClassInfoUtil.argumentPublic), element, false) || + PsiTreeUtil.isAncestor(superParentArgumentInfo.getArgumentPassedToParameter(R6ClassInfoUtil.argumentPrivate), element, false) || + PsiTreeUtil.isAncestor(superParentArgumentInfo.getArgumentPassedToParameter(R6ClassInfoUtil.argumentActive), element, false) -> { + val parent = element.parent + if (parent !is RNamedArgument || parent.assignedValue != element) null + else R6CreateClassMembersContext(element, superParentCall, superParentArgumentInfo) + } + + else -> null + } + } + } + + override fun equals(other: Any?): Boolean { + if (other == null || other !is R6ContextProvider<*>) return false + return this::class.java.name == other::class.java.name + } +} \ No newline at end of file diff --git a/src/org/jetbrains/r/classes/r6/context/R6SetClassMembersContextProvider.kt b/src/org/jetbrains/r/classes/r6/context/R6SetClassMembersContextProvider.kt new file mode 100644 index 000000000..6b84a1f93 --- /dev/null +++ b/src/org/jetbrains/r/classes/r6/context/R6SetClassMembersContextProvider.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2000-2021 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.r.classes.r6.context + +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.r.classes.common.context.ILibraryClassContext +import org.jetbrains.r.classes.r6.R6ClassInfoUtil +import org.jetbrains.r.hints.parameterInfo.RArgumentInfo +import org.jetbrains.r.psi.api.RCallExpression +import org.jetbrains.r.psi.api.RMemberExpression +import org.jetbrains.r.psi.api.RPsiElement +import org.jetbrains.r.psi.references.RSearchScopeUtil +import org.jetbrains.r.psi.stubs.classes.R6ClassNameIndex + +sealed class R6SetClassMembersContext : ILibraryClassContext { + override val functionName = R6ClassInfoUtil.functionSet +} + +// MyClass$set() +// MyClass$set("") +data class R6SetClassMembersContextVisibility(override val originalElement: RPsiElement, + override val functionCall: RCallExpression, + override val argumentInfo: RArgumentInfo?) : R6SetClassMembersContext() + +// MyClass$set(visibility, ) +// MyClass$set("visibility", ) +data class R6SetClassMembersContextName(override val originalElement: RPsiElement, + override val functionCall: RCallExpression, + override val argumentInfo: RArgumentInfo) : R6SetClassMembersContext() + +class R6SetClassMembersContextProvider : R6ContextProvider() { + override fun getContext(element: RPsiElement): R6SetClassMembersContext? { + return CachedValuesManager.getCachedValue(element) { + CachedValueProvider.Result.create(getR6ContextInner(element), element) + } + } + + override fun getR6ContextInner(element: RPsiElement): R6SetClassMembersContext? { + val parentCall = PsiTreeUtil.getParentOfType(element, RCallExpression::class.java) ?: return null + if (!isFromR6Library(parentCall)) return null + if (isElementNthArgumentFromRCallExpression(0, element, parentCall)) { + return R6SetClassMembersContextVisibility(element, parentCall, null) + } + + return null + } + + private fun isElementNthArgumentFromRCallExpression(num: Int, element: RPsiElement, rCallExpression: RCallExpression): Boolean { + val arguments = rCallExpression.lastChild.children + if (arguments.isNullOrEmpty()) return false + + return arguments[num] != null && arguments[num].textMatches(element) + } + + private fun isFromR6Library(rCallExpression: RCallExpression): Boolean { + val memberExpression = rCallExpression.expression as? RMemberExpression ?: return false + if (!memberExpression.lastChild.textMatches(R6ClassInfoUtil.functionSet)) return false + + val r6ClassIdentifier = memberExpression.firstChild + val cachedClasses = R6ClassNameIndex.findClassInfos(r6ClassIdentifier.text, memberExpression.project, + RSearchScopeUtil.getScope(rCallExpression)) + return (!cachedClasses.isNullOrEmpty()) + } + + override fun equals(other: Any?): Boolean { + if (other == null || other !is R6ContextProvider<*>) return false + return this::class.java.name == other::class.java.name + } +} \ No newline at end of file diff --git a/src/org/jetbrains/r/classes/s4/RS4ClassInfo.kt b/src/org/jetbrains/r/classes/s4/RS4ClassInfo.kt index 56237871f..68afa50bc 100644 --- a/src/org/jetbrains/r/classes/s4/RS4ClassInfo.kt +++ b/src/org/jetbrains/r/classes/s4/RS4ClassInfo.kt @@ -37,4 +37,26 @@ data class RS4ClassInfo(val className: String, return RS4ClassInfo(className, packageName, slots, superClasses, isVirtual) } } + + override fun toString() : String { + return buildString { + append("setClass('").append(className).append("', ") + append("slots = c(") + slots.forEachIndexed { ind, slot -> + if (ind != 0) append(", ") + append(slot.name).append(" = '").append(slot.type).append("'") + } + append("), ") + append("contains = c(") + superClasses.forEachIndexed { ind, superClass -> + if (ind != 0) append(", ") + append("'").append(superClass).append("'") + } + if (isVirtual) { + if (superClasses.isNotEmpty()) append(", ") + append("'VIRTUAL'") + } + append("))") + } + } } \ No newline at end of file diff --git a/src/org/jetbrains/r/classes/s4/RS4ClassInfoUtil.kt b/src/org/jetbrains/r/classes/s4/RS4ClassInfoUtil.kt index 57cd09047..e74b5f658 100644 --- a/src/org/jetbrains/r/classes/s4/RS4ClassInfoUtil.kt +++ b/src/org/jetbrains/r/classes/s4/RS4ClassInfoUtil.kt @@ -14,7 +14,7 @@ import org.jetbrains.r.psi.api.* import org.jetbrains.r.psi.isFunctionFromLibrary import org.jetbrains.r.psi.isFunctionFromLibrarySoft import org.jetbrains.r.psi.references.RSearchScopeUtil -import org.jetbrains.r.psi.stubs.RS4ClassNameIndex +import org.jetbrains.r.psi.stubs.classes.RS4ClassNameIndex import org.jetbrains.r.skeleton.psi.RSkeletonCallExpression object RS4ClassInfoUtil { @@ -35,7 +35,7 @@ object RS4ClassInfoUtil { if (callExpression == null) return emptyList() if (callExpression is RSkeletonCallExpression) { // S4 classes from packages and so contains all slots - return callExpression.associatedS4ClassInfo.slots + return callExpression.associatedS4ClassInfo?.slots ?: emptyList() } if (!callExpression.isFunctionFromLibrary("setClass", "methods")) return emptyList() return CachedValuesManager.getProjectPsiDependentCache(callExpression) { @@ -53,7 +53,7 @@ object RS4ClassInfoUtil { if (callExpression == null) return emptyList() if (callExpression is RSkeletonCallExpression) { // S4 classes from packages and so contains all super classes - return callExpression.associatedS4ClassInfo.superClasses + return callExpression.associatedS4ClassInfo?.superClasses ?: emptyList() } if (!callExpression.isFunctionFromLibrary("setClass", "methods")) return emptyList() return CachedValuesManager.getProjectPsiDependentCache(callExpression) { diff --git a/src/org/jetbrains/r/classes/s4/context/RS4Context.kt b/src/org/jetbrains/r/classes/s4/context/RS4Context.kt deleted file mode 100644 index 6b75e4fbc..000000000 --- a/src/org/jetbrains/r/classes/s4/context/RS4Context.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.jetbrains.r.classes.s4.context - -import org.jetbrains.r.hints.parameterInfo.RArgumentInfo -import org.jetbrains.r.psi.api.RCallExpression -import org.jetbrains.r.psi.api.RPsiElement - -interface RS4Context { - val functionName: String - val functionCall: RCallExpression - val argumentInfo: RArgumentInfo - val originalElement: RPsiElement -} \ No newline at end of file diff --git a/src/org/jetbrains/r/classes/s4/context/RS4ContextProvider.kt b/src/org/jetbrains/r/classes/s4/context/RS4ContextProvider.kt index 9f02da0f6..ecbaa58be 100644 --- a/src/org/jetbrains/r/classes/s4/context/RS4ContextProvider.kt +++ b/src/org/jetbrains/r/classes/s4/context/RS4ContextProvider.kt @@ -1,30 +1,34 @@ package org.jetbrains.r.classes.s4.context import com.intellij.openapi.extensions.ExtensionPointName +import org.jetbrains.r.classes.common.context.ILibraryClassContext +import org.jetbrains.r.classes.r6.context.R6ContextProvider +import org.jetbrains.r.classes.r6.context.R6CreateClassContextProvider +import org.jetbrains.r.classes.r6.context.R6SetClassMembersContextProvider import org.jetbrains.r.psi.api.RPsiElement import java.lang.reflect.ParameterizedType -abstract class RS4ContextProvider { +abstract class RS4ContextProvider { - abstract fun getS4Context(element: RPsiElement): T? + abstract fun getContext(element: RPsiElement): T? @Suppress("UNCHECKED_CAST") private val contextClass = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class companion object { - private val EP_NAME: ExtensionPointName> = - ExtensionPointName.create("com.intellij.rS4ContextProvider") + fun getProviders(): List> = listOf( + RS4NewObjectContextProvider(), + RS4SetClassContextProvider() + ) - fun getProviders(): List> = EP_NAME.extensionList - - fun getS4Context(element: RPsiElement): RS4Context? { - return getS4Context(element, RS4Context::class.java) + fun getS4Context(element: RPsiElement): ILibraryClassContext? { + return getS4Context(element, ILibraryClassContext::class.java) } - fun getS4Context(element: RPsiElement, vararg searchedContexts: Class): T? { + fun getS4Context(element: RPsiElement, vararg searchedContexts: Class): T? { for (provider in getProviders()) { if (searchedContexts.any { it.isAssignableFrom(provider.contextClass) }) { - val s4Context = provider.getS4Context(element) + val s4Context = provider.getContext(element) if (s4Context != null) { @Suppress("UNCHECKED_CAST") return s4Context as T? diff --git a/src/org/jetbrains/r/classes/s4/context/RS4NewObjectContextProvider.kt b/src/org/jetbrains/r/classes/s4/context/RS4NewObjectContextProvider.kt index e13847138..54d94042d 100644 --- a/src/org/jetbrains/r/classes/s4/context/RS4NewObjectContextProvider.kt +++ b/src/org/jetbrains/r/classes/s4/context/RS4NewObjectContextProvider.kt @@ -3,6 +3,7 @@ package org.jetbrains.r.classes.s4.context import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.r.classes.common.context.ILibraryClassContext import org.jetbrains.r.hints.parameterInfo.RArgumentInfo import org.jetbrains.r.hints.parameterInfo.RParameterInfoUtil import org.jetbrains.r.psi.RPsiUtil @@ -11,7 +12,7 @@ import org.jetbrains.r.psi.api.RNamedArgument import org.jetbrains.r.psi.api.RPsiElement import org.jetbrains.r.psi.isFunctionFromLibrary -sealed class RS4NewObjectContext : RS4Context { +sealed class RS4NewObjectContext : ILibraryClassContext { override val functionName = "new" } @@ -27,7 +28,7 @@ data class RS4NewObjectSlotNameContext(override val originalElement: RPsiElement override val argumentInfo: RArgumentInfo) : RS4NewObjectContext() class RS4NewObjectContextProvider : RS4ContextProvider() { - override fun getS4Context(element: RPsiElement): RS4NewObjectContext? { + override fun getContext(element: RPsiElement): RS4NewObjectContext? { return CachedValuesManager.getCachedValue(element) { CachedValueProvider.Result.create(getS4ContextInner(element), element) } @@ -54,4 +55,9 @@ class RS4NewObjectContextProvider : RS4ContextProvider() { } } } -} + + override fun equals(other: Any?): Boolean { + if (other == null || other !is RS4ContextProvider<*>) return false + return this::class.java.name == other::class.java.name + } +} \ No newline at end of file diff --git a/src/org/jetbrains/r/classes/s4/context/RS4SetClassContextProvider.kt b/src/org/jetbrains/r/classes/s4/context/RS4SetClassContextProvider.kt index 188165695..479b515f2 100644 --- a/src/org/jetbrains/r/classes/s4/context/RS4SetClassContextProvider.kt +++ b/src/org/jetbrains/r/classes/s4/context/RS4SetClassContextProvider.kt @@ -3,6 +3,7 @@ package org.jetbrains.r.classes.s4.context import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.r.classes.common.context.ILibraryClassContext import org.jetbrains.r.hints.parameterInfo.RArgumentInfo import org.jetbrains.r.hints.parameterInfo.RParameterInfoUtil import org.jetbrains.r.psi.api.RCallExpression @@ -10,7 +11,7 @@ import org.jetbrains.r.psi.api.RNamedArgument import org.jetbrains.r.psi.api.RPsiElement import org.jetbrains.r.psi.isFunctionFromLibrary -sealed class RS4SetClassContext : RS4Context { +sealed class RS4SetClassContext : ILibraryClassContext { override val functionName = "setClass" } @@ -35,7 +36,7 @@ data class RS4SetClassDependencyClassNameContext(override val originalElement: R override val argumentInfo: RArgumentInfo) : RS4SetClassContext() class RS4SetClassContextProvider : RS4ContextProvider() { - override fun getS4Context(element: RPsiElement): RS4SetClassContext? { + override fun getContext(element: RPsiElement): RS4SetClassContext? { return CachedValuesManager.getCachedValue(element) { CachedValueProvider.Result.create(getS4ContextInner(element), element) } @@ -85,4 +86,9 @@ class RS4SetClassContextProvider : RS4ContextProvider() { } } } -} + + override fun equals(other: Any?): Boolean { + if (other == null || other !is RS4ContextProvider<*>) return false + return this::class.java.name == other::class.java.name + } +} \ No newline at end of file diff --git a/src/org/jetbrains/r/codeInsight/findUsages/RTargetElementEvaluator.kt b/src/org/jetbrains/r/codeInsight/findUsages/RTargetElementEvaluator.kt index 6c42474a9..67f1f76f4 100644 --- a/src/org/jetbrains/r/codeInsight/findUsages/RTargetElementEvaluator.kt +++ b/src/org/jetbrains/r/codeInsight/findUsages/RTargetElementEvaluator.kt @@ -3,6 +3,7 @@ package org.jetbrains.r.codeInsight.findUsages import com.intellij.codeInsight.TargetElementEvaluatorEx2 import com.intellij.psi.PsiElement import org.jetbrains.r.psi.api.RAssignmentStatement +import org.jetbrains.r.psi.api.RNamedArgument import org.jetbrains.r.psi.api.RParameter class RTargetElementEvaluator: TargetElementEvaluatorEx2() { @@ -12,10 +13,9 @@ class RTargetElementEvaluator: TargetElementEvaluatorEx2() { return grandParent.assignee == parent } - if (grandParent is RParameter) { - return true + return when (grandParent) { + is RParameter, is RNamedArgument -> true + else -> false } - - return false } } \ No newline at end of file diff --git a/src/org/jetbrains/r/console/RConsoleRuntimeInfo.kt b/src/org/jetbrains/r/console/RConsoleRuntimeInfo.kt index 724dbde7a..878a74730 100644 --- a/src/org/jetbrains/r/console/RConsoleRuntimeInfo.kt +++ b/src/org/jetbrains/r/console/RConsoleRuntimeInfo.kt @@ -9,6 +9,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key import com.intellij.psi.PsiFile import org.jetbrains.annotations.TestOnly +import org.jetbrains.r.classes.r6.R6ClassInfo import org.jetbrains.r.classes.s4.RS4ClassInfo import org.jetbrains.r.hints.parameterInfo.RExtraNamedArgumentsInfo import org.jetbrains.r.psi.TableInfo @@ -33,6 +34,7 @@ interface RConsoleRuntimeInfo { fun loadShortS4ClassInfos(): List fun loadS4ClassInfoByObjectName(objectName: String): RS4ClassInfo? fun loadS4ClassInfoByClassName(className: String): RS4ClassInfo? + fun loadR6ClassInfoByObjectName(objectName: String): R6ClassInfo? fun getFormalArguments(expression: String) : List fun loadTableColumns(expression: String): TableInfo val rInterop: RInterop @@ -61,7 +63,10 @@ class RConsoleRuntimeInfoImpl(override val rInterop: RInterop) : RConsoleRuntime private val tableColumnsCache by rInterop.Cached { mutableMapOf() } private val s4ClassInfosByObjectNameCache by rInterop.Cached { mutableMapOf() } private val s4ClassInfosByClassNameCache by rInterop.Cached { mutableMapOf() } + private val r6ClassInfosByObjectNameCache by rInterop.Cached { mutableMapOf() } + private val r6ClassInfosByClassNameCache by rInterop.Cached { mutableMapOf() } private val loadedShortS4ClassInfosCache by rInterop.Cached { AtomicReference?>(null) } + private val loadedShortR6ClassInfosCache by rInterop.Cached { AtomicReference?>(null) } override val rMarkdownChunkOptions by lazy { rInterop.rMarkdownChunkOptions } @@ -133,6 +138,12 @@ class RConsoleRuntimeInfoImpl(override val rInterop: RInterop) : RConsoleRuntime } } + override fun loadR6ClassInfoByObjectName(objectName: String): R6ClassInfo? { + return r6ClassInfosByObjectNameCache.getOrPut(objectName) { + rInterop.getR6ClassInfoByObjectName(RReference.expressionRef(objectName, rInterop)) + } + } + override fun getFormalArguments(expression: String): List { return formalArgumentsCache.getOrPut(expression) { rInterop.getFormalArguments(RReference.expressionRef(expression, rInterop)) } } diff --git a/src/org/jetbrains/r/editor/RCompletionContributor.kt b/src/org/jetbrains/r/editor/RCompletionContributor.kt index 24b55f4de..8f774afc6 100755 --- a/src/org/jetbrains/r/editor/RCompletionContributor.kt +++ b/src/org/jetbrains/r/editor/RCompletionContributor.kt @@ -20,6 +20,8 @@ import com.intellij.psi.util.PsiTreeUtil import com.intellij.util.ProcessingContext import com.intellij.util.Processor import org.jetbrains.r.RLanguage +import org.jetbrains.r.classes.r6.* +import org.jetbrains.r.classes.r6.context.* import org.jetbrains.r.classes.s4.* import org.jetbrains.r.classes.s4.context.* import org.jetbrains.r.codeInsight.libraries.RLibrarySupportProvider @@ -35,7 +37,8 @@ import org.jetbrains.r.parsing.RElementTypes.* import org.jetbrains.r.psi.* import org.jetbrains.r.psi.api.* import org.jetbrains.r.psi.references.RSearchScopeUtil -import org.jetbrains.r.psi.stubs.RS4ClassNameIndex +import org.jetbrains.r.psi.stubs.classes.R6ClassNameIndex +import org.jetbrains.r.psi.stubs.classes.RS4ClassNameIndex import org.jetbrains.r.refactoring.RNamesValidator import org.jetbrains.r.rinterop.RValueFunction import org.jetbrains.r.skeleton.psi.RSkeletonAssignmentStatement @@ -53,6 +56,7 @@ class RCompletionContributor : CompletionContributor() { addMemberAccessCompletion() addAtAccessCompletion() addS4ClassContextCompletion() + addR6ClassContextCompletion() addIdentifierCompletion() } @@ -93,9 +97,14 @@ class RCompletionContributor : CompletionContributor() { .and(RElementFilters.S4_CONTEXT_FILTER), S4ClassContextCompletionProvider()) } + private fun addR6ClassContextCompletion() { + extend(CompletionType.BASIC, psiElement().withLanguage(RLanguage.INSTANCE) + .and(RElementFilters.R6_CONTEXT_FILTER), R6ClassContextCompletionProvider()) + } + private fun addStringLiteralCompletion() { extend(CompletionType.BASIC, psiElement().withLanguage(RLanguage.INSTANCE) - .and(RElementFilters.STRING_EXCEPT_S4_CONTEXT_FILTER), StringLiteralCompletionProvider()) + .and(RElementFilters.STRING_EXCEPT_OTHER_LIBRARIES_CONTEXT_FILTER), StringLiteralCompletionProvider()) } private class MemberAccessCompletionProvider : CompletionProvider() { @@ -105,16 +114,82 @@ class RCompletionContributor : CompletionContributor() { val info = file.runtimeInfo val memberAccess = PsiTreeUtil.getParentOfType(position, RMemberExpression::class.java) ?: return val leftExpr = memberAccess.leftExpr ?: return + + val shownNames = addStaticRuntimeCompletionDependsOfFile(memberAccess, file, result, MemberStaticRuntimeCompletionProvider) + if (info != null) { val noCalls = PsiTreeUtil.processElements(leftExpr) { it !is RCallExpression } if (noCalls) { - info.loadObjectNames(leftExpr.text).forEach { result.consume(rCompletionElementFactory.createNamespaceAccess(it)) } + info.loadObjectNames(leftExpr.text).forEach { + if (!shownNames.contains(it)) { + result.consume(rCompletionElementFactory.createNamespaceAccess(it)) + } + } } } for (extension in RLibrarySupportProvider.EP_NAME.extensions) { extension.completeMembers(leftExpr, rCompletionElementFactory, result) } } + + private object MemberStaticRuntimeCompletionProvider : RStaticRuntimeCompletionProvider { + override fun addCompletionFromRuntime(psiElement: RMemberExpression, + shownNames: MutableSet, + result: CompletionResultSet, + runtimeInfo: RConsoleRuntimeInfo): Boolean { + val obj = psiElement.leftExpr ?: return false + // obj$ + // env$obj$ + if (obj !is RIdentifierExpression && + (obj !is RMemberExpression || obj.rightExpr !is RIdentifierExpression)) { + return false + } + + val text = obj.text + runtimeInfo.loadR6ClassInfoByObjectName(text)?.let { classInfo -> + return addMembersCompletion(classInfo.fields + classInfo.methods + classInfo.activeBindings, shownNames, result) + } + return false + } + + override fun addCompletionStatically(psiElement: RMemberExpression, + shownNames: MutableSet, + result: CompletionResultSet): Boolean { + val className = R6ClassInfoUtil.getClassNameFromInternalClassMemberUsageExpression(psiElement) + if (className != null) { + R6ClassNameIndex.findClassDefinitions(className, psiElement.project, + RSearchScopeUtil.getScope(psiElement)).forEach { + return addMembersCompletion(R6ClassInfoUtil.getAllClassMembers(it) + R6ClassKeywordsProvider.predefinedClassMethods, shownNames, result) + } + } + + return false + } + + private fun addMembersCompletion(r6ClassMembers: List?, + shownNames: MutableSet, + result: CompletionResultSet): Boolean { + var hasNewResults = false + if (r6ClassMembers.isNullOrEmpty()) return hasNewResults + + for (r6Member in r6ClassMembers) { + if (r6Member.name in shownNames) continue + + when (r6Member){ + is R6ClassField, + is R6ClassActiveBinding + -> result.consume(rCompletionElementFactory.createAtAccess(r6Member.name)) + is R6ClassMethod + -> result.consume(rCompletionElementFactory.createFunctionLookupElement(r6Member.name)) + } + + shownNames.add(r6Member.name) + hasNewResults = true + } + + return hasNewResults + } + } } private class AtAccessCompletionProvider : CompletionProvider() { @@ -152,7 +227,8 @@ class RCompletionContributor : CompletionContributor() { val definition = resolveResult.element as? RAssignmentStatement ?: return@forEach (definition.assignedValue as? RCallExpression)?.let { call -> val className = RS4ClassInfoUtil.getAssociatedClassName(call) ?: return@forEach - RS4ClassNameIndex.findClassDefinitions(className, psiElement.project, RSearchScopeUtil.getScope(psiElement)).forEach { + RS4ClassNameIndex.findClassDefinitions(className, psiElement.project, + RSearchScopeUtil.getScope(psiElement)).forEach { return addSlotsCompletion(RS4ClassInfoUtil.getAllAssociatedSlots(it), shownNames, result) } } @@ -201,8 +277,7 @@ class RCompletionContributor : CompletionContributor() { val position = if (probableIdentifier != null) { // operator surrounded by % or identifier PsiTreeUtil.findChildOfType(probableIdentifier, RInfixOperator::class.java) ?: probableIdentifier - } - else { + } else { // operator with parser error PsiTreeUtil.getParentOfType(parameters.position, RPsiElement::class.java, false) ?: return } @@ -255,8 +330,7 @@ class RCompletionContributor : CompletionContributor() { val element = RElementFactory.createRPsiElementFromTextOrNull(originFile.project, code) as? RAssignmentStatement ?: return@forEach result.consume(elementFactory.createFunctionLookupElement(element, isLocal = true)) - } - else { + } else { result.consume(elementFactory.createLocalVariableLookupElement(name, false)) } } @@ -301,8 +375,7 @@ class RCompletionContributor : CompletionContributor() { val parent = it.variableDescription.firstDefinition.parent if (parent is RAssignmentStatement && parent.isFunctionDeclaration) { result.consume(elementFactory.createFunctionLookupElement(parent, true)) - } - else { + } else { result.consume(elementFactory.createLocalVariableLookupElement(name, parent is RParameter)) } } @@ -362,8 +435,7 @@ class RCompletionContributor : CompletionContributor() { arg.parameterList?.parameterList?.map { it.name }?.forEach { consumeParameter(it, shownNames, result) } - } - else { + } else { arg.reference?.multiResolve(false)?.forEach { resolveResult -> (resolveResult.element as? RAssignmentStatement)?.let { assignment -> val inhNamedArgs = info?.loadInheritorNamedArguments(assignment.name) ?: emptyList() @@ -423,9 +495,9 @@ class RCompletionContributor : CompletionContributor() { result.addAllElements(lookupElements.map { val column = it.column if (column.quoteNeeded) { - rCompletionElementFactory.createQuotedLookupElement(column.name, TABLE_MANIPULATION_PRIORITY, true, AllIcons.Nodes.Field, column.type) - } - else { + rCompletionElementFactory.createQuotedLookupElement(column.name, TABLE_MANIPULATION_PRIORITY, true, AllIcons.Nodes.Field, + column.type) + } else { PrioritizedLookupElement.withPriority( RLookupElement(column.name, true, AllIcons.Nodes.Field, packageName = column.type), TABLE_MANIPULATION_PRIORITY @@ -466,7 +538,8 @@ class RCompletionContributor : CompletionContributor() { override fun addCompletionStatically(psiElement: RCallExpression, shownNames: MutableSet, result: CompletionResultSet): Boolean { - RS4ClassNameIndex.findClassDefinitions(className, psiElement.project, RSearchScopeUtil.getScope(psiElement)).singleOrNull()?.let { definition -> + RS4ClassNameIndex.findClassDefinitions(className, psiElement.project, RSearchScopeUtil.getScope( + psiElement)).singleOrNull()?.let { definition -> RS4ClassInfoUtil.getAllAssociatedSlots(definition).forEach { result.consume(RLookupElementFactory.createNamedArgumentLookupElement(it.name, it.type, SLOT_NAME_PRIORITY)) } @@ -537,19 +610,80 @@ class RCompletionContributor : CompletionContributor() { val projectDir = classDeclaration.project.guessProjectDir() if (virtualFile == null || projectDir == null) "" else VfsUtil.getRelativePath(virtualFile, projectDir) ?: "" - } - else packageName + } else packageName if (classNameExpression is RStringLiteralExpression) { addElement(RLookupElementFactory.createLookupElementWithPriority( RLookupElement(escape(className), true, AllIcons.Nodes.Field, packageName = location), STRING_LITERAL_INSERT_HANDLER, priority)) - } - else { + } else { addElement(rCompletionElementFactory.createQuotedLookupElement(className, priority, true, AllIcons.Nodes.Field, location)) } } } + private class R6ClassContextCompletionProvider : CompletionProvider() { + override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) { + val expression = PsiTreeUtil.getParentOfType(parameters.position, RExpression::class.java, false) ?: return + val file = parameters.originalFile + addR6ClassNameCompletion(expression, file, result) + addR6AdditionalMembersAfterCreation(expression, file, result) + } + + private fun addR6ClassNameCompletion(classNameExpression: RExpression, file: PsiFile, result: CompletionResultSet) { + val r6Context = R6ContextProvider.getR6Context(classNameExpression, R6CreateClassContext::class.java) ?: return + val shownNames = HashSet() + + when (r6Context) { + is R6CreateClassNameContext -> { // suggestion of name of `<- R6Class("")` + result.addR6ClassNameCompletion(classNameExpression, shownNames) + } + + else -> return + } + } + + private fun addR6AdditionalMembersAfterCreation(classNameExpression: RExpression, file: PsiFile, result: CompletionResultSet) { + val r6Context = R6ContextProvider.getR6Context(classNameExpression, R6SetClassMembersContext::class.java) ?: return + val shownNames = HashSet() + + when (r6Context) { + is R6SetClassMembersContextVisibility -> { // suggestion of name of `classObject$set("")` + result.addR6SetAdditionalMembersAfterCreationCompletion(classNameExpression, shownNames) + } + + else -> return + } + } + + private fun CompletionResultSet.addR6SetAdditionalMembersAfterCreationCompletion(classNameExpression: RExpression, shownNames: MutableSet) { + val virtualFile = classNameExpression.containingFile.virtualFile + val projectDir = classNameExpression.project.guessProjectDir() + val location = if (virtualFile == null || projectDir == null) "" + else VfsUtil.getRelativePath(virtualFile, projectDir) ?: "" + + R6ClassKeywordsProvider.visibilityModifiers.forEach { visibilityModifier -> + if (visibilityModifier in shownNames) return + shownNames.add(visibilityModifier) + addElement(rCompletionElementFactory.createQuotedLookupElement(visibilityModifier, LANGUAGE_R6_CLASS_NAME, true, AllIcons.Nodes.Field, location)) + } + } + + private fun CompletionResultSet.addR6ClassNameCompletion(classNameExpression: RExpression, + shownNames: MutableSet) { + val classAssignmentExpression = PsiTreeUtil.getParentOfType(classNameExpression, + RAssignmentStatement::class.java) as RAssignmentStatement + val classNameToSuggest = classAssignmentExpression.assignee?.text ?: return + if (classNameToSuggest in shownNames) return + shownNames.add(classNameToSuggest) + + val virtualFile = classNameExpression.containingFile.virtualFile + val projectDir = classNameExpression.project.guessProjectDir() + val location = if (virtualFile == null || projectDir == null) "" + else VfsUtil.getRelativePath(virtualFile, projectDir) ?: "" + + addElement(rCompletionElementFactory.createQuotedLookupElement(classNameToSuggest, LANGUAGE_R6_CLASS_NAME, true, AllIcons.Nodes.Field, location)) + } + } private class StringLiteralCompletionProvider : CompletionProvider() { override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) { @@ -610,7 +744,7 @@ class RCompletionContributor : CompletionContributor() { private fun findParentheses(text: String, offset: Int): Int? { var whitespaceNo = 0 while (offset + whitespaceNo < text.length && text[offset + whitespaceNo] == ' ') whitespaceNo += 1 - return whitespaceNo.takeIf { (offset + whitespaceNo< text.length && text[offset + whitespaceNo ] == '(') } + return whitespaceNo.takeIf { (offset + whitespaceNo < text.length && text[offset + whitespaceNo] == '(') } } private object RFunctionCompletionInsertHandler : RLookupElementInsertHandler { @@ -626,6 +760,18 @@ class RCompletionContributor : CompletionContributor() { context.editor.caretModel.moveCaretRelatively(relativeCaretOffset, 0, false, false, false) } } + + override fun getInsertHandlerForLookupString(lookupString: String): InsertHandler { + return InsertHandler { context, _ -> + val document = context.document + val findParentheses = findParentheses(document.text, context.tailOffset) + if (findParentheses == null) { + document.insertString(context.tailOffset, "()") + } + val relativeCaretOffset = 2 + (findParentheses ?: 0) + context.editor.caretModel.moveCaretRelatively(relativeCaretOffset, 0, false, false, false) + } + } } private fun getFileNamePrefix(filepath: String): String? { @@ -671,19 +817,20 @@ class RCompletionContributor : CompletionContributor() { private fun addStaticRuntimeCompletionDependsOfFile(psiElement: T, file: PsiFile, result: CompletionResultSet, - provider: RStaticRuntimeCompletionProvider) { + provider: RStaticRuntimeCompletionProvider) : Set { val runtimeInfo = file.runtimeInfo val shownNames = HashSet() if (file.getUserData(RConsoleView.IS_R_CONSOLE_KEY) == true) { if (runtimeInfo == null || !provider.addCompletionFromRuntime(psiElement, shownNames, result, runtimeInfo)) { provider.addCompletionStatically(psiElement, shownNames, result) } - } - else { + } else { if (!provider.addCompletionStatically(psiElement, shownNames, result)) { runtimeInfo?.let { provider.addCompletionFromRuntime(psiElement, shownNames, result, it) } } } + + return shownNames } private fun addArgumentValueCompletion(position: PsiElement, result: CompletionResultSet) { diff --git a/src/org/jetbrains/r/editor/completion/RLookupElementFactory.kt b/src/org/jetbrains/r/editor/completion/RLookupElementFactory.kt index ab165c48b..5ca3ae415 100644 --- a/src/org/jetbrains/r/editor/completion/RLookupElementFactory.kt +++ b/src/org/jetbrains/r/editor/completion/RLookupElementFactory.kt @@ -29,6 +29,7 @@ const val LOADED_S4_CLASS_NAME = 100.0 const val VARIABLE_GROUPING = 90 const val NOT_LOADED_S4_CLASS_NAME = 50.0 const val LANGUAGE_S4_CLASS_NAME = 25.0 +const val LANGUAGE_R6_CLASS_NAME = 25.0 const val PACKAGE_PRIORITY = -1.0 const val GLOBAL_GROUPING = 0 const val NAMESPACE_NAME_GROUPING = -1 @@ -67,10 +68,12 @@ data class TableManipulationColumnLookup(val column: TableColumnInfo) { interface RLookupElementInsertHandler { fun getInsertHandlerForAssignment(assignment: RAssignmentStatement): InsertHandler + fun getInsertHandlerForLookupString(lookupString: String): InsertHandler } class REmptyLookupElementInsertHandler : RLookupElementInsertHandler { override fun getInsertHandlerForAssignment(assignment: RAssignmentStatement) = BasicInsertHandler() + override fun getInsertHandlerForLookupString(lookupString: String) = BasicInsertHandler() } class RLookupElementFactory(private val functionInsertHandler: RLookupElementInsertHandler = REmptyLookupElementInsertHandler(), @@ -98,6 +101,12 @@ class RLookupElementFactory(private val functionInsertHandler: RLookupElementIns if (isLocal) VARIABLE_GROUPING else GLOBAL_GROUPING) } + fun createFunctionLookupElement(lookupString: String): LookupElement { + val icon = AllIcons.Nodes.Function + return createLookupElementWithGrouping(RLookupElement(lookupString, false, icon), + functionInsertHandler.getInsertHandlerForLookupString(lookupString), + GLOBAL_GROUPING) + } fun createNamespaceAccess(lookupString: String): LookupElement { val insertHandler = InsertHandler { context, _ -> diff --git a/src/org/jetbrains/r/inspections/UnresolvedReferenceInspection.kt b/src/org/jetbrains/r/inspections/UnresolvedReferenceInspection.kt index 1856f4344..d78b73d76 100644 --- a/src/org/jetbrains/r/inspections/UnresolvedReferenceInspection.kt +++ b/src/org/jetbrains/r/inspections/UnresolvedReferenceInspection.kt @@ -20,7 +20,7 @@ import org.jetbrains.r.psi.api.* import org.jetbrains.r.psi.isFunctionFromLibrary import org.jetbrains.r.psi.references.RReferenceBase import org.jetbrains.r.psi.references.RSearchScopeUtil -import org.jetbrains.r.psi.stubs.RS4ClassNameIndex +import org.jetbrains.r.psi.stubs.classes.RS4ClassNameIndex class UnresolvedReferenceInspection : RInspection() { diff --git a/src/org/jetbrains/r/inspections/classes/r6/UnmatchingR6ClassNameInspection.kt b/src/org/jetbrains/r/inspections/classes/r6/UnmatchingR6ClassNameInspection.kt new file mode 100644 index 000000000..cfd150483 --- /dev/null +++ b/src/org/jetbrains/r/inspections/classes/r6/UnmatchingR6ClassNameInspection.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2021 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.r.inspections.classes.r6 + +import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.PsiElementVisitor +import org.jetbrains.r.RBundle +import org.jetbrains.r.classes.r6.R6ClassInfoUtil +import org.jetbrains.r.inspections.RInspection +import org.jetbrains.r.psi.api.RCallExpression +import org.jetbrains.r.psi.api.RStringLiteralExpression +import org.jetbrains.r.psi.api.RVisitor +import org.jetbrains.r.psi.isFunctionFromLibrary + +class UnmatchingR6ClassNameInspection : RInspection() { + override fun getDisplayName() = RBundle.message("inspection.r6class.naming.convention.classname") + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { + return Visitor(holder) + } + + private class Visitor(private val myProblemHolder: ProblemsHolder) : RVisitor() { + override fun visitCallExpression(call: RCallExpression) { + if (!call.isFunctionFromLibrary(R6ClassInfoUtil.R6CreateClassMethod, R6ClassInfoUtil.R6PackageName)) return + val classNameExpression = call.argumentList.expressionList.firstOrNull() as? RStringLiteralExpression ?: return + val className = classNameExpression.name ?: return + val userClassVariableName = R6ClassInfoUtil.getAssociatedClassNameFromR6ClassCall(call) ?: return + + if (className != userClassVariableName) { + myProblemHolder.registerProblem(classNameExpression, + RBundle.message("inspection.r6class.naming.convention.classname", className), + ProblemHighlightType.GENERIC_ERROR) + } + } + } +} \ No newline at end of file diff --git a/src/org/jetbrains/r/inspections/s4class/DeprecatedSetClassArgsInspection.kt b/src/org/jetbrains/r/inspections/classes/s4/DeprecatedSetClassArgsInspection.kt similarity index 94% rename from src/org/jetbrains/r/inspections/s4class/DeprecatedSetClassArgsInspection.kt rename to src/org/jetbrains/r/inspections/classes/s4/DeprecatedSetClassArgsInspection.kt index 964344a8f..0736b64ec 100644 --- a/src/org/jetbrains/r/inspections/s4class/DeprecatedSetClassArgsInspection.kt +++ b/src/org/jetbrains/r/inspections/classes/s4/DeprecatedSetClassArgsInspection.kt @@ -1,8 +1,8 @@ /* - * Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + * Copyright 2000-2021 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.r.inspections.s4class +package org.jetbrains.r.inspections.classes.s4 import com.intellij.codeInspection.LocalInspectionToolSession import com.intellij.codeInspection.ProblemHighlightType diff --git a/src/org/jetbrains/r/inspections/s4class/InstanceOfVirtualS4ClassInspection.kt b/src/org/jetbrains/r/inspections/classes/s4/InstanceOfVirtualS4ClassInspection.kt similarity index 94% rename from src/org/jetbrains/r/inspections/s4class/InstanceOfVirtualS4ClassInspection.kt rename to src/org/jetbrains/r/inspections/classes/s4/InstanceOfVirtualS4ClassInspection.kt index aaaaf36e3..b39e9aa39 100644 --- a/src/org/jetbrains/r/inspections/s4class/InstanceOfVirtualS4ClassInspection.kt +++ b/src/org/jetbrains/r/inspections/classes/s4/InstanceOfVirtualS4ClassInspection.kt @@ -2,7 +2,7 @@ * Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. */ -package org.jetbrains.r.inspections.s4class +package org.jetbrains.r.inspections.classes.s4 import com.intellij.codeInspection.LocalInspectionToolSession import com.intellij.codeInspection.ProblemHighlightType @@ -15,7 +15,7 @@ import org.jetbrains.r.psi.api.RStringLiteralExpression import org.jetbrains.r.psi.api.RVisitor import org.jetbrains.r.psi.isFunctionFromLibrary import org.jetbrains.r.psi.references.RSearchScopeUtil -import org.jetbrains.r.psi.stubs.RS4ClassNameIndex +import org.jetbrains.r.psi.stubs.classes.RS4ClassNameIndex class InstanceOfVirtualS4ClassInspection : RInspection() { override fun getDisplayName() = RBundle.message("inspection.virtual.s4class.instance.name") diff --git a/src/org/jetbrains/r/psi/RCallExpressionElementType.kt b/src/org/jetbrains/r/psi/RCallExpressionElementType.kt index aec97b347..34ac169d7 100644 --- a/src/org/jetbrains/r/psi/RCallExpressionElementType.kt +++ b/src/org/jetbrains/r/psi/RCallExpressionElementType.kt @@ -9,14 +9,17 @@ import com.intellij.psi.stubs.IndexSink import com.intellij.psi.stubs.StubElement import com.intellij.psi.stubs.StubInputStream import com.intellij.psi.stubs.StubOutputStream +import org.jetbrains.r.classes.r6.R6ClassInfo +import org.jetbrains.r.classes.r6.R6ClassInfoUtil import org.jetbrains.r.classes.s4.RS4ClassInfo import org.jetbrains.r.classes.s4.RS4ClassInfoUtil import org.jetbrains.r.psi.api.RCallExpression import org.jetbrains.r.psi.impl.RCallExpressionImpl import org.jetbrains.r.psi.stubs.RCallExpressionStub import org.jetbrains.r.psi.stubs.RCallExpressionStubImpl -import org.jetbrains.r.psi.stubs.RS4ClassNameIndex import org.jetbrains.r.psi.stubs.RStubElementType +import org.jetbrains.r.psi.stubs.classes.R6ClassNameIndex +import org.jetbrains.r.psi.stubs.classes.RS4ClassNameIndex import java.io.IOException class RCallExpressionElementType(debugName: String) : RStubElementType(debugName) { @@ -25,29 +28,35 @@ class RCallExpressionElementType(debugName: String) : RStubElementType): RCallExpressionStub { - return RCallExpressionStubImpl(parentStub, this, RS4ClassInfoUtil.parseS4ClassInfo(psi)) + return RCallExpressionStubImpl(parentStub, this, RS4ClassInfoUtil.parseS4ClassInfo(psi), R6ClassInfoUtil.parseR6ClassInfo(psi)) } @Throws(IOException::class) override fun serialize(stub: RCallExpressionStub, dataStream: StubOutputStream) { dataStream.writeBoolean(stub.s4ClassInfo != null) + dataStream.writeBoolean(stub.r6ClassInfo != null) + stub.s4ClassInfo?.serialize(dataStream) + stub.r6ClassInfo?.serialize(dataStream) } @Throws(IOException::class) override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>): RCallExpressionStub { val s4ClassExists = dataStream.readBoolean() + val r6ClassExists = dataStream.readBoolean() + val s4ClassInfo = if (s4ClassExists) RS4ClassInfo.deserialize(dataStream) else null - return RCallExpressionStubImpl(parentStub, this, s4ClassInfo) + val r6ClassInfo = if (r6ClassExists) R6ClassInfo.deserialize(dataStream) else null + return RCallExpressionStubImpl(parentStub, this, s4ClassInfo, r6ClassInfo) } override fun indexStub(stub: RCallExpressionStub, sink: IndexSink) { - stub.s4ClassInfo?.className?.let { - RS4ClassNameIndex.sink(sink, it) - } + stub.s4ClassInfo?.className?.let { RS4ClassNameIndex.sink(sink, it) } + stub.r6ClassInfo?.className?.let { R6ClassNameIndex.sink(sink, it) } } override fun shouldCreateStub(node: ASTNode?): Boolean { - return (node?.psi as? RCallExpression)?.isFunctionFromLibrarySoft("setClass", "methods") == true + return (node?.psi as? RCallExpression)?.isFunctionFromLibrarySoft("setClass", "methods") == true || + (node?.psi as? RCallExpression)?.isFunctionFromLibrarySoft(R6ClassInfoUtil.R6CreateClassMethod, R6ClassInfoUtil.R6PackageName) == true } } \ No newline at end of file diff --git a/src/org/jetbrains/r/psi/RElementFilters.kt b/src/org/jetbrains/r/psi/RElementFilters.kt index 2205d6ef1..7f748c28e 100644 --- a/src/org/jetbrains/r/psi/RElementFilters.kt +++ b/src/org/jetbrains/r/psi/RElementFilters.kt @@ -11,6 +11,7 @@ import com.intellij.psi.filters.NotFilter import com.intellij.psi.filters.position.FilterPattern import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.elementType +import org.jetbrains.r.classes.r6.context.R6ContextProvider import org.jetbrains.r.classes.s4.context.RS4ContextProvider import org.jetbrains.r.parsing.RElementTypes import org.jetbrains.r.psi.api.* @@ -26,7 +27,8 @@ object RElementFilters { val IDENTIFIER_OR_STRING_FILTER = FilterPattern(IdentifierOrStringFilter()) val STRING_FILTER = FilterPattern(StringFilter()) val S4_CONTEXT_FILTER = FilterPattern(S4ContextFilter()) - val STRING_EXCEPT_S4_CONTEXT_FILTER = FilterPattern(AndFilter(StringFilter(), NotFilter(S4ContextFilter()))) + val R6_CONTEXT_FILTER = FilterPattern(R6ContextFilter()) + val STRING_EXCEPT_OTHER_LIBRARIES_CONTEXT_FILTER = FilterPattern(AndFilter(StringFilter(), NotFilter(S4ContextFilter()))) } class MemberFilter : ElementFilter { @@ -124,3 +126,12 @@ class S4ContextFilter : ElementFilter { override fun isClassAcceptable(hintClass: Class<*>?) = true } + +class R6ContextFilter : ElementFilter { + override fun isAcceptable(element: Any?, context: PsiElement?): Boolean { + val expression = PsiTreeUtil.getParentOfType(context, RExpression::class.java, false) ?: return false + return R6ContextProvider.getR6Context(expression) != null + } + + override fun isClassAcceptable(hintClass: Class<*>?) = true +} \ No newline at end of file diff --git a/src/org/jetbrains/r/psi/impl/RPsiImplUtil.kt b/src/org/jetbrains/r/psi/impl/RPsiImplUtil.kt index 19a1aa601..5d0878a87 100644 --- a/src/org/jetbrains/r/psi/impl/RPsiImplUtil.kt +++ b/src/org/jetbrains/r/psi/impl/RPsiImplUtil.kt @@ -21,6 +21,8 @@ import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.elementType import com.intellij.util.IncorrectOperationException import org.jetbrains.r.RElementGenerator +import org.jetbrains.r.classes.r6.R6ClassInfo +import org.jetbrains.r.classes.r6.R6ClassInfoUtil import org.jetbrains.r.classes.s4.RS4ClassInfo import org.jetbrains.r.classes.s4.RS4ClassInfoUtil import org.jetbrains.r.parsing.RElementTypes.* @@ -402,6 +404,11 @@ internal object RPsiImplUtil { return callExpression.greenStub?.s4ClassInfo ?: RS4ClassInfoUtil.parseS4ClassInfo(callExpression) } + @JvmStatic + fun getAssociatedR6ClassInfo(callExpression: RCallExpressionImpl): R6ClassInfo? { + return callExpression.greenStub?.r6ClassInfo ?: R6ClassInfoUtil.parseR6ClassInfo(callExpression) + } + private fun getLoopImpl(element: RPsiElement): RLoopStatement? { val loop = PsiTreeUtil.getParentOfType(element, RLoopStatement::class.java, RFunctionExpression::class.java) return if (loop is RLoopStatement) loop else null diff --git a/src/org/jetbrains/r/psi/references/RReferenceImpl.kt b/src/org/jetbrains/r/psi/references/RReferenceImpl.kt index 2377a402b..44aa56fb1 100644 --- a/src/org/jetbrains/r/psi/references/RReferenceImpl.kt +++ b/src/org/jetbrains/r/psi/references/RReferenceImpl.kt @@ -11,11 +11,13 @@ import com.intellij.psi.PsiElementResolveResult import com.intellij.psi.ResolveResult import com.intellij.util.IncorrectOperationException import com.intellij.util.Processor +import org.jetbrains.r.classes.r6.R6ClassPsiUtil import org.jetbrains.r.codeInsight.libraries.RLibrarySupportProvider import org.jetbrains.r.codeInsight.table.RTableContextManager import org.jetbrains.r.psi.* import org.jetbrains.r.psi.api.* import org.jetbrains.r.skeleton.psi.RSkeletonAssignmentStatement +import java.util.ArrayList class RReferenceImpl(element: RIdentifierExpression) : RReferenceBase(element) { @@ -31,12 +33,14 @@ class RReferenceImpl(element: RIdentifierExpression) : RReferenceBase() val resolveProcessor = object : Processor { override fun process(it: TableColumnInfo): Boolean { @@ -63,13 +67,11 @@ class RReferenceImpl(element: RIdentifierExpression) : RReferenceBase parameter.name == element.name}?.let { PsiElementResolveResult(it) } + assignment.getParameters().firstOrNull { parameter -> parameter.name == element.name }?.let { PsiElementResolveResult(it) } }.toTypedArray() } + private fun resolveDependantIdentifier(): Array { + val r6SearchedIdentifierDefinition = R6ClassPsiUtil.getSearchedIdentifier(element) ?: return emptyArray() + + val result = ArrayList() + result.add(PsiElementResolveResult(r6SearchedIdentifierDefinition)) + return result.toTypedArray() + } + @Throws(IncorrectOperationException::class) override fun handleElementRename(newElementName: String): PsiElement? { return element.setName(newElementName) diff --git a/src/org/jetbrains/r/psi/stubs/RCallExpressionStub.kt b/src/org/jetbrains/r/psi/stubs/RCallExpressionStub.kt index 469214c1f..dcba5410b 100644 --- a/src/org/jetbrains/r/psi/stubs/RCallExpressionStub.kt +++ b/src/org/jetbrains/r/psi/stubs/RCallExpressionStub.kt @@ -4,9 +4,11 @@ package org.jetbrains.r.psi.stubs import com.intellij.psi.stubs.StubElement +import org.jetbrains.r.classes.r6.R6ClassInfo import org.jetbrains.r.classes.s4.RS4ClassInfo import org.jetbrains.r.psi.api.RCallExpression interface RCallExpressionStub : StubElement { val s4ClassInfo: RS4ClassInfo? + val r6ClassInfo: R6ClassInfo? } \ No newline at end of file diff --git a/src/org/jetbrains/r/psi/stubs/RCallExpressionStubImpl.kt b/src/org/jetbrains/r/psi/stubs/RCallExpressionStubImpl.kt index 69f2ff206..508e4c70d 100644 --- a/src/org/jetbrains/r/psi/stubs/RCallExpressionStubImpl.kt +++ b/src/org/jetbrains/r/psi/stubs/RCallExpressionStubImpl.kt @@ -6,15 +6,17 @@ package org.jetbrains.r.psi.stubs import com.intellij.psi.stubs.IStubElementType import com.intellij.psi.stubs.StubBase import com.intellij.psi.stubs.StubElement +import org.jetbrains.r.classes.r6.R6ClassInfo import org.jetbrains.r.classes.s4.RS4ClassInfo import org.jetbrains.r.psi.api.RCallExpression class RCallExpressionStubImpl(parent: StubElement<*>, stubElementType: IStubElementType<*, *>, - override val s4ClassInfo: RS4ClassInfo?) + override val s4ClassInfo: RS4ClassInfo?, + override val r6ClassInfo: R6ClassInfo?) : StubBase(parent, stubElementType), RCallExpressionStub { override fun toString(): String { - return "RCallExpressionStub(${s4ClassInfo?.className})" + return "RCallExpressionStub(${s4ClassInfo?.className};${r6ClassInfo?.className})" } } \ No newline at end of file diff --git a/src/org/jetbrains/r/psi/stubs/RS4ClassNameIndex.kt b/src/org/jetbrains/r/psi/stubs/RS4ClassNameIndex.kt deleted file mode 100644 index 8712903c2..000000000 --- a/src/org/jetbrains/r/psi/stubs/RS4ClassNameIndex.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. - */ - -package org.jetbrains.r.psi.stubs - -import com.intellij.openapi.project.Project -import com.intellij.psi.search.GlobalSearchScope -import com.intellij.psi.stubs.IndexSink -import com.intellij.psi.stubs.StringStubIndexExtension -import com.intellij.psi.stubs.StubIndex -import com.intellij.psi.stubs.StubIndexKey -import com.intellij.util.Processor -import org.jetbrains.r.classes.s4.RS4ClassInfo -import org.jetbrains.r.psi.api.RCallExpression - - class RS4ClassNameIndex : StringStubIndexExtension() { - override fun getKey(): StubIndexKey { - return KEY - } - - companion object { - private val KEY = StubIndexKey.createIndexKey("R.s4class.shortName") - - fun processAllS4ClassInfos(project: Project, scope: GlobalSearchScope?, processor: Processor>) { - val stubIndex = StubIndex.getInstance() - stubIndex.processAllKeys(KEY, project) { key -> - stubIndex.processElements(KEY, key, project, scope, RCallExpression::class.java) { declaration -> - declaration.associatedS4ClassInfo?.let { processor.process(declaration to it) } ?: true - } - } - } - - fun findClassInfos(name: String, project: Project, scope: GlobalSearchScope?): List { - return StubIndex.getElements(KEY, name, project, scope, RCallExpression::class.java).mapNotNull { it.associatedS4ClassInfo } - } - - fun findClassDefinitions(name: String, project: Project, scope: GlobalSearchScope?): Collection { - return StubIndex.getElements(KEY, name, project, scope, RCallExpression::class.java) - } - - fun sink(sink: IndexSink, name: String) { - sink.occurrence(KEY, name) - } - } -} \ No newline at end of file diff --git a/src/org/jetbrains/r/psi/stubs/classes/LibraryClassNameIndexUtil.kt b/src/org/jetbrains/r/psi/stubs/classes/LibraryClassNameIndexUtil.kt new file mode 100644 index 000000000..7512c316e --- /dev/null +++ b/src/org/jetbrains/r/psi/stubs/classes/LibraryClassNameIndexUtil.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2021 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.r.psi.stubs.classes + +import com.intellij.openapi.project.Project +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.stubs.IndexSink +import com.intellij.psi.stubs.StringStubIndexExtension +import com.intellij.psi.stubs.StubIndex +import com.intellij.psi.stubs.StubIndexKey +import com.intellij.util.Processor +import org.jetbrains.r.classes.s4.RS4ClassInfo +import org.jetbrains.r.psi.api.RCallExpression + +abstract class LibraryClassNameIndexUtil : StringStubIndexExtension() { + companion object{ + fun processClassInfos(project: Project, + scope: GlobalSearchScope?, + indexKey: StubIndexKey, + indexProcessFunc: RCallExpression.() -> Boolean) { + val stubIndex = StubIndex.getInstance() + stubIndex.processAllKeys(indexKey, project) { key -> + stubIndex.processElements(indexKey, key, project, scope, RCallExpression::class.java) { + declaration -> indexProcessFunc(declaration) + } + } + } + + fun findClassInfos(indexKey: StubIndexKey, + mapClassInfoFunction: RCallExpression.() -> T?, + name: String, + project: Project, + scope: GlobalSearchScope?): List { + return StubIndex.getElements(indexKey, name, project, scope, RCallExpression::class.java).mapNotNull { mapClassInfoFunction(it) } + } + + fun findClassDefinitions(indexKey: StubIndexKey, name: String, project: Project, scope: GlobalSearchScope?): Collection { + return StubIndex.getElements(indexKey, name, project, scope, RCallExpression::class.java) + } + + fun sink(indexKey: StubIndexKey, sink: IndexSink, name: String) { + sink.occurrence(indexKey, name) + } + } +} \ No newline at end of file diff --git a/src/org/jetbrains/r/psi/stubs/classes/R6ClassNameIndex.kt b/src/org/jetbrains/r/psi/stubs/classes/R6ClassNameIndex.kt new file mode 100644 index 000000000..8f19e3a71 --- /dev/null +++ b/src/org/jetbrains/r/psi/stubs/classes/R6ClassNameIndex.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2021 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.r.psi.stubs.classes + +import com.intellij.openapi.project.Project +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.stubs.IndexSink +import com.intellij.psi.stubs.StringStubIndexExtension +import com.intellij.psi.stubs.StubIndexKey +import com.intellij.util.Processor +import org.jetbrains.r.classes.r6.R6ClassInfo +import org.jetbrains.r.psi.api.RCallExpression + +class R6ClassNameIndex : StringStubIndexExtension() { + override fun getKey(): StubIndexKey { + return KEY + } + + companion object { + private val KEY = StubIndexKey.createIndexKey("R.r6class.shortName") + + fun processAllR6ClassInfos(project: Project, + scope: GlobalSearchScope?, + processor: Processor>) { + val processingFunction = fun (declaration: RCallExpression) : Boolean = + declaration.associatedR6ClassInfo?.let { processor.process(declaration to it) } ?: true + + return LibraryClassNameIndexUtil.processClassInfos(project, scope, KEY, processingFunction) + } + + fun findClassInfos(name: String, project: Project, scope: GlobalSearchScope?): List { + val mapFunction = fun (declaration: RCallExpression) : R6ClassInfo? = declaration.associatedR6ClassInfo + + return LibraryClassNameIndexUtil.findClassInfos(KEY, mapFunction, name, project, scope) + } + + fun findClassDefinitions(name: String, project: Project, scope: GlobalSearchScope?): Collection { + return LibraryClassNameIndexUtil.findClassDefinitions(KEY, name, project, scope) + } + + fun sink(sink: IndexSink, name: String) { + return LibraryClassNameIndexUtil.sink(KEY, sink, name) + } + } +} \ No newline at end of file diff --git a/src/org/jetbrains/r/psi/stubs/classes/RS4ClassNameIndex.kt b/src/org/jetbrains/r/psi/stubs/classes/RS4ClassNameIndex.kt new file mode 100644 index 000000000..50ca3d70c --- /dev/null +++ b/src/org/jetbrains/r/psi/stubs/classes/RS4ClassNameIndex.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2021 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.r.psi.stubs.classes + +import com.intellij.openapi.project.Project +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.stubs.IndexSink +import com.intellij.psi.stubs.StringStubIndexExtension +import com.intellij.psi.stubs.StubIndexKey +import com.intellij.util.Processor +import org.jetbrains.r.classes.s4.RS4ClassInfo +import org.jetbrains.r.psi.api.RCallExpression + +class RS4ClassNameIndex : StringStubIndexExtension() { + override fun getKey(): StubIndexKey { + return KEY + } + + companion object { + private val KEY = StubIndexKey.createIndexKey("R.s4class.shortName") + + fun processAllS4ClassInfos(project: Project, + scope: GlobalSearchScope?, + processor: Processor>) { + val processingFunction = fun (declaration: RCallExpression) : Boolean = + declaration.associatedS4ClassInfo?.let { processor.process(declaration to it) } ?: true + + return LibraryClassNameIndexUtil.processClassInfos(project, scope, KEY, processingFunction) + } + + fun findClassInfos(name: String, project: Project, scope: GlobalSearchScope?): List { + val mapFunction = fun (declaration: RCallExpression) : RS4ClassInfo? = declaration.associatedS4ClassInfo + + return LibraryClassNameIndexUtil.findClassInfos(KEY, mapFunction, name, project, scope) + } + + fun findClassDefinitions(name: String, project: Project, scope: GlobalSearchScope?): Collection { + return LibraryClassNameIndexUtil.findClassDefinitions(KEY, name, project, scope) + } + + fun sink(sink: IndexSink, name: String) { + return LibraryClassNameIndexUtil.sink(KEY, sink, name) + } + } +} \ No newline at end of file diff --git a/src/org/jetbrains/r/refactoring/rename/RenameRPsiElementProcessor.kt b/src/org/jetbrains/r/refactoring/rename/RenameRPsiElementProcessor.kt index 60bc54464..158786b91 100644 --- a/src/org/jetbrains/r/refactoring/rename/RenameRPsiElementProcessor.kt +++ b/src/org/jetbrains/r/refactoring/rename/RenameRPsiElementProcessor.kt @@ -30,7 +30,7 @@ class RenameRPsiElementProcessor : RenamePsiElementProcessor() { } } is RForStatement -> element.target - is RAssignmentStatement, is RParameter, is RFile -> element + is RAssignmentStatement, is RNamedArgument, is RParameter, is RFile -> element else -> null } } diff --git a/src/org/jetbrains/r/rinterop/RInterop.kt b/src/org/jetbrains/r/rinterop/RInterop.kt index d8c2d8462..2f18a2422 100644 --- a/src/org/jetbrains/r/rinterop/RInterop.kt +++ b/src/org/jetbrains/r/rinterop/RInterop.kt @@ -35,6 +35,8 @@ import io.grpc.stub.StreamObserver import org.jetbrains.annotations.TestOnly import org.jetbrains.concurrency.* import org.jetbrains.r.RBundle +import org.jetbrains.r.classes.r6.* +import org.jetbrains.r.classes.r6.R6ClassInfo import org.jetbrains.r.classes.s4.RS4ClassInfo import org.jetbrains.r.classes.s4.RS4ClassSlot import org.jetbrains.r.debugger.RDebuggerUtil @@ -871,6 +873,43 @@ class RInterop(val interpreter: RInterpreter, val processHandler: ProcessHandler } } + /** + * @return list of [R6ClassInfo] without information about [R6ClassInfo.fields], [R6ClassInfo.methods] and [R6ClassInfo.activeBindings] + */ + fun getLoadedShortR6ClassInfos(): List? { + return try { + executeWithCheckCancel(asyncStub::getLoadedShortR6ClassInfos, Empty.getDefaultInstance()).shortR6ClassInfosList.map { + R6ClassInfo(it.name, emptyList(), emptyList(), emptyList(), emptyList()) + } + } catch (e: RInteropTerminated) { + null + } + } + + fun getR6ClassInfoByObjectName(ref: RReference): R6ClassInfo? { + return try { + val res = executeWithCheckCancel(asyncStub::getR6ClassInfoByObjectName, ref.proto) + R6ClassInfo(res.className, res.superClassesList, + res.fieldsList.map { R6ClassField(it.name, it.isPublic) }, + res.methodsList.map { R6ClassMethod(it.name, it.isPublic) }, + res.activeBindingsList.map { R6ClassActiveBinding(it.name) }) + } catch (e: RInteropTerminated) { + null + } + } + + fun getR6ClassInfoByClassName(className: String): R6ClassInfo? { + return try { + val res = executeWithCheckCancel(asyncStub::getR6ClassInfoByClassName, StringValue.of(className)) + R6ClassInfo(res.className, res.superClassesList, + res.fieldsList.map { R6ClassField(it.name, it.isPublic) }, + res.methodsList.map { R6ClassMethod(it.name, it.isPublic) }, + res.activeBindingsList.map { R6ClassActiveBinding(it.name) }) + } catch (e: RInteropTerminated) { + null + } + } + fun getFormalArguments(function: RReference): List { return try { executeWithCheckCancel(asyncStub::getFormalArguments, function.proto).listList diff --git a/src/org/jetbrains/r/roxygen/RoxygenCompletionContributor.kt b/src/org/jetbrains/r/roxygen/RoxygenCompletionContributor.kt index 261f33429..fc20bb01b 100755 --- a/src/org/jetbrains/r/roxygen/RoxygenCompletionContributor.kt +++ b/src/org/jetbrains/r/roxygen/RoxygenCompletionContributor.kt @@ -109,6 +109,14 @@ class RoxygenCompletionContributor : CompletionContributor() { document.insertString(offset, "()") context.editor.caretModel.moveCaretRelatively(4, 0, false, false, false) } + + override fun getInsertHandlerForLookupString(lookupString: String) = InsertHandler { context, _ -> + val offset = context.tailOffset + val document = context.document + insertSpaceAfterLinkIfNeeded(document, offset) + document.insertString(offset, "()") + context.editor.caretModel.moveCaretRelatively(4, 0, false, false, false) + } } private object RoxygenConstantLinkInsertHandler : RLookupElementInsertHandler { @@ -117,6 +125,12 @@ class RoxygenCompletionContributor : CompletionContributor() { insertSpaceAfterLinkIfNeeded(document, context.tailOffset) context.editor.caretModel.moveCaretRelatively(2, 0, false, false, false) } + + override fun getInsertHandlerForLookupString(lookupString: String) = InsertHandler { context, _ -> + val document = context.document + insertSpaceAfterLinkIfNeeded(document, context.tailOffset) + context.editor.caretModel.moveCaretRelatively(2, 0, false, false, false) + } } private fun insertSpaceAfterLinkIfNeeded(document: Document, tailOffset: Int) { diff --git a/src/org/jetbrains/r/skeleton/RSkeletonFileStubBuilder.kt b/src/org/jetbrains/r/skeleton/RSkeletonFileStubBuilder.kt index 2f96b06de..f02d32581 100644 --- a/src/org/jetbrains/r/skeleton/RSkeletonFileStubBuilder.kt +++ b/src/org/jetbrains/r/skeleton/RSkeletonFileStubBuilder.kt @@ -8,6 +8,7 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.stubs.BinaryFileStubBuilder import com.intellij.psi.stubs.Stub import com.intellij.util.indexing.FileContent +import org.jetbrains.r.classes.r6.* import org.jetbrains.r.classes.s4.RS4ClassInfo import org.jetbrains.r.classes.s4.RS4ClassSlot import org.jetbrains.r.hints.parameterInfo.RExtraNamedArgumentsInfo @@ -31,27 +32,43 @@ class RSkeletonFileStubBuilder : BinaryFileStubBuilder { LibrarySummary.RLibraryPackage.parseFrom(it) } for (symbol in binPackage.symbolsList) { - if (symbol.representationCase == RepresentationCase.S4CLASSREPRESENTATION) { - val s4ClassRepresentation = symbol.s4ClassRepresentation - RSkeletonCallExpressionStub(skeletonFileStub, - R_SKELETON_CALL_EXPRESSION, - RS4ClassInfo(symbol.name, - s4ClassRepresentation.packageName, - s4ClassRepresentation.slotsList.map { RS4ClassSlot(it.name, it.type) }, - s4ClassRepresentation.superClassesList, - s4ClassRepresentation.isVirtual)) - } - else { - val functionRepresentation = symbol.functionRepresentation - val extraNamedArguments = functionRepresentation.extraNamedArguments - RSkeletonAssignmentStub(skeletonFileStub, - R_SKELETON_ASSIGNMENT_STATEMENT, - symbol.name, - symbol.type, - functionRepresentation.parameters, - symbol.exported, - RExtraNamedArgumentsInfo(extraNamedArguments.argNamesList, - extraNamedArguments.funArgNamesList)) + when (symbol.representationCase) { + RepresentationCase.S4CLASSREPRESENTATION -> { + val s4ClassRepresentation = symbol.s4ClassRepresentation + RSkeletonCallExpressionStub(skeletonFileStub, + R_SKELETON_CALL_EXPRESSION, + RS4ClassInfo(symbol.name, + s4ClassRepresentation.packageName, + s4ClassRepresentation.slotsList.map { RS4ClassSlot(it.name, it.type) }, + s4ClassRepresentation.superClassesList, + s4ClassRepresentation.isVirtual), + null) + } + + RepresentationCase.R6CLASSREPRESENTATION -> { + val r6ClassRepresentation = symbol.r6ClassRepresentation + RSkeletonCallExpressionStub(skeletonFileStub, + R_SKELETON_CALL_EXPRESSION, + null, + R6ClassInfo(symbol.name, + r6ClassRepresentation.superClassesList, + r6ClassRepresentation.fieldsList.map { R6ClassField(it.name, it.isPublic) }, + r6ClassRepresentation.methodsList.map { R6ClassMethod(it.name, it.isPublic) }, + r6ClassRepresentation.activeBindingsList.map { R6ClassActiveBinding(it.name) })) + } + + else -> { + val functionRepresentation = symbol.functionRepresentation + val extraNamedArguments = functionRepresentation.extraNamedArguments + RSkeletonAssignmentStub(skeletonFileStub, + R_SKELETON_ASSIGNMENT_STATEMENT, + symbol.name, + symbol.type, + functionRepresentation.parameters, + symbol.exported, + RExtraNamedArgumentsInfo(extraNamedArguments.argNamesList, + extraNamedArguments.funArgNamesList)) + } } } return skeletonFileStub diff --git a/src/org/jetbrains/r/skeleton/psi/RSkeletonCallExpression.kt b/src/org/jetbrains/r/skeleton/psi/RSkeletonCallExpression.kt index b1d39a6b5..fdb47c42d 100644 --- a/src/org/jetbrains/r/skeleton/psi/RSkeletonCallExpression.kt +++ b/src/org/jetbrains/r/skeleton/psi/RSkeletonCallExpression.kt @@ -8,6 +8,7 @@ import com.intellij.psi.PsiElement import com.intellij.psi.stubs.IStubElementType import com.intellij.psi.stubs.StubElement import com.intellij.util.IncorrectOperationException +import org.jetbrains.r.classes.r6.R6ClassInfo import org.jetbrains.r.classes.s4.RS4ClassInfo import org.jetbrains.r.psi.api.RArgumentList import org.jetbrains.r.psi.api.RCallExpression @@ -27,30 +28,17 @@ class RSkeletonCallExpression(private val myStub: RSkeletonCallExpressionStub) : override fun getElementType(): IStubElementType, *> = stub.stubType - override fun getName(): String = myStub.s4ClassInfo.className + override fun getName(): String = myStub.s4ClassInfo?.className ?: "" override fun canNavigate(): Boolean = false override fun getText(): String { - val info = stub.s4ClassInfo + val s4Info = stub.s4ClassInfo + val r6Info = stub.r6ClassInfo + return buildString { - append("setClass('").append(info.className).append("', ") - append("slots = c(") - info.slots.forEachIndexed { ind, slot -> - if (ind != 0) append(", ") - append(slot.name).append(" = '").append(slot.type).append("'") - } - append("), ") - append("contains = c(") - info.superClasses.forEachIndexed { ind, superClass -> - if (ind != 0) append(", ") - append("'").append(superClass).append("'") - } - if (info.isVirtual) { - if (info.superClasses.isNotEmpty()) append(", ") - append("'VIRTUAL'") - } - append("))") + if (s4Info != null) { append(s4Info.toString()) } + if (r6Info != null) { append(r6Info.toString()) } } } @@ -58,7 +46,9 @@ class RSkeletonCallExpression(private val myStub: RSkeletonCallExpressionStub) : throw IncorrectOperationException("Operation not supported in: $javaClass") } - override fun getAssociatedS4ClassInfo(): RS4ClassInfo = myStub.s4ClassInfo + override fun getAssociatedS4ClassInfo(): RS4ClassInfo? = myStub.s4ClassInfo + + override fun getAssociatedR6ClassInfo(): R6ClassInfo? = myStub.r6ClassInfo override fun getReference(): RReferenceBase<*>? = null diff --git a/src/org/jetbrains/r/skeleton/psi/RSkeletonCallExpressionElementType.kt b/src/org/jetbrains/r/skeleton/psi/RSkeletonCallExpressionElementType.kt index 85b14e492..4cc91419c 100644 --- a/src/org/jetbrains/r/skeleton/psi/RSkeletonCallExpressionElementType.kt +++ b/src/org/jetbrains/r/skeleton/psi/RSkeletonCallExpressionElementType.kt @@ -11,23 +11,33 @@ import com.intellij.psi.stubs.StubElement import com.intellij.psi.stubs.StubInputStream import com.intellij.psi.stubs.StubOutputStream import com.intellij.util.IncorrectOperationException +import org.jetbrains.r.classes.r6.R6ClassInfo import org.jetbrains.r.classes.s4.RS4ClassInfo import org.jetbrains.r.psi.api.RCallExpression -import org.jetbrains.r.psi.stubs.RS4ClassNameIndex import org.jetbrains.r.psi.stubs.RStubElementType +import org.jetbrains.r.psi.stubs.classes.R6ClassNameIndex +import org.jetbrains.r.psi.stubs.classes.RS4ClassNameIndex -class RSkeletonCallExpressionElementType : RStubElementType("R bin s4") { +class RSkeletonCallExpressionElementType : RStubElementType("R bin s4 r6") { override fun createPsi(stub: RSkeletonCallExpressionStub): RCallExpression { return RSkeletonCallExpression(stub) } override fun serialize(stub: RSkeletonCallExpressionStub, dataStream: StubOutputStream) { - stub.s4ClassInfo.serialize(dataStream) + dataStream.writeBoolean(stub.s4ClassInfo != null) + dataStream.writeBoolean(stub.r6ClassInfo != null) + + stub.s4ClassInfo?.serialize(dataStream) + stub.r6ClassInfo?.serialize(dataStream) } override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>): RSkeletonCallExpressionStub { - val s4ClassInfo = RS4ClassInfo.deserialize(dataStream) - return RSkeletonCallExpressionStub(parentStub, this, s4ClassInfo) + val s4ClassExists = dataStream.readBoolean() + val r6ClassExists = dataStream.readBoolean() + + val s4ClassInfo = if (s4ClassExists) RS4ClassInfo.deserialize(dataStream) else null + val r6ClassInfo = if (r6ClassExists) R6ClassInfo.deserialize(dataStream) else null + return RSkeletonCallExpressionStub(parentStub, this, s4ClassInfo, r6ClassInfo) } override fun createStub(psi: RCallExpression, parentStub: StubElement<*>?): RSkeletonCallExpressionStub { @@ -39,7 +49,7 @@ class RSkeletonCallExpressionElementType : RStubElementType, elementType: RSkeletonCallExpressionElementType, - override val s4ClassInfo: RS4ClassInfo) - : StubBase(parent, elementType), RCallExpressionStub + override val s4ClassInfo: RS4ClassInfo?, + override val r6ClassInfo: R6ClassInfo?) + : StubBase(parent, elementType), RCallExpressionStub { + + override fun toString(): String { + return buildString { + append("RSkeletonCallExpressionStub:") + if (s4ClassInfo != null) { append("s4='${s4ClassInfo.className}'") } + if (r6ClassInfo != null) { append("r6='${r6ClassInfo.className}'") } + } + } +} diff --git a/test/org/jetbrains/r/RUsefulTestCase.kt b/test/org/jetbrains/r/RUsefulTestCase.kt index 5a4d6a5d3..897b5b3f9 100644 --- a/test/org/jetbrains/r/RUsefulTestCase.kt +++ b/test/org/jetbrains/r/RUsefulTestCase.kt @@ -259,6 +259,7 @@ abstract class RUsefulTestCase : BasePlatformTestCase() { grDevices magrittr methods + R6 stats utils roxygen2 diff --git a/test/org/jetbrains/r/classes/RClassesUtilTestsBase.kt b/test/org/jetbrains/r/classes/RClassesUtilTestsBase.kt new file mode 100644 index 000000000..74a6bd30d --- /dev/null +++ b/test/org/jetbrains/r/classes/RClassesUtilTestsBase.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2021 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.r.classes + +import com.intellij.psi.PsiElement +import org.jetbrains.r.RLanguage +import org.jetbrains.r.psi.api.RAssignmentStatement +import org.jetbrains.r.psi.api.RCallExpression +import org.jetbrains.r.psi.api.RFile +import org.jetbrains.r.run.RProcessHandlerBaseTestCase + +abstract class RClassesUtilTestsBase : RProcessHandlerBaseTestCase() { + override fun setUp() { + super.setUp() + addLibraries() + } + + protected fun getRCallExpressionFromAssignment(assignmentStatement: RAssignmentStatement): RCallExpression? { + if (assignmentStatement.children?.size!! < 3) return null + return assignmentStatement.children[2] as RCallExpression; + } + + protected fun getRootElementOfPsi(code: String): PsiElement { + val rFile = myFixture.configureByText("foo.R", code) as RFile + val viewProvider = rFile.getViewProvider(); + val rPsi = viewProvider.getPsi(RLanguage.INSTANCE) + return rPsi.originalElement.firstChild + } +} \ No newline at end of file diff --git a/test/org/jetbrains/r/classes/r6/R6ClassInfoUtilTests.kt b/test/org/jetbrains/r/classes/r6/R6ClassInfoUtilTests.kt new file mode 100644 index 000000000..0f7d2ba90 --- /dev/null +++ b/test/org/jetbrains/r/classes/r6/R6ClassInfoUtilTests.kt @@ -0,0 +1,189 @@ +/* + * Copyright 2000-2021 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.r.classes.r6 + +import com.intellij.psi.util.PsiTreeUtil +import junit.framework.TestCase +import org.jetbrains.r.classes.RClassesUtilTestsBase +import org.jetbrains.r.psi.api.RAssignmentStatement +import org.jetbrains.r.psi.api.RCallExpression + +class R6ClassInfoUtilTests : RClassesUtilTestsBase() { + private val fullClassCodeDefinition = """ + Car <- R6Class("Car", + inherit = Vehicle, + public = list( + weight = 0, + speed = 0, + accelerate = function(acc = 1){ + self${"$"}speed <- self${"$"}speed + acc + invisible(self) + }, + slowDown = function(neg_acc = 1){ + self${"$"}speed <- self${"$"}speed - acc + invisible(self) + } + ), + private = list( + engine_rpm = 1000, + maximize = function(rmp = 1000) { + self${"$"}engine_rpm <- self${"$"}engine_rpm + rmp + invisible(self) + } + ), + active = list( + asd = 0, + random = function(value) { + if (missing(value)) { + runif(1) + } else { + stop("Can't set `${"$"}random`", call. = FALSE) + } + } + ) + ) + """.trimIndent() + + private val shortedClassCodeDefinition = """ + Car <- R6Class("Car", + inherit = Vehicle, + list( + weight = 0, + speed = 0, + accelerate = function(acc = 1){ + self${"$"}speed <- self${"$"}speed + acc + invisible(self) + }, + slowDown = function(neg_acc = 1){ + self${"$"}speed <- self${"$"}speed - acc + invisible(self) + } + ) + ) + """.trimIndent() + + fun testGetAssociatedClassName(){ + val rAssignmentStatement = getRootElementOfPsi(fullClassCodeDefinition) as RAssignmentStatement + val rCallExpression = getRCallExpressionFromAssignment(rAssignmentStatement) + val className = R6ClassInfoUtil.getAssociatedClassNameFromR6ClassCall(rCallExpression!!) + assertEquals("Car", className) + } + + fun testGetAssociatedClassNameFromInstantiationCall(){ + val rAssignmentStatement = getRootElementOfPsi(""" + obj <- MyClass${'$'}new() + """.trimIndent()) as RAssignmentStatement + + val call = rAssignmentStatement.lastChild as RCallExpression + val className = R6ClassInfoUtil.getAssociatedClassNameFromInstantiationCall(call) + assertEquals("MyClass", className) + } + + fun testGetAssociatedSuperClasses(){ + val psiTree = getRootElementOfPsi(""" + SuperParentClass <- R6Class("SuperParentClass") + ParentClass <- R6Class("ParentClass", inherit = SuperParentClass) + ChildClass <- R6Class("ChildClass", inherit = ParentClass) + """.trimIndent()).parent + + val lastClassAssignment = PsiTreeUtil.getChildrenOfType(psiTree, RAssignmentStatement::class.java).last() as RAssignmentStatement + val rCallExpression = getRCallExpressionFromAssignment(lastClassAssignment) + val superClassNames = R6ClassInfoUtil.getAssociatedSuperClassesHierarchy(rCallExpression!!) + + assertNotNull(superClassNames) + assert(superClassNames!!.contains("ParentClass")) + assert(superClassNames.contains("SuperParentClass")) + } + + fun testClassContainsFields(){ + val rAssignmentStatement = getRootElementOfPsi(fullClassCodeDefinition) as RAssignmentStatement + val rCallExpression = getRCallExpressionFromAssignment(rAssignmentStatement) + val classFields = R6ClassInfoUtil.getAssociatedFields(rCallExpression!!) + + assertNotNull(classFields) + + val classFieldsNames = classFields!!.map { it.name } + assertContainsElements(classFieldsNames, "weight") + assertEquals(true, classFields[0].isPublic) + + assertContainsElements(classFieldsNames, "speed") + assertEquals(true, classFields[1].isPublic) + + assertContainsElements(classFieldsNames, "engine_rpm") + assertEquals(false, classFields[2].isPublic) + } + + fun testClassContainsMethods(){ + val rAssignmentStatement = getRootElementOfPsi(fullClassCodeDefinition) as RAssignmentStatement + val rCallExpression = getRCallExpressionFromAssignment(rAssignmentStatement) + val classMethods = R6ClassInfoUtil.getAssociatedMethods(rCallExpression!!) + + assertNotNull(classMethods) + + val classMethodsNames = classMethods!!.map { it.name } + assertContainsElements(classMethodsNames, "accelerate") + assertEquals(classMethods[0].isPublic, true) + + assertContainsElements(classMethodsNames, "slowDown") + assertEquals(classMethods[1].isPublic, true) + + assertContainsElements(classMethodsNames, "maximize") + assertEquals(classMethods[2].isPublic, false) + } + + fun testGetAssociatedActiveBindings(){ + val rAssignmentStatement = getRootElementOfPsi(fullClassCodeDefinition) as RAssignmentStatement + val rCallExpression = getRCallExpressionFromAssignment(rAssignmentStatement) + val classActiveBindings = R6ClassInfoUtil.getAssociatedActiveBindings(rCallExpression!!) + + assertNotNull(classActiveBindings) + assertEquals(classActiveBindings!!.size, 1) + + val classActiveBindingsNames = classActiveBindings.map { it.name } + assertContainsElements(classActiveBindingsNames, "random") + } + + fun testGetShortenedClassAssociatedFields(){ + val rAssignmentStatement = getRootElementOfPsi(shortedClassCodeDefinition) as RAssignmentStatement + val rCallExpression = getRCallExpressionFromAssignment(rAssignmentStatement) + val classFields = R6ClassInfoUtil.getAssociatedFields(rCallExpression!!) + + assertNotNull(classFields) + + val classFieldsNames = classFields!!.map { it.name } + assertContainsElements(classFieldsNames, "weight") + assertContainsElements(classFieldsNames, "speed") + classFields.forEach { assertEquals(true, it.isPublic) } + } + + fun testGetShortenedClassAssociatedMethods(){ + val rAssignmentStatement = getRootElementOfPsi(shortedClassCodeDefinition) as RAssignmentStatement + val rCallExpression = getRCallExpressionFromAssignment(rAssignmentStatement) + val classMethods = R6ClassInfoUtil.getAssociatedMethods(rCallExpression!!) + + assertNotNull(classMethods) + + val classMethodsNames = classMethods!!.map { it.name } + assertContainsElements(classMethodsNames, "accelerate") + assertContainsElements(classMethodsNames, "slowDown") + classMethods.forEach { assertEquals(true, it.isPublic) } + } + + fun testGetMembers(){ + val rAssignmentStatement = getRootElementOfPsi(fullClassCodeDefinition) as RAssignmentStatement + val rCallExpression = getRCallExpressionFromAssignment(rAssignmentStatement) + val classMethods = R6ClassInfoUtil.getAssociatedMembers(rCallExpression!!) + + assertNotNull(classMethods) + + val classMethodsNames = classMethods!!.map { it.name } + assertContainsElements(classMethodsNames, "weight") + assertContainsElements(classMethodsNames, "speed") + assertContainsElements(classMethodsNames, "engine_rpm") + assertContainsElements(classMethodsNames, "maximize") + assertContainsElements(classMethodsNames, "accelerate") + assertContainsElements(classMethodsNames, "slowDown") + } +} \ No newline at end of file diff --git a/test/org/jetbrains/r/classes/s4/RS4ClassInfoUtilTests.kt b/test/org/jetbrains/r/classes/s4/RS4ClassInfoUtilTests.kt new file mode 100644 index 000000000..15fdb6eb4 --- /dev/null +++ b/test/org/jetbrains/r/classes/s4/RS4ClassInfoUtilTests.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2000-2021 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.r.classes.s4 + +import org.jetbrains.r.classes.RClassesUtilTestsBase +import org.jetbrains.r.psi.api.RCallExpression + +class RS4ClassInfoUtilTests : RClassesUtilTestsBase() { + private val setClassCode = """ + setClass("Person", + contains = "Being", + slots = c( + name = "character", + age = "numeric" + ) + ) + """.trimIndent() + + fun testGetAssociatedClassName(){ + val setClassExpression = getRootElementOfPsi(setClassCode) as RCallExpression + val className = RS4ClassInfoUtil.getAssociatedClassName(setClassExpression) + + assertEquals("Person", className) + } + + fun testGetAllAssociatedSlots(){ + val setClassExpression = getRootElementOfPsi(setClassCode) as RCallExpression + val classSlots = RS4ClassInfoUtil.getAllAssociatedSlots(setClassExpression) + + assertEquals(2, classSlots.size) + assertEquals(classSlots[0].name, "name") + assertEquals(classSlots[0].type, "character") + assertEquals(classSlots[1].name, "age") + assertEquals(classSlots[1].type, "numeric") + } + + fun testGetAllAssociatedSuperClasses(){ + val setClassExpression = getRootElementOfPsi(setClassCode) as RCallExpression + val superClasses = RS4ClassInfoUtil.getAllAssociatedSuperClasses(setClassExpression) + + assertEquals(1, superClasses.size) + assertEquals(superClasses[0], "Being") + } +} \ No newline at end of file diff --git a/test/org/jetbrains/r/completion/AutoPopupTest.kt b/test/org/jetbrains/r/completion/AutoPopupTest.kt index df57a400e..8d8fe0f3d 100644 --- a/test/org/jetbrains/r/completion/AutoPopupTest.kt +++ b/test/org/jetbrains/r/completion/AutoPopupTest.kt @@ -10,6 +10,7 @@ import com.intellij.util.ThrowableRunnable import org.jetbrains.r.RFileType import org.jetbrains.r.RLightCodeInsightFixtureTestCase import org.jetbrains.r.classes.s4.RS4ClassInfo +import org.jetbrains.r.classes.r6.R6ClassInfo import org.jetbrains.r.console.RConsoleRuntimeInfo import org.jetbrains.r.console.addRuntimeInfo import org.jetbrains.r.hints.parameterInfo.RExtraNamedArgumentsInfo @@ -198,6 +199,10 @@ class AutoPopupTest : RLightCodeInsightFixtureTestCase() { throw NotImplementedError() } + override fun loadR6ClassInfoByObjectName(objectName: String): R6ClassInfo? { + throw NotImplementedError() + } + override fun loadExtraNamedArguments(functionName: String, functionExpression: RFunctionExpression): RExtraNamedArgumentsInfo { throw NotImplementedError() } diff --git a/test/org/jetbrains/r/completion/classes/R6ClassCompletionTest.kt b/test/org/jetbrains/r/completion/classes/R6ClassCompletionTest.kt new file mode 100644 index 000000000..0b673b1cc --- /dev/null +++ b/test/org/jetbrains/r/completion/classes/R6ClassCompletionTest.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2000-2021 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.r.completion.classes + +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.lookup.LookupElementPresentation +import org.jetbrains.r.console.RConsoleRuntimeInfoImpl +import org.jetbrains.r.console.RConsoleView +import org.jetbrains.r.console.addRuntimeInfo +import org.jetbrains.r.run.RProcessHandlerBaseTestCase + +class R6ClassCompletionTest : RProcessHandlerBaseTestCase() { + + override fun setUp() { + super.setUp() + addLibraries() + } + + fun testUserClassWithSingleField() { + doTest(""" + MyClass <- R6Class("MyClass", list( someField = 0 )) + obj <- MyClass${"$"}new() + obj$ + """.trimIndent(), "clone" to "", "someField" to "") + } + + fun testUserClassWithSingleFieldChainedUsage() { + doTest(""" + MyClass <- R6Class("MyClass", list( someField = 0 )) + obj <- MyClass${"$"}new() + obj${'$'}someField${'$'} + """.trimIndent(), "clone" to "", "someField" to "") + } + + fun testUserClassForInternalUsage() { + doTest(""" + MyClass <- R6Class("MyClass", list( someField = 0, add = function(x = 1) { self$ } )) + """.trimIndent(), "add" to "", "clone" to "", "someField" to "") + } + + fun testUserClassWithSeveralMembers() { + doTest(""" + MyClass <- R6Class("MyClass", list( someField = 0, someMethod = function (x = 1) { print(x) } )) + obj <- MyClass${"$"}new() + obj$ + """.trimIndent(), "clone" to "", "someField" to "", "someMethod" to "") + } + + fun testUserClassWithFieldMethodActiveBinding() { + doTest(""" + MyClass <- R6Class("MyClass", list( someField = 0, someMethod = function (x = 1) { print(x) }, random = function() { print('it is a random active binding') } )) + obj <- MyClass${"$"}new() + obj$ + """.trimIndent(), "clone" to "", "random" to "", "someField" to "", "someMethod" to "") + } + + fun testInheritedUserClassFieldsVisibility() { + doTest(""" + ParentClass <- R6Class("ParentClass", list( someField = 0 )) + ChildClass <- R6Class("ChildClass", inherit = ParentClass, list( add = function(x = 1) { print(x) } )) + obj <- ChildClass${"$"}new() + obj${'$'}someField${'$'} + """.trimIndent(), "add" to "", "clone" to "", "someField" to "") + } + + fun testUserClassNameSuggestion() { + doTest(""" + MyClass <- R6Class() + """.trimIndent(), "MyClass" to "string") + } + + fun testSetMemberVisibilityModifierCompletionToUserClass() { + doTest(""" + MyClass <- R6Class("MyClass", list(someField = 0)) + MyClass${'$'}set() + """.trimIndent(), "\"active\"" to "string", "\"public\"" to "string", "\"private\"" to "string") + } + + fun testConsoleMembersSuggestion() { + rInterop.executeCode(""" + library(R6) + MyClass <- R6Class("MyClass", public = list(someField = 0, someMethod = function (x = 1) { print(x) }, random = function() { print('it is a random active binding') } ), + private = list(somePrivateField = 0, somePrivateMethod = function(x = 1) { print(x) } ), + active = list(someActiveFunction = function(x) { print(x) } ) + ) + obj <- MyClass${'$'}new() + """.trimIndent()) + doTest("obj${'$'}", "clone" to "", "random" to "", "someField" to "", "someMethod" to "", "someActiveFunction" to "", + withRuntimeInfo = true, inConsole = true,) + } + + private fun doWrongVariantsTest(text: String, vararg variants: String, withRuntimeInfo: Boolean = false, inConsole: Boolean = false) { + val result = doTestBase(text, withRuntimeInfo, inConsole) + assertNotNull(result) + val lookupStrings = result.map { it.lookupString } + assertDoesntContain(lookupStrings, *variants) + } + + private fun doTest(text: String, + vararg variants: Pair, // + withRuntimeInfo: Boolean = false, + inConsole: Boolean = false) { + val result = doTestBase(text, withRuntimeInfo, inConsole) + assertNotNull(result) + val lookupStrings = result.map { + val elementPresentation = LookupElementPresentation() + it.renderElement(elementPresentation) + elementPresentation.itemText to elementPresentation.typeText + } + + variants.forEach { expectedSuggestion -> + assert(lookupStrings.any { it.first == expectedSuggestion.first }) + } + } + + private fun doTestBase(text: String, withRuntimeInfo: Boolean = false, inConsole: Boolean = false): Array { + myFixture.configureByText("foo.R", text) + if (inConsole) { + myFixture.file.putUserData(RConsoleView.IS_R_CONSOLE_KEY, true) + } + if (withRuntimeInfo) { + myFixture.file.addRuntimeInfo(RConsoleRuntimeInfoImpl(rInterop)) + } + return myFixture.completeBasic() + } +} diff --git a/test/org/jetbrains/r/completion/S4ClassCompletionTest.kt b/test/org/jetbrains/r/completion/classes/S4ClassCompletionTest.kt similarity index 99% rename from test/org/jetbrains/r/completion/S4ClassCompletionTest.kt rename to test/org/jetbrains/r/completion/classes/S4ClassCompletionTest.kt index e8efaca00..cd78df20c 100644 --- a/test/org/jetbrains/r/completion/S4ClassCompletionTest.kt +++ b/test/org/jetbrains/r/completion/classes/S4ClassCompletionTest.kt @@ -1,8 +1,8 @@ /* - * Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + * Copyright 2000-2021 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.r.completion +package org.jetbrains.r.completion.classes import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElementPresentation diff --git a/test/org/jetbrains/r/findUsages/FindUsagesTestBase.kt b/test/org/jetbrains/r/findUsages/FindUsagesTestBase.kt new file mode 100644 index 000000000..1d56e43b1 --- /dev/null +++ b/test/org/jetbrains/r/findUsages/FindUsagesTestBase.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2000-2021 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.r.findUsages + +import com.intellij.testFramework.UsefulTestCase +import com.intellij.usages.PsiElementUsageTarget +import com.intellij.usages.UsageTargetUtil +import org.intellij.lang.annotations.Language +import org.jetbrains.r.run.RProcessHandlerBaseTestCase + +abstract class FindUsagesTestBase : RProcessHandlerBaseTestCase() { + override fun setUp() { + super.setUp() + addLibraries() + } + + fun doTest(@Language("R") code: String, expected: String) { + myFixture.configureByText("test.R", code.trimIndent()) + val element = myFixture.elementAtCaret + val targets = UsageTargetUtil.findUsageTargets(element) + assertNotNull(targets) + assertTrue(targets.size > 0) + val target = (targets[0] as PsiElementUsageTarget).element + val actual = myFixture.getUsageViewTreeTextRepresentation(target) + UsefulTestCase.assertSameLines(expected.trimIndent(), actual) + } +} \ No newline at end of file diff --git a/test/org/jetbrains/r/findUsages/R6FindUsagesTest.kt b/test/org/jetbrains/r/findUsages/R6FindUsagesTest.kt new file mode 100644 index 000000000..d5a457d02 --- /dev/null +++ b/test/org/jetbrains/r/findUsages/R6FindUsagesTest.kt @@ -0,0 +1,132 @@ +/* + * Copyright 2000-2021 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.r.findUsages + +class R6FindUsagesTest : FindUsagesTestBase() { + override fun setUp() { + super.setUp() + addLibraries() + } + + fun testR6ClassFieldOutOfClassUsage() { + doTest(""" + MyClass <- R6Class("MyClass", list( someField = 0, someMethod = function(x = 1) { print(x) } )) + obj <- MyClass${'$'}new() + obj${'$'}someField + obj${'$'}someField + """, """ + Usage (2 usages) + Variable + someField = 0 + Found usages (2 usages) + Unclassified (2 usages) + light_idea_test_case (2 usages) + (2 usages) + 3obj${"$"}someField + 4obj${"$"}someField + """) + } + + fun testR6ClassFunctionOutOfClassUsage() { + doTest(""" + MyClass <- R6Class("MyClass", list( someField = 0, someMethod = function(x = 1) { print(x) } )) + obj <- MyClass${'$'}new() + obj${'$'}someMethod() + obj${'$'}someField + """, """ + Usage (1 usage) + Variable + someMethod = function(x = 1) { print(x) } + Found usages (1 usage) + Unclassified (1 usage) + light_idea_test_case (1 usage) + (1 usage) + 3obj${"$"}someMethod() + """) + } + + fun testR6ClassFieldOutOfClassChainedUsage() { + doTest(""" + MyClass <- R6Class("MyClass", list( someField = 0, someMethod = function(x = 1) { print(x) } )) + obj <- MyClass${'$'}new() + obj${'$'}someField + obj${'$'}someMethod()${'$'}someField + """, """ + Usage (2 usages) + Variable + someField = 0 + Found usages (2 usages) + Unclassified (2 usages) + light_idea_test_case (2 usages) + (2 usages) + 3obj${'$'}someField + 4obj${'$'}someMethod()${'$'}someField + """) + } + + fun testR6ClassInsideMethodFieldUsage(){ + doTest(""" + Accumulator <- R6Class("Accumulator", list( + sum = 0, + add = function(x = 1) { + self${"$"}sum <- self${"$"}sum + x + invisible(self) + }) + ) + + x <- Accumulator${"$"}new() + x${"$"}add(4)${"$"}sum + x${"$"}sum + """, """ + Usage (3 usages) + Variable + sum = 0 + Found usages (3 usages) + Unclassified (3 usages) + light_idea_test_case (3 usages) + (3 usages) + 4self${'$'}sum <- self${'$'}sum + x + 10x${'$'}add(4)${'$'}sum + 11x${'$'}sum + """) + } + + fun testR6ClassFieldFromDefinition() { + doTest(""" + MyClass <- R6Class("MyClass", list( someField = 0, someMethod = function(x = 1) { print(x) } )) + obj <- MyClass${'$'}new() + obj${'$'}someField + obj${'$'}someField + """, """ + Usage (2 usages) + Variable + someField + Found usages (2 usages) + Unclassified (2 usages) + light_idea_test_case (2 usages) + (2 usages) + 3obj${"$"}someField + 4obj${"$"}someField + """) + } + + fun testR6ClassFieldWithSuperClass() { + doTest(""" + ParentClass <- R6Class("ParentClass", list( someField = 0 )) + ChildClass <- R6Class("ChildClass", inherit = ParentClass, list( add = function(x = 1) { print(x) } )) + obj <- ChildClass${'$'}new() + obj${'$'}someField + """, """ + Usage (1 usage) + Variable + someField = 0 + Found usages (1 usage) + Unclassified (1 usage) + light_idea_test_case (1 usage) + (1 usage) + 4obj${'$'}someField + """) + } +} \ No newline at end of file diff --git a/test/org/jetbrains/r/findUsages/RFindUsagesTest.kt b/test/org/jetbrains/r/findUsages/RCommonFindUsagesTest.kt similarity index 71% rename from test/org/jetbrains/r/findUsages/RFindUsagesTest.kt rename to test/org/jetbrains/r/findUsages/RCommonFindUsagesTest.kt index 5447c3733..4d6ed63e5 100644 --- a/test/org/jetbrains/r/findUsages/RFindUsagesTest.kt +++ b/test/org/jetbrains/r/findUsages/RCommonFindUsagesTest.kt @@ -10,13 +10,7 @@ import com.intellij.usages.UsageTargetUtil import org.intellij.lang.annotations.Language import org.jetbrains.r.run.RProcessHandlerBaseTestCase -class RFindUsagesTest : RProcessHandlerBaseTestCase() { - - override fun setUp() { - super.setUp() - addLibraries() - } - +class RCommonFindUsagesTest : FindUsagesTestBase() { fun testLocalVariable() { doTest(""" my.local.variable <- 10 @@ -33,9 +27,8 @@ class RFindUsagesTest : RProcessHandlerBaseTestCase() { Unclassified (2 usages) light_idea_test_case (2 usages) (2 usages) - test.R (2 usages) - 2print(my.local.variable) - 5print(my.local.variable + 20) + 2print(my.local.variable) + 5print(my.local.variable + 20) """) } @@ -56,9 +49,8 @@ class RFindUsagesTest : RProcessHandlerBaseTestCase() { Unclassified (2 usages) light_idea_test_case (2 usages) (2 usages) - test.R (2 usages) - 2print(my.local.function(2, 3)) - 5print(my.local.function(4, 5)) + 2print(my.local.function(2, 3)) + 5print(my.local.function(4, 5)) """) } @@ -74,9 +66,8 @@ class RFindUsagesTest : RProcessHandlerBaseTestCase() { Unclassified (2 usages) light_idea_test_case (2 usages) (2 usages) - test.R (2 usages) - 1base.package <- packageDescription("base") - 2dplyr.package <- packageDescription("dplyr") + 1base.package <- packageDescription("base") + 2dplyr.package <- packageDescription("dplyr") """) } @@ -97,9 +88,8 @@ class RFindUsagesTest : RProcessHandlerBaseTestCase() { Unclassified (2 usages) light_idea_test_case (2 usages) (2 usages) - test.R (2 usages) - 2x + y + z - 7func(x = p) + 2x + y + z + 7func(x = p) """) } @@ -124,13 +114,11 @@ class RFindUsagesTest : RProcessHandlerBaseTestCase() { Unclassified (1 usage) light_idea_test_case (1 usage) (1 usage) - test.R (1 usage) - 10x + y + z + 10x + y + z Usage in roxygen2 documentation (1 usage) light_idea_test_case (1 usage) (1 usage) - test.R (1 usage) - 5#' @param x, y X and y + 5#' @param x, y X and y """) } @@ -157,24 +145,11 @@ class RFindUsagesTest : RProcessHandlerBaseTestCase() { Unclassified (1 usage) light_idea_test_case (1 usage) (1 usage) - test.R (1 usage) - 10bar(x) + y + z + 10bar(x) + y + z Usage in roxygen2 documentation (1 usage) light_idea_test_case (1 usage) (1 usage) - test.R (1 usage) - 5#' @see [bar] + 5#' @see [bar] """) } - - private fun doTest(@Language("R") code: String, expected: String) { - myFixture.configureByText("test.R", code.trimIndent()) - val element = myFixture.elementAtCaret - val targets = UsageTargetUtil.findUsageTargets(element) - assertNotNull(targets) - assertTrue(targets.size > 0) - val target = (targets[0] as PsiElementUsageTarget).element - val actual = myFixture.getUsageViewTreeTextRepresentation(target) - UsefulTestCase.assertSameLines(expected.trimIndent(), actual) - } } \ No newline at end of file diff --git a/test/org/jetbrains/r/inspections/classes/r6/UnmatchingR6ClassNameInspectionTest.kt b/test/org/jetbrains/r/inspections/classes/r6/UnmatchingR6ClassNameInspectionTest.kt new file mode 100644 index 000000000..2d2fc3701 --- /dev/null +++ b/test/org/jetbrains/r/inspections/classes/r6/UnmatchingR6ClassNameInspectionTest.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2021 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.r.inspections.classes.r6 + +import org.jetbrains.r.RBundle +import org.jetbrains.r.inspections.RInspectionTest + +class UnmatchingR6ClassNameInspectionTest : RInspectionTest() { + override fun setUp() { + super.setUp() + addLibraries() + } + + fun testClassNameInspection() { + doExprTest(""" + UserClass <- R6Class("UserClass") + """.trimIndent()) + + doExprTest(""" + UserClass <- R6Class(${makeError("MyClass")}) + """.trimIndent()) + } + + override val inspection = UnmatchingR6ClassNameInspection::class.java + + companion object { + private fun msg(argumentName: String) = RBundle.message("inspection.r6class.naming.convention.classname", argumentName) + + private fun makeError(className: String): String { + return "'$className'" + } + } +} \ No newline at end of file diff --git a/test/org/jetbrains/r/inspections/DeprecatedSetClassArgsInspectionTest.kt b/test/org/jetbrains/r/inspections/classes/s4/DeprecatedSetClassArgsInspectionTest.kt similarity index 91% rename from test/org/jetbrains/r/inspections/DeprecatedSetClassArgsInspectionTest.kt rename to test/org/jetbrains/r/inspections/classes/s4/DeprecatedSetClassArgsInspectionTest.kt index 40100f427..9bd2875fa 100644 --- a/test/org/jetbrains/r/inspections/DeprecatedSetClassArgsInspectionTest.kt +++ b/test/org/jetbrains/r/inspections/classes/s4/DeprecatedSetClassArgsInspectionTest.kt @@ -1,11 +1,11 @@ /* - * Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + * Copyright 2000-2021 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.r.inspections +package org.jetbrains.r.inspections.classes.s4 import org.jetbrains.r.RBundle -import org.jetbrains.r.inspections.s4class.DeprecatedSetClassArgsInspection +import org.jetbrains.r.inspections.RInspectionTest class DeprecatedSetClassArgsInspectionTest : RInspectionTest() { diff --git a/test/org/jetbrains/r/inspections/InstanceOfVirtualS4ClassInspectionTest.kt b/test/org/jetbrains/r/inspections/classes/s4/InstanceOfVirtualS4ClassInspectionTest.kt similarity index 87% rename from test/org/jetbrains/r/inspections/InstanceOfVirtualS4ClassInspectionTest.kt rename to test/org/jetbrains/r/inspections/classes/s4/InstanceOfVirtualS4ClassInspectionTest.kt index a84dc8884..e56e07dab 100644 --- a/test/org/jetbrains/r/inspections/InstanceOfVirtualS4ClassInspectionTest.kt +++ b/test/org/jetbrains/r/inspections/classes/s4/InstanceOfVirtualS4ClassInspectionTest.kt @@ -1,11 +1,11 @@ /* - * Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + * Copyright 2000-2021 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.r.inspections +package org.jetbrains.r.inspections.classes.s4 import org.jetbrains.r.RBundle -import org.jetbrains.r.inspections.s4class.InstanceOfVirtualS4ClassInspection +import org.jetbrains.r.inspections.RInspectionTest class InstanceOfVirtualS4ClassInspectionTest : RInspectionTest() { diff --git a/test/org/jetbrains/r/rename/RRenameTest.kt b/test/org/jetbrains/r/rename/RRenameTest.kt index c27eda4c8..7ca6419b3 100644 --- a/test/org/jetbrains/r/rename/RRenameTest.kt +++ b/test/org/jetbrains/r/rename/RRenameTest.kt @@ -91,22 +91,41 @@ class RRenameTest : RLightCodeInsightFixtureTestCase() { fun testRenameDocumentationFunctionLink() = doTestWithProject("baz") - private fun doTestWithProject(newName: String, isInlineAvailable: Boolean = true, isRmd: Boolean = false, isSourceTest: Boolean = false) { + fun testRenameR6FieldFromUsage() = doTestWithProject("summary", isInOtherDirectory = true) + + fun testRenameR6MethodFromUsage() = doTestWithProject("additiveOperator", isInOtherDirectory = true) + + fun testRenameR6ActiveBindingFromUsage() = doTestWithProject("rnd", isInOtherDirectory = true) + + private fun doTestWithProject(newName: String, isInlineAvailable: Boolean = true, isRmd: Boolean = false, isSourceTest: Boolean = false, isInOtherDirectory: Boolean = false) { val dotFileExtension = getDotExtension(isRmd) lateinit var startFiles: List lateinit var endFiles: List - if (isSourceTest) { - addLibraries() - val files = File(myFixture.testDataPath + "/rename/" + getTestName(true)) - .listFiles() - ?.map { it.absolutePath.replace(myFixture.testDataPath, "") } - ?.sortedByDescending { it.contains("main") } ?: error("Cannot find root test directory") - startFiles = files.filter { !it.contains(".after.") } - endFiles = files - startFiles - myFixture.configureByFiles(*startFiles.toTypedArray()) - } - else { - myFixture.configureByFile("rename/" + getTestName(true) + dotFileExtension) + + when { + isInOtherDirectory -> { + addLibraries() + val files = File(myFixture.testDataPath + "/rename/").walkBottomUp() + .filter { it -> it.name.contains(getTestName(true)) } + .map { it.absolutePath.replace(myFixture.testDataPath, "") } + .sortedByDescending { it.contains("main") }.toList() ?: error("Cannot find root test directory") + startFiles = files.filter { !it.contains(".after.") } + endFiles = files - startFiles + myFixture.configureByFiles(*startFiles.toTypedArray()) + } + isSourceTest -> { + addLibraries() + val files = File(myFixture.testDataPath + "/rename/" + getTestName(true)) + .listFiles() + ?.map { it.absolutePath.replace(myFixture.testDataPath, "") } + ?.sortedByDescending { it.contains("main") } ?: error("Cannot find root test directory") + startFiles = files.filter { !it.contains(".after.") } + endFiles = files - startFiles + myFixture.configureByFiles(*startFiles.toTypedArray()) + } + else -> { + myFixture.configureByFile("rename/" + getTestName(true) + dotFileExtension) + } } val variableHandler = RVariableInplaceRenameHandler() val memberHandler = RMemberInplaceRenameHandler() @@ -125,7 +144,7 @@ class RRenameTest : RLightCodeInsightFixtureTestCase() { } CodeInsightTestUtil.doInlineRename(handler, newName, myFixture) - if (isSourceTest) { + if (isSourceTest || isInOtherDirectory) { if (endFiles.size != startFiles.size) error("Different number of start and end files") for (i in startFiles.indices) { myFixture.checkResultByFile(startFiles[i], endFiles[i], false) diff --git a/testData/rename/classes/R6/renameR6ActiveBindingFromUsage.R b/testData/rename/classes/R6/renameR6ActiveBindingFromUsage.R new file mode 100644 index 000000000..cc9cc9c09 --- /dev/null +++ b/testData/rename/classes/R6/renameR6ActiveBindingFromUsage.R @@ -0,0 +1,17 @@ +Accumulator <- R6Class("Accumulator", list( + summary = 0, + add = function(x = 1) { + self$summary <- self$summary + x + invisible(self) + }, + random = function(value) { + if (missing(value)) { + runif(1) + } else { + stop("Can't set `$random`", call. = FALSE) + } + }) +) + +x <- Accumulator$new() +x$random \ No newline at end of file diff --git a/testData/rename/classes/R6/renameR6ActiveBindingFromUsage.after.R b/testData/rename/classes/R6/renameR6ActiveBindingFromUsage.after.R new file mode 100644 index 000000000..368f402c8 --- /dev/null +++ b/testData/rename/classes/R6/renameR6ActiveBindingFromUsage.after.R @@ -0,0 +1,17 @@ +Accumulator <- R6Class("Accumulator", list( + summary = 0, + add = function(x = 1) { + self$summary <- self$summary + x + invisible(self) + }, + rnd = function(value) { + if (missing(value)) { + runif(1) + } else { + stop("Can't set `$random`", call. = FALSE) + } + }) +) + +x <- Accumulator$new() +x$rnd \ No newline at end of file diff --git a/testData/rename/classes/R6/renameR6FieldFromUsage.R b/testData/rename/classes/R6/renameR6FieldFromUsage.R new file mode 100644 index 000000000..5ed7f7f47 --- /dev/null +++ b/testData/rename/classes/R6/renameR6FieldFromUsage.R @@ -0,0 +1,11 @@ +Accumulator <- R6Class("Accumulator", list( + sum = 0, + add = function(x = 1) { + self$sum <- self$sum + x + invisible(self) + }) +) + +x <- Accumulator$new() +x$sum$add(4)$sum$add(4)$sum +x$sum diff --git a/testData/rename/classes/R6/renameR6FieldFromUsage.after.R b/testData/rename/classes/R6/renameR6FieldFromUsage.after.R new file mode 100644 index 000000000..a480bb3f3 --- /dev/null +++ b/testData/rename/classes/R6/renameR6FieldFromUsage.after.R @@ -0,0 +1,11 @@ +Accumulator <- R6Class("Accumulator", list( + summary = 0, + add = function(x = 1) { + self$summary <- self$summary + x + invisible(self) + }) +) + +x <- Accumulator$new() +x$summary$add(4)$summary$add(4)$summary +x$summary diff --git a/testData/rename/classes/R6/renameR6MethodFromUsage.R b/testData/rename/classes/R6/renameR6MethodFromUsage.R new file mode 100644 index 000000000..a457981fd --- /dev/null +++ b/testData/rename/classes/R6/renameR6MethodFromUsage.R @@ -0,0 +1,11 @@ +Accumulator <- R6Class("Accumulator", list( + sum = 0, + add = function(x = 1) { + self$sum <- self$sum + x + invisible(self) + }) +) + +x <- Accumulator$new() +x$add(4)$sum +x$sum diff --git a/testData/rename/classes/R6/renameR6MethodFromUsage.after.R b/testData/rename/classes/R6/renameR6MethodFromUsage.after.R new file mode 100644 index 000000000..eae41bb57 --- /dev/null +++ b/testData/rename/classes/R6/renameR6MethodFromUsage.after.R @@ -0,0 +1,11 @@ +Accumulator <- R6Class("Accumulator", list( + sum = 0, + additiveOperator = function(x = 1) { + self$sum <- self$sum + x + invisible(self) + }) +) + +x <- Accumulator$new() +x$additiveOperator(4)$sum +x$sum