Split some non applicability based inspections into isApplicable and apply

This is needed to fit new nj2k post-processing structure
This commit is contained in:
Ilya Kirillov
2019-09-17 22:32:36 +03:00
parent 3724e2bb02
commit a4cf7a912b
10 changed files with 423 additions and 316 deletions
@@ -40,37 +40,69 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import java.util.* import java.util.*
class CanBeValInspection @JvmOverloads constructor(val ignoreNotUsedVals: Boolean = true) : AbstractKotlinInspection() { class CanBeValInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object: KtVisitorVoid() { return object : KtVisitorVoid() {
private val pseudocodeCache = HashMap<KtDeclaration, Pseudocode>() private val pseudocodeCache = HashMap<KtDeclaration, Pseudocode>()
override fun visitDeclaration(declaration: KtDeclaration) { override fun visitDeclaration(declaration: KtDeclaration) {
super.visitDeclaration(declaration) super.visitDeclaration(declaration)
if (declaration is KtValVarKeywordOwner && canBeVal(declaration, pseudocodeCache, ignoreNotUsedVals = true)) {
reportCanBeVal(declaration)
}
}
private fun reportCanBeVal(declaration: KtValVarKeywordOwner) {
val keyword = declaration.valOrVarKeyword!!
val problemDescriptor = holder.manager.createProblemDescriptor(
keyword,
keyword,
"Variable is never modified and can be declared immutable using 'val'",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
isOnTheFly,
IntentionWrapper(ChangeVariableMutabilityFix(declaration, false), declaration.containingFile)
)
holder.registerProblem(problemDescriptor)
}
}
}
companion object {
fun canBeVal(
declaration: KtDeclaration,
pseudocodeCache: HashMap<KtDeclaration, Pseudocode> = HashMap(),
ignoreNotUsedVals: Boolean
): Boolean {
when (declaration) { when (declaration) {
is KtProperty -> { is KtProperty -> {
if (declaration.isVar && declaration.isLocal && !declaration.hasModifier(KtTokens.LATEINIT_KEYWORD) && if (declaration.isVar && declaration.isLocal && !declaration.hasModifier(KtTokens.LATEINIT_KEYWORD) &&
canBeVal(declaration, canBeVal(
declaration,
declaration.hasInitializer() || declaration.hasDelegateExpression(), declaration.hasInitializer() || declaration.hasDelegateExpression(),
listOf(declaration))) { listOf(declaration),
reportCanBeVal(declaration) ignoreNotUsedVals,
pseudocodeCache
)
) {
return true
} }
} }
is KtDestructuringDeclaration -> { is KtDestructuringDeclaration -> {
val entries = declaration.entries val entries = declaration.entries
if (declaration.isVar && entries.all { canBeVal(it, true, entries) }) { if (declaration.isVar && entries.all { canBeVal(it, true, entries, ignoreNotUsedVals, pseudocodeCache) }) {
reportCanBeVal(declaration) return true
} }
} }
} }
return false
} }
private fun canBeVal( private fun canBeVal(
declaration: KtVariableDeclaration, declaration: KtVariableDeclaration,
hasInitializerOrDelegate: Boolean, hasInitializerOrDelegate: Boolean,
allDeclarations: Collection<KtVariableDeclaration> allDeclarations: Collection<KtVariableDeclaration>,
ignoreNotUsedVals: Boolean,
pseudocodeCache: MutableMap<KtDeclaration, Pseudocode>
): Boolean { ): Boolean {
if (ignoreNotUsedVals && allDeclarations.all { ReferencesSearch.search(it, it.useScope).none() }) { if (ignoreNotUsedVals && allDeclarations.all { ReferencesSearch.search(it, it.useScope).none() }) {
// do not report for unused var's (otherwise we'll get it highlighted immediately after typing the declaration // do not report for unused var's (otherwise we'll get it highlighted immediately after typing the declaration
@@ -82,10 +114,9 @@ class CanBeValInspection @JvmOverloads constructor(val ignoreNotUsedVals: Boolea
(it as? KtSimpleNameReference)?.element?.readWriteAccess(useResolveForReadWrite = true)?.isWrite == true (it as? KtSimpleNameReference)?.element?.readWriteAccess(useResolveForReadWrite = true)?.isWrite == true
} }
!hasWriteUsages !hasWriteUsages
} } else {
else {
val bindingContext = declaration.analyze(BodyResolveMode.FULL) val bindingContext = declaration.analyze(BodyResolveMode.FULL)
val pseudocode = pseudocode(declaration, bindingContext) ?: return false val pseudocode = pseudocode(declaration, bindingContext, pseudocodeCache) ?: return false
val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] ?: return false val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] ?: return false
val writeInstructions = pseudocode.collectWriteInstructions(descriptor) val writeInstructions = pseudocode.collectWriteInstructions(descriptor)
@@ -95,13 +126,17 @@ class CanBeValInspection @JvmOverloads constructor(val ignoreNotUsedVals: Boolea
} }
} }
private fun pseudocode(element: KtElement, bindingContext: BindingContext): Pseudocode? { private fun pseudocode(
element: KtElement,
bindingContext: BindingContext,
pseudocodeCache: MutableMap<KtDeclaration, Pseudocode>
): Pseudocode? {
val declaration = element.containingDeclarationForPseudocode ?: return null val declaration = element.containingDeclarationForPseudocode ?: return null
return pseudocodeCache.getOrPut(declaration) { PseudocodeUtil.generatePseudocode(declaration, bindingContext) } return pseudocodeCache.getOrPut(declaration) { PseudocodeUtil.generatePseudocode(declaration, bindingContext) }
} }
private fun Pseudocode.collectWriteInstructions(descriptor: DeclarationDescriptor): Set<WriteValueInstruction> = private fun Pseudocode.collectWriteInstructions(descriptor: DeclarationDescriptor): Set<WriteValueInstruction> =
with (instructionsIncludingDeadCode) { with(instructionsIncludingDeadCode) {
filterIsInstance<WriteValueInstruction>() filterIsInstance<WriteValueInstruction>()
.asSequence() .asSequence()
.filter { (it.target as? AccessTarget.Call)?.resolvedCall?.resultingDescriptor == descriptor } .filter { (it.target as? AccessTarget.Call)?.resolvedCall?.resultingDescriptor == descriptor }
@@ -112,7 +147,11 @@ class CanBeValInspection @JvmOverloads constructor(val ignoreNotUsedVals: Boolea
.flatten() .flatten()
} }
private fun canReach(from: Instruction, targets: Set<Instruction>, visited: HashSet<Instruction> = HashSet<Instruction>()): Boolean { private fun canReach(
from: Instruction,
targets: Set<Instruction>,
visited: HashSet<Instruction> = HashSet<Instruction>()
): Boolean {
// special algorithm for linear code to avoid too deep recursion // special algorithm for linear code to avoid too deep recursion
var instruction = from var instruction = from
while (instruction is InstructionWithNext) { while (instruction is InstructionWithNext) {
@@ -134,19 +173,5 @@ class CanBeValInspection @JvmOverloads constructor(val ignoreNotUsedVals: Boolea
} }
return false return false
} }
private fun reportCanBeVal(declaration: KtValVarKeywordOwner) {
val keyword = declaration.valOrVarKeyword!!
val problemDescriptor = holder.manager.createProblemDescriptor(
keyword,
keyword,
"Variable is never modified and can be declared immutable using 'val'",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
isOnTheFly,
IntentionWrapper(ChangeVariableMutabilityFix(declaration, false), declaration.containingFile)
)
holder.registerProblem(problemDescriptor)
}
}
} }
} }
@@ -21,13 +21,11 @@ import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemHighlightType.LIKE_UNUSED_SYMBOL import com.intellij.codeInspection.ProblemHighlightType.LIKE_UNUSED_SYMBOL
import com.intellij.codeInspection.ProblemsHolder import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.config.LanguageFeature.CallableReferencesToClassMembersWithEmptyLHS
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.intentions.getCallableDescriptor import org.jetbrains.kotlin.idea.intentions.getCallableDescriptor
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.* import org.jetbrains.kotlin.idea.util.*
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
@@ -38,30 +36,39 @@ import org.jetbrains.kotlin.synthetic.SyntheticJavaPropertyDescriptor
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
class ExplicitThisInspection : AbstractKotlinInspection() { class ExplicitThisInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : KtVisitorVoid() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : KtVisitorVoid() {
override fun visitCallableReferenceExpression(expression: KtCallableReferenceExpression) { override fun visitExpression(expression: KtExpression) {
if (!expression.languageVersionSettings.supportsFeature(CallableReferencesToClassMembersWithEmptyLHS)) return val thisExpression = expression.thisAsReceiverOrNull() ?: return
if (hasExplicitThis(expression)) {
val thisExpression = expression.receiverExpression as? KtThisExpression ?: return holder.registerProblem(
val selectorExpression = expression.callableReference thisExpression,
"Redundant explicit this",
handle(expression, thisExpression, selectorExpression) LIKE_UNUSED_SYMBOL,
ExplicitThisExpressionFix(thisExpression.text)
)
}
}
} }
override fun visitDotQualifiedExpression(expression: KtDotQualifiedExpression) { companion object {
val thisExpression = expression.receiverExpression as? KtThisExpression ?: return fun KtExpression.thisAsReceiverOrNull() = when (this) {
val selectorExpression = expression.selectorExpression as? KtReferenceExpression ?: return is KtCallableReferenceExpression -> receiverExpression as? KtThisExpression
is KtDotQualifiedExpression -> receiverExpression as? KtThisExpression
handle(expression, thisExpression, selectorExpression) else -> null
} }
private fun handle(expression: KtExpression, thisExpression: KtThisExpression, reference: KtReferenceExpression) { fun hasExplicitThis(expression: KtExpression): Boolean {
val thisExpression = expression.thisAsReceiverOrNull() ?: return false
val reference = when (expression) {
is KtCallableReferenceExpression -> expression.callableReference
is KtDotQualifiedExpression -> expression.selectorExpression as? KtReferenceExpression
else -> null
} ?: return false
val context = expression.analyze() val context = expression.analyze()
val scope = expression.getResolutionScope(context) ?: return val scope = expression.getResolutionScope(context) ?: return false
val referenceExpression = reference as? KtNameReferenceExpression ?: reference.getChildOfType() ?: return val referenceExpression = reference as? KtNameReferenceExpression ?: reference.getChildOfType() ?: return false
val receiverType = context[BindingContext.EXPRESSION_TYPE_INFO, thisExpression]?.type ?: return val receiverType = context[BindingContext.EXPRESSION_TYPE_INFO, thisExpression]?.type ?: return false
//we avoid overload-related problems by enforcing that there is only one candidate //we avoid overload-related problems by enforcing that there is only one candidate
val name = referenceExpression.getReferencedNameAsName() val name = referenceExpression.getReferencedNameAsName()
@@ -74,19 +81,18 @@ class ExplicitThisInspection : AbstractKotlinInspection() {
scope.getAllAccessibleVariables(name) scope.getAllAccessibleVariables(name)
} }
if (referenceExpression.getCallableDescriptor() is SyntheticJavaPropertyDescriptor) { if (referenceExpression.getCallableDescriptor() is SyntheticJavaPropertyDescriptor) {
if (candidates.map { it.containingDeclaration }.distinct().size != 1) return if (candidates.map { it.containingDeclaration }.distinct().size != 1) return false
} else { } else {
val candidate = candidates.singleOrNull() ?: return val candidate = candidates.singleOrNull() ?: return false
val extensionType = candidate.extensionReceiverParameter?.type val extensionType = candidate.extensionReceiverParameter?.type
if (extensionType != null && extensionType != receiverType && receiverType.isSubtypeOf(extensionType)) return if (extensionType != null && extensionType != receiverType && receiverType.isSubtypeOf(extensionType)) return false
} }
val expressionFactory = scope.getFactoryForImplicitReceiverWithSubtypeOf(receiverType) ?: return val expressionFactory = scope.getFactoryForImplicitReceiverWithSubtypeOf(receiverType) ?: return false
val label = thisExpression.getLabelName() ?: "" val label = thisExpression.getLabelName() ?: ""
if (!expressionFactory.matchesLabel(label)) return if (!expressionFactory.matchesLabel(label)) return false
return true
holder.registerProblem(thisExpression, "Redundant explicit this", LIKE_UNUSED_SYMBOL, Fix(thisExpression.text))
} }
private fun VariableDescriptor.canInvoke(): Boolean { private fun VariableDescriptor.canInvoke(): Boolean {
@@ -99,12 +105,18 @@ class ExplicitThisInspection : AbstractKotlinInspection() {
return label == implicitLabel || (label == "" && isImmediate) return label == implicitLabel || (label == "" && isImmediate)
} }
} }
}
private class Fix(private val text: String) : LocalQuickFix { class ExplicitThisExpressionFix(private val text: String) : LocalQuickFix {
override fun getFamilyName(): String = "Remove redundant '$text'" override fun getFamilyName(): String = "Remove redundant '$text'"
override fun applyFix(project: Project, descriptor: ProblemDescriptor) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val thisExpression = descriptor.psiElement as? KtThisExpression ?: return val thisExpression = descriptor.psiElement as? KtThisExpression ?: return
removeExplicitThisExpression(thisExpression)
}
companion object {
fun removeExplicitThisExpression(thisExpression: KtThisExpression) {
when (val parent = thisExpression.parent) { when (val parent = thisExpression.parent) {
is KtDotQualifiedExpression -> parent.replace(parent.selectorExpression ?: return) is KtDotQualifiedExpression -> parent.replace(parent.selectorExpression ?: return)
is KtCallableReferenceExpression -> thisExpression.delete() is KtCallableReferenceExpression -> thisExpression.delete()
@@ -112,3 +124,4 @@ class ExplicitThisInspection : AbstractKotlinInspection() {
} }
} }
} }
@@ -33,25 +33,20 @@ class LiftReturnOrAssignmentInspection @JvmOverloads constructor(private val ski
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession) = override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession) =
object : KtVisitorVoid() { object : KtVisitorVoid() {
private fun visitIfOrWhenOrTry(expression: KtExpression, keyword: PsiElement) { override fun visitExpression(expression: KtExpression) {
if (skipLongExpressions && expression.lineCount() > LINES_LIMIT) return val states = getState(expression, skipLongExpressions) ?: return
if (expression.isElseIf()) return states.forEach { state ->
registerProblem(
val foldableReturns = BranchedFoldingUtils.getFoldableReturns(expression) expression,
if (foldableReturns?.isNotEmpty() == true) { state.keyword,
val hasOtherReturns = expression.anyDescendantOfType<KtReturnExpression> { it !in foldableReturns } state.isSerious,
val isSerious = !hasOtherReturns && foldableReturns.size > 1 when (state.liftType) {
registerProblem(expression, keyword, isSerious, LiftReturnOutFix(keyword.text)) LiftType.LIFT_RETURN_OUT -> LiftReturnOutFix(state.keyword.text)
foldableReturns.forEach { LiftType.LIFT_ASSIGNMENT_OUT -> LiftAssignmentOutFix(state.keyword.text)
registerProblem(expression, keyword, isSerious, LiftReturnOutFix(keyword.text), it, INFORMATION) },
} state.highlightElement,
return state.highlightType
} )
val assignmentNumber = BranchedFoldingUtils.getFoldableAssignmentNumber(expression)
if (assignmentNumber > 0) {
val isSerious = assignmentNumber > 1
registerProblem(expression, keyword, isSerious, LiftAssignmentOutFix(keyword.text))
} }
} }
@@ -75,22 +70,6 @@ class LiftReturnOrAssignmentInspection @JvmOverloads constructor(private val ski
) )
} }
override fun visitIfExpression(expression: KtIfExpression) {
super.visitIfExpression(expression)
visitIfOrWhenOrTry(expression, expression.ifKeyword)
}
override fun visitWhenExpression(expression: KtWhenExpression) {
super.visitWhenExpression(expression)
visitIfOrWhenOrTry(expression, expression.whenKeyword)
}
override fun visitTryExpression(expression: KtTryExpression) {
super.visitTryExpression(expression)
expression.tryKeyword?.let {
visitIfOrWhenOrTry(expression, it)
}
}
} }
private class LiftReturnOutFix(private val keyword: String) : LocalQuickFix { private class LiftReturnOutFix(private val keyword: String) : LocalQuickFix {
@@ -116,5 +95,51 @@ class LiftReturnOrAssignmentInspection @JvmOverloads constructor(private val ski
companion object { companion object {
private const val LINES_LIMIT = 15 private const val LINES_LIMIT = 15
fun getState(expression: KtExpression, skipLongExpressions: Boolean) = when (expression) {
is KtWhenExpression -> getStateForWhenOrTry(expression, expression.whenKeyword, skipLongExpressions)
is KtIfExpression -> getStateForWhenOrTry(expression, expression.ifKeyword, skipLongExpressions)
is KtTryExpression -> expression.tryKeyword?.let {
getStateForWhenOrTry(expression, it, skipLongExpressions)
}
else -> null
}
private fun getStateForWhenOrTry(
expression: KtExpression,
keyword: PsiElement,
skipLongExpressions: Boolean
): List<LiftState>? {
if (skipLongExpressions && expression.lineCount() > LINES_LIMIT) return null
if (expression.isElseIf()) return null
val foldableReturns = BranchedFoldingUtils.getFoldableReturns(expression)
if (foldableReturns?.isNotEmpty() == true) {
val hasOtherReturns = expression.anyDescendantOfType<KtReturnExpression> { it !in foldableReturns }
val isSerious = !hasOtherReturns && foldableReturns.size > 1
return foldableReturns.map {
LiftState(keyword, isSerious, LiftType.LIFT_RETURN_OUT, it, INFORMATION)
} + LiftState(keyword, isSerious, LiftType.LIFT_RETURN_OUT)
}
val assignmentNumber = BranchedFoldingUtils.getFoldableAssignmentNumber(expression)
if (assignmentNumber > 0) {
val isSerious = assignmentNumber > 1
return listOf(LiftState(keyword, isSerious, LiftType.LIFT_ASSIGNMENT_OUT))
}
return null
}
enum class LiftType {
LIFT_RETURN_OUT, LIFT_ASSIGNMENT_OUT
}
data class LiftState(
val keyword: PsiElement,
val isSerious: Boolean,
val liftType: LiftType,
val highlightElement: PsiElement = keyword,
val highlightType: ProblemHighlightType = if (isSerious) GENERIC_ERROR_OR_WARNING else INFORMATION
)
} }
} }
@@ -33,54 +33,61 @@ import org.jetbrains.kotlin.resolve.scopes.utils.findVariable
class RedundantCompanionReferenceInspection : AbstractKotlinInspection() { class RedundantCompanionReferenceInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return referenceExpressionVisitor(fun(expression) { return referenceExpressionVisitor(fun(expression) {
val parent = expression.parent as? KtDotQualifiedExpression ?: return if (isRedundantCompanionReference(expression)) {
val selectorExpression = parent.selectorExpression
if (expression == selectorExpression && parent.parent !is KtDotQualifiedExpression) return
if (parent.getStrictParentOfType<KtImportDirective>() != null) return
val objectDeclaration = expression.mainReference.resolve() as? KtObjectDeclaration ?: return
if (!objectDeclaration.isCompanion()) return
if (expression.text != objectDeclaration.name) return
val context = expression.analyze()
val containingClass = objectDeclaration.containingClass() ?: return
if (expression.containingClass() != containingClass && expression == parent.receiverExpression) return
val containingClassDescriptor = containingClass.descriptor as? ClassDescriptor ?: return
val selectorDescriptor = selectorExpression?.getResolvedCall(context)?.resultingDescriptor
when (selectorDescriptor) {
is PropertyDescriptor -> {
val name = selectorDescriptor.name
if (containingClassDescriptor.findMemberVariable(name) != null) return
val variable = expression.getResolutionScope().findVariable(name, NoLookupLocation.FROM_IDE)
if (variable != null && variable.isLocalOrExtension(containingClassDescriptor)) return
}
is FunctionDescriptor -> {
val name = selectorDescriptor.name
if (containingClassDescriptor.findMemberFunction(name) != null) return
val function = expression.getResolutionScope().findFunction(name, NoLookupLocation.FROM_IDE)
if (function != null && function.isLocalOrExtension(containingClassDescriptor)) return
}
}
(expression as? KtSimpleNameExpression)?.getReceiverExpression()?.getQualifiedElementSelector()
?.mainReference?.resolveToDescriptors(context)?.firstOrNull()
?.let { if (it != containingClassDescriptor) return }
val grandParent = parent.parent as? KtQualifiedExpression
if (grandParent != null) {
val grandParentDescriptor = grandParent.getResolvedCall(context)?.resultingDescriptor ?: return
if (grandParentDescriptor is ConstructorDescriptor || grandParentDescriptor is FakeCallableDescriptorForObject) return
}
holder.registerProblem( holder.registerProblem(
expression, expression,
"Redundant Companion reference", "Redundant Companion reference",
ProblemHighlightType.LIKE_UNUSED_SYMBOL, ProblemHighlightType.LIKE_UNUSED_SYMBOL,
RemoveRedundantCompanionReferenceFix() RemoveRedundantCompanionReferenceFix()
) )
}
}) })
} }
companion object {
fun isRedundantCompanionReference(reference: KtReferenceExpression): Boolean {
val parent = reference.parent as? KtDotQualifiedExpression ?: return false
val selectorExpression = parent.selectorExpression
if (reference == selectorExpression && parent.parent !is KtDotQualifiedExpression) return false
if (parent.getStrictParentOfType<KtImportDirective>() != null) return false
val objectDeclaration = reference.mainReference.resolve() as? KtObjectDeclaration ?: return false
if (!objectDeclaration.isCompanion()) return false
if (reference.text != objectDeclaration.name) return false
val context = reference.analyze()
val containingClass = objectDeclaration.containingClass() ?: return false
if (reference.containingClass() != containingClass && reference == parent.receiverExpression) return false
val containingClassDescriptor = containingClass.descriptor as? ClassDescriptor ?: return false
val selectorDescriptor = selectorExpression?.getResolvedCall(context)?.resultingDescriptor
when (selectorDescriptor) {
is PropertyDescriptor -> {
val name = selectorDescriptor.name
if (containingClassDescriptor.findMemberVariable(name) != null) return false
val variable = reference.getResolutionScope().findVariable(name, NoLookupLocation.FROM_IDE)
if (variable != null && variable.isLocalOrExtension(containingClassDescriptor)) return false
}
is FunctionDescriptor -> {
val name = selectorDescriptor.name
if (containingClassDescriptor.findMemberFunction(name) != null) return false
val function = reference.getResolutionScope().findFunction(name, NoLookupLocation.FROM_IDE)
if (function != null && function.isLocalOrExtension(containingClassDescriptor)) return false
}
}
(reference as? KtSimpleNameExpression)?.getReceiverExpression()?.getQualifiedElementSelector()
?.mainReference?.resolveToDescriptors(context)?.firstOrNull()
?.let { if (it != containingClassDescriptor) return false }
val grandParent = parent.parent as? KtQualifiedExpression
if (grandParent != null) {
val grandParentDescriptor = grandParent.getResolvedCall(context)?.resultingDescriptor ?: return false
if (grandParentDescriptor is ConstructorDescriptor || grandParentDescriptor is FakeCallableDescriptorForObject) return false
}
return true
}
}
} }
private fun <D : MemberDescriptor> ClassDescriptor.findMemberByName(name: Name, find: ClassDescriptor.(Name) -> D?): D? { private fun <D : MemberDescriptor> ClassDescriptor.findMemberByName(name: Name, find: ClassDescriptor.(Name) -> D?): D? {
@@ -111,16 +118,22 @@ private fun CallableDescriptor.isLocalOrExtension(extensionClassDescriptor: Clas
extensionReceiverParameter?.type?.constructor?.declarationDescriptor == extensionClassDescriptor extensionReceiverParameter?.type?.constructor?.declarationDescriptor == extensionClassDescriptor
} }
private class RemoveRedundantCompanionReferenceFix : LocalQuickFix { class RemoveRedundantCompanionReferenceFix : LocalQuickFix {
override fun getName() = "Remove redundant Companion reference" override fun getName() = "Remove redundant Companion reference"
override fun getFamilyName() = name override fun getFamilyName() = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val expression = descriptor.psiElement as? KtReferenceExpression ?: return val expression = descriptor.psiElement as? KtReferenceExpression ?: return
removeRedundantCompanionReference(expression)
}
companion object {
fun removeRedundantCompanionReference(expression: KtReferenceExpression) {
val parent = expression.parent as? KtDotQualifiedExpression ?: return val parent = expression.parent as? KtDotQualifiedExpression ?: return
val selector = parent.selectorExpression ?: return val selector = parent.selectorExpression ?: return
val receiver = parent.receiverExpression val receiver = parent.receiverExpression
if (expression == receiver) parent.replace(selector) else parent.replace(receiver) if (expression == receiver) parent.replace(selector) else parent.replace(receiver)
} }
}
} }
@@ -18,55 +18,63 @@ import org.jetbrains.kotlin.types.AbbreviatedType
class RedundantExplicitTypeInspection : AbstractKotlinInspection() { class RedundantExplicitTypeInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) =
propertyVisitor(fun(property) { propertyVisitor(fun(property) {
if (!property.isLocal) return
val typeReference = property.typeReference ?: return val typeReference = property.typeReference ?: return
val initializer = property.initializer ?: return if (hasRedundantType(property)) {
val type = property.resolveToDescriptorIfAny()?.type ?: return
if (type is AbbreviatedType) return
when (initializer) {
is KtConstantExpression -> {
when (initializer.node.elementType) {
KtNodeTypes.BOOLEAN_CONSTANT -> {
if (!KotlinBuiltIns.isBoolean(type)) return
}
KtNodeTypes.INTEGER_CONSTANT -> {
if (initializer.text.endsWith("L")) {
if (!KotlinBuiltIns.isLong(type)) return
} else {
if (!KotlinBuiltIns.isInt(type)) return
}
}
KtNodeTypes.FLOAT_CONSTANT -> {
if (initializer.text.endsWith("f") || initializer.text.endsWith("F")) {
if (!KotlinBuiltIns.isFloat(type)) return
} else {
if (!KotlinBuiltIns.isDouble(type)) return
}
}
KtNodeTypes.CHARACTER_CONSTANT -> {
if (!KotlinBuiltIns.isChar(type)) return
}
else -> return
}
}
is KtStringTemplateExpression -> {
if (!KotlinBuiltIns.isString(type)) return
}
is KtNameReferenceExpression -> {
if (typeReference.text != initializer.getReferencedName()) return
}
is KtCallExpression -> {
if (typeReference.text != initializer.calleeExpression?.text) return
}
else -> return
}
holder.registerProblem( holder.registerProblem(
typeReference, typeReference,
"Explicitly given type is redundant here", "Explicitly given type is redundant here",
ProblemHighlightType.LIKE_UNUSED_SYMBOL, ProblemHighlightType.LIKE_UNUSED_SYMBOL,
IntentionWrapper(RemoveExplicitTypeIntention(), property.containingKtFile) IntentionWrapper(RemoveExplicitTypeIntention(), property.containingKtFile)
) )
}
}) })
companion object {
fun hasRedundantType(property: KtProperty): Boolean {
if (!property.isLocal) return false
val typeReference = property.typeReference ?: return false
val initializer = property.initializer ?: return false
val type = property.resolveToDescriptorIfAny()?.type ?: return false
if (type is AbbreviatedType) return false
when (initializer) {
is KtConstantExpression -> {
when (initializer.node.elementType) {
KtNodeTypes.BOOLEAN_CONSTANT -> {
if (!KotlinBuiltIns.isBoolean(type)) return false
}
KtNodeTypes.INTEGER_CONSTANT -> {
if (initializer.text.endsWith("L")) {
if (!KotlinBuiltIns.isLong(type)) return false
} else {
if (!KotlinBuiltIns.isInt(type)) return false
}
}
KtNodeTypes.FLOAT_CONSTANT -> {
if (initializer.text.endsWith("f") || initializer.text.endsWith("F")) {
if (!KotlinBuiltIns.isFloat(type)) return false
} else {
if (!KotlinBuiltIns.isDouble(type)) return false
}
}
KtNodeTypes.CHARACTER_CONSTANT -> {
if (!KotlinBuiltIns.isChar(type)) return false
}
else -> return false
}
}
is KtStringTemplateExpression -> {
if (!KotlinBuiltIns.isString(type)) return false
}
is KtNameReferenceExpression -> {
if (typeReference.text != initializer.getReferencedName()) return false
}
is KtCallExpression -> {
if (typeReference.text != initializer.calleeExpression?.text) return false
}
else -> return false
}
return true
}
}
} }
@@ -31,7 +31,7 @@ class RedundantGetterInspection : AbstractKotlinInspection(), CleanupLocalInspec
} }
} }
private fun KtPropertyAccessor.isRedundantGetter(): Boolean { fun KtPropertyAccessor.isRedundantGetter(): Boolean {
if (!isGetter) return false if (!isGetter) return false
val expression = bodyExpression ?: return canBeCompletelyDeleted() val expression = bodyExpression ?: return canBeCompletelyDeleted()
if (expression.isBackingFieldReferenceTo(property)) return true if (expression.isBackingFieldReferenceTo(property)) return true
@@ -61,23 +61,28 @@ fun KtPropertyAccessor.deleteBody() {
deleteChildRange(leftParenthesis, lastChild) deleteChildRange(leftParenthesis, lastChild)
} }
private class RemoveRedundantGetterFix : LocalQuickFix { class RemoveRedundantGetterFix : LocalQuickFix {
override fun getName() = "Remove redundant getter" override fun getName() = "Remove redundant getter"
override fun getFamilyName() = name override fun getFamilyName() = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val accessor = descriptor.psiElement as? KtPropertyAccessor ?: return val accessor = descriptor.psiElement as? KtPropertyAccessor ?: return
val property = accessor.property removeRedundantGetter(accessor)
}
val accessorTypeReference = accessor.returnTypeReference companion object {
fun removeRedundantGetter(getter: KtPropertyAccessor) {
val property = getter.property
val accessorTypeReference = getter.returnTypeReference
if (accessorTypeReference != null && property.typeReference == null && property.initializer == null) { if (accessorTypeReference != null && property.typeReference == null && property.initializer == null) {
property.typeReference = accessorTypeReference property.typeReference = accessorTypeReference
} }
if (accessor.canBeCompletelyDeleted()) { if (getter.canBeCompletelyDeleted()) {
accessor.delete() getter.delete()
} else { } else {
accessor.deleteBody() getter.deleteBody()
}
} }
} }
} }
@@ -37,7 +37,7 @@ class RedundantSemicolonInspection : AbstractKotlinInspection(), CleanupLocalIns
override fun visitElement(element: PsiElement) { override fun visitElement(element: PsiElement) {
super.visitElement(element) super.visitElement(element)
if (element.node.elementType == KtTokens.SEMICOLON && isRedundant(element)) { if (element.node.elementType == KtTokens.SEMICOLON && isRedundantSemicolon(element)) {
holder.registerProblem( holder.registerProblem(
element, element,
"Redundant semicolon", "Redundant semicolon",
@@ -49,7 +49,8 @@ class RedundantSemicolonInspection : AbstractKotlinInspection(), CleanupLocalIns
} }
} }
private fun isRedundant(semicolon: PsiElement): Boolean { companion object {
fun isRedundantSemicolon(semicolon: PsiElement): Boolean {
val nextLeaf = semicolon.nextLeaf { it !is PsiWhiteSpace && it !is PsiComment || it.isLineBreak() } val nextLeaf = semicolon.nextLeaf { it !is PsiWhiteSpace && it !is PsiComment || it.isLineBreak() }
val isAtEndOfLine = nextLeaf == null || nextLeaf.isLineBreak() val isAtEndOfLine = nextLeaf == null || nextLeaf.isLineBreak()
if (!isAtEndOfLine) { if (!isAtEndOfLine) {
@@ -129,4 +130,5 @@ class RedundantSemicolonInspection : AbstractKotlinInspection(), CleanupLocalIns
} }
private val softModifierKeywords = KtTokens.SOFT_KEYWORDS.types.mapNotNull { (it as? KtModifierKeywordToken)?.toString() } private val softModifierKeywords = KtTokens.SOFT_KEYWORDS.types.mapNotNull { (it as? KtModifierKeywordToken)?.toString() }
}
} }
@@ -30,7 +30,7 @@ class RedundantSetterInspection : AbstractKotlinInspection(), CleanupLocalInspec
} }
} }
private fun KtPropertyAccessor.isRedundantSetter(): Boolean { fun KtPropertyAccessor.isRedundantSetter(): Boolean {
if (!isSetter) return false if (!isSetter) return false
val expression = bodyExpression ?: return canBeCompletelyDeleted() val expression = bodyExpression ?: return canBeCompletelyDeleted()
if (expression is KtBlockExpression) { if (expression is KtBlockExpression) {
@@ -45,18 +45,23 @@ private fun KtPropertyAccessor.isRedundantSetter(): Boolean {
} }
private class RemoveRedundantSetterFix : LocalQuickFix { class RemoveRedundantSetterFix : LocalQuickFix {
override fun getName() = "Remove redundant setter" override fun getName() = "Remove redundant setter"
override fun getFamilyName() = name override fun getFamilyName() = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) { override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val accessor = descriptor.psiElement as? KtPropertyAccessor ?: return val accessor = descriptor.psiElement as? KtPropertyAccessor ?: return
removeRedundantSetter(accessor)
}
if (accessor.canBeCompletelyDeleted()) { companion object {
accessor.delete() fun removeRedundantSetter(setter: KtPropertyAccessor) {
if (setter.canBeCompletelyDeleted()) {
setter.delete()
} else { } else {
accessor.deleteBody() setter.deleteBody()
}
} }
} }
} }
@@ -13,6 +13,7 @@ import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.intentions.RemoveExplicitTypeIntention import org.jetbrains.kotlin.idea.intentions.RemoveExplicitTypeIntention
import org.jetbrains.kotlin.psi.KtCodeFragment import org.jetbrains.kotlin.psi.KtCodeFragment
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.namedFunctionVisitor import org.jetbrains.kotlin.psi.namedFunctionVisitor
import org.jetbrains.kotlin.types.typeUtil.isUnit import org.jetbrains.kotlin.types.typeUtil.isUnit
@@ -21,17 +22,23 @@ class RedundantUnitReturnTypeInspection : AbstractKotlinInspection(), CleanupLoc
return namedFunctionVisitor(fun(function) { return namedFunctionVisitor(fun(function) {
if (function.containingFile is KtCodeFragment) return if (function.containingFile is KtCodeFragment) return
val typeElement = function.typeReference?.typeElement ?: return val typeElement = function.typeReference?.typeElement ?: return
val descriptor = function.resolveToDescriptorIfAny() ?: return if (hasRedundantUnitReturnType(function)) {
if (descriptor.returnType?.isUnit() == true) { holder.registerProblem(
if (!function.hasBlockBody()) { typeElement,
return
}
holder.registerProblem(typeElement,
"Redundant 'Unit' return type", "Redundant 'Unit' return type",
ProblemHighlightType.LIKE_UNUSED_SYMBOL, ProblemHighlightType.LIKE_UNUSED_SYMBOL,
IntentionWrapper(RemoveExplicitTypeIntention(), function.containingKtFile)) IntentionWrapper(RemoveExplicitTypeIntention(), function.containingKtFile)
)
} }
}) })
} }
companion object {
fun hasRedundantUnitReturnType(function: KtNamedFunction): Boolean {
if (!function.hasBlockBody()) return false
if (function.typeReference?.typeElement == null) return false
val descriptor = function.resolveToDescriptorIfAny() ?: return false
return descriptor.returnType?.isUnit() == true
}
}
} }
@@ -38,13 +38,17 @@ class RemoveExplicitTypeIntention : SelfTargetingRangeIntention<KtCallableDeclar
} }
override fun applyTo(element: KtCallableDeclaration, editor: Editor?) { override fun applyTo(element: KtCallableDeclaration, editor: Editor?) {
removeExplicitType(element)
}
companion object {
fun removeExplicitType(element: KtCallableDeclaration) {
val initializer = (element as? KtProperty)?.initializer val initializer = (element as? KtProperty)?.initializer
val typeArgumentList = initializer?.let { getQualifiedTypeArgumentList(it) } val typeArgumentList = initializer?.let { getQualifiedTypeArgumentList(it) }
element.typeReference = null element.typeReference = null
if (typeArgumentList != null) addTypeArgumentsIfNeeded(initializer, typeArgumentList) if (typeArgumentList != null) addTypeArgumentsIfNeeded(initializer, typeArgumentList)
} }
companion object {
fun getRange(element: KtCallableDeclaration): TextRange? { fun getRange(element: KtCallableDeclaration): TextRange? {
if (element.containingFile is KtCodeFragment) return null if (element.containingFile is KtCodeFragment) return null
val typeReference = element.typeReference ?: return null val typeReference = element.typeReference ?: return null