New: Added inspection suppression possibility and quickfix.

This commit is contained in:
Chris Hodges 2021-08-09 10:08:41 +02:00
parent 593719043e
commit a3f7ddb4f7
6 changed files with 172 additions and 0 deletions

View File

@ -39,6 +39,9 @@ good enough" to get started, and I can return to demo coding with its current st
### Inspections
The plugin provides a few inspections for code analysis. An error or warning can be suppressed by placing a `; suppress <InspectionName>` comment either on an
end of line comment behind the statement or in a full line comment above the statement.
#### M68kSyntaxInspection - Assembly instruction validity
Checks the validity of the current instruction. If an instruction is not recognized, you may get one of the following errors:
@ -150,6 +153,7 @@ make it work with JUnit 5. Feel free to use the code (in package ```de.platon42.
- Enhancement: `include`, `incdir` and `incbin` and `output` with `<pathname>` quotes no longer cause syntax error.
- New: Files in `include` directives can be referenced and renamed/refactored.
- New: Code completion for local label definitions, suggesting undefined labels already referenced.
- New: Added inspection suppression possibility and quickfix.
### V0.5 (06-Aug-21)

View File

@ -63,6 +63,7 @@ patchPluginXml {
<li>Enhancement: 'include', 'incdir' and 'incbin' and 'output' with '&lt;pathname&gt;' quotes no longer cause syntax error.
<li>New: Files in 'include' directives can be referenced and renamed/refactored.
<li>New: Code completion for local label definitions, suggesting undefined labels already referenced.
<li>New: Added inspection suppression possibility and quickfix.
</ul>
<h4>V0.5 (06-Aug-21)</h4>
<ul>

View File

@ -0,0 +1,83 @@
package de.platon42.intellij.plugins.m68k.inspections
import com.intellij.codeInspection.InspectionSuppressor
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.SuppressQuickFix
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import de.platon42.intellij.plugins.m68k.psi.M68kPsiElement
import de.platon42.intellij.plugins.m68k.psi.M68kStatement
class M68kInspectionSuppressor : InspectionSuppressor {
companion object {
const val MARKER = "suppress "
}
override fun isSuppressedFor(element: PsiElement, toolId: String): Boolean {
if (element !is M68kPsiElement) return false
val statement = PsiTreeUtil.getParentOfType(element, M68kStatement::class.java) ?: return false
val nextToken = PsiTreeUtil.skipWhitespacesForward(statement)
return isSuppressedWithComment(nextToken, toolId) || isSuppressedWithComment(PsiTreeUtil.skipWhitespacesBackward(statement), toolId)
}
override fun getSuppressActions(element: PsiElement?, toolId: String): Array<SuppressQuickFix> {
return arrayOf(LineSuppressQuickFix(toolId))
}
private fun isSuppressedWithComment(nextToken: PsiElement?, toolId: String): Boolean {
return if (nextToken is PsiComment) {
val comment = nextToken.text
comment.contains(MARKER) && comment.contains(toolId)
} else {
false
}
}
class LineSuppressQuickFix(private val toolId: String) : SuppressQuickFix {
override fun getFamilyName() = "Suppress for statement"
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val statement = PsiTreeUtil.getParentOfType(descriptor.startElement, M68kStatement::class.java) ?: return
val document = PsiDocumentManager.getInstance(project).getDocument(statement.containingFile) ?: return
val lineNumber = document.getLineNumber(statement.textOffset)
var usePrevLine = false
val nextToken = PsiTreeUtil.skipWhitespacesForward(statement)
if (nextToken is PsiComment) {
val comment = nextToken.text
if (comment.contains(MARKER)) {
if (!comment.contains(toolId)) {
document.insertString(document.getLineEndOffset(lineNumber), " $toolId")
}
return
} else {
usePrevLine = true
}
}
val prevToken = PsiTreeUtil.skipWhitespacesBackward(statement)
if (prevToken is PsiComment) {
val comment = prevToken.text
if (comment.contains(MARKER)) {
if (!comment.contains(toolId)) {
document.insertString(document.getLineEndOffset(document.getLineNumber(prevToken.textOffset)), " $toolId")
}
return
}
}
if (usePrevLine) {
document.insertString(document.getLineStartOffset(lineNumber), "; $MARKER$toolId\n")
} else {
document.insertString(document.getLineEndOffset(lineNumber), "\t; $MARKER$toolId")
}
}
override fun isAvailable(project: Project, context: PsiElement) = true
override fun isSuppressAll() = false
}
}

View File

@ -30,6 +30,7 @@
<lang.quoteHandler language="MC68000" implementationClass="de.platon42.intellij.plugins.m68k.M68kStringQuoteHandler"/>
<lang.findUsagesProvider language="MC68000" implementationClass="de.platon42.intellij.plugins.m68k.scanner.M68kFindUsagesProvider"/>
<lang.psiStructureViewFactory language="MC68000" implementationClass="de.platon42.intellij.plugins.m68k.structureview.M68kStructureViewFactory"/>
<lang.inspectionSuppressor language="MC68000" implementationClass="de.platon42.intellij.plugins.m68k.inspections.M68kInspectionSuppressor"/>
<lang.elementManipulator forClass="de.platon42.intellij.plugins.m68k.psi.M68kSymbolReference"
implementationClass="de.platon42.intellij.plugins.m68k.refs.M68kSymbolReferenceElementManipulator"/>
<lang.elementManipulator forClass="de.platon42.intellij.plugins.m68k.psi.M68kMacroCall"

View File

@ -1,5 +1,6 @@
package de.platon42.intellij.plugins.m68k.inspections
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import de.platon42.intellij.jupiter.LightCodeInsightExtension
import de.platon42.intellij.jupiter.TestDataPath
@ -16,4 +17,16 @@ abstract class AbstractInspectionTest : AbstractM68kTest() {
assertThat(myFixture.doHighlighting())
.areExactly(count, Condition({ it.description?.contains(snippet) ?: false }, "containing"))
}
protected fun executeQuickFixes(myFixture: CodeInsightTestFixture, regex: Regex, expectedFixes: Int) {
val quickfixes = getQuickFixes(myFixture, regex, expectedFixes)
assertThat(quickfixes.groupBy { it.familyName }).hasSize(1)
quickfixes.forEach(myFixture::launchAction)
}
protected fun getQuickFixes(myFixture: CodeInsightTestFixture, regex: Regex, expectedFixes: Int): List<IntentionAction> {
val quickfixes = myFixture.getAllQuickFixes().filter { it.text.matches(regex) }
assertThat(quickfixes).`as`("Fixes matched by $regex: ${myFixture.getAllQuickFixes().map { it.text }}").hasSize(expectedFixes)
return quickfixes
}
}

View File

@ -0,0 +1,70 @@
package de.platon42.intellij.plugins.m68k.inspections
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
import de.platon42.intellij.jupiter.MyFixture
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
internal class M68kInspectionSuppressorTest : AbstractInspectionTest() {
companion object {
private const val SUPPRESS_FOR_STATEMENT = "Suppress for statement"
}
@Test
internal fun suppresses_inspection_with_end_of_line_comment(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.enableInspections(M68kSyntaxInspection::class.java)
myFixture.configureByText("syntax.asm", " rts d0 ; suppress M68kSyntax")
assertThat(myFixture.doHighlighting()).isEmpty()
}
@Test
internal fun suppresses_inspection_with_full_line_comment_on_line_before(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.enableInspections(M68kSyntaxInspection::class.java)
myFixture.configureByText("syntax.asm", "; suppress M68kSyntax\n rts d0")
assertThat(myFixture.doHighlighting()).isEmpty()
}
@Test
internal fun suppression_works_without_prior_comment(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.enableInspections(M68kSyntaxInspection::class.java)
myFixture.configureByText("syntax.asm", " rts<caret> d0")
executeSuppressAction(myFixture, SUPPRESS_FOR_STATEMENT)
myFixture.checkResult(" rts d0\t; suppress M68kSyntax")
}
@Test
internal fun suppression_works_with_prior_suppression_comment(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.enableInspections(M68kSyntaxInspection::class.java)
myFixture.configureByText("syntax.asm", " rts<caret> d0; suppress foobar")
executeSuppressAction(myFixture, SUPPRESS_FOR_STATEMENT)
myFixture.checkResult(" rts d0; suppress foobar M68kSyntax")
}
@Test
internal fun suppression_works_with_prior_suppression_comment_in_line_above(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.enableInspections(M68kSyntaxInspection::class.java)
myFixture.configureByText("syntax.asm", "; suppress foobar\n rts<caret> d0")
executeSuppressAction(myFixture, SUPPRESS_FOR_STATEMENT)
myFixture.checkResult("; suppress foobar M68kSyntax\n rts d0")
}
@Test
internal fun suppression_works_by_adding_suppression_comment_in_line_above(@MyFixture myFixture: CodeInsightTestFixture) {
myFixture.enableInspections(M68kSyntaxInspection::class.java)
myFixture.configureByText("syntax.asm", " rts<caret> d0 ; unrelated comment")
executeSuppressAction(myFixture, SUPPRESS_FOR_STATEMENT)
myFixture.checkResult("; suppress M68kSyntax\n rts d0 ; unrelated comment")
}
private fun executeSuppressAction(myFixture: CodeInsightTestFixture, suppressAction: String) {
val highlightInfos = myFixture.doHighlighting()
assertThat(highlightInfos).hasSize(1)
val quickFixPair = highlightInfos[0].quickFixActionRanges[0]
val intentionActionDescriptor = quickFixPair.first
val element = myFixture.file.findElementAt(quickFixPair.second.startOffset)!!
val suppressQuickFix = intentionActionDescriptor.getOptions(element, myFixture.editor)!!
.first { it.text == suppressAction }
myFixture.launchAction(suppressQuickFix)
}
}