Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow suppressing lint inspection with attributes #265

Merged
merged 1 commit into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading