From c6c57a6a269bd9a8a131f8f73f05bac957a8a801 Mon Sep 17 00:00:00 2001 From: Alec Strong Date: Tue, 19 Apr 2022 15:22:40 -0400 Subject: [PATCH] Optimize the inspectors and enable them to fail silently for expected exception types (#3121) * Optimize the inspectors and enable them to fail silently for expected exception types * Update sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/InspectionExt.kt Co-authored-by: Jake Wharton * Fix up failing tests Co-authored-by: Jake Wharton --- .../core/compiler/SqlDelightCompiler.kt | 10 ++- .../core/compiler/model/NamedQuery.kt | 2 +- .../core/lang/SqlDelightQueriesFile.kt | 2 +- .../sqldelight/core/lang/util/TreeUtil.kt | 3 +- .../intellij/inspections/InspectionExt.kt | 53 ++++++++++++++ .../MismatchJoinColumnInspection.kt | 13 +--- ...MixedNamedAndPositionalParamsInspection.kt | 5 +- .../inspections/NullEqualityInspection.kt | 5 +- .../inspections/OptimisticLockAnnotator.kt | 2 +- .../RedundantNullCheckInspection.kt | 37 +++++----- .../SchemaNeedsMigrationInspection.kt | 71 +++++++++---------- .../inspections/UnusedColumnInspection.kt | 70 ++++++++---------- .../inspections/UnusedImportInspection.kt | 5 +- .../inspections/UnusedQueryInspection.kt | 23 ++---- .../lang/SqlDelightFileViewProviderFactory.kt | 2 +- .../lang/SqlDelightHighlightVisitor.kt | 14 ++-- .../intellij/SqlDelightFixtureTestCase.kt | 2 + .../column-inspection/src/CreateView.sq | 1 + .../column-inspection/src/SelectStar.sq | 1 + .../column-inspection/src/SelectTwo.sq | 1 + 20 files changed, 168 insertions(+), 154 deletions(-) create mode 100644 sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/InspectionExt.kt diff --git a/sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/compiler/SqlDelightCompiler.kt b/sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/compiler/SqlDelightCompiler.kt index 00a2e752e2b6..9a7b5b41c406 100644 --- a/sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/compiler/SqlDelightCompiler.kt +++ b/sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/compiler/SqlDelightCompiler.kt @@ -44,9 +44,13 @@ object SqlDelightCompiler { file: SqlDelightQueriesFile, output: FileAppender ) { - writeTableInterfaces(file, output) - writeQueryInterfaces(file, output) - writeQueries(module, dialect, file, output) + try { + writeTableInterfaces(file, output) + writeQueryInterfaces(file, output) + writeQueries(module, dialect, file, output) + } catch (e: InvalidElementDetectedException) { + // It's okay if compilation is cut short, we can just quit out. + } } fun writeInterfaces( diff --git a/sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/compiler/model/NamedQuery.kt b/sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/compiler/model/NamedQuery.kt index 69cab9ec9889..8196cb4239ea 100644 --- a/sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/compiler/model/NamedQuery.kt +++ b/sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/compiler/model/NamedQuery.kt @@ -56,7 +56,7 @@ data class NamedQuery( * Explodes the sqlite query into an ordered list (same order as the query) of types to be exposed * by the generated api. */ - internal val resultColumns: List by lazy { + val resultColumns: List by lazy { if (queryable is SelectQueryable) resultColumns(queryable.select) else queryable.select.typesExposed(LinkedHashSet()) } diff --git a/sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/lang/SqlDelightQueriesFile.kt b/sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/lang/SqlDelightQueriesFile.kt index 6206f8421c43..96d6b1e51343 100644 --- a/sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/lang/SqlDelightQueriesFile.kt +++ b/sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/lang/SqlDelightQueriesFile.kt @@ -44,7 +44,7 @@ class SqlDelightQueriesFile( } } - internal val namedQueries by lazy { + val namedQueries by lazy { transactions().filterIsInstance() + sqlStatements() .filter { typeResolver.queryWithResults(it.statement) != null && it.identifier.name != null } .map { diff --git a/sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/lang/util/TreeUtil.kt b/sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/lang/util/TreeUtil.kt index c3a3ca01e380..e94e3fc4ba3d 100644 --- a/sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/lang/util/TreeUtil.kt +++ b/sqldelight-compiler/src/main/kotlin/app/cash/sqldelight/core/lang/util/TreeUtil.kt @@ -41,7 +41,6 @@ import com.alecstrong.sql.psi.core.psi.SqlTypeName import com.alecstrong.sql.psi.core.psi.SqlTypes import com.alecstrong.sql.psi.core.psi.mixins.ColumnDefMixin import com.intellij.lang.ASTNode -import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiElement import com.intellij.psi.PsiWhiteSpace @@ -57,7 +56,7 @@ internal inline fun PsiElement.parentOfType(): R { return PsiTreeUtil.getParentOfType(this, R::class.java)!! } -internal fun PsiElement.type(): IntermediateType = if (!isValid) throw ProcessCanceledException() else when (this) { +internal fun PsiElement.type(): IntermediateType = when (this) { is ExposableType -> type() is SqlTypeName -> sqFile().typeResolver.definitionType(this) is AliasElement -> source().type().copy(name = name) diff --git a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/InspectionExt.kt b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/InspectionExt.kt new file mode 100644 index 000000000000..7aaec475b92b --- /dev/null +++ b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/InspectionExt.kt @@ -0,0 +1,53 @@ +package app.cash.sqldelight.intellij.inspections + +import app.cash.sqldelight.core.SqlDelightFileIndex +import app.cash.sqldelight.core.lang.SqlDelightFile +import com.alecstrong.sql.psi.core.psi.InvalidElementDetectedException +import com.intellij.codeInspection.LocalInspectionTool +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.openapi.module.Module +import com.intellij.psi.PsiElementVisitor +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiInvalidElementAccessException + +internal inline fun LocalInspectionTool.ensureReady(file: PsiFile, block: InspectionProperties.() -> PsiElementVisitor): PsiElementVisitor { + return ensureReady(file, { PsiElementVisitor.EMPTY_VISITOR }, block) +} + +internal inline fun LocalInspectionTool.ensureFileReady(file: PsiFile, block: InspectionProperties.() -> Array): Array { + return ensureReady(file, { emptyArray() }, block) +} + +@Suppress("unused") // Receiver to enforce usage. +internal inline fun PsiElementVisitor.ignoreInvalidElements(block: () -> Unit) { + try { + block() + } catch (_: InvalidElementDetectedException) { + } catch (_: PsiInvalidElementAccessException) { + } +} + +private inline fun ensureReady( + file: PsiFile, + defaultValue: () -> T, + block: InspectionProperties.() -> T +): T { + val sqlDelightFile = file as? SqlDelightFile ?: return defaultValue() + val module = file.module ?: return defaultValue() + val fileIndex = SqlDelightFileIndex.getInstance(module) + if (!fileIndex.isConfigured) return defaultValue() + + try { + return InspectionProperties(module, sqlDelightFile, fileIndex).block() + } catch (_: InvalidElementDetectedException) { + return defaultValue() + } catch (_: PsiInvalidElementAccessException) { + return defaultValue() + } +} + +internal data class InspectionProperties( + val module: Module, + val sqlDelightFile: SqlDelightFile, + val fileIndex: SqlDelightFileIndex, +) diff --git a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/MismatchJoinColumnInspection.kt b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/MismatchJoinColumnInspection.kt index 330c847480cb..40121db1d067 100644 --- a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/MismatchJoinColumnInspection.kt +++ b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/MismatchJoinColumnInspection.kt @@ -1,7 +1,5 @@ package app.cash.sqldelight.intellij.inspections -import app.cash.sqldelight.core.SqlDelightProjectService -import app.cash.sqldelight.core.lang.SqlDelightFile import app.cash.sqldelight.core.lang.psi.isColumnSameAs import app.cash.sqldelight.core.lang.psi.isTypeSameAs import app.cash.sqldelight.core.lang.util.findChildrenOfType @@ -12,7 +10,6 @@ import com.alecstrong.sql.psi.core.psi.SqlJoinConstraint import com.alecstrong.sql.psi.core.psi.SqlParenExpr import com.intellij.codeInspection.InspectionManager import com.intellij.codeInspection.LocalInspectionTool -import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ProblemHighlightType import com.intellij.psi.PsiFile @@ -21,15 +18,7 @@ internal class MismatchJoinColumnInspection : LocalInspectionTool() { file: PsiFile, manager: InspectionManager, isOnTheFly: Boolean - ): Array { - if (file !is SqlDelightFile) return emptyArray() - - val projectService = SqlDelightProjectService.getInstance(file.project) - if (file.module?.let { module -> projectService.fileIndex(module).isConfigured } != true) { - // Do not attempt to inspect the file types if the project is not configured yet. - return emptyArray() - } - + ) = ensureFileReady(file) { val joinConstraints = file.findChildrenOfType() .mapNotNull { it.expr?.binaryEqualityExpr() } diff --git a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/MixedNamedAndPositionalParamsInspection.kt b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/MixedNamedAndPositionalParamsInspection.kt index 85ad24bd8875..eee9fd378ef0 100644 --- a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/MixedNamedAndPositionalParamsInspection.kt +++ b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/MixedNamedAndPositionalParamsInspection.kt @@ -10,16 +10,15 @@ import com.alecstrong.sql.psi.core.psi.SqlVisitor import com.intellij.codeInspection.LocalInspectionTool import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder -import com.intellij.psi.PsiElementVisitor private val positional = "^\\?\\d*\$".toRegex() private val named = "^[:@$][a-zA-Z0-9]*\$".toRegex() internal class MixedNamedAndPositionalParamsInspection : LocalInspectionTool() { - override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = ensureReady(holder.file) { return object : SqlVisitor() { - override fun visitExpr(o: SqlExpr) { + override fun visitExpr(o: SqlExpr) = ignoreInvalidElements { if (o !is SqlBinaryExpr) return checkExpression(o) diff --git a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/NullEqualityInspection.kt b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/NullEqualityInspection.kt index 3c46a790f286..e5eebe4109b5 100644 --- a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/NullEqualityInspection.kt +++ b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/NullEqualityInspection.kt @@ -8,7 +8,6 @@ import com.intellij.codeInspection.LocalInspectionTool import com.intellij.codeInspection.LocalInspectionToolSession import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder -import com.intellij.psi.PsiElementVisitor internal class NullEqualityInspection : LocalInspectionTool() { @@ -16,10 +15,10 @@ internal class NullEqualityInspection : LocalInspectionTool() { holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession - ): PsiElementVisitor { + ) = ensureReady(session.file) { return object : SqlVisitor() { - override fun visitBinaryEqualityExpr(o: SqlBinaryEqualityExpr) { + override fun visitBinaryEqualityExpr(o: SqlBinaryEqualityExpr) = ignoreInvalidElements { val exprList = o.getExprList() if (exprList.size < 2) return val (expr1, expr2) = exprList diff --git a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/OptimisticLockAnnotator.kt b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/OptimisticLockAnnotator.kt index f1fe7dc0a2ef..a7d5e52829a6 100644 --- a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/OptimisticLockAnnotator.kt +++ b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/OptimisticLockAnnotator.kt @@ -7,7 +7,7 @@ import com.intellij.codeInsight.intention.IntentionAction import com.intellij.psi.PsiElement class OptimisticLockAnnotator : OptimisticLockValidator() { - override fun quickFix(element: PsiElement, lock: ColumnDefMixin): IntentionAction? { + override fun quickFix(element: PsiElement, lock: ColumnDefMixin): IntentionAction { return AddOptimisticLockIntention(element, lock) } } diff --git a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/RedundantNullCheckInspection.kt b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/RedundantNullCheckInspection.kt index 68b5c2629396..a20a95df6535 100644 --- a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/RedundantNullCheckInspection.kt +++ b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/RedundantNullCheckInspection.kt @@ -10,7 +10,6 @@ import com.intellij.codeInspection.LocalInspectionTool import com.intellij.codeInspection.LocalInspectionToolSession import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder -import com.intellij.psi.PsiElementVisitor import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.elementType import com.intellij.psi.util.parentOfType @@ -21,27 +20,29 @@ internal class RedundantNullCheckInspection : LocalInspectionTool() { holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession - ): PsiElementVisitor = object : SqlVisitor() { + ) = ensureReady(session.file) { + object : SqlVisitor() { - override fun visitIsExpr(o: SqlIsExpr) { - if (o.context !is SqlSelectStmt) return - if (PsiTreeUtil.prevVisibleLeaf(o).elementType != SqlTypes.WHERE) return + override fun visitIsExpr(o: SqlIsExpr) = ignoreInvalidElements { + if (o.context !is SqlSelectStmt) return + if (PsiTreeUtil.prevVisibleLeaf(o).elementType != SqlTypes.WHERE) return - val firstChild = o.firstChild - if (firstChild !is SqlColumnExpr) return - val clauseText = o.text - if (!clauseText.endsWith("IS NOT NULL") && !clauseText.endsWith("IS NULL")) return + val firstChild = o.firstChild + if (firstChild !is SqlColumnExpr) return + val clauseText = o.text + if (!clauseText.endsWith("IS NOT NULL") && !clauseText.endsWith("IS NULL")) return - val columnName = firstChild.columnName - val sqlColumnDef = columnName.reference?.resolve()?.parentOfType() ?: return - val hasNotNullConstraint = sqlColumnDef.columnConstraintList.any { constraint -> - constraint.textMatches("NOT NULL") - } - if (!hasNotNullConstraint) { - return - } + val columnName = firstChild.columnName + val sqlColumnDef = columnName.reference?.resolve()?.parentOfType() ?: return + val hasNotNullConstraint = sqlColumnDef.columnConstraintList.any { constraint -> + constraint.textMatches("NOT NULL") + } + if (!hasNotNullConstraint) { + return + } - holder.registerProblem(o, "Column ${columnName.name} defined as NOT NULL", ProblemHighlightType.WARNING) + holder.registerProblem(o, "Column ${columnName.name} defined as NOT NULL", ProblemHighlightType.WARNING) + } } } } diff --git a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/SchemaNeedsMigrationInspection.kt b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/SchemaNeedsMigrationInspection.kt index 610904c2c9ab..c481eb58bdf2 100644 --- a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/SchemaNeedsMigrationInspection.kt +++ b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/SchemaNeedsMigrationInspection.kt @@ -1,9 +1,7 @@ package app.cash.sqldelight.intellij.inspections -import app.cash.sqldelight.core.SqlDelightFileIndex import app.cash.sqldelight.core.SqlDelightProjectService import app.cash.sqldelight.core.lang.MigrationFile -import app.cash.sqldelight.core.lang.SqlDelightQueriesFile import app.cash.sqldelight.core.lang.psi.parameterValue import app.cash.sqldelight.core.lang.util.migrationFiles import app.cash.sqldelight.intellij.refactoring.SqlDelightSignatureBuilder @@ -17,7 +15,6 @@ import com.intellij.codeInspection.ProblemHighlightType.GENERIC_ERROR import com.intellij.codeInspection.ProblemsHolder 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.refactoring.suggested.SuggestedRefactoringSupport.Signature @@ -29,22 +26,21 @@ internal class SchemaNeedsMigrationInspection : LocalInspectionTool() { override fun buildVisitor( holder: ProblemsHolder, isOnTheFly: Boolean - ): PsiElementVisitor = object : SqlVisitor() { - override fun visitCreateTableStmt(createTable: SqlCreateTableStmt) { - val file = createTable.containingFile as? SqlDelightQueriesFile ?: return - val module = file.module ?: return - - val dbFile = file.findDbFile() ?: return - val fileIndex = SqlDelightFileIndex.getInstance(module) - val topMigrationFile = fileIndex.sourceFolders(file).asSequence() - .flatMap { it.migrationFiles() } - .maxByOrNull { it.version } - - val tables = (topMigrationFile ?: dbFile).tables(true) - val tableWithSameName = tables.find { it.tableName.name == createTable.tableName.name } - val signature = signatureBuilder.signature(createTable) ?: return - - if (tableWithSameName == null) { + ) = ensureReady(holder.file) { + object : SqlVisitor() { + override fun visitCreateTableStmt(createTable: SqlCreateTableStmt) = ignoreInvalidElements { + val dbFile = sqlDelightFile.findDbFile() ?: return + val topMigrationFile = fileIndex.sourceFolders(sqlDelightFile).asSequence() + .flatMap { it.migrationFiles() } + .maxByOrNull { it.version } + + val tables = (topMigrationFile ?: dbFile).tables(true) + val tableWithSameName = tables.find { it.tableName.name == createTable.tableName.name } + val signature = signatureBuilder.signature(createTable) ?: return + + if (tableWithSameName == null) { + /* + * TODO: Reenable this once it performs faster. Evaluating oldQuery.query is expensive. val tableWithDifferentName = tables.find { oldQuery -> val oldColumns = oldQuery.query.columns oldColumns.size == signature.parameters.size && @@ -59,27 +55,28 @@ internal class SchemaNeedsMigrationInspection : LocalInspectionTool() { ) ) return - } + }*/ - holder.registerProblem( - createTable.tableName, "Table needs to be added in a migration", GENERIC_ERROR, - CreateTableMigrationQuickFix(createTable, topMigrationFile) - ) - return - } + holder.registerProblem( + createTable.tableName, "Table needs to be added in a migration", GENERIC_ERROR, + CreateTableMigrationQuickFix(createTable, topMigrationFile) + ) + return + } - val oldSignature = Signature.create( - name = tableWithSameName.tableName.name, - type = null, - parameters = tableWithSameName.query.columns.mapNotNull { it.parameterValue() }, - additionalData = null, - ) ?: return + val oldSignature = Signature.create( + name = tableWithSameName.tableName.name, + type = null, + parameters = tableWithSameName.query.columns.mapNotNull { it.parameterValue() }, + additionalData = null, + ) ?: return - if (oldSignature != signature) { - holder.registerProblem( - createTable.tableName, "Table needs to be altered in a migration", GENERIC_ERROR, - AlterTableMigrationQuickFix(createTable, oldSignature, signature) - ) + if (oldSignature != signature) { + holder.registerProblem( + createTable.tableName, "Table needs to be altered in a migration", GENERIC_ERROR, + AlterTableMigrationQuickFix(createTable, oldSignature, signature) + ) + } } } } diff --git a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/UnusedColumnInspection.kt b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/UnusedColumnInspection.kt index 3a30f7bf8795..d6153bc58a88 100644 --- a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/UnusedColumnInspection.kt +++ b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/UnusedColumnInspection.kt @@ -1,12 +1,9 @@ package app.cash.sqldelight.intellij.inspections -import app.cash.sqldelight.core.lang.SqlDelightFile import app.cash.sqldelight.core.lang.SqlDelightFileType +import app.cash.sqldelight.core.lang.SqlDelightQueriesFile import app.cash.sqldelight.core.lang.util.findChildOfType -import app.cash.sqldelight.core.lang.util.findChildrenOfType -import com.alecstrong.sql.psi.core.psi.AliasElement import com.alecstrong.sql.psi.core.psi.SqlColumnDef -import com.alecstrong.sql.psi.core.psi.SqlColumnName import com.alecstrong.sql.psi.core.psi.SqlCreateTableStmt import com.alecstrong.sql.psi.core.psi.SqlTypes import com.alecstrong.sql.psi.core.psi.SqlVisitor @@ -19,7 +16,6 @@ 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.PsiFileFactory import com.intellij.psi.PsiManager @@ -35,44 +31,38 @@ internal class UnusedColumnInspection : LocalInspectionTool() { holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession - ): PsiElementVisitor = object : SqlVisitor() { - override fun visitCreateTableStmt(o: SqlCreateTableStmt) { - val candidates = o.findChildrenOfType() - .filter { columnDef -> - val columnName = columnDef.columnName - ReferencesSearch.search(columnName, columnName.useScope) - .allMatch { reference -> reference.element.parent is SqlColumnDef } + ) = ensureReady(session.file) { + object : SqlVisitor() { + override fun visitCreateTableStmt(o: SqlCreateTableStmt) = ignoreInvalidElements { + val candidates = o.columnDefList + .filter { columnDef -> + val columnName = columnDef.columnName + ReferencesSearch.search(columnName, columnName.useScope) + .allMatch { reference -> reference.element.parent is SqlColumnDef } + } + .toMutableList() + + if (candidates.isEmpty()) { + return } - .toMutableList() - if (candidates.isEmpty()) { - return - } - - val project = o.project - val psiManager = PsiManager.getInstance(project) - - fun PsiElement.columnDef(): SqlColumnDef? { - if (this is SqlColumnName) return parent.columnDef() - if (this is SqlColumnDef) return this - if (this is AliasElement) return source().columnDef() - return null - } - - FileTypeIndex.getFiles(SqlDelightFileType, GlobalSearchScope.allScope(project)) - .mapNotNull { vFile -> psiManager.findFile(vFile) as SqlDelightFile? } - .flatMap { file -> file.sqlStmtList?.stmtList.orEmpty() } - .flatMap { stmt -> stmt.compoundSelectStmt?.queryExposed().orEmpty() } - .flatMap { queryResult -> queryResult.columns } - .forEach { column -> - column.element.columnDef()?.let(candidates::remove) + val project = o.project + val psiManager = PsiManager.getInstance(project) + + FileTypeIndex.getFiles(SqlDelightFileType, GlobalSearchScope.allScope(project)).asSequence() + .mapNotNull { vFile -> psiManager.findFile(vFile) as SqlDelightQueriesFile? } + .flatMap { file -> file.namedQueries } + .flatMap { it.resultColumns.mapNotNull { it.column } } + .forEach { column -> + column.let(candidates::remove) + } + + candidates.forEach { columnDef -> + holder.registerProblem( + columnDef.columnName, "Unused symbol", ProblemHighlightType.LIKE_UNUSED_SYMBOL, + SafeDeleteQuickFix(o, columnDef) + ) } - - candidates.forEach { columnDef -> - holder.registerProblem( - columnDef.columnName, "Unused symbol", ProblemHighlightType.LIKE_UNUSED_SYMBOL, - SafeDeleteQuickFix(o, columnDef) - ) } } } diff --git a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/UnusedImportInspection.kt b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/UnusedImportInspection.kt index b05bf215d98e..2d562ce33ef6 100644 --- a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/UnusedImportInspection.kt +++ b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/UnusedImportInspection.kt @@ -9,7 +9,6 @@ import com.intellij.codeInsight.actions.OptimizeImportsProcessor import com.intellij.codeInspection.InspectionManager import com.intellij.codeInspection.LocalInspectionTool import com.intellij.codeInspection.LocalQuickFixOnPsiElement -import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ProblemHighlightType import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement @@ -24,10 +23,10 @@ internal class UnusedImportInspection : LocalInspectionTool() { file: PsiFile, manager: InspectionManager, isOnTheFly: Boolean - ): Array { + ) = ensureFileReady(file) { val javaTypes = file.columnJavaTypes() - return file.findChildrenOfType() + file.findChildrenOfType() .filter { importStmtMixin -> importStmtMixin.javaType.text.substringAfterLast(".").removeSuffix(";") !in javaTypes } diff --git a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/UnusedQueryInspection.kt b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/UnusedQueryInspection.kt index 6a7d16572f33..9ef8f0a5264b 100644 --- a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/UnusedQueryInspection.kt +++ b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/inspections/UnusedQueryInspection.kt @@ -1,6 +1,5 @@ package app.cash.sqldelight.intellij.inspections -import app.cash.sqldelight.core.lang.SqlDelightFile import app.cash.sqldelight.core.lang.psi.StmtIdentifierMixin import app.cash.sqldelight.core.lang.queriesName import app.cash.sqldelight.core.lang.util.findChildOfType @@ -15,12 +14,10 @@ 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.module.ModuleUtil 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.PsiInvalidElementAccessException import com.intellij.psi.SmartPointerManager import com.intellij.psi.search.FilenameIndex import com.intellij.psi.search.GlobalSearchScope @@ -30,22 +27,18 @@ 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 - ): PsiElementVisitor { - val sqlDelightFile = session.file as SqlDelightFile - val module = - ModuleUtil.findModuleForPsiElement(sqlDelightFile) ?: return PsiElementVisitor.EMPTY_VISITOR + ) = 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[0].methods return object : SqlDelightVisitor() { - override fun visitStmtIdentifier(o: SqlDelightStmtIdentifier) { + override fun visitStmtIdentifier(o: SqlDelightStmtIdentifier) = ignoreInvalidElements { if (o !is StmtIdentifierMixin || o.identifier() == null) { return } @@ -54,16 +47,8 @@ internal class UnusedQueryInspection : LocalInspectionTool() { } for (generatedMethod in generatedMethods) { val lightMethods = generatedMethod.toLightMethods() - for (psiMethod in lightMethods) { - val reference = try { - ReferencesSearch.search(psiMethod, psiMethod.useScope).findFirst() - } catch (e: PsiInvalidElementAccessException) { - // Failing during this search should be non fatal and ignored. - return - } - if (reference != null) { - return - } + if (lightMethods.any { ReferencesSearch.search(it, it.useScope).findFirst() != null }) { + return } } holder.registerProblem( diff --git a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/lang/SqlDelightFileViewProviderFactory.kt b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/lang/SqlDelightFileViewProviderFactory.kt index 42fa17a6f5f0..1e654b815943 100644 --- a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/lang/SqlDelightFileViewProviderFactory.kt +++ b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/lang/SqlDelightFileViewProviderFactory.kt @@ -112,7 +112,7 @@ private class SqlDelightFileViewProvider( try { generateSqlDelightCode() } catch (e: ProcessCanceledException) { - null + throw e } catch (e: Throwable) { // IDE generating code should be best effort - source of truth is always the gradle // build, and its better to ignore the error and try again than crash and require diff --git a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/lang/SqlDelightHighlightVisitor.kt b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/lang/SqlDelightHighlightVisitor.kt index ca468d101680..178cdd30e505 100644 --- a/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/lang/SqlDelightHighlightVisitor.kt +++ b/sqldelight-idea-plugin/src/main/kotlin/app/cash/sqldelight/intellij/lang/SqlDelightHighlightVisitor.kt @@ -19,8 +19,6 @@ import com.intellij.openapi.editor.DefaultLanguageHighlighterColors import com.intellij.openapi.editor.colors.TextAttributesKey import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile -import com.intellij.psi.impl.source.tree.LeafPsiElement -import com.intellij.psi.util.PsiTreeUtil class SqlDelightHighlightVisitor : SqlVisitor(), HighlightVisitor { @@ -108,14 +106,10 @@ class SqlDelightHighlightVisitor : SqlVisitor(), HighlightVisitor { } private fun visitImportStmt(o: SqlDelightImportStmt) { - PsiTreeUtil.findChildrenOfType(o, LeafPsiElement::class.java).forEach { - if (it.textMatches("import")) { - val info = HighlightInfo.newHighlightInfo(createSymbolTypeInfo(SQL_TYPE_NAME)) - .range(it.textRange) - .create() - myHolder?.add(info) - } - } + val info = HighlightInfo.newHighlightInfo(createSymbolTypeInfo(SQL_TYPE_NAME)) + .range(o.firstChild.textRange) + .create() + myHolder?.add(info) } private fun createSymbolTypeInfo(attributesKey: TextAttributesKey): HighlightInfoType { diff --git a/sqldelight-idea-plugin/src/test/kotlin/app/cash/sqldelight/intellij/SqlDelightFixtureTestCase.kt b/sqldelight-idea-plugin/src/test/kotlin/app/cash/sqldelight/intellij/SqlDelightFixtureTestCase.kt index 8cc851824968..dad00d09deb1 100644 --- a/sqldelight-idea-plugin/src/test/kotlin/app/cash/sqldelight/intellij/SqlDelightFixtureTestCase.kt +++ b/sqldelight-idea-plugin/src/test/kotlin/app/cash/sqldelight/intellij/SqlDelightFixtureTestCase.kt @@ -18,6 +18,7 @@ package app.cash.sqldelight.intellij import app.cash.sqldelight.core.SqlDelightDatabaseName import app.cash.sqldelight.core.SqlDelightFileIndex +import app.cash.sqldelight.core.SqlDelightProjectService import app.cash.sqldelight.core.SqldelightParserUtil import app.cash.sqldelight.core.lang.SqlDelightFile import app.cash.sqldelight.dialects.sqlite_3_18.SqliteDialect @@ -39,6 +40,7 @@ abstract class SqlDelightFixtureTestCase : LightJavaCodeInsightFixtureTestCase() SqliteDialect().setup() SqldelightParserUtil.overrideSqlParser() FileIndexMap.defaultIndex = LightFileIndex() + SqlDelightProjectService.getInstance(project).dialect = SqliteDialect() } inner class LightFileIndex : SqlDelightFileIndex { diff --git a/sqldelight-idea-plugin/testData/column-inspection/src/CreateView.sq b/sqldelight-idea-plugin/testData/column-inspection/src/CreateView.sq index d61e89363106..db76e4c172af 100644 --- a/sqldelight-idea-plugin/testData/column-inspection/src/CreateView.sq +++ b/sqldelight-idea-plugin/testData/column-inspection/src/CreateView.sq @@ -8,4 +8,5 @@ CREATE VIEW hockeyPlayerView AS SELECT * FROM hockeyPlayer; +someSelect: SELECT * FROM hockeyPlayerView; \ No newline at end of file diff --git a/sqldelight-idea-plugin/testData/column-inspection/src/SelectStar.sq b/sqldelight-idea-plugin/testData/column-inspection/src/SelectStar.sq index 741c65bf4fb4..ab2e6049a8ef 100644 --- a/sqldelight-idea-plugin/testData/column-inspection/src/SelectStar.sq +++ b/sqldelight-idea-plugin/testData/column-inspection/src/SelectStar.sq @@ -4,4 +4,5 @@ CREATE TABLE example ( dateTime TEXT ); +someSelect: SELECT * FROM example; \ No newline at end of file diff --git a/sqldelight-idea-plugin/testData/column-inspection/src/SelectTwo.sq b/sqldelight-idea-plugin/testData/column-inspection/src/SelectTwo.sq index b7fe4963c132..b6dbd9d256c2 100644 --- a/sqldelight-idea-plugin/testData/column-inspection/src/SelectTwo.sq +++ b/sqldelight-idea-plugin/testData/column-inspection/src/SelectTwo.sq @@ -4,4 +4,5 @@ CREATE TABLE example2 ( dateTime TEXT ); +someSelect: SELECT date, time FROM example2; \ No newline at end of file