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

#51 Migrate java inspections to kotlin #61

Merged
merged 4 commits into from
Dec 1, 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
9 changes: 9 additions & 0 deletions .github/workflows/run-ui-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ jobs:

steps:

# Free GitHub Actions Environment Disk Space
- name: Maximize Build Space
if : ${{ matrix.os == 'ubuntu-latest' }}
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
with:
tool-cache: false
large-packages: false


# Check out the current repository
- name: Fetch Sources
uses: actions/checkout@v4
Expand Down

This file was deleted.

30 changes: 30 additions & 0 deletions src/main/kotlin/ac/quant/quickfixspec/common/spec/XmlUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,34 @@ object XmlUtils {

return DEFINITION_GROUP_NAME.containsValue(parentTagName)
}

@JvmStatic
fun getCurrentTag(element: PsiElement): XmlTag {
if (element is XmlTag) {
return element
}

// iterate up the tree until we find the tag
var current = element
while (current !is XmlTag) {
current = current.parent
}
return current
}

//Check if the tag is a reference to a component tag
@JvmStatic
fun isComponentReference(tag: XmlTag): Boolean {
val parentTag = tag.parentTag ?: return false
if (parentTag.name == "components") {
return false
}

// if the tag is not self-closing that means it is a definition
if (tag.subTags.size > 0) {
return false
}

return true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package ac.quant.quickfixspec.inspections

import ac.quant.quickfixspec.common.spec.XmlUtils.findDefinition
import ac.quant.quickfixspec.common.spec.XmlUtils.getRootTag
import ac.quant.quickfixspec.common.spec.XmlUtils.isComponentReference
import ac.quant.quickfixspec.common.spec.XmlUtils.getCurrentTag

import ac.quant.quickfixspec.inspections.ReplaceWithDefinitionInspection.ReplaceWithDefinitionQuickFix
import com.intellij.codeInsight.intention.FileModifier.SafeFieldForPreview
import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
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.SmartPointerManager
import com.intellij.psi.SmartPsiElementPointer
import com.intellij.psi.xml.XmlTag
import com.intellij.util.IncorrectOperationException


class ReplaceWithDefinitionInspection : LocalInspectionTool() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : PsiElementVisitor() {
override fun visitElement(element: PsiElement) {
val rootTag = getRootTag(element) ?: return

val currentTag: XmlTag = getCurrentTag(element)
if (currentTag.name != "component") {
return
}


// return if the tag is not a component reference, and it's actually a definition from the "components" group
if (!isComponentReference(currentTag)) {
return
}

val componentName = currentTag.getAttributeValue("name") ?: return
val definition = findDefinition(componentName, "component", rootTag) ?: return

// check if we didn't already register a problem for this tag
if (holder.isOnTheFly && holder.results.any { it.psiElement == currentTag }) {
return
}

holder.registerProblem(currentTag, "Replace with definition", ReplaceWithDefinitionQuickFix(definition))
}
}
}

private class ReplaceWithDefinitionQuickFix(definition: XmlTag) : LocalQuickFix {
@SafeFieldForPreview
private val definitionPointer: SmartPsiElementPointer<XmlTag?> = SmartPointerManager.createPointer<XmlTag?>(definition)

override fun getName(): String {
return "Replace component reference with definition"
}

override fun getFamilyName(): String {
return name
}

@Throws(IncorrectOperationException::class)
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
// Get the <component> tag that is to be replaced
val tagToReplace = descriptor.psiElement as XmlTag

// Retrieve the full definition of the <component> from the pointer
val definition = definitionPointer.getElement()

if (definition != null) {
// Create a copy of the definition tag to be inserted
val newTag = definition.copy() as XmlTag

// Replace the original <component> tag with the full definition
tagToReplace.replace(newTag)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
<html lang="en">
<body>
<p>
Detects shorthand <code>&lt;component&gt;</code> tags (e.g., <code>&lt;component name="compName1" /&gt;</code>) within
<code>&lt;message&gt;</code> sections and suggests replacing them with their full definition from the
<code>&lt;components&gt;</code> section of the same XML file.
Detects shorthand <code>&lt;component&gt;</code> tags (e.g., <code>&lt;component name="compName1" /&gt;</code>)
and suggests replacing them with their full definition from the <code>&lt;components&gt;</code> section of the same XML file.
</p>

<h3>Example</h3>
<pre><code class="language-xml">
&lt;message&gt;
&lt;field name="field1"/&gt;
&lt;component name="compName1" /&gt;
&lt;field name="field2" /&gt;
&lt;/message&gt;

&lt;components&gt;
Expand All @@ -21,14 +22,19 @@ <h3>Example</h3>
</code></pre>

<p>
Suggestion: Expand <code>&lt;component name="compName1" /&gt;</code> to:
Suggestion: <strong>Replace component reference with definition</strong>
</p>

<p>Result:</p>
<pre><code class="language-xml">
&lt;component name="compName1"&gt;
&lt;field name="aa" /&gt;
&lt;field name="bb" /&gt;
&lt;/component&gt;
&lt;message&gt;
&lt;field name="field1"/&gt;
&lt;component name="compName1"&gt;
&lt;field name="aa" /&gt;
&lt;field name="bb" /&gt;
&lt;/component&gt;
&lt;field name="field2" /&gt;
&lt;/message&gt;
</code></pre>

</body>
Expand Down
25 changes: 22 additions & 3 deletions src/test/kotlin/ac/quant/quickfixspec/common/spec/TestXmlUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ class TestXmlUtils : BasePlatformTestCase() {
}
}


fun testFindDefinition() {
val psiFile = getXmlFile(myFixture, "FIX44.xml")
val rootTag = psiFile.findElementAt(0)!!.parent as XmlTag
Expand Down Expand Up @@ -111,10 +110,8 @@ class TestXmlUtils : BasePlatformTestCase() {
}
}


fun testIsTagDeclaration() {
val psiFile = getXmlFile(myFixture, "FIX44.xml")
val rootTag = psiFile.findElementAt(0)!!.parent as XmlTag

val indexes = arrayOf(2650, 2785, 108734, 167517)
val expectedDeclarations = arrayOf(false, false, true, true)
Expand All @@ -133,6 +130,28 @@ class TestXmlUtils : BasePlatformTestCase() {

}

fun testGetCurrentTag() {
val psiFile = getXmlFile(myFixture, "FIX44.xml")

val expectedTagsMap = mapOf(
0 to "fix",
2650 to "field",
2785 to "component",
4879 to "message",
12818 to "group",
108734 to "component",
167517 to "field"
)

for (positionInFile in expectedTagsMap.keys) {
val expectedTag = expectedTagsMap[positionInFile]!!
val psiElement = psiFile.findElementAt(positionInFile)!!

val currentTag = XmlUtils.getCurrentTag(psiElement)
assertEquals("Current tag for element at positionInFile $positionInFile should be '$expectedTag' but was '${currentTag.name}'", expectedTag, currentTag.name)
}
}

override fun getTestDataPath() = "src/main/resources/spec"

}