Skip to content

Commit

Permalink
allow suppressing lint inspection with attributes (#265)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkurnikov authored Dec 24, 2024
1 parent f60e335 commit 3318588
Show file tree
Hide file tree
Showing 17 changed files with 222 additions and 23 deletions.
4 changes: 2 additions & 2 deletions src/main/kotlin/org/move/ide/formatter/impl/spacing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import com.intellij.psi.tree.IElementType
import com.intellij.psi.tree.TokenSet
import org.move.ide.formatter.MvFmtContext
import org.move.lang.MvElementTypes.*
import org.move.lang.core.MOVE_COMMENTS
import org.move.lang.core.MV_COMMENTS
import org.move.lang.core.MOVE_KEYWORDS
import org.move.lang.core.psi.MvAddressBlock
import org.move.lang.core.psi.MvModule
Expand Down Expand Up @@ -219,7 +219,7 @@ private fun ASTNode?.isWhiteSpaceWithLineBreak(): Boolean =
this != null && elementType == TokenType.WHITE_SPACE && textContains('\n')

private fun SpacingContext.needsBlankLineBetweenItems(): Boolean {
if (elementType1 in MOVE_COMMENTS || elementType2 in MOVE_COMMENTS)
if (elementType1 in MV_COMMENTS || elementType2 in MV_COMMENTS)
return false

// Allow to keep consecutive runs of `use`, `const` or other "one line" items without blank lines
Expand Down
98 changes: 98 additions & 0 deletions src/main/kotlin/org/move/ide/inspections/MvInspectionSuppressor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.move.ide.inspections

import com.intellij.codeInspection.*
import com.intellij.codeInspection.util.IntentionFamilyName
import com.intellij.codeInspection.util.IntentionName
import com.intellij.icons.AllIcons
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Iconable
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiWhiteSpace
import org.move.lang.core.MV_COMMENTS
import org.move.lang.core.psi.MvAttr
import org.move.lang.core.psi.MvFunction
import org.move.lang.core.psi.MvModule
import org.move.lang.core.psi.ext.*
import org.move.lang.core.psi.psiFactory
import javax.swing.Icon

class MvInspectionSuppressor: InspectionSuppressor {
override fun getSuppressActions(element: PsiElement?, toolId: String): Array<out SuppressQuickFix> {
val ancestors = element?.ancestors.orEmpty().filterIsInstance<MvDocAndAttributeOwner>()
// todo: add suppression with comments for other inspections
if (toolId !in APTOS_LINTS) return emptyArray()
return ancestors.mapNotNull {
when (it) {
is MvFunction -> {
SuppressInspectionWithAttributeFix(it, toolId, "function")
}
is MvModule -> SuppressInspectionWithAttributeFix(it, toolId, "module")
else -> null
}
}.toList().toTypedArray()
}

override fun isSuppressedFor(element: PsiElement, toolId: String): Boolean {
return element.ancestors.filterIsInstance<MvDocAndAttributeOwner>()
.any {
isSuppressedByComment(it, toolId)
|| isSuppressedByAttribute(it, toolId)
}
}

private fun isSuppressedByComment(element: MvDocAndAttributeOwner, toolId: String): Boolean {
return element.leadingComments().any { comment ->
val matcher = SuppressionUtil.SUPPRESS_IN_LINE_COMMENT_PATTERN.matcher(comment.text)
matcher.matches() && SuppressionUtil.isInspectionToolIdMentioned(matcher.group(1), toolId)
}
}

// special alternativeId for some inspection that could be suppressed by the attributes, in form of
// lint::LINT_NAME, and it's suppressable by the #[lint::skip(LINT_NAME)]
private fun isSuppressedByAttribute(element: MvDocAndAttributeOwner, toolId: String): Boolean {
if (toolId !in APTOS_LINTS) return false
return element.queryAttributes
.getAttrItemsByPath("lint::skip")
.any { it.innerAttrItems.any { it.textMatches(toolId) } }
}

@Suppress("PrivatePropertyName")
private val APTOS_LINTS = setOf(MvNeedlessDerefRefInspection.LINT_ID)
}

private class SuppressInspectionWithAttributeFix(
item: MvDocAndAttributeOwner,
val toolId: String,
val itemId: String,
): LocalQuickFixOnPsiElement(item), ContainerBasedSuppressQuickFix, Iconable {

override fun getFamilyName(): @IntentionFamilyName String = "Suppress '$toolId' inspections"
override fun getText(): @IntentionName String = "Suppress with '#[lint::skip($toolId)]' for $itemId"

override fun isSuppressAll(): Boolean = false
override fun getIcon(flags: Int): Icon? = AllIcons.Ide.HectorOff

override fun getContainer(context: PsiElement?): PsiElement? = this.startElement

override fun isAvailable(
project: Project,
context: PsiElement
): Boolean = context.isValid && getContainer(context) != null

override fun invoke(project: Project, file: PsiFile, startElement: PsiElement, endElement: PsiElement) {
val element = startElement as? MvDocAndAttributeOwner ?: return
val skipAttribute = project.psiFactory.attribute("#[lint::skip($toolId)]")
val anchor = element.childrenWithLeaves
.dropWhile { it is PsiComment || it is PsiWhiteSpace || it is MvAttr }.firstOrNull()
val addedAttribute = element.addBefore(skipAttribute, anchor)
element.addAfter(project.psiFactory.newline(), addedAttribute)
}
}

private fun MvDocAndAttributeOwner.leadingComments(): Sequence<PsiComment> =
generateSequence(firstChild) { psi ->
psi.nextSibling.takeIf { it.elementType in MV_COMMENTS || it is PsiWhiteSpace }
}
.filterIsInstance<PsiComment>()
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ abstract class MvLocalInspectionTool : LocalInspectionTool() {

abstract class DiagnosticFix<T : PsiElement>(element: T) : LocalQuickFixOnPsiElement(element) {

val targetElement: T? get() = this.startElement
protected val targetElement: T? get() = this.startElement

override fun getStartElement(): T? {
@Suppress("UNCHECKED_CAST")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,24 @@ import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.NonNls
import org.move.lang.core.psi.MvBorrowExpr
import org.move.lang.core.psi.MvDerefExpr
import org.move.lang.core.psi.MvExpr
import org.move.lang.core.psi.MvParensExpr
import org.move.lang.core.psi.MvVisitor
import org.move.lang.core.psi.ext.unwrap

class MvRedundantRefDerefInspection: MvLocalInspectionTool() {
class MvNeedlessDerefRefInspection: MvLocalInspectionTool() {
override fun getID(): @NonNls String = LINT_ID

override fun buildMvVisitor(
holder: ProblemsHolder,
isOnTheFly: Boolean
): MvVisitor = object: MvVisitor() {

override fun visitDerefExpr(o: MvDerefExpr) {
val a = 1
val innerExpr = o.innerExpr
if (innerExpr is MvBorrowExpr) {
if (innerExpr.expr == null) return
Expand All @@ -41,6 +45,10 @@ class MvRedundantRefDerefInspection: MvLocalInspectionTool() {
element.replace(itemExpr)
}
}

companion object {
const val LINT_ID = "needless_deref_ref"
}
}

private val MvDerefExpr.innerExpr: MvExpr? get() {
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/org/move/ide/refactoring/MvImportOptimizer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,11 @@ class MvImportOptimizer : ImportOptimizer {
.sorted()
for ((useWrapper, nextUseWrapper) in sortedUses.withNext()) {
val addedUseItem = itemsOwner.addBefore(useWrapper.useStmt, firstItem)
itemsOwner.addAfter(psiFactory.createNewline(), addedUseItem)
itemsOwner.addAfter(psiFactory.newline(), addedUseItem)
val addNewLine =
useWrapper.packageGroupLevel != nextUseWrapper?.packageGroupLevel
if (addNewLine) {
itemsOwner.addAfter(psiFactory.createNewline(), addedUseItem)
itemsOwner.addAfter(psiFactory.newline(), addedUseItem)
}
}
useStmts.forEach {
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/org/move/ide/search/MvWordsScanner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package org.move.ide.search
import com.intellij.lang.cacheBuilder.DefaultWordsScanner
import org.move.lang.MvElementTypes.BYTE_STRING_LITERAL
import org.move.lang.MvElementTypes.IDENTIFIER
import org.move.lang.core.MOVE_COMMENTS
import org.move.lang.core.MV_COMMENTS
import org.move.lang.core.lexer.createMoveLexer
import org.move.lang.core.tokenSetOf

class MvWordsScanner : DefaultWordsScanner(
createMoveLexer(),
tokenSetOf(IDENTIFIER),
MOVE_COMMENTS,
MV_COMMENTS,
tokenSetOf(BYTE_STRING_LITERAL)
)
6 changes: 3 additions & 3 deletions src/main/kotlin/org/move/ide/typing/MvBraceMatcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import com.intellij.psi.tree.TokenSet
import org.move.lang.MoveFileType
import org.move.lang.MoveLanguage
import org.move.lang.MvElementTypes.*
import org.move.lang.core.MOVE_COMMENTS
import org.move.lang.core.MV_COMMENTS
import java.util.*

private class MvPairedBraceMatcher : PairedBraceMatcher {
Expand All @@ -32,7 +32,7 @@ private class MvPairedBraceMatcher : PairedBraceMatcher {
)

private val InsertPairBraceBefore = TokenSet.orSet(
MOVE_COMMENTS,
MV_COMMENTS,
TokenSet.create(
TokenType.WHITE_SPACE,
SEMICOLON,
Expand Down Expand Up @@ -144,7 +144,7 @@ class MvBraceMatcher : PairedBraceMatcherAdapter(MvPairedBraceMatcher(), MoveLan
private val OPEN_BRACES = TokenSet.create(LT, L_PAREN, L_BRACE, L_BRACK)

val TYPE_PARAMETER_TOKENS = TokenSet.orSet(
MOVE_COMMENTS,
MV_COMMENTS,
TokenSet.create(
TokenType.WHITE_SPACE,
IDENTIFIER,
Expand Down
3 changes: 1 addition & 2 deletions src/main/kotlin/org/move/ide/utils/imports/ImportUtils.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.move.ide.utils.imports

import com.intellij.util.concurrency.annotations.RequiresWriteLock
import org.move.ide.inspections.imports.usageScope
import org.move.lang.core.psi.*
import org.move.lang.core.psi.ext.*
Expand Down Expand Up @@ -159,7 +158,7 @@ private val <T: MvElement> List<T>.lastElement: T? get() = maxByOrNull { it.text
@Suppress("SameReturnValue")
private fun insertUseStmtAtTheCorrectLocation(mod: MvItemsOwner, useStmt: MvUseStmt): Boolean {
val psiFactory = MvPsiFactory(mod.project)
val newline = psiFactory.createNewline()
val newline = psiFactory.newline()
val useStmts = mod.childrenOfType<MvUseStmt>().map(::UseStmtWrapper)
if (useStmts.isEmpty()) {
val anchor = mod.firstItem
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/org/move/lang/core/MoveParserUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ object MoveParserUtil: GeneratedParserUtilBase() {
@JvmStatic
private fun consumeStopOnWhitespaceOrComment(b: PsiBuilder, tokens: TokenSet): Boolean {
val nextTokenType = b.rawLookup(1)
if (nextTokenType in MOVE_COMMENTS || nextTokenType == WHITE_SPACE) {
if (nextTokenType in MV_COMMENTS || nextTokenType == WHITE_SPACE) {
consumeToken(b, tokens)
return false
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/org/move/lang/core/MvTokenType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ val MOVE_KEYWORDS = TokenSet.orSet(
val FUNCTION_MODIFIERS = tokenSetOf(VISIBILITY_MODIFIER, NATIVE, ENTRY, INLINE)
val TYPES = tokenSetOf(PATH_TYPE, REF_TYPE, TUPLE_TYPE)

val MOVE_COMMENTS = tokenSetOf(BLOCK_COMMENT, EOL_COMMENT, EOL_DOC_COMMENT)
val MV_COMMENTS = tokenSetOf(BLOCK_COMMENT, EOL_COMMENT, EOL_DOC_COMMENT)

val MOVE_ARITHMETIC_BINARY_OPS = tokenSetOf(
PLUS, MINUS, MUL, DIV, MODULO,
Expand Down
10 changes: 7 additions & 3 deletions src/main/kotlin/org/move/lang/core/psi/MvPsiFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@ class MvPsiFactory(val project: Project) {
?: error("`$text`")
}

fun attribute(attrText: String): MvAttr {
return createFromText("$attrText module 0x0::_DummyModule {} ")
?: error("Invalid attribute `$attrText`")
}

fun function(text: String, moduleName: String = "_Dummy"): MvFunction =
createFromText("module $moduleName { $text } ")
?: error("Failed to create a function from text: `$text`")
Expand All @@ -199,10 +204,9 @@ class MvPsiFactory(val project: Project) {
createFromText("module $moduleName { $text } ")
?: error("Failed to create a function from text: `$text`")

fun createWhitespace(ws: String): PsiElement =
PsiParserFacade.getInstance(project).createWhiteSpaceFromText(ws)
fun createWhitespace(ws: String): PsiElement = PsiParserFacade.getInstance(project).createWhiteSpaceFromText(ws)

fun createNewline(): PsiElement = createWhitespace("\n")
fun newline(): PsiElement = createWhitespace("\n")

inline fun <reified T: MvElement> createFromText(@Language("Move") code: CharSequence): T? {
val dummyFile = PsiFileFactory.getInstance(project)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ class QueryAttributes(
fun getAttrItem(attributeName: String): MvAttrItem? =
this.attrItems.find { it.unqualifiedName == attributeName }

fun getAttrItemsByPath(fqPath: String): Sequence<MvAttrItem> =
this.attrItems.filter { it.path.text == fqPath }

val attrItems: Sequence<MvAttrItem> get() = this.attributes.flatMap { it.attrItemList }

override fun toString(): String =
Expand Down
6 changes: 3 additions & 3 deletions src/main/kotlin/org/move/lang/core/psi/ext/PsiElement.kt
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ inline fun <reified T: PsiElement> PsiElement.ancestorStrict(stopAt: Class<out P
inline fun <reified T: PsiElement> PsiElement.ancestorOrSelf(): T? =
PsiTreeUtil.getParentOfType(this, T::class.java, false)

inline fun <reified T: PsiElement> PsiElement.ancestorOrSelf(stopAt: Class<out PsiElement>): T? =
PsiTreeUtil.getParentOfType(this, T::class.java, false, stopAt)

fun <T: PsiElement> PsiElement.ancestorOfClass(psiClass: Class<T>, strict: Boolean = false): T? =
PsiTreeUtil.getParentOfType(this, psiClass, strict)

Expand All @@ -180,9 +183,6 @@ inline fun <reified T: PsiElement> PsiElement.hasAncestor(): Boolean =
inline fun <reified T: PsiElement> PsiElement.hasAncestorOrSelf(): Boolean =
ancestorOrSelf<T>() != null

inline fun <reified T: PsiElement> PsiElement.ancestorOrSelf(stopAt: Class<out PsiElement>): T? =
PsiTreeUtil.getParentOfType(this, T::class.java, false, stopAt)

inline fun <reified T: PsiElement> PsiElement.stubAncestorStrict(): T? =
PsiTreeUtil.getStubOrPsiParentOfType(this, T::class.java)

Expand Down
4 changes: 3 additions & 1 deletion src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@
<!-- displayName="Attempt to override a built-in function"-->
<!-- enabledByDefault="true" level="ERROR"-->
<!-- implementationClass="org.move.ide.inspections.lints.FunctionNamingInspection"/>-->
<lang.inspectionSuppressor language="Move"
implementationClass="org.move.ide.inspections.MvInspectionSuppressor" />
<localInspection language="Move" groupName="Move"
displayName="Unresolved reference"
enabledByDefault="true"
Expand Down Expand Up @@ -308,7 +310,7 @@
<localInspection language="Move" groupName="Move"
displayName="Needless pair of `*` and `&amp;` operators"
enabledByDefault="true" level="WEAK WARNING"
implementationClass="org.move.ide.inspections.MvRedundantRefDerefInspection" />
implementationClass="org.move.ide.inspections.MvNeedlessDerefRefInspection" />

<!-- cannot be run on-the-fly, therefore enabled -->
<globalInspection language="Move" groupName="Move"
Expand Down
Loading

0 comments on commit 3318588

Please sign in to comment.