Inspection for highlighting problem API usage

It's planned to use this inspection for show patchset branches problems
during development process.
This commit is contained in:
Nikolay Krasko
2018-03-19 01:38:44 +03:00
committed by Nikolay Krasko
parent a95ec598f2
commit 3ef67e1d9d
25 changed files with 849 additions and 3 deletions
@@ -72,7 +72,7 @@ abstract class AbstractKotlinInspection: LocalInspectionTool(), CustomSuppressab
}
}
private fun toSeverity(highlightDisplayLevel: HighlightDisplayLevel): Severity {
fun toSeverity(highlightDisplayLevel: HighlightDisplayLevel): Severity {
return when (highlightDisplayLevel) {
HighlightDisplayLevel.DO_NOT_SHOW -> Severity.INFO
@@ -0,0 +1,5 @@
<html>
<body>
This inspection reports usages of incompatible API.
</body>
</html>
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.inspections.api
import com.intellij.codeInsight.TargetElementUtil
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.EditorAction
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
import com.intellij.profile.codeInspection.ProjectInspectionProfileManager
class AddIncompatibleApiAction : EditorAction(AddIncompatibleApiInspectionHandler())
private class AddIncompatibleApiInspectionHandler : EditorActionHandler() {
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
if (!super.isEnabledForCaret(editor, caret, dataContext)) {
return false
}
val project = editor.project ?: return false
val incompatibleAPIToolState = ProjectInspectionProfileManager.getInstance(project).currentProfile.getToolDefaultState(
IncompatibleAPIInspection.SHORT_NAME, project
)
return incompatibleAPIToolState.isEnabled
}
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
val project = editor.project ?: return
val element = TargetElementUtil.findTargetElement(
editor, TargetElementUtil.ELEMENT_NAME_ACCEPTED or TargetElementUtil.REFERENCED_ELEMENT_ACCEPTED
) ?: return
val qualified = getQualifiedNameFromProviders(element) ?: return
AddToIncompatibleApiDialog(project, qualified).show()
}
}
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.inspections.api.AddToIncompatibleApiDialog">
<grid id="cbd77" binding="contentPane" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="10" left="10" bottom="10" right="10"/>
<constraints>
<xy x="48" y="54" width="436" height="297"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<grid id="e3588" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="6" anchor="0" fill="3" indent="0" use-parent-layout="false">
<minimum-size width="600" height="-1"/>
</grid>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="35cae" class="javax.swing.JLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<labelFor value="49f89"/>
<text value="Re&amp;ference"/>
</properties>
</component>
<vspacer id="eac19">
<constraints>
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="49f89" class="javax.swing.JTextField" binding="referenceTextField">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
<component id="bcdcc" class="javax.swing.JLabel">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<labelFor value="ec77c"/>
<text value="&amp;Reason"/>
</properties>
</component>
<component id="ec77c" class="javax.swing.JTextField" binding="reasonTextField">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
</children>
</grid>
</children>
</grid>
</form>
@@ -0,0 +1,81 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.inspections.api;
import com.intellij.codeInspection.ex.InspectionProfileModifiableModelKt;
import com.intellij.codeInspection.ex.ToolsImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
public class AddToIncompatibleApiDialog extends DialogWrapper {
private JPanel contentPane;
private JTextField referenceTextField;
private JTextField reasonTextField;
private final Project project;
public AddToIncompatibleApiDialog(@NotNull Project project, @NotNull String qualifiedReference) {
super(project, true);
this.project = project;
setModal(true);
setTitle("Report as Incompatible API");
referenceTextField.setText(qualifiedReference);
init();
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return reasonTextField;
}
@Override
protected JComponent createCenterPanel() {
return contentPane;
}
@Override
public Container getContentPane() {
return contentPane;
}
@Override
protected void doOKAction() {
String reference = referenceTextField.getText();
String reason = reasonTextField.getText();
reference = reference != null ? reference.trim() : null;
reason = reason != null ? reason.trim() : null;
if (reference != null && !reference.isEmpty()) {
String finalReference = reference;
String finalReason = reason == null || reason.isEmpty() ? null : reason;
InspectionProfileModifiableModelKt.modifyAndCommitProjectProfile(project, inspectionProfileModifiableModel -> {
ToolsImpl toolImpl = inspectionProfileModifiableModel.getToolsOrNull(IncompatibleAPIInspection.SHORT_NAME, project);
if (toolImpl != null) {
IncompatibleAPIInspection incompatibleAPIInspection =
(IncompatibleAPIInspection) toolImpl.getDefaultState().getTool().getTool();
incompatibleAPIInspection.addProblem(finalReference, finalReason);
}
});
}
super.doOKAction();
}
@Override
public void dispose() {
super.dispose();
}
}
@@ -0,0 +1,201 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.inspections.api
import com.intellij.codeInsight.daemon.HighlightDisplayKey
import com.intellij.codeInspection.*
import com.intellij.ide.actions.QualifiedNameProvider
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.project.DumbService
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiReference
import com.intellij.util.xmlb.annotations.Attribute
import org.jdom.Element
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.highlighter.createSuppressWarningActions
import org.jetbrains.kotlin.idea.inspections.api.IncompatibleAPIInspection.Companion.DEFAULT_REASON
import org.jetbrains.kotlin.idea.inspections.toSeverity
class IncompatibleAPIInspection : LocalInspectionTool(), CustomSuppressableInspectionTool {
class Problem(
@Attribute var reference: String? = "",
@Attribute var reason: String? = ""
)
// Stored in inspection setting
var problems: List<Problem> = arrayListOf()
fun addProblem(reference: String, reason: String?) {
problems += Problem(reference, reason)
problemsCache.update(problems)
}
override fun readSettings(node: Element) {
super.readSettings(node)
problemsCache.update(problems)
}
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
if (problems.isEmpty()) {
return super.buildVisitor(holder, isOnTheFly)
}
return when (holder.file.language) {
KotlinLanguage.INSTANCE -> {
IncompatibleAPIKotlinVisitor(holder, problemsCache)
}
JavaLanguage.INSTANCE -> {
IncompatibleAPIJavaVisitor(holder, problemsCache)
}
else -> {
super.buildVisitor(holder, isOnTheFly)
}
}
}
override fun getSuppressActions(element: PsiElement?): Array<SuppressIntentionAction>? {
return when {
element == null -> emptyArray()
element.language == JavaLanguage.INSTANCE -> {
val key = HighlightDisplayKey.find(shortName)
?: throw AssertionError("HighlightDisplayKey.find($shortName) is null. Inspection: $javaClass")
return SuppressManager.getInstance().createSuppressActions(key)
}
element.language == KotlinLanguage.INSTANCE -> {
createSuppressWarningActions(element, toSeverity(defaultLevel), suppressionKey).toTypedArray()
}
else -> emptyArray()
}
}
override fun isSuppressedFor(element: PsiElement): Boolean {
if (SuppressManager.getInstance()!!.isSuppressedFor(element, id)) {
return true
}
val project = element.project
if (KotlinCacheService.getInstance(project).getSuppressionCache().isSuppressed(element, suppressionKey, toSeverity(defaultLevel))) {
return true
}
return false
}
private val suppressionKey: String get() = this.shortName
private val problemsCache = ProblemsCache()
companion object {
const val SHORT_NAME = "IncompatibleAPI"
const val DEFAULT_REASON = "Incompatible API"
}
}
internal class ProblemsCache {
var forbiddenApiReferences: Map<String, IncompatibleAPIInspection.Problem> = emptyMap()
private set
private var words: Set<String> = emptySet()
fun update(problems: List<IncompatibleAPIInspection.Problem>) {
val validProblems = problems.filter { !it.reference.isNullOrBlank() }
forbiddenApiReferences = validProblems.map { it.reference!! to it }.toMap()
words = validProblems.mapTo(HashSet()) {
val reference = it.reference!!
if (reference.contains('#')) {
reference.substringAfterLast('#').substringBefore('(')
} else {
reference.substringAfterLast('.')
}
}
}
fun containsWord(word: String) = word in words
}
internal fun registerProblemForReference(
reference: PsiReference?,
holder: ProblemsHolder,
problem: IncompatibleAPIInspection.Problem
) {
if (reference == null) return
holder.registerProblem(
reference,
problem.reason ?: DEFAULT_REASON,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING
)
}
internal fun registerProblemForElement(
element: PsiElement?,
holder: ProblemsHolder,
problem: IncompatibleAPIInspection.Problem
) {
if (element == null) return
holder.registerProblem(
element,
problem.reason ?: DEFAULT_REASON,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING
)
}
internal fun findProblem(
resolvedTo: PsiElement?,
problemsCache: ProblemsCache
): IncompatibleAPIInspection.Problem? {
if (resolvedTo == null) return null
val referenceStr = getQualifiedNameFromProviders(resolvedTo) ?: return null
val forbiddenApiReferences = problemsCache.forbiddenApiReferences
return forbiddenApiReferences[referenceStr] ?: run {
// check constructor
val lastPart = referenceStr.substringAfterLast('#', "")
if (lastPart.isNotEmpty()) {
val classFqName = referenceStr.substringBeforeLast('#')
val className = classFqName.substringAfterLast('.')
if (lastPart.startsWith("$className(")) {
return@run forbiddenApiReferences[classFqName]
}
}
null
} ?: run {
// Check reference without params
val beforeParams = referenceStr.substringBeforeLast('(', "")
if (beforeParams.isNotEmpty()) {
val overloadSignatureKey = "$beforeParams("
val particularOverloadPresent = forbiddenApiReferences.keys.any { it.startsWith(overloadSignatureKey) }
if (!particularOverloadPresent) {
return@run forbiddenApiReferences[beforeParams]
}
}
null
}
}
internal fun getQualifiedNameFromProviders(element: PsiElement): String? {
DumbService.getInstance(element.project).isAlternativeResolveEnabled = true
try {
for (provider in Extensions.getExtensions(QualifiedNameProvider.EP_NAME)) {
val result = provider.getQualifiedName(element)
if (result != null) return result
}
} finally {
DumbService.getInstance(element.project).isAlternativeResolveEnabled = false
}
return null
}
@@ -0,0 +1,106 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.inspections.api
import com.intellij.codeInsight.AnnotationUtil
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.psi.*
import com.intellij.psi.javadoc.PsiDocComment
import org.jetbrains.kotlin.psi.psiUtil.parents
class IncompatibleAPIJavaVisitor internal constructor(
private val myHolder: ProblemsHolder,
private val problemsCache: ProblemsCache
) : JavaElementVisitor() {
override fun visitDocComment(comment: PsiDocComment) {
// No references inside doc comment are of interest.
}
override fun visitImportList(list: PsiImportList?) {
// Do not report anything in imports
}
override fun visitClass(aClass: PsiClass) {}
override fun visitReferenceExpression(expression: PsiReferenceExpression) {
visitReferenceElement(expression)
}
override fun visitReferenceElement(reference: PsiJavaCodeReferenceElement) {
val isInsideImport = reference.element.parents
.takeWhile { it is PsiJavaCodeReferenceElement || it is PsiImportStatement }
.any { it is PsiImportStatement }
if (isInsideImport) {
return
}
ModuleUtilCore.findModuleForPsiElement(reference.element) ?: return
super.visitReferenceElement(reference)
val name = reference.referenceName
if (name == null || !problemsCache.containsWord(name)) {
return
}
val psiMember = reference.resolve() as? PsiMember ?: return
val problem = findProblem(psiMember, problemsCache) ?: return
registerProblemForReference(reference, myHolder, problem)
}
override fun visitNewExpression(expression: PsiNewExpression) {
ModuleUtilCore.findModuleForPsiElement(expression) ?: return
super.visitNewExpression(expression)
val name = expression.classReference?.referenceName
if (name == null || !problemsCache.containsWord(name)) {
return
}
val constructor = expression.resolveConstructor()
if (exitOnNonCompiled(constructor)) return
val problem = findProblem(constructor, problemsCache)
if (problem != null) {
registerProblemForReference(expression.classReference, myHolder, problem)
}
}
override fun visitMethod(method: PsiMethod) {
ModuleUtilCore.findModuleForPsiElement(method) ?: return
super.visitMethod(method)
if (!problemsCache.containsWord(method.name)) {
return
}
(if (!method.isConstructor) AnnotationUtil.findAnnotation(method, CommonClassNames.JAVA_LANG_OVERRIDE) else null) ?: return
val methods = method.findSuperMethods()
for (superMethod in methods) {
if (exitOnNonCompiled(superMethod)) {
return
}
val problem = findProblem(superMethod, problemsCache)
if (problem != null) {
registerProblemForElement(method.nameIdentifier, myHolder, problem)
return
}
}
}
private fun exitOnNonCompiled(psiElement: PsiElement?): Boolean {
if (psiElement != null && psiElement !is PsiCompiledElement) {
if (!ApplicationManager.getApplication().isInternal && !ApplicationManager.getApplication().isUnitTestMode) {
return true
}
}
return false
}
}
@@ -0,0 +1,86 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.inspections.api
import com.intellij.codeInspection.ProblemsHolder
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isInImportDirective
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.synthetic.JavaSyntheticPropertiesScope
internal class IncompatibleAPIKotlinVisitor(
private val holder: ProblemsHolder,
private val problemsCache: ProblemsCache
) : KtVisitorVoid() {
override fun visitImportList(importList: KtImportList) {
// Do not report anything in imports
}
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
val nameStr = expression.text
if (!Name.isValidIdentifier(nameStr)) {
return
}
val names = HashSet<String>()
names.add(nameStr)
val gettersNames = JavaSyntheticPropertiesScope.possibleGetMethodNames(
Name.identifier(nameStr)
)
if (!gettersNames.isEmpty()) {
names.addAll(gettersNames.map { it.identifier })
names.add(JavaSyntheticPropertiesScope.setMethodName(gettersNames.first()).identifier)
}
if (names.none { name -> problemsCache.containsWord(name) }) {
return
}
checkReference(expression)
}
override fun visitNamedFunction(function: KtNamedFunction) {
super.visitNamedFunction(function)
if (function.modifierList?.getModifier(KtTokens.OVERRIDE_KEYWORD) == null) {
return
}
val funName = function.name
if (funName == null || !problemsCache.containsWord(funName)) {
return
}
val functionDescriptor = function.resolveToDescriptorIfAny() ?: return
for (overriddenDescriptor in functionDescriptor.original.overriddenDescriptors) {
val psi = overriddenDescriptor.source.getPsi() ?: continue
val problem = findProblem(psi, problemsCache)
if (problem != null) {
registerProblemForElement(function.nameIdentifier, holder, problem)
return
}
}
}
private fun checkReference(expression: KtSimpleNameExpression) {
if (expression.isInImportDirective()) {
// Ignore imports
return
}
for (reference in expression.references) {
val resolveTo = reference.resolve()
val problem = findProblem(resolveTo, problemsCache) ?: continue
registerProblemForReference(reference, holder, problem)
break
}
}
}
+12
View File
@@ -57,6 +57,10 @@
text="Kotlin Project Post-Open Activity" internal="true">
<add-to-group group-id="KotlinInternalGroup"/>
</action>
<action id="AddToProblemApiInspection" class="org.jetbrains.kotlin.idea.inspections.api.AddIncompatibleApiAction"
text="Report as incompatible API">
</action>
</actions>
<extensions defaultExtensionNs="com.intellij">
@@ -118,6 +122,14 @@
<editorNotificationProvider implementation="org.jetbrains.kotlin.idea.versions.UnsupportedAbiVersionNotificationPanelProvider"/>
<localInspection
groupName="Plugin DevKit"
shortName="IncompatibleAPI"
enabledByDefault="false"
level="ERROR"
displayName="Incompatible API usage"
implementationClass="org.jetbrains.kotlin.idea.inspections.api.IncompatibleAPIInspection"/>
<projectService serviceInterface="org.jetbrains.uast.kotlin.KotlinUastBindingContextProviderService"
serviceImplementation="org.jetbrains.uast.kotlin.internal.IdeaKotlinUastBindingContextProviderService"/>
</extensions>
+45
View File
@@ -0,0 +1,45 @@
package problem.api;
import lib.LibClass;
import lib.LibMethods;
import lib.LibSuper;
import lib.LibConstructor;
public class JavaTest {
void test() {
new LibClass();
LibMethods.staticMethod();
}
static void overloads(LibMethods lib) {
lib.overload1(12);
lib.overload1("Some");
lib.overload2(12);
lib.overload2("Some");
//noinspection IncompatibleAPI
lib.overload2(13);
}
public static class Extends extends LibClass {
}
public class Subclass extends LibSuper {
@Override
public void test(String str) {
}
}
public class SubclassSuppress extends LibSuper {
@SuppressWarnings("IncompatibleAPI")
@Override
public void test(String str) {
}
}
public static constructor() {
new LibConstructor(null, "some");
new LibConstructor(null);
}
}
+17
View File
@@ -0,0 +1,17 @@
package problem.api.kotlin.classes
import lib.LibClass
fun ktTest() {
LibClass()
}
class KtUsage : LibClass()
fun ktTestSuppress() {
@Suppress("IncompatibleAPI")
LibClass()
}
@Suppress("IncompatibleAPI")
class KtUsageSuppress : LibClass()
@@ -0,0 +1,8 @@
package problem.api.kotlin.constructors
import lib.LibConstructor
fun ktOverloads(lib: LibMethods) {
LibConstructor(null, "some")
LibConstructor(null)
}
@@ -0,0 +1,87 @@
<problems>
<problem>
<file>classes.kt</file>
<line>6</line>
<description>class example</description>
</problem>
<problem>
<file>classes.kt</file>
<line>9</line>
<description>class example</description>
</problem>
<problem>
<file>overloads.kt</file>
<line>6</line>
<description>concrete overload example</description>
</problem>
<problem>
<file>overloads.kt</file>
<line>9</line>
<description>overload with no params example</description>
</problem>
<problem>
<file>overloads.kt</file>
<line>10</line>
<description>overload with no params example</description>
</problem>
<problem>
<file>static.kt</file>
<line>6</line>
<description>static method example</description>
</problem>
<problem>
<file>JavaTest.java</file>
<line>10</line>
<description>class example</description>
</problem>
<problem>
<file>JavaTest.java</file>
<line>11</line>
<description>static method example</description>
</problem>
<problem>
<file>JavaTest.java</file>
<line>15</line>
<description>concrete overload example</description>
</problem>
<problem>
<file>JavaTest.java</file>
<line>18</line>
<description>overload with no params example</description>
</problem>
<problem>
<file>JavaTest.java</file>
<line>19</line>
<description>overload with no params example</description>
</problem>
<problem>
<file>JavaTest.java</file>
<line>25</line>
<description>class example</description>
</problem>
<problem>
<file>JavaTest.java</file>
<line>30</line>
<description>signature changed example</description>
</problem>
<problem>
<file>JavaTest.java</file>
<line>43</line>
<description>constructor example</description>
</problem>
<problem>
<file>super.kt</file>
<line>6</line>
<description>signature changed example</description>
</problem>
<problem>
<file>constructors.kt</file>
<line>7</line>
<description>constructor example</description>
</problem>
</problems>
@@ -0,0 +1 @@
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.api.IncompatibleAPIInspection
@@ -0,0 +1,12 @@
<inspection_tool class="IncompatibleAPI" enabled="true" level="WARNING" enabled_by_default="true">
<option name="problems">
<list>
<Problem reference="lib.LibClass" reason="class example" />
<Problem reference="lib.LibMethods#staticMethod" reason="static method example" />
<Problem reference="lib.LibMethods#overload1(int)" reason="concrete overload example" />
<Problem reference="lib.LibMethods#overload2" reason="overload with no params example" />
<Problem reference="lib.LibSuper#test" reason="signature changed example" />
<Problem reference="lib.LibConstructor#LibConstructor(java.lang.Object)" reason="constructor example" />
</list>
</option>
</inspection_tool>
@@ -0,0 +1,4 @@
package lib;
public class LibClass {
}
@@ -0,0 +1,9 @@
package lib;
public class LibConstructor {
public LibConstructor(Object object) {
}
public LibConstructor(Object object, String other) {
}
}
@@ -0,0 +1,12 @@
package lib;
public class LibMethods {
public static void staticMethod() {
}
public void overload1(int i) {}
public void overload1(String s) {}
public void overload2(int i) {}
public void overload2(String s) {}
}
@@ -0,0 +1,7 @@
package lib;
import org.jetbrains.annotations.Nullable;
public class LibSuper {
public void test(@Nullable String str) {}
}
+11
View File
@@ -0,0 +1,11 @@
package problem.api.kotlin.overloads
import lib.LibMethods
fun ktOverloads(lib: LibMethods) {
lib.overload1(12)
lib.overload1("Some")
lib.overload2(12)
lib.overload2("Some")
}
+7
View File
@@ -0,0 +1,7 @@
package problem.api.kotlin.statics
import lib.LibMethods
fun ktTest() {
LibMethods.staticMethod()
}
+14
View File
@@ -0,0 +1,14 @@
package problem.api.kotlin.s
import lib.LibSuper
class Subclass : LibSuper() {
override fun test(str: String?) {
}
}
class SubclassSuppress : LibSuper() {
@Suppress("IncompatibleAPI")
override fun test(str: String?) {
}
}
@@ -21,6 +21,8 @@ import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiFile
import com.intellij.testFramework.TestLoggerFactory
import org.jdom.Document
import org.jdom.input.SAXBuilder
import org.jetbrains.kotlin.idea.inspections.runInspection
import org.jetbrains.kotlin.idea.test.*
import org.jetbrains.kotlin.idea.util.application.runWriteAction
@@ -66,6 +68,13 @@ abstract class AbstractInspectionTest : KotlinLightCodeInsightFixtureTestCase()
val inspectionsTestDir = optionsFile.parentFile!!
val srcDir = inspectionsTestDir.parentFile!!
val settingsFile = File(inspectionsTestDir, "settings.xml")
val settingsElement = if (settingsFile.exists()) {
(SAXBuilder().build(settingsFile) as Document).rootElement
} else {
null
}
with(myFixture) {
testDataPath = "${KotlinTestUtils.getHomeDirectory()}/$srcDir"
@@ -110,7 +119,9 @@ abstract class AbstractInspectionTest : KotlinLightCodeInsightFixtureTestCase()
configExtra(psiFiles, options)
val presentation = runInspection(
inspectionClass, project, files = psiFiles.map { it.virtualFile!! }, withTestDir = inspectionsTestDir.path
inspectionClass, project,
settings = settingsElement,
files = psiFiles.map { it.virtualFile!! }, withTestDir = inspectionsTestDir.path
)
if (afterFiles.isNotEmpty()) {
@@ -174,6 +174,11 @@ public class InspectionTestGenerated extends AbstractInspectionTest {
runTest("idea/testData/inspections/hasPlatformType/inspectionData/inspections.test");
}
@TestMetadata("incompatibleAPI/inspectionData/inspections.test")
public void testIncompatibleAPI_inspectionData_Inspections_test() throws Exception {
runTest("idea/testData/inspections/incompatibleAPI/inspectionData/inspections.test");
}
@TestMetadata("javaCollectionsStaticMethodOnImmutableList/inspectionData/inspections.test")
public void testJavaCollectionsStaticMethodOnImmutableList_inspectionData_Inspections_test() throws Exception {
runTest("idea/testData/inspections/javaCollectionsStaticMethodOnImmutableList/inspectionData/inspections.test");
@@ -24,6 +24,7 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.testFramework.InspectionTestUtil
import com.intellij.testFramework.createGlobalContextForTool
import org.jdom.Element
fun runInspection(
inspection: LocalInspectionTool, project: Project, files: List<VirtualFile>? = null, withTestDir: String? = null
@@ -46,8 +47,12 @@ fun runInspection(
}
fun runInspection(
inspectionClass: Class<*>, project: Project, files: List<VirtualFile>? = null, withTestDir: String? = null
inspectionClass: Class<*>, project: Project,
settings: Element? = null, files: List<VirtualFile>? = null, withTestDir: String? = null
): InspectionToolPresentation {
val inspection = inspectionClass.newInstance() as LocalInspectionTool
if (settings != null) {
inspection.readSettings(settings)
}
return runInspection(inspection, project, files, withTestDir)
}