diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml
index dd4125b2b..44da70467 100644
--- a/resources/META-INF/plugin.xml
+++ b/resources/META-INF/plugin.xml
@@ -500,6 +500,9 @@
+
diff --git a/resources/inspectionDescriptions/LatexIncorrectSectionNesting.html b/resources/inspectionDescriptions/LatexIncorrectSectionNesting.html
new file mode 100644
index 000000000..50f270ce3
--- /dev/null
+++ b/resources/inspectionDescriptions/LatexIncorrectSectionNesting.html
@@ -0,0 +1,5 @@
+
+
+ It is encouraged to use subsection only within section, subparagraph in paragraph and so on.
+
+
diff --git a/src/nl/hannahsten/texifyidea/inspections/latex/LatexIncorrectSectionNestingInspection.kt b/src/nl/hannahsten/texifyidea/inspections/latex/LatexIncorrectSectionNestingInspection.kt
new file mode 100644
index 000000000..b4192b377
--- /dev/null
+++ b/src/nl/hannahsten/texifyidea/inspections/latex/LatexIncorrectSectionNestingInspection.kt
@@ -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 {
+
+ 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""")
+ )
+ }
+}
diff --git a/test/nl/hannahsten/texifyidea/inspections/latex/LatexIncorrectSectionNestingInspectionTest.kt b/test/nl/hannahsten/texifyidea/inspections/latex/LatexIncorrectSectionNestingInspectionTest.kt
new file mode 100644
index 000000000..c01bb978b
--- /dev/null
+++ b/test/nl/hannahsten/texifyidea/inspections/latex/LatexIncorrectSectionNestingInspectionTest.kt
@@ -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{}
+ \subsubsection{}
+ \end{document}
+ """.trimIndent())
+ }
+
+ @Test
+ fun `test subsection after chapter warning`() {
+ testInsertMissingParentCommandQuickFix("""
+ \begin{document}
+ \chapter{}
+ \subsection{}
+ \end{document}
+ """.trimIndent(), """
+ \begin{document}
+ \chapter{}
+ \section{}
+ \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{}
+ \subparagraph{}
+ \end{document}
+ """.trimIndent())
+ }
+
+ @Test
+ fun `test missing parent command warning`() {
+ myFixture.configureByText(LatexFileType, """
+ \begin{document}
+ \section{}
+ \subsubsection{}
+ \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)
+ }
+}
\ No newline at end of file