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

Subsection not being child of Section inspection #1492

Merged
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
3 changes: 3 additions & 0 deletions resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,9 @@
<localInspection language="Latex" implementationClass="nl.hannahsten.texifyidea.inspections.latex.LatexMissingLabelInspection"
groupName="LaTeX" displayName="Missing labels"
enabledByDefault="true"/>
<localInspection language="Latex" implementationClass="nl.hannahsten.texifyidea.inspections.latex.LatexIncorrectSectionNestingInspection"
groupName="LaTeX" displayName="Incorrect nesting"
enabledByDefault="true"/>
<localInspection language="Latex" implementationClass="nl.hannahsten.texifyidea.inspections.latex.LatexLabelBeforeCaptionInspection"
groupName="LaTeX" displayName="Label before caption"
enabledByDefault="true"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<html>
<body>
It is encouraged to use subsection only within section, subparagraph in paragraph and so on.
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package nl.hannahsten.texifyidea.inspections.latex

import com.intellij.codeInspection.InspectionManager
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import nl.hannahsten.texifyidea.index.LatexCommandsIndex
import nl.hannahsten.texifyidea.insight.InsightGroup
import nl.hannahsten.texifyidea.inspections.TexifyInspectionBase
import nl.hannahsten.texifyidea.psi.LatexCommands
import nl.hannahsten.texifyidea.util.files.document
import nl.hannahsten.texifyidea.util.files.openedEditor
import nl.hannahsten.texifyidea.util.lineIndentation
import nl.hannahsten.texifyidea.util.replaceString

/**
* @author Johannes Berger
*/
open class LatexIncorrectSectionNestingInspection : TexifyInspectionBase() {

override val inspectionGroup = InsightGroup.LATEX

override val inspectionId = "IncorrectSectionNesting"

override fun getDisplayName() = "Incorrect nesting"

override fun inspectFile(file: PsiFile, manager: InspectionManager, isOntheFly: Boolean): List<ProblemDescriptor> {

return LatexCommandsIndex.getCommandsByNames(file, *sectioningCommands())
.sortedBy { it.textOffset }
.zipWithNext()
.filter { (first, second) -> first.commandName() in commandToForbiddenPredecessors[second.commandName()] ?: error("Unexpected command") }
.map { manager.createProblemDescriptor(it.second,
"Incorrect nesting",
arrayOf(InsertParentCommandFix(), ChangeToParentCommandFix()),
ProblemHighlightType.WEAK_WARNING,
isOntheFly,
false)
}
}

private fun sectioningCommands() = commandToForbiddenPredecessors.keys.toTypedArray()

private fun LatexCommands.commandName(): String = this.commandToken.text

private class InsertParentCommandFix : LocalQuickFix {

override fun getFamilyName() = "Insert missing parent command"

override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val command = descriptor.psiElement as LatexCommands
val document = command.containingFile.document() ?: return
val offset = command.textOffset
val lineNumber = document.getLineNumber(offset)
val newParentCommand = command.commandToken.text.replaceFirst("sub", "")
val replacement = "$newParentCommand{}\n${document.lineIndentation(lineNumber)}"
val caret = command.containingFile.openedEditor()?.caretModel
document.insertString(offset, replacement)
caret?.moveToOffset(offset + newParentCommand.length + 1)
}
}

private class ChangeToParentCommandFix : LocalQuickFix {

override fun getFamilyName() = "Change to parent command"

override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val command = descriptor.psiElement as LatexCommands
val document = command.containingFile.document() ?: return
val range = command.commandToken.textRange
val newParentCommand = command.commandToken.text.replaceFirst("sub", "")
document.replaceString(range, newParentCommand)
}
}

companion object {

val commandToForbiddenPredecessors = mapOf(
"""\part""" to emptyList(),
"""\chapter""" to emptyList(),
"""\section""" to emptyList(),
"""\subsection""" to listOf("""\part""", """\chapter"""),
"""\subsubsection""" to listOf("""\part""", """\chapter""", """\section"""),
"""\paragraph""" to emptyList(),
"""\subparagraph""" to listOf("""\part""", """\chapter""", """\section""", """\subsection""", """\subsubsection""")
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package nl.hannahsten.texifyidea.inspections.latex

import com.intellij.testFramework.fixtures.BasePlatformTestCase
import nl.hannahsten.texifyidea.file.LatexFileType
import nl.hannahsten.texifyidea.testutils.writeCommand
import org.junit.Test

internal class LatexIncorrectSectionNestingInspectionTest : BasePlatformTestCase() {

override fun setUp() {
super.setUp()
myFixture.enableInspections(LatexIncorrectSectionNestingInspection())
}

@Test
fun `test document missing subsection warning`() {
testInsertMissingParentCommandQuickFix("""
\begin{document}
\section{}
\subsubsection{}
\end{document}
""".trimIndent(), """
\begin{document}
\section{}
\subsection{<caret>}
\subsubsection{}
\end{document}
""".trimIndent())
}

@Test
fun `test subsection after chapter warning`() {
testInsertMissingParentCommandQuickFix("""
\begin{document}
\chapter{}
\subsection{}
\end{document}
""".trimIndent(), """
\begin{document}
\chapter{}
\section{<caret>}
\subsection{}
\end{document}
""".trimIndent())
}

@Test
fun `test change subsubsection to subsection quick fix`() {
testChangeToParentCommandQuickFix("""
\begin{document}
\section{}
\subsubsection{}
\end{document}
""".trimIndent(), """
\begin{document}
\section{}
\subsection{}
\end{document}
""".trimIndent())
}

@Test
fun `test subparagraph after section warning`() {
testInsertMissingParentCommandQuickFix("""
\begin{document}
\section{}
\subparagraph{}
\end{document}
""".trimIndent(), """
\begin{document}
\section{}
\paragraph{<caret>}
\subparagraph{}
\end{document}
""".trimIndent())
}

@Test
fun `test missing parent command warning`() {
myFixture.configureByText(LatexFileType, """
\begin{document}
\section{}
<weak_warning descr="Incorrect nesting">\subsubsection{}</weak_warning>
\end{document}
""".trimIndent())
myFixture.checkHighlighting(false, false, true, false)
}

@Test
fun `test no warning on correct nesting`() {
myFixture.configureByText(LatexFileType, """
\begin{document}
\part{}
\part{}
\chapter{}
\section{}
\section{}
\subsection{}
\subsubsection{}
\subsubsection{}
\section{}
\paragraph{}
\paragraph{}
\subparagraph{}
\subparagraph{}
\end{document}
""".trimIndent())
myFixture.checkHighlighting(false, false, true, false)
}

private fun testInsertMissingParentCommandQuickFix(before: String, after: String) {
myFixture.configureByText(LatexFileType, before)
val quickFixes = myFixture.getAllQuickFixes()
writeCommand(myFixture.project) {
quickFixes.first { it.familyName == "Insert missing parent command" }.invoke(myFixture.project, myFixture.editor, myFixture.file)
}

myFixture.checkResult(after)
}

private fun testChangeToParentCommandQuickFix(before: String, after: String) {
myFixture.configureByText(LatexFileType, before)
val quickFixes = myFixture.getAllQuickFixes()
writeCommand(myFixture.project) {
quickFixes.first { it.familyName == "Change to parent command" }.invoke(myFixture.project, myFixture.editor, myFixture.file)
}

myFixture.checkResult(after)
}
}