Moved Kotlin/JS inspection pack features into the main IDEA Kotlin plugin.

Inspections formerly implemented here: https://github.com/Kotlin/kotlin-js-inspection-pack-plugin and previously published as https://plugins.jetbrains.com/plugin/17183-kotlin-js-inspection-pack are now in the main plugin.

^KTIJ-23610 Fixed

GitOrigin-RevId: 58fc1f53ebc5b9a03527da03ce1b3d6aa585cbeb
This commit is contained in:
Alexander Chernikov
2022-11-14 21:27:08 +00:00
committed by intellij-monorepo-bot
parent 1b64049311
commit cef87386a3
6 changed files with 192 additions and 1 deletions
@@ -2532,3 +2532,14 @@ inspection.replace.with.string.builder.append.range.display.name='StringBuilder.
progress.title.collect.hierarchy=Collect ''{0}'' hierarchy ...
add.fun.modifier.to.0=Add ''fun'' modifier to ''{0}''
convert.to.nullable.type=Convert to nullable type
add.external.keyword=Add external keyword
interface.should.be.external=Interface should be external
class.should.be.external.interface=Class should be external interface
object.should.be.external.interface=Object should be external interface
boolean.property.in.external.interface.should.be.nullable=Boolean property in external interface should be nullable
property.in.external.interface.should.be.var=Property in external interface should be var
external.interface.contains.val.property.name=External interface contains val property
external.interface.contains.non.nullable.property.name=External interface contains non-nullable boolean property
non.external.classifier.extending.state.or.props.name=Non-external classifier extending State or Props
@@ -0,0 +1,7 @@
<html>
<body>
<p>
Reports non-external classifier extending State or Props. Read more in the <a href="https://kotlinlang.org/docs/js-ir-migration.html#convert-js-and-react-related-classes-and-interfaces-to-external-interfaces">migration guide</a>.
</p>
</body>
</html>
@@ -0,0 +1,7 @@
<html>
<body>
<p>
Reports non-nullable boolean properties in external interface. Read more in the <a href="https://kotlinlang.org/docs/js-ir-migration.html#make-boolean-properties-nullable-in-external-interfaces">migration guide</a>.
</p>
</body>
</html>
@@ -0,0 +1,7 @@
<html>
<body>
<p>
Reports not var properties in external interface. Read more in the <a href="https://kotlinlang.org/docs/js-ir-migration.html#convert-properties-of-external-interfaces-to-var">migration guide</a>.
</p>
</body>
</html>
@@ -0,0 +1,135 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.inspections
import com.intellij.codeInspection.IntentionWrapper
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.base.resources.KotlinBundle
import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.idea.codeinsights.impl.base.quickFix.ChangeVariableMutabilityFix
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.idea.core.setType
import org.jetbrains.kotlin.idea.base.facet.platform.platform
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.js.isJs
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
import org.jetbrains.kotlin.types.typeUtil.isBoolean
import org.jetbrains.kotlin.types.typeUtil.makeNullable
class NonExternalClassifierExtendingStateOrPropsInspection : AbstractKotlinInspection() {
override fun buildVisitor(
holder: ProblemsHolder, isOnTheFly: Boolean,
): PsiElementVisitor = classOrObjectVisitor { classOrObject ->
if (classOrObject.platform.isJs()) {
val classDescriptor = classOrObject.descriptor as? ClassDescriptor ?: return@classOrObjectVisitor
if (classDescriptor.implementsRProps || classDescriptor.implementsRState) {
if (classOrObject is KtClass) {
if (classOrObject.isInterface() && !classDescriptor.isExternal) {
val nameIdentifier = classOrObject.nameIdentifier ?: return@classOrObjectVisitor
holder.registerProblem(nameIdentifier,
KotlinBundle.message("interface.should.be.external"),
AddExternalQuickFix)
} else {
val classKeyword = classOrObject.getClassKeyword() ?: return@classOrObjectVisitor
holder.registerProblem(classKeyword,
KotlinBundle.message("class.should.be.external.interface"))
}
}
if (classOrObject is KtObjectDeclaration) {
val objectKeyword = classOrObject.getObjectKeyword() ?: return@classOrObjectVisitor
holder.registerProblem(objectKeyword,
KotlinBundle.message("object.should.be.external.interface"))
}
}
}
}
}
class NonNullableBooleanPropertyInExternalInterfaceInspection : AbstractKotlinInspection() {
override fun buildVisitor(
holder: ProblemsHolder,
isOnTheFly: Boolean,
): PsiElementVisitor = declarationVisitor { declaration ->
if (!declaration.platform.isJs()) return@declarationVisitor
val parent = declaration.containingClassOrObject as? KtClass ?: return@declarationVisitor
val property = declaration as? KtProperty ?: return@declarationVisitor
val type = property.type() ?: return@declarationVisitor
if (parent.isInterface() && parent.hasModifier(KtTokens.EXTERNAL_KEYWORD) && type.isBoolean() && !type.isMarkedNullable) {
holder.registerProblem(property,
KotlinBundle.message("boolean.property.in.external.interface.should.be.nullable"),
ConvertToNullableTypeFix())
}
}
}
class NonVarPropertyInExternalInterfaceInspection : AbstractKotlinInspection() {
override fun buildVisitor(
holder: ProblemsHolder,
isOnTheFly: Boolean,
): PsiElementVisitor = declarationVisitor { declaration ->
if (!declaration.platform.isJs()) return@declarationVisitor
val parent = declaration.containingClassOrObject as? KtClass ?: return@declarationVisitor
val property = declaration as? KtProperty ?: return@declarationVisitor
val parentClassDescriptor = parent.descriptor as? ClassDescriptor ?: return@declarationVisitor
val parentImplementsReactStateOrProps =
parentClassDescriptor.implementsRProps || parentClassDescriptor.implementsRState
if (parent.isInterface() && parent.hasModifier(KtTokens.EXTERNAL_KEYWORD) && parentImplementsReactStateOrProps && !property.isVar) {
holder.registerProblem(
property.valOrVarKeyword,
KotlinBundle.message("property.in.external.interface.should.be.var"),
IntentionWrapper(ChangeVariableMutabilityFix(property, true))
)
}
}
}
private val REACT_PACKAGE_ID = Name.identifier("react")
private val REACT_PACKAGE = FqName(REACT_PACKAGE_ID.identifier)
private val R_PROPS = REACT_PACKAGE.child(Name.identifier("RProps"))
private val R_STATE = REACT_PACKAGE.child(Name.identifier("RState"))
private val STATE = REACT_PACKAGE.child(Name.identifier("State"))
private val PROPS = REACT_PACKAGE.child(Name.identifier("Props"))
private val ClassDescriptor.implementsRState: Boolean
get() = getSuperInterfaces().any { it.fqNameSafe == STATE || it.fqNameSafe == R_STATE || it.implementsRState }
private val ClassDescriptor.implementsRProps: Boolean
get() = getSuperInterfaces().any { it.fqNameSafe == PROPS || it.fqNameSafe == R_PROPS || it.implementsRProps }
object AddExternalQuickFix : LocalQuickFix {
override fun getName(): String = KotlinBundle.message("add.external.keyword")
override fun getFamilyName(): String = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val psiElement = descriptor.psiElement.parent
if (psiElement is KtClass) {
psiElement.addModifier(KtTokens.EXTERNAL_KEYWORD)
}
}
}
class ConvertToNullableTypeFix : LocalQuickFix {
override fun getName(): String = KotlinBundle.message("convert.to.nullable.type")
override fun getFamilyName(): String = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val parameter = descriptor.psiElement as? KtProperty ?: return
val type = parameter.type() ?: return
parameter.setType(type.makeNullable())
}
}
private fun KtDeclaration.type() = (resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType
@@ -2894,6 +2894,30 @@
language="kotlin"
key="inspection.replace.with.string.builder.append.range.display.name" bundle="messages.KotlinBundle"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NonExternalClassifierExtendingStateOrPropsInspection"
groupPath="Kotlin,React"
groupBundle="messages.KotlinBundle" groupKey="group.names.probable.bugs"
enabledByDefault="true"
level="WARNING"
language="kotlin"
key="non.external.classifier.extending.state.or.props.name" bundle="messages.KotlinBundle"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NonVarPropertyInExternalInterfaceInspection"
groupPath="Kotlin"
groupBundle="messages.KotlinBundle" groupKey="group.names.probable.bugs"
enabledByDefault="true"
level="WARNING"
language="kotlin"
key="external.interface.contains.val.property.name" bundle="messages.KotlinBundle"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.NonNullableBooleanPropertyInExternalInterfaceInspection"
groupPath="Kotlin"
groupBundle="messages.KotlinBundle" groupKey="group.names.probable.bugs"
enabledByDefault="false"
level="WARNING"
language="kotlin"
key="external.interface.contains.non.nullable.property.name" bundle="messages.KotlinBundle"/>
</extensions>
</idea-plugin>
</idea-plugin>