-
Notifications
You must be signed in to change notification settings - Fork 496
/
UnusedQueryInspection.kt
92 lines (84 loc) · 3.53 KB
/
UnusedQueryInspection.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package app.cash.sqldelight.intellij.inspections
import app.cash.sqldelight.core.lang.psi.StmtIdentifierMixin
import app.cash.sqldelight.core.lang.queriesName
import app.cash.sqldelight.core.lang.util.findChildOfType
import app.cash.sqldelight.core.psi.SqlDelightStmtIdentifier
import app.cash.sqldelight.core.psi.SqlDelightVisitor
import com.alecstrong.sql.psi.core.psi.SqlStmtList
import com.alecstrong.sql.psi.core.psi.SqlTypes
import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.LocalQuickFixOnPsiElement
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.ReadOnlyModificationException
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiFile
import com.intellij.psi.SmartPointerManager
import com.intellij.psi.search.FilenameIndex
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.psi.KtFile
internal class UnusedQueryInspection : LocalInspectionTool() {
override fun buildVisitor(
holder: ProblemsHolder,
isOnTheFly: Boolean,
session: LocalInspectionToolSession,
) = ensureReady(session.file) {
val fileName = "${sqlDelightFile.virtualFile?.queriesName}.kt"
val generatedFile = FilenameIndex.getFilesByName(
sqlDelightFile.project,
fileName,
GlobalSearchScope.moduleScope(module),
).firstOrNull() as KtFile? ?: return PsiElementVisitor.EMPTY_VISITOR
val allMethods = generatedFile.classes.getOrNull(0)?.methods
if (allMethods == null) {
return PsiElementVisitor.EMPTY_VISITOR
}
return object : SqlDelightVisitor() {
override fun visitStmtIdentifier(o: SqlDelightStmtIdentifier) = ignoreInvalidElements {
if (o !is StmtIdentifierMixin || o.identifier() == null) {
return
}
val generatedMethods = allMethods.filter { namedFunction ->
namedFunction.name == o.identifier()?.text
}
for (generatedMethod in generatedMethods) {
val lightMethods = generatedMethod.toLightMethods()
if (lightMethods.any { ReferencesSearch.search(it, it.useScope).findFirst() != null }) {
return
}
}
holder.registerProblem(
o,
"Unused symbol",
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
SafeDeleteQuickFix(o),
)
}
}
}
class SafeDeleteQuickFix(element: PsiElement) : LocalQuickFixOnPsiElement(element) {
private val ref = SmartPointerManager.getInstance(element.project)
.createSmartPsiElementPointer(element, element.containingFile)
override fun getFamilyName(): String = name
override fun getText(): String = "Safe delete ${ref.element?.text?.removeSuffix(":").orEmpty()}"
override fun invoke(
project: Project,
file: PsiFile,
startElement: PsiElement,
endElement: PsiElement,
) {
WriteCommandAction.writeCommandAction(project).run<ReadOnlyModificationException> {
val element = ref.element ?: return@run
val semicolon = PsiTreeUtil.findSiblingForward(element, SqlTypes.SEMI, false, null)
file.findChildOfType<SqlStmtList>()?.deleteChildRange(element, semicolon)
}
}
}
}